diff --git a/js/modules/k6/http/request.go b/js/modules/k6/http/request.go index 6a13b278506..b8413bda33b 100644 --- a/js/modules/k6/http/request.go +++ b/js/modules/k6/http/request.go @@ -13,6 +13,7 @@ import ( "net/textproto" "net/url" "os" + "path/filepath" "reflect" "strings" "time" @@ -41,19 +42,19 @@ func (c *Client) getMethodClosure(method string) func(url goja.Value, args ...go } } -func (c *Client) getHTTP3Client() (*http.Client, error) { +func (c *Client) configureHTTP3RoundTripper() { qconf := quic.Config{ - Tracer: func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer { tracers := make([]*logging.ConnectionTracer, 0) - tracers = append(tracers, httpext.NewTracer(c.moduleInstance.vu, c.moduleInstance.metrics)) + tracers = append(tracers, httpext.NewTracer(ctx, c.moduleInstance.vu, c.moduleInstance.metrics)) + //nolint:forbidigo if os.Getenv("HTTP3_QLOG") == "1" { role := "server" if p == logging.PerspectiveClient { role = "client" } filename := fmt.Sprintf("./log_%s_%s.qlog", connID, role) - f, _ := os.Create(filename) + f, _ := os.Create(filepath.Clean(filename)) // TODO: handle the error tracers = append(tracers, qlog.NewConnectionTracer(f, p, connID)) } @@ -67,6 +68,7 @@ func (c *Client) getHTTP3Client() (*http.Client, error) { } insecure := false c.http3RoundTripper = &http3.RoundTripper{ + //nolint:gosec TLSClientConfig: &tls.Config{ RootCAs: pool, InsecureSkipVerify: insecure, @@ -74,7 +76,6 @@ func (c *Client) getHTTP3Client() (*http.Client, error) { QuicConfig: &qconf, } } - return nil, nil } // Request makes an http request of the provided `method` and returns a corresponding response by @@ -92,9 +93,14 @@ func (c *Client) Request(method string, url goja.Value, args ...goja.Value) (*Re } if req.Req.Proto == httpext.HTTP3Proto { - c.getHTTP3Client() + c.configureHTTP3RoundTripper() } - resp, err := httpext.MakeRequest(context.WithValue(c.moduleInstance.vu.Context(), httpext.CtxKeyHTTP3RoundTripper, c.moduleInstance.defaultClient.http3RoundTripper), state, req) + contextWithRoundTripper := context.WithValue( + c.moduleInstance.vu.Context(), + httpext.CtxKeyHTTP3RoundTripper, + c.moduleInstance.defaultClient.http3RoundTripper, + ) + resp, err := httpext.MakeRequest(contextWithRoundTripper, state, req) if err != nil { return nil, err } @@ -153,7 +159,12 @@ func (c *Client) asyncRequest(method string, url goja.Value, args ...goja.Value) callback := c.moduleInstance.vu.RegisterCallback() go func() { - resp, err := httpext.MakeRequest(context.WithValue(c.moduleInstance.vu.Context(), httpext.CtxKeyHTTP3RoundTripper, c.moduleInstance.defaultClient.http3RoundTripper), state, req) + contextWithRoundTripper := context.WithValue( + c.moduleInstance.vu.Context(), + httpext.CtxKeyHTTP3RoundTripper, + c.moduleInstance.defaultClient.http3RoundTripper, + ) + resp, err := httpext.MakeRequest(contextWithRoundTripper, state, req) callback(func() error { if err != nil { reject(err) @@ -550,9 +561,18 @@ func (c *Client) Batch(reqsV ...goja.Value) (interface{}, error) { } reqCount := len(batchReqs) + contextWithRoundTripper := context.WithValue( + c.moduleInstance.vu.Context(), + httpext.CtxKeyHTTP3RoundTripper, + c.moduleInstance.defaultClient.http3RoundTripper, + ) errs := httpext.MakeBatchRequests( - context.WithValue(c.moduleInstance.vu.Context(), httpext.CtxKeyHTTP3RoundTripper, c.moduleInstance.defaultClient.http3RoundTripper), state, batchReqs, reqCount, - int(state.Options.Batch.Int64), int(state.Options.BatchPerHost.Int64), + contextWithRoundTripper, + state, + batchReqs, + reqCount, + int(state.Options.Batch.Int64), + int(state.Options.BatchPerHost.Int64), ) for i := 0; i < reqCount; i++ { diff --git a/lib/netext/httpext/http3.go b/lib/netext/httpext/http3.go index 707e172d8a8..b718fe00fee 100644 --- a/lib/netext/httpext/http3.go +++ b/lib/netext/httpext/http3.go @@ -8,11 +8,14 @@ import ( type ctxKey int const ( + // CtxKeyHTTP3RoundTripper is a context key for the HTTP3RoundTripper. CtxKeyHTTP3RoundTripper ctxKey = iota ) +// HTTP3Proto is the HTTP/3 protocol name. const HTTP3Proto = "HTTP/3" +// HTTP3Metrics is a set of metrics for HTTP/3. type HTTP3Metrics struct { HTTP3ReqDuration *metrics.Metric HTTP3ReqSending *metrics.Metric @@ -24,15 +27,23 @@ type HTTP3Metrics struct { } const ( - HTTP3ReqDurationName = "http3_req_duration" - HTTP3ReqSendingName = "http3_req_sending" - HTTP3ReqWaitingName = "http3_req_waiting" - HTTP3ReqReceivingName = "http3_req_receiving" + // HTTP3ReqDurationName is the name of the HTTP3ReqDuration metric. + HTTP3ReqDurationName = "http3_req_duration" + // HTTP3ReqSendingName is the name of the HTTP3ReqSending metric. + HTTP3ReqSendingName = "http3_req_sending" + // HTTP3ReqWaitingName is the name of the HTTP3ReqWaiting metric. + HTTP3ReqWaitingName = "http3_req_waiting" + // HTTP3ReqReceivingName is the name of the HTTP3ReqReceiving metric. + HTTP3ReqReceivingName = "http3_req_receiving" + // HTTP3ReqTLSHandshakingName is the name of the HTTP3ReqTLSHandshaking metric. HTTP3ReqTLSHandshakingName = "http3_req_tls_handshaking" - HTTP3ReqConnectingName = "http3_req_connecting" - HTTP3ReqsName = "http3_reqs" + // HTTP3ReqConnectingName is the name of the HTTP3ReqConnecting metric. + HTTP3ReqConnectingName = "http3_req_connecting" + // HTTP3ReqsName is the name of the HTTP3Reqs metric. + HTTP3ReqsName = "http3_reqs" ) +// RegisterMetrics registers the HTTP3Metrics. func RegisterMetrics(vu modules.VU) (*HTTP3Metrics, error) { var err error registry := vu.InitEnv().Registry diff --git a/lib/netext/httpext/http3Tracer.go b/lib/netext/httpext/http3Tracer.go index 7b70161e445..e64fd6fb002 100644 --- a/lib/netext/httpext/http3Tracer.go +++ b/lib/netext/httpext/http3Tracer.go @@ -1,6 +1,7 @@ package httpext import ( + "context" "net" "time" @@ -32,7 +33,7 @@ type metricHandler struct { streams map[int64]*streamMetrics } -func (mh *metricHandler) sendConnectionMetrics() { +func (mh *metricHandler) sendConnectionMetrics(ctx context.Context) { handshakeTime := mh.connectionMetrics.handshakeDone.Sub(mh.connectionMetrics.handshakeStart) connectTime := mh.connectionMetrics.handshakeStart.Sub(mh.connectionMetrics.connectionStart) @@ -56,12 +57,13 @@ func (mh *metricHandler) sendConnectionMetrics() { Time: mh.connectionMetrics.handshakeStart, Value: metrics.D(connectTime), }, - }} + }, + } mh.vu.State().Samples <- samples - mh.sendDataMetrics() + mh.sendDataMetrics(ctx) } -func (mh *metricHandler) sendDataMetrics() { +func (mh *metricHandler) sendDataMetrics(ctx context.Context) { samples := metrics.ConnectedSamples{ Samples: []metrics.Sample{ { @@ -82,13 +84,14 @@ func (mh *metricHandler) sendDataMetrics() { Value: float64(mh.connectionMetrics.dataSent), Metadata: mh.vu.State().Tags.GetCurrentValues().Metadata, }, - }} - metrics.PushIfNotDone(mh.vu.Context(), mh.vu.State().Samples, samples) + }, + } + metrics.PushIfNotDone(ctx, mh.vu.State().Samples, samples) mh.connectionMetrics.dataReceived = 0 mh.connectionMetrics.dataSent = 0 } -func (mh *metricHandler) sendStreamMetrics(streamID int64) { +func (mh *metricHandler) sendStreamMetrics(ctx context.Context, streamID int64) { streamMetrics := mh.getStreamMetrics(streamID, false) if streamMetrics == nil { return @@ -149,18 +152,19 @@ func (mh *metricHandler) sendStreamMetrics(streamID int64) { Time: streamMetrics.responseFin, Value: 1, }, - }} + }, + } mh.vu.State().Samples <- samples - mh.sendDataMetrics() + mh.sendDataMetrics(ctx) } -func (mh *metricHandler) handleFramesReceived(frames []logging.Frame) { +func (mh *metricHandler) handleFramesReceived(ctx context.Context, frames []logging.Frame) { for _, frame := range frames { switch f := frame.(type) { case *logging.HandshakeDoneFrame: { mh.connectionMetrics.handshakeDone = time.Now() - mh.sendConnectionMetrics() + mh.sendConnectionMetrics(ctx) } case *logging.StreamFrame: { @@ -175,7 +179,7 @@ func (mh *metricHandler) handleFramesReceived(frames []logging.Frame) { } if f.Fin { streamMetrics.responseFin = time.Now() - mh.sendStreamMetrics(streamID) + mh.sendStreamMetrics(ctx, streamID) } } } @@ -184,23 +188,19 @@ func (mh *metricHandler) handleFramesReceived(frames []logging.Frame) { func (mh *metricHandler) handleFramesSent(frames []logging.Frame) { for _, frame := range frames { - switch f := frame.(type) { - case *logging.StreamFrame: - { - streamID := int64(f.StreamID) - streamMetrics := mh.getStreamMetrics(streamID, true) - streamMetrics.sentBytes += int(f.Length) - if f.Fin { - streamMetrics.requestFin = time.Now() - } + if f, ok := frame.(*logging.StreamFrame); ok { + streamID := int64(f.StreamID) + streamMetrics := mh.getStreamMetrics(streamID, true) + streamMetrics.sentBytes += int(f.Length) + if f.Fin { + streamMetrics.requestFin = time.Now() } } } - } -func (mh *metricHandler) packetReceived(bc logging.ByteCount, f []logging.Frame) { - mh.handleFramesReceived(f) +func (mh *metricHandler) packetReceived(ctx context.Context, bc logging.ByteCount, f []logging.Frame) { + mh.handleFramesReceived(ctx, f) mh.connectionMetrics.dataReceived += int(bc) } @@ -221,7 +221,8 @@ func (mh *metricHandler) getStreamMetrics(streamID int64, createIfNotFound bool) return m } -func NewTracer(vu modules.VU, http3Metrics *HTTP3Metrics) *logging.ConnectionTracer { +// NewTracer creates a new connection tracer for the given VU, that will measure and emit different metrics +func NewTracer(ctx context.Context, vu modules.VU, http3Metrics *HTTP3Metrics) *logging.ConnectionTracer { mh := &metricHandler{ vu: vu, metrics: http3Metrics, @@ -237,15 +238,25 @@ func NewTracer(vu modules.VU, http3Metrics *HTTP3Metrics) *logging.ConnectionTra if mh.connectionMetrics.handshakeStart.IsZero() && eh.Type == 3 /*protocol.PacketTypeHandshake*/ { mh.connectionMetrics.handshakeStart = time.Now() } - mh.packetReceived(bc, f) + mh.packetReceived(ctx, bc, f) }, ReceivedShortHeaderPacket: func(sh *logging.ShortHeader, bc logging.ByteCount, e logging.ECN, f []logging.Frame) { - mh.packetReceived(bc, f) + mh.packetReceived(ctx, bc, f) }, - SentLongHeaderPacket: func(eh *logging.ExtendedHeader, bc logging.ByteCount, e logging.ECN, af *logging.AckFrame, f []logging.Frame) { + SentLongHeaderPacket: func(eh *logging.ExtendedHeader, + bc logging.ByteCount, + e logging.ECN, + af *logging.AckFrame, + f []logging.Frame, + ) { mh.packetSent(bc, f) }, - SentShortHeaderPacket: func(sh *logging.ShortHeader, bc logging.ByteCount, e logging.ECN, af *logging.AckFrame, f []logging.Frame) { + SentShortHeaderPacket: func(sh *logging.ShortHeader, + bc logging.ByteCount, + e logging.ECN, + af *logging.AckFrame, + f []logging.Frame, + ) { mh.packetSent(bc, f) }, } diff --git a/lib/netext/httpext/http3Transport.go b/lib/netext/httpext/http3Transport.go index d62aedcbd60..fedd42b83ab 100644 --- a/lib/netext/httpext/http3Transport.go +++ b/lib/netext/httpext/http3Transport.go @@ -20,11 +20,9 @@ type http3Transport struct { roundTripper http.RoundTripper } -// newTransport returns a new http.RoundTripper implementation that wraps around -// the provided state's Transport. It uses a httpext.Tracer to measure all HTTP -// requests made through it and annotates and emits the recorded metric samples -// through the state.Samples channel. -func newHttp3Transport( +// newHTTP3Transport returns a new http.RoundTripper implementation that wraps around +// the provided RoundTripper. +func newHTTP3Transport( ctx context.Context, state *lib.State, tagsAndMeta *metrics.TagsAndMeta, diff --git a/lib/netext/httpext/request.go b/lib/netext/httpext/request.go index 09b6037d687..49d1ba05623 100644 --- a/lib/netext/httpext/request.go +++ b/lib/netext/httpext/request.go @@ -183,7 +183,9 @@ func MakeRequest(ctx context.Context, state *lib.State, preq *ParsedHTTPRequest) tracerTransport := newTransport(ctx, state, &preq.TagsAndMeta, preq.ResponseCallback) var transport http.RoundTripper if preq.Req.Proto == HTTP3Proto { - transport = newHttp3Transport(ctx, state, &preq.TagsAndMeta, preq.ResponseCallback, ctx.Value(CtxKeyHTTP3RoundTripper).(http.RoundTripper)) + if http3RoundTripper, ok := ctx.Value(CtxKeyHTTP3RoundTripper).(http.RoundTripper); ok { + transport = newHTTP3Transport(ctx, state, &preq.TagsAndMeta, preq.ResponseCallback, http3RoundTripper) + } } else { transport = tracerTransport }