-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…3186) * 🚚 [RUM-6868] move crypto in a separate module * 🚚 [RUM-6868] move identifier logic in a separate module * 🚚 [RUM-6868] simplify identifier implementation This commit reduces the scope of identifiers to make alternative identifier implementations easier. * ✨ [RUM-6868] make trace identifiers 64 bits To comply with other tracers identifiers and be able to properly implement probabilistic sampling, let's make trace identifiers 64 bits. This PR differentiate Span and Trace identifiers to make sure we use each at the right place. * ✅ [RUM-6868] consider identifiers byte ordering little endian This is not strictly needed from a functionality point of view, but helps implementing tests that work with both the current UintArray-based identifier and the future BigInt-based implementation. The implementation is also simpler. Rational: In our tests, we are mocking `getRandomValues` to edit the random bytes of the identifier, then we make sure the identifier gets formated as a string correctly. Currently, we set the last byte to 0xff, and since we consider this byte to be the least significant byte (big endian), naturally the output is ff. But in the next commit we'll introduce an alternative implementation of identifiers based on BigInt generated using a BigUInt64Array. In this setup, bigints are usually encoded as little endian. So if we edit the last byte to 0xff, we actually edit the most significant byte, and the output would be ff00000000000. To keep tests simple, let's use the same endianness for both implementation, so editing the same byte results in the same trace id. * ✨ [RUM-6868] implement trace ids via bigints * ✨ [RUM-6868] implement consistent trace sampling * 🚩 [RUM-6868] put consistent sampling behind an experimental flag * 👌 add comment about ie11 support --------- Co-authored-by: Thomas Lebeau <[email protected]>
- Loading branch information
1 parent
f87c2fe
commit 518c07a
Showing
13 changed files
with
339 additions
and
123 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
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,4 @@ | ||
export function getCrypto() { | ||
// TODO: remove msCrypto when IE11 support is dropped | ||
return window.crypto || (window as any).msCrypto | ||
} |
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
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
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
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
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,90 @@ | ||
import { ExperimentalFeature } from '@datadog/browser-core' | ||
import { mockExperimentalFeatures } from '../../../../core/test' | ||
import { getCrypto } from '../../browser/crypto' | ||
import { createSpanIdentifier, createTraceIdentifier, toPaddedHexadecimalString } from './identifier' | ||
|
||
describe('identifier', () => { | ||
describe('TraceIdentifier', () => { | ||
it('generates a random id', () => { | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString()).toMatch(/^\d+$/) | ||
}) | ||
|
||
it('formats using base 16', () => { | ||
mockRandomValues((buffer) => (buffer[0] = 0xff)) | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString(16)).toEqual('ff') | ||
}) | ||
|
||
it('should generate a max value of 64 bits', () => { | ||
mockRandomValues((buffer) => fill(buffer, 0xff)) | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString(16)).toEqual('ffffffffffffffff') | ||
}) | ||
}) | ||
|
||
describe('SpanIdentifier', () => { | ||
it('generates a max value of 63 bits', () => { | ||
mockRandomValues((buffer) => fill(buffer, 0xff)) | ||
const identifier = createSpanIdentifier() | ||
expect(identifier.toString(16)).toEqual('7fffffffffffffff') | ||
}) | ||
}) | ||
|
||
// Run the same tests again with consistent trace sampling enabled, which uses the BigInt | ||
// implementation | ||
describe('with CONSISTENT_TRACE_SAMPLING enabled', () => { | ||
beforeEach(() => { | ||
mockExperimentalFeatures([ExperimentalFeature.CONSISTENT_TRACE_SAMPLING]) | ||
}) | ||
|
||
describe('TraceIdentifier', () => { | ||
it('generates a random id', () => { | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString()).toMatch(/^\d+$/) | ||
}) | ||
|
||
it('formats using base 16', () => { | ||
mockRandomValues((buffer) => (buffer[0] = 0xff)) | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString(16)).toEqual('ff') | ||
}) | ||
|
||
it('should generate a max value of 64 bits', () => { | ||
mockRandomValues((buffer) => fill(buffer, 0xff)) | ||
const identifier = createTraceIdentifier() | ||
expect(identifier.toString(16)).toEqual('ffffffffffffffff') | ||
}) | ||
}) | ||
|
||
describe('SpanIdentifier', () => { | ||
it('generates a max value of 63 bits', () => { | ||
mockRandomValues((buffer) => fill(buffer, 0xff)) | ||
const identifier = createSpanIdentifier() | ||
expect(identifier.toString(16)).toEqual('7fffffffffffffff') | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('toPaddedHexadecimalString', () => { | ||
it('should pad the string to 16 characters', () => { | ||
mockRandomValues((buffer) => (buffer[0] = 0x01)) | ||
const identifier = createTraceIdentifier() | ||
expect(toPaddedHexadecimalString(identifier)).toEqual('0000000000000001') | ||
}) | ||
}) | ||
|
||
function mockRandomValues(cb: (buffer: Uint8Array) => void) { | ||
spyOn(getCrypto(), 'getRandomValues').and.callFake((bufferView) => { | ||
cb(new Uint8Array(bufferView!.buffer)) | ||
return bufferView | ||
}) | ||
} | ||
|
||
// TODO: replace with `buffer.fill(value)` when we drop support for IE11 | ||
function fill(buffer: Uint8Array, value: number) { | ||
for (let i = 0; i < buffer.length; i++) { | ||
buffer[i] = value | ||
} | ||
} |
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,86 @@ | ||
import { ExperimentalFeature, isExperimentalFeatureEnabled } from '@datadog/browser-core' | ||
import { getCrypto } from '../../browser/crypto' | ||
|
||
interface BaseIdentifier { | ||
toString(radix?: number): string | ||
} | ||
|
||
export interface TraceIdentifier extends BaseIdentifier { | ||
// We use a brand to distinguish between TraceIdentifier and SpanIdentifier, else TypeScript | ||
// considers them as the same type | ||
__brand: 'traceIdentifier' | ||
} | ||
|
||
export interface SpanIdentifier extends BaseIdentifier { | ||
__brand: 'spanIdentifier' | ||
} | ||
|
||
export function createTraceIdentifier() { | ||
return createIdentifier(64) as TraceIdentifier | ||
} | ||
|
||
export function createSpanIdentifier() { | ||
return createIdentifier(63) as SpanIdentifier | ||
} | ||
|
||
let createIdentifierImplementationCache: ((bits: 63 | 64) => BaseIdentifier) | undefined | ||
|
||
function createIdentifier(bits: 63 | 64): BaseIdentifier { | ||
if (!createIdentifierImplementationCache) { | ||
createIdentifierImplementationCache = | ||
isExperimentalFeatureEnabled(ExperimentalFeature.CONSISTENT_TRACE_SAMPLING) && areBigIntIdentifiersSupported() | ||
? createIdentifierUsingBigInt | ||
: createIdentifierUsingUint32Array | ||
} | ||
return createIdentifierImplementationCache(bits) | ||
} | ||
|
||
export function areBigIntIdentifiersSupported() { | ||
try { | ||
crypto.getRandomValues(new BigUint64Array(1)) | ||
return true | ||
} catch { | ||
return false | ||
} | ||
} | ||
|
||
function createIdentifierUsingBigInt(bits: 63 | 64): BaseIdentifier { | ||
let id = crypto.getRandomValues(new BigUint64Array(1))[0] | ||
if (bits === 63) { | ||
// eslint-disable-next-line no-bitwise | ||
id >>= BigInt('1') | ||
} | ||
return id | ||
} | ||
|
||
// TODO: remove this when all browser we support have BigInt support | ||
function createIdentifierUsingUint32Array(bits: 63 | 64): BaseIdentifier { | ||
const buffer = getCrypto().getRandomValues(new Uint32Array(2)) | ||
if (bits === 63) { | ||
// eslint-disable-next-line no-bitwise | ||
buffer[buffer.length - 1] >>>= 1 // force 63-bit | ||
} | ||
|
||
return { | ||
toString(radix = 10) { | ||
let high = buffer[1] | ||
let low = buffer[0] | ||
let str = '' | ||
|
||
do { | ||
const mod = (high % radix) * 4294967296 + low | ||
high = Math.floor(high / radix) | ||
low = Math.floor(mod / radix) | ||
str = (mod % radix).toString(radix) + str | ||
} while (high || low) | ||
|
||
return str | ||
}, | ||
} | ||
} | ||
|
||
export function toPaddedHexadecimalString(id: BaseIdentifier) { | ||
const traceId = id.toString(16) | ||
// TODO: replace with String.prototype.padStart when we drop IE11 support | ||
return Array(17 - traceId.length).join('0') + traceId | ||
} |
Oops, something went wrong.