Skip to content

Commit

Permalink
128 bit trace IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-saia-datadog committed Jun 28, 2024
1 parent 8b325de commit 7319839
Show file tree
Hide file tree
Showing 11 changed files with 693 additions and 136 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ packages/**/*.tgz
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Jest
coverage
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@react-native/metro-config": "0.73.5",
"@react-native/typescript-config": "0.73.1",
"@testing-library/react-native": "7.0.2",
"@types/jest": "^29.5.12",
"@types/jest": "^29.5.0",
"@types/react": "^18.2.6",
"@types/react-native": "0.71.0",
"@types/react-test-renderer": "18.0.0",
Expand All @@ -52,7 +52,7 @@
"eslint-plugin-react-hooks": "4.3.0",
"eslint-plugin-react-native": "3.10.0",
"genversion": "3.0.2",
"jest": "^29.6.3",
"jest": "^29.7.0",
"lerna": "7.1.0",
"pod-install": "0.1.14",
"prettier": "2.2.0",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/DdSdkReactNative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class DdSdkReactNative {
private static readonly DD_SDK_VERSION = '_dd.sdk_version';
private static readonly DD_VERSION = '_dd.version';
private static readonly DD_VERSION_SUFFIX = '_dd.version_suffix';

private static wasAutoInstrumented = false;
private static features?: AutoInstrumentationConfiguration;
private static _isInitialized = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

/**
* Available formats for representing the {@link TracingIdentifier} as a string.
*/
export enum TracingIdFormat {
/**
* Decimal string representation of the full tracing id.
*/
decimal,

/**
* The low bits of the tracing id as a decimal.
*/
lowDecimal,

/**
* The high bits of the tracing id as a decimal.
*/
highDecimal,

/**
* Hexadecimal string representation of the full tracing id.
*/
hex,

/**
* Hexadecimal string representation of the low bits of the tracing id.
*/
lowHex,

/**
* Hexadecimal string representation of the high bits of the tracing id.
*/
highHex,

/**
* Padded hexadecimal string representation of the full tracing id.
*/
paddedHex,

/**
* Padded hexadecimal string representation of the low bits of the tracing id.
*/
paddedLowHex,

/**
* Padded hexadecimal string representation of the high bits of the tracing id.
*/
paddedHighHex
}

/**
* A {@link TracingIdentifier} used for Traces (128 bit).
*/
export type TraceId = TracingIdentifier & {
_brand: 'traceId';
};

/**
* A {@link TracingIdentifier} used for Spans (64 bit).
*/
export type SpanId = TracingIdentifier & {
_brand: 'spanId';
};

/**
* The tracing identifier type.
*/
export type TracingIdentifierType = 'trace' | 'span';

/**
* Value used to mask the low 64 bits of the trace identifier.
*/
const LOW_64BIT_MASK = (BigInt('0xffffffff') << 32n) + BigInt('0xffffffff');

/**
* Value used to mask the low 32 bits of the trace identifier.
*/
const LOW_32BIT_MASK = (BigInt('0xffff') << 16n) + BigInt('0xffff');

/**
* A {@link TracingIdentifier} is a unique UUID that can be 64bit or 128bit, and provides
* convenient methods to represent it as a HEX or DECIMAL string, and it allows the masking
* of its low or high bits.
*
* Create a new identifier by calling {@link TracingIdentifier.createTraceId()} or
* {@link TracingIdentifier.createSpanId()}.
*/
export class TracingIdentifier {
/**
* Read-only generated ID as a {@link bigint}.
*/
readonly id: bigint;

/**
* Read-only type to determine whether the identifier is a {@link TraceId} or a {@link SpanId}.
*/
readonly type: TracingIdentifierType;

/**
* Creates a new unique Trace ID.
* @returns the generated {@link TraceId}.
*/
public static createTraceId(): TraceId {
return new TracingIdentifier('trace') as TraceId;
}

/**
* Creates a new unique Span ID.
* @returns the generated {@link SpanId}.
*/
public static createSpanId(): SpanId {
return new TracingIdentifier('span') as SpanId;
}

/**
* Private constructor to initialize the {@link TracingIdentifier} based on the given
* {@link TracingIdentifierType}
*/
private constructor(type: TracingIdentifierType) {
switch (type) {
case 'trace':
this.id = this.generateUUID('128bit');
break;
case 'span':
this.id = this.generateUUID('64bit');
break;
}

this.type = type;
}

/**
* Generates a unique ID with the given format.
* @param format - the desired format (64bit or 128bit).
* @returns the generated UUID as a {@link bigint}.
*/
private generateUUID(format: '64bit' | '128bit'): bigint {
// Get the current Unix timestamp in seconds
const unixSeconds = Math.floor(Date.now() / 1000);

// Ensure the Unix timestamp is 32 bits
const unixSeconds32 = unixSeconds & 0xffffffff;

// 32 bits of zero
const zeros32 = 0;

// Generate 64 random bits using Math.random()
const random32Bit1 = Math.floor(Math.random() * 0xffffffff);
const random32Bit2 = Math.floor(Math.random() * 0xffffffff);
const random64Hex =
random32Bit1.toString(16).padStart(8, '0') +
random32Bit2.toString(16).padStart(8, '0');

// If format is 64bit we return it immediately
if (format === '64bit') {
return BigInt(`0x${random64Hex}`);
}

// Convert parts to hexadecimal strings
const unixSecondsHex = unixSeconds32.toString(16).padStart(8, '0');
const zerosHex = zeros32.toString(16).padStart(8, '0');

// Combine parts to form the 128-bit ID
const hex128BitID = unixSecondsHex + zerosHex + random64Hex;

return BigInt(`0x${hex128BitID}`);
}

/**
* Returns a string representation of the TracingId.
* @param representation - The type of representation to use.
* @returns The ID as a string in the specified representation type.
*/
public toString(representation: TracingIdFormat): string {
const lowTraceMask =
this.type === 'trace' ? LOW_64BIT_MASK : LOW_32BIT_MASK;
const highTraceMask = this.type === 'trace' ? 64n : 32n;
const padding = this.type === 'trace' ? 32 : 16;

switch (representation) {
case TracingIdFormat.decimal:
return this.id.toString(10);

case TracingIdFormat.lowDecimal:
return (this.id & lowTraceMask).toString(10);

case TracingIdFormat.highDecimal:
return (this.id >> highTraceMask).toString(10);

case TracingIdFormat.hex:
return this.id.toString(16);

case TracingIdFormat.lowHex:
return (this.id & lowTraceMask).toString(16);

case TracingIdFormat.highHex:
return (this.id >> highTraceMask).toString(16);

case TracingIdFormat.paddedHex:
return this.toString(TracingIdFormat.hex).padStart(
padding,
'0'
);

case TracingIdFormat.paddedLowHex:
return this.toString(TracingIdFormat.lowHex).padStart(
padding / 2,
'0'
);

case TracingIdFormat.paddedHighHex:
return this.toString(TracingIdFormat.highHex).padStart(
padding / 2,
'0'
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

export default class TracingIdentifierUtils {
/**
* Extracts the Unix timestamp from the 128-bit hex string representation.
* @param idHex - The 128-bit ID as a hexadecimal string.
* @returns The Unix timestamp as a number.
*/
public static extractTimestamp(idHex: string): number {
// Extract the first 8 characters which represent the 32-bit Unix timestamp
const timestampHex = idHex.substring(0, 8);

// Convert the hexadecimal string to a number
const timestamp = parseInt(timestampHex, 16);

return timestamp;
}

/**
* Checks if a string representation of an ID in a given radix is within 32 bits.
* @param idString - The string representation of the ID.
* @param radix - Optional base to use for the conversion (default is 10).
* @returns True if the ID is within 32 bits, otherwise false.
*/
public static isLessThan32Bits(
idString: string,
radix: number = 10
): boolean {
const bigIntValue = BigInt(parseInt(idString, radix));
return bigIntValue < BigInt(1) << BigInt(32);
}

/**
* Checks if a string representation of an ID in a given radix is within 64 bits.
* @param idString - The string representation of the ID.
* @param radix - Optional base to use for the conversion (default is 10).
* @returns True if the ID is within 64 bits, otherwise false.
*/
public static isLessThan64Bits(
idString: string,
radix: number = 10
): boolean {
const bigIntValue = BigInt(parseInt(idString, radix));
return bigIntValue < BigInt(1) << BigInt(64);
}

/**
* Checks if a string representation of an ID in a given radix is within 128 bits.
* @param idString - The string representation of the ID.
* @param radix - Optional base to use for the conversion (default is 10).
* @returns True if the ID is within 128 bits, otherwise false.
*/
public static isLessThan128Bits(
idString: string,
radix: number = 10
): boolean {
const bigIntValue = BigInt(parseInt(idString, radix));
return bigIntValue < BigInt(1) << BigInt(128);
}
}
Loading

0 comments on commit 7319839

Please sign in to comment.