diff --git a/js/modules/k6/experimental/tracing/trace_id.go b/js/modules/k6/experimental/tracing/trace_id.go new file mode 100644 index 000000000000..c4900332adac --- /dev/null +++ b/js/modules/k6/experimental/tracing/trace_id.go @@ -0,0 +1,80 @@ +package tracing + +import ( + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" +) + +const ( + // Being 075 the ASCII code for 'K' :) + k6Prefix = 0o756 + + // To ingest and process the related spans in k6 Cloud. + k6CloudCode = 12 + + // To not ingest and process the related spans, b/c they are part of a non-cloud run. + k6LocalCode = 33 +) + +// TraceID represents a trace-id as defined by the [W3c specification], and +// used by w3c, b3 and jaeger propagators. See Considerations for trace-id field [generation] +// for more information. +// +// [W3c specification]: https://www.w3.org/TR/trace-context/#trace-id +// [generation]: https://www.w3.org/TR/trace-context/#considerations-for-trace-id-field-generation +type TraceID struct { + // Prefix is the first 2 bytes of the trace-id, and is used to identify the + // vendor of the trace-id. + Prefix int16 + + // Code is the third byte of the trace-id, and is used to identify the + // vendor's specific trace-id format. + Code int8 + + // UnixTimestampNano is the last 8 bytes of the trace-id, and is used to + // identify the time of the trace-id generation, in nanoseconds. It also + // participates in ensuring that trace-ids uniqueness. + UnixTimestampNano uint64 +} + +// Encode encodes the TraceID into a hex string. +// +// The encoding is done as follows: +// 1. The first 2 bytes are the Prefix +// 2. The third byte is the Code. +// 3. The following 8 bytes are UnixTimestampNano. +// 4. The remaining 5 bytes are random bytes. +func (t TraceID) Encode() (string, error) { + if !t.isValid() { + return "", fmt.Errorf("failed to encode traceID: %v", t) + } + + buf := make([]byte, 16) + + n := binary.PutVarint(buf, int64(t.Prefix)) + n += binary.PutVarint(buf[n:], int64(t.Code)) + n += binary.PutUvarint(buf[n:], t.UnixTimestampNano) + + randomness := make([]byte, 16-n) + err := binary.Read(rand.Reader, binary.BigEndian, randomness) + if err != nil { + return "", fmt.Errorf("failed to read random bytes from os; reason: %w", err) + } + + buf = append(buf[:n], randomness...) + hx := hex.EncodeToString(buf) + + return hx, nil +} + +func (t TraceID) isValid() bool { + var ( + isk6Prefix = t.Prefix == k6Prefix + isk6Cloud = t.Code == k6CloudCode + isk6Local = t.Code == k6LocalCode + ) + + return isk6Prefix && (isk6Cloud || isk6Local) +} diff --git a/js/modules/k6/experimental/tracing/trace_id_test.go b/js/modules/k6/experimental/tracing/trace_id_test.go new file mode 100644 index 000000000000..4745ae5ee01e --- /dev/null +++ b/js/modules/k6/experimental/tracing/trace_id_test.go @@ -0,0 +1,72 @@ +package tracing + +import "testing" + +func TestTraceID_isValid(t *testing.T) { + t.Parallel() + + type fields struct { + Prefix int16 + Code int8 + UnixTimestampNano uint64 + } + testCases := []struct { + name string + fields fields + want bool + }{ + { + name: "traceID with k6 cloud code is valid", + fields: fields{ + Prefix: k6Prefix, + Code: k6CloudCode, + UnixTimestampNano: 123456789, + }, + want: true, + }, + { + name: "traceID with k6 local code is valid", + fields: fields{ + Prefix: k6Prefix, + Code: k6LocalCode, + UnixTimestampNano: 123456789, + }, + want: true, + }, + { + name: "traceID with prefix != k6Prefix is invalid", + fields: fields{ + Prefix: 0, + Code: k6CloudCode, + UnixTimestampNano: 123456789, + }, + want: false, + }, + { + name: "traceID code with code != k6CloudCode and code != k6LocalCode is invalid", + fields: fields{ + Prefix: k6Prefix, + Code: 0, + UnixTimestampNano: 123456789, + }, + want: false, + }, + } + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + tr := &TraceID{ + Prefix: tc.fields.Prefix, + Code: tc.fields.Code, + UnixTimestampNano: tc.fields.UnixTimestampNano, + } + + if got := tr.isValid(); got != tc.want { + t.Errorf("TraceID.isValid() = %v, want %v", got, tc.want) + } + }) + } +}