Skip to content

Commit

Permalink
Implement trace-id generation logic
Browse files Browse the repository at this point in the history
This commit implements trace-id generation based on the recommendations
of the W3C specification.

A lot of the code and logic present in this
commit are inherited and refactored from the xk6-distributed-tracing
project.
  • Loading branch information
oleiade committed Jan 16, 2023
1 parent 60b8b15 commit 77143dc
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
80 changes: 80 additions & 0 deletions js/modules/k6/experimental/tracing/trace_id.go
Original file line number Diff line number Diff line change
@@ -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)
}
72 changes: 72 additions & 0 deletions js/modules/k6/experimental/tracing/trace_id_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}

0 comments on commit 77143dc

Please sign in to comment.