@@ -130,6 +130,12 @@ type Transport struct {
130130 // Defaults to 15s.
131131 PingTimeout time.Duration
132132
133+ // CountError, if non-nil, is called on HTTP/2 transport errors.
134+ // It's intended to increment a metric for monitoring, such
135+ // as an expvar or Prometheus metric.
136+ // The errType consists of only ASCII word characters.
137+ CountError func (errType string )
138+
133139 // t1, if non-nil, is the standard library Transport using
134140 // this transport. Its settings are used (but not its
135141 // RoundTrip method, etc).
@@ -943,6 +949,9 @@ func (cc *ClientConn) Close() error {
943949// closes the client connection immediately. In-flight requests are interrupted.
944950func (cc * ClientConn ) closeForLostPing () error {
945951 err := errors .New ("http2: client connection lost" )
952+ if f := cc .t .CountError ; f != nil {
953+ f ("conn_close_lost_ping" )
954+ }
946955 return cc .closeForError (err )
947956}
948957
@@ -1830,6 +1839,33 @@ func (rl *clientConnReadLoop) cleanup() {
18301839 cc .mu .Unlock ()
18311840}
18321841
1842+ // countReadFrameError calls Transport.CountError with a string
1843+ // representing err.
1844+ func (cc * ClientConn ) countReadFrameError (err error ) {
1845+ f := cc .t .CountError
1846+ if f == nil || err == nil {
1847+ return
1848+ }
1849+ if ce , ok := err .(ConnectionError ); ok {
1850+ errCode := ErrCode (ce )
1851+ f (fmt .Sprintf ("read_frame_conn_error_%s" , errCode .stringToken ()))
1852+ return
1853+ }
1854+ if errors .Is (err , io .EOF ) {
1855+ f ("read_frame_eof" )
1856+ return
1857+ }
1858+ if errors .Is (err , io .ErrUnexpectedEOF ) {
1859+ f ("read_frame_unexpected_eof" )
1860+ return
1861+ }
1862+ if errors .Is (err , ErrFrameTooLarge ) {
1863+ f ("read_frame_too_large" )
1864+ return
1865+ }
1866+ f ("read_frame_other" )
1867+ }
1868+
18331869func (rl * clientConnReadLoop ) run () error {
18341870 cc := rl .cc
18351871 rl .closeWhenIdle = cc .t .disableKeepAlives () || cc .singleUse
@@ -1860,6 +1896,7 @@ func (rl *clientConnReadLoop) run() error {
18601896 }
18611897 continue
18621898 } else if err != nil {
1899+ cc .countReadFrameError (err )
18631900 return err
18641901 }
18651902 if VerboseLogs {
@@ -2352,6 +2389,10 @@ func (rl *clientConnReadLoop) processGoAway(f *GoAwayFrame) error {
23522389 if f .ErrCode != 0 {
23532390 // TODO: deal with GOAWAY more. particularly the error code
23542391 cc .vlogf ("transport got GOAWAY with error code = %v" , f .ErrCode )
2392+ if fn := cc .t .CountError ; fn != nil {
2393+ fn ("recv_goaway_" + f .ErrCode .stringToken ())
2394+ }
2395+
23552396 }
23562397 cc .setGoAway (f )
23572398 return nil
@@ -2466,9 +2507,9 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
24662507 if f .ErrCode == ErrCodeProtocol {
24672508 rl .cc .SetDoNotReuse ()
24682509 serr .Cause = errFromPeer
2469- // TODO(bradfitz): increment a varz here, once Transport
2470- // takes an optional interface-typed field that expvar.Map.Add
2471- // implements.
2510+ }
2511+ if fn := cs . cc . t . CountError ; fn != nil {
2512+ fn ( "recv_rststream_" + f . ErrCode . stringToken ())
24722513 }
24732514 cs .resetErr = serr
24742515 close (cs .peerReset )
0 commit comments