-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental tracing module (3/3): define and expose a instrumentHTTP
function
#2855
Changes from 5 commits
31d317d
22f1f70
2ff5246
7f858e9
7163425
60e1e3b
7d468aa
d5616dc
7187e12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,37 +56,7 @@ func TestTracingModuleClient(t *testing.T) { | |
jsonResults, err := afero.ReadFile(ts.FS, "results.json") | ||
require.NoError(t, err) | ||
|
||
gotHTTPDataPoints := false | ||
|
||
for _, jsonLine := range bytes.Split(jsonResults, []byte("\n")) { | ||
if len(jsonLine) == 0 { | ||
continue | ||
} | ||
|
||
var line sampleEnvelope | ||
require.NoError(t, json.Unmarshal(jsonLine, &line)) | ||
|
||
if line.Type != "Point" { | ||
continue | ||
} | ||
|
||
// Filter metric samples which are not related to http | ||
if !strings.HasPrefix(line.Metric, "http_") { | ||
continue | ||
} | ||
|
||
gotHTTPDataPoints = true | ||
|
||
anyTraceID, hasTraceID := line.Data.Metadata["trace_id"] | ||
require.True(t, hasTraceID) | ||
|
||
traceID, gotTraceID := anyTraceID.(string) | ||
require.True(t, gotTraceID) | ||
|
||
assert.Len(t, traceID, 32) | ||
} | ||
|
||
assert.True(t, gotHTTPDataPoints) | ||
assertHasTraceIDMetadata(t, jsonResults) | ||
} | ||
|
||
func TestTracingClient_DoesNotInterfereWithHTTPModule(t *testing.T) { | ||
|
@@ -128,6 +98,227 @@ func TestTracingClient_DoesNotInterfereWithHTTPModule(t *testing.T) { | |
assert.Equal(t, int64(2), atomic.LoadInt64(&gotInstrumentedRequests)) | ||
} | ||
|
||
func TestTracingInstrumentHTTP_W3C(t *testing.T) { | ||
t.Parallel() | ||
tb := httpmultibin.NewHTTPMultiBin(t) | ||
|
||
var gotRequests int64 | ||
|
||
tb.Mux.HandleFunc("/tracing", func(w http.ResponseWriter, r *http.Request) { | ||
atomic.AddInt64(&gotRequests, 1) | ||
assert.NotEmpty(t, r.Header.Get("traceparent")) | ||
assert.Len(t, r.Header.Get("traceparent"), 55) | ||
}) | ||
|
||
script := tb.Replacer.Replace(` | ||
import http from "k6/http"; | ||
import tracing from "k6/experimental/tracing"; | ||
|
||
tracing.instrumentHTTP({ | ||
propagator: "w3c", | ||
}) | ||
|
||
export default function () { | ||
http.del("HTTPBIN_IP_URL/tracing"); | ||
http.get("HTTPBIN_IP_URL/tracing"); | ||
http.head("HTTPBIN_IP_URL/tracing"); | ||
http.options("HTTPBIN_IP_URL/tracing"); | ||
http.patch("HTTPBIN_IP_URL/tracing"); | ||
http.post("HTTPBIN_IP_URL/tracing"); | ||
http.put("HTTPBIN_IP_URL/tracing"); | ||
http.request("GET", "HTTPBIN_IP_URL/tracing"); | ||
}; | ||
`) | ||
|
||
ts := getSingleFileTestState(t, script, []string{"--out", "json=results.json"}, 0) | ||
cmd.ExecuteWithGlobalState(ts.GlobalState) | ||
|
||
assert.Equal(t, int64(8), atomic.LoadInt64(&gotRequests)) | ||
|
||
jsonResults, err := afero.ReadFile(ts.FS, "results.json") | ||
require.NoError(t, err) | ||
|
||
assertHasTraceIDMetadata(t, jsonResults) | ||
} | ||
|
||
func TestTracingInstrumentHTTP_Jaeger(t *testing.T) { | ||
t.Parallel() | ||
tb := httpmultibin.NewHTTPMultiBin(t) | ||
|
||
var gotRequests int64 | ||
|
||
tb.Mux.HandleFunc("/tracing", func(w http.ResponseWriter, r *http.Request) { | ||
atomic.AddInt64(&gotRequests, 1) | ||
assert.NotEmpty(t, r.Header.Get("uber-trace-id")) | ||
assert.Len(t, r.Header.Get("uber-trace-id"), 45) | ||
}) | ||
|
||
script := tb.Replacer.Replace(` | ||
import http from "k6/http"; | ||
import { check } from "k6"; | ||
import tracing from "k6/experimental/tracing"; | ||
|
||
tracing.instrumentHTTP({ | ||
propagator: "jaeger", | ||
}) | ||
|
||
export default function () { | ||
http.del("HTTPBIN_IP_URL/tracing"); | ||
http.get("HTTPBIN_IP_URL/tracing"); | ||
http.head("HTTPBIN_IP_URL/tracing"); | ||
http.options("HTTPBIN_IP_URL/tracing"); | ||
http.patch("HTTPBIN_IP_URL/tracing"); | ||
http.post("HTTPBIN_IP_URL/tracing"); | ||
http.put("HTTPBIN_IP_URL/tracing"); | ||
http.request("GET", "HTTPBIN_IP_URL/tracing"); | ||
}; | ||
`) | ||
|
||
ts := getSingleFileTestState(t, script, []string{"--out", "json=results.json"}, 0) | ||
cmd.ExecuteWithGlobalState(ts.GlobalState) | ||
|
||
assert.Equal(t, int64(8), atomic.LoadInt64(&gotRequests)) | ||
|
||
jsonResults, err := afero.ReadFile(ts.FS, "results.json") | ||
require.NoError(t, err) | ||
|
||
assertHasTraceIDMetadata(t, jsonResults) | ||
} | ||
|
||
func TestTracingInstrumentHTTP_FillsParams(t *testing.T) { | ||
t.Parallel() | ||
tb := httpmultibin.NewHTTPMultiBin(t) | ||
|
||
var gotRequests int64 | ||
|
||
tb.Mux.HandleFunc("/tracing", func(w http.ResponseWriter, r *http.Request) { | ||
atomic.AddInt64(&gotRequests, 1) | ||
|
||
assert.NotEmpty(t, r.Header.Get("traceparent")) | ||
assert.Len(t, r.Header.Get("traceparent"), 55) | ||
|
||
assert.NotEmpty(t, r.Header.Get("X-Test-Header")) | ||
assert.Equal(t, "test", r.Header.Get("X-Test-Header")) | ||
}) | ||
|
||
script := tb.Replacer.Replace(` | ||
import http from "k6/http"; | ||
import tracing from "k6/experimental/tracing"; | ||
|
||
tracing.instrumentHTTP({ | ||
propagator: "w3c", | ||
}) | ||
|
||
const testHeaders = { | ||
"X-Test-Header": "test", | ||
} | ||
|
||
export default function () { | ||
http.del("HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
http.get("HTTPBIN_IP_URL/tracing", { headers: testHeaders }); | ||
http.head("HTTPBIN_IP_URL/tracing", { headers: testHeaders }); | ||
http.options("HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
http.patch("HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
http.post("HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
http.put("HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
http.request("GET", "HTTPBIN_IP_URL/tracing", null, { headers: testHeaders }); | ||
}; | ||
`) | ||
|
||
ts := getSingleFileTestState(t, script, []string{"--out", "json=results.json"}, 0) | ||
cmd.ExecuteWithGlobalState(ts.GlobalState) | ||
|
||
assert.Equal(t, int64(8), atomic.LoadInt64(&gotRequests)) | ||
|
||
jsonResults, err := afero.ReadFile(ts.FS, "results.json") | ||
require.NoError(t, err) | ||
|
||
assertHasTraceIDMetadata(t, jsonResults) | ||
} | ||
|
||
func TestTracingInstrumentHTTP_FailsOutsideOfInitContext(t *testing.T) { | ||
t.Parallel() | ||
tb := httpmultibin.NewHTTPMultiBin(t) | ||
|
||
script := tb.Replacer.Replace(` | ||
import tracing from "k6/experimental/tracing"; | ||
|
||
export default function() { | ||
tracing.instrumentHTTP({ | ||
propagator: "w3c", | ||
}) | ||
} | ||
`) | ||
|
||
ts := getSingleFileTestState(t, script, []string{}, 0) | ||
ts.ExpectedExitCode = 0 | ||
cmd.ExecuteWithGlobalState(ts.GlobalState) | ||
|
||
assert.Contains(t, ts.Stderr.String(), "instrumentHTTP can only be called in the init context") | ||
} | ||
|
||
func TestTracingInstrumentHTTP_CannotBeCalledTwice(t *testing.T) { | ||
t.Parallel() | ||
tb := httpmultibin.NewHTTPMultiBin(t) | ||
|
||
script := tb.Replacer.Replace(` | ||
import tracing from "k6/experimental/tracing"; | ||
|
||
tracing.instrumentHTTP({ | ||
propagator: "w3c", | ||
}) | ||
|
||
tracing.instrumentHTTP({ | ||
propagator: "w3c", | ||
}) | ||
|
||
export default function() { | ||
} | ||
`) | ||
|
||
ts := getSingleFileTestState(t, script, []string{}, 0) | ||
ts.ExpectedExitCode = 107 | ||
cmd.ExecuteWithGlobalState(ts.GlobalState) | ||
|
||
assert.Contains(t, ts.Stderr.String(), "instrumentHTTP can only be called once") | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of prefer if those are not integration tests as they are more expensive and in this case there is no need for them IMO. The functionality tested is pretty common :
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me see if I can easily turn them into standard unit tests indeed. I've struggled with |
||
|
||
// assertHasTraceIDMetadata checks that the trace_id metadata is present and has the correct format | ||
// for all http metrics in the json results file. | ||
func assertHasTraceIDMetadata(t *testing.T, jsonResults []byte) { | ||
gotHTTPDataPoints := false | ||
|
||
for _, jsonLine := range bytes.Split(jsonResults, []byte("\n")) { | ||
if len(jsonLine) == 0 { | ||
continue | ||
} | ||
|
||
var line sampleEnvelope | ||
require.NoError(t, json.Unmarshal(jsonLine, &line)) | ||
|
||
if line.Type != "Point" { | ||
continue | ||
} | ||
|
||
// Filter metric samples which are not related to http | ||
if !strings.HasPrefix(line.Metric, "http_") { | ||
continue | ||
} | ||
|
||
gotHTTPDataPoints = true | ||
|
||
anyTraceID, hasTraceID := line.Data.Metadata["trace_id"] | ||
require.True(t, hasTraceID) | ||
|
||
traceID, gotTraceID := anyTraceID.(string) | ||
require.True(t, gotTraceID) | ||
|
||
assert.Len(t, traceID, 32) | ||
} | ||
|
||
assert.True(t, gotHTTPDataPoints) | ||
} | ||
|
||
// sampleEnvelope is a trimmed version of the struct found | ||
// in output/json/wrapper.go | ||
// TODO: use the json output's wrapper struct instead if it's ever exported | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of the same comment about this not checking that all of those have tracing ids.
Maybe
assertHasTraceIDMetadata
can have a number argument that is how many it expects 🤷.Or to take the expected url(s) (as vararg not required argument) and check that all of those have trace id metadata sample.