http2: add Transport.CountErrors
Like the earlier Server.CountErrors. This lets people observe hook up
an http2.Transport to monitoring (expvar/Prometheus/etc) and spot
pattern changes over time.
Change-Id: I12a4d0b3499fb0be75d5a56342ced0719c00654d
Reviewed-on: https://go-review.googlesource.com/c/net/+/350709
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Trust: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/http2/errors.go b/http2/errors.go
index c789fa3..2663e5d 100644
--- a/http2/errors.go
+++ b/http2/errors.go
@@ -53,6 +53,13 @@
return fmt.Sprintf("unknown error code 0x%x", uint32(e))
}
+func (e ErrCode) stringToken() string {
+ if s, ok := errCodeName[e]; ok {
+ return s
+ }
+ return fmt.Sprintf("ERR_UNKNOWN_%d", uint32(e))
+}
+
// ConnectionError is an error that results in the termination of the
// entire connection.
type ConnectionError ErrCode
diff --git a/http2/transport.go b/http2/transport.go
index 74c76da..aaaffa8 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -130,6 +130,12 @@
// Defaults to 15s.
PingTimeout time.Duration
+ // CountError, if non-nil, is called on HTTP/2 transport errors.
+ // It's intended to increment a metric for monitoring, such
+ // as an expvar or Prometheus metric.
+ // The errType consists of only ASCII word characters.
+ CountError func(errType string)
+
// t1, if non-nil, is the standard library Transport using
// this transport. Its settings are used (but not its
// RoundTrip method, etc).
@@ -943,6 +949,9 @@
// closes the client connection immediately. In-flight requests are interrupted.
func (cc *ClientConn) closeForLostPing() error {
err := errors.New("http2: client connection lost")
+ if f := cc.t.CountError; f != nil {
+ f("conn_close_lost_ping")
+ }
return cc.closeForError(err)
}
@@ -1830,6 +1839,33 @@
cc.mu.Unlock()
}
+// countReadFrameError calls Transport.CountError with a string
+// representing err.
+func (cc *ClientConn) countReadFrameError(err error) {
+ f := cc.t.CountError
+ if f == nil || err == nil {
+ return
+ }
+ if ce, ok := err.(ConnectionError); ok {
+ errCode := ErrCode(ce)
+ f(fmt.Sprintf("read_frame_conn_error_%s", errCode.stringToken()))
+ return
+ }
+ if errors.Is(err, io.EOF) {
+ f("read_frame_eof")
+ return
+ }
+ if errors.Is(err, io.ErrUnexpectedEOF) {
+ f("read_frame_unexpected_eof")
+ return
+ }
+ if errors.Is(err, ErrFrameTooLarge) {
+ f("read_frame_too_large")
+ return
+ }
+ f("read_frame_other")
+}
+
func (rl *clientConnReadLoop) run() error {
cc := rl.cc
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
@@ -1860,6 +1896,7 @@
}
continue
} else if err != nil {
+ cc.countReadFrameError(err)
return err
}
if VerboseLogs {
@@ -2352,6 +2389,10 @@
if f.ErrCode != 0 {
// TODO: deal with GOAWAY more. particularly the error code
cc.vlogf("transport got GOAWAY with error code = %v", f.ErrCode)
+ if fn := cc.t.CountError; fn != nil {
+ fn("recv_goaway_" + f.ErrCode.stringToken())
+ }
+
}
cc.setGoAway(f)
return nil
@@ -2466,9 +2507,9 @@
if f.ErrCode == ErrCodeProtocol {
rl.cc.SetDoNotReuse()
serr.Cause = errFromPeer
- // TODO(bradfitz): increment a varz here, once Transport
- // takes an optional interface-typed field that expvar.Map.Add
- // implements.
+ }
+ if fn := cs.cc.t.CountError; fn != nil {
+ fn("recv_rststream_" + f.ErrCode.stringToken())
}
cs.resetErr = serr
close(cs.peerReset)