-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
2 changed files
with
152 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |