From 5f6d1cf9c348707af31db4565da05192cf06275f Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Fri, 24 Jul 2020 13:30:35 -0400 Subject: [PATCH 1/6] feat: fast span and trace id generation --- .../src/platform/browser/id.ts | 49 +++++++------------ .../src/platform/node/id.ts | 18 ++++--- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/packages/opentelemetry-core/src/platform/browser/id.ts b/packages/opentelemetry-core/src/platform/browser/id.ts index 24f33972fd0..ad9089ab131 100644 --- a/packages/opentelemetry-core/src/platform/browser/id.ts +++ b/packages/opentelemetry-core/src/platform/browser/id.ts @@ -13,44 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -declare type WindowWithMsCrypto = Window & { - msCrypto?: Crypto; -}; -const cryptoLib = window.crypto || (window as WindowWithMsCrypto).msCrypto; const SPAN_ID_BYTES = 8; const TRACE_ID_BYTES = 16; -const randomBytesArray = new Uint8Array(TRACE_ID_BYTES); /** Returns a random 16-byte trace ID formatted as a 32-char hex string. */ -export function randomTraceId(): string { - cryptoLib.getRandomValues(randomBytesArray); - return toHex(randomBytesArray.slice(0, TRACE_ID_BYTES)); -} +export const randomTraceId = getIdGenerator(TRACE_ID_BYTES); /** Returns a random 8-byte span ID formatted as a 16-char hex string. */ -export function randomSpanId(): string { - cryptoLib.getRandomValues(randomBytesArray); - return toHex(randomBytesArray.slice(0, SPAN_ID_BYTES)); -} - -/** - * Get the hex string representation of a byte array - * - * @param byteArray - */ -function toHex(byteArray: Uint8Array) { - const chars: number[] = new Array(byteArray.length * 2); - const alpha = 'a'.charCodeAt(0) - 10; - const digit = '0'.charCodeAt(0); - - let p = 0; - for (let i = 0; i < byteArray.length; i++) { - let nibble = (byteArray[i] >>> 4) & 0xf; - chars[p++] = nibble > 9 ? nibble + alpha : nibble + digit; - nibble = byteArray[i] & 0xf; - chars[p++] = nibble > 9 ? nibble + alpha : nibble + digit; - } +export const randomSpanId = getIdGenerator(SPAN_ID_BYTES); - return String.fromCharCode.apply(null, chars); +const SHARED_CHAR_CODES_ARRAY = Array(32); +function getIdGenerator(bytes: number): () => string { + return function generateId() { + for (let i = 0; i < bytes * 2; i++) { + SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48; + // valid hex characters in the range 48-57 and 97-102 + if (SHARED_CHAR_CODES_ARRAY[i] >= 58) { + SHARED_CHAR_CODES_ARRAY[i] += 39; + } + } + return String.fromCharCode.apply( + null, + SHARED_CHAR_CODES_ARRAY.slice(0, bytes * 2) + ); + }; } diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index 970025385c6..d9e534ce7ca 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import * as crypto from 'crypto'; - const SPAN_ID_BYTES = 8; const TRACE_ID_BYTES = 16; @@ -23,14 +21,20 @@ const TRACE_ID_BYTES = 16; * Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex * characters corresponding to 128 bits. */ -export function randomTraceId(): string { - return crypto.randomBytes(TRACE_ID_BYTES).toString('hex'); -} +export const randomTraceId = getIdGenerator(TRACE_ID_BYTES); /** * Returns a random 8-byte span ID formatted/encoded as a 16 lowercase hex * characters corresponding to 64 bits. */ -export function randomSpanId(): string { - return crypto.randomBytes(SPAN_ID_BYTES).toString('hex'); +export const randomSpanId = getIdGenerator(SPAN_ID_BYTES); + +const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); +function getIdGenerator(bytes: number): () => string { + return function generateId() { + for (let i = 0; i < bytes; i++) { + SHARED_BUFFER[i] = Math.floor(Math.random() * 256); + } + return SHARED_BUFFER.slice(0, bytes).toString('hex'); + }; } From 2f157a8e49791ae5f25908ff7e13991acc82f263 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 28 Jul 2020 14:09:06 -0400 Subject: [PATCH 2/6] chore: remove unnecessary buffer alloc --- packages/opentelemetry-core/src/platform/node/id.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index d9e534ce7ca..db3b3f23a5d 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -35,6 +35,6 @@ function getIdGenerator(bytes: number): () => string { for (let i = 0; i < bytes; i++) { SHARED_BUFFER[i] = Math.floor(Math.random() * 256); } - return SHARED_BUFFER.slice(0, bytes).toString('hex'); + return SHARED_BUFFER.toString('hex', 0, bytes); }; } From 33e9a4a186e740f1bc61a71dc2d27fd497c74b4e Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 28 Jul 2020 14:30:25 -0400 Subject: [PATCH 3/6] chore: minimize calls to random --- packages/opentelemetry-core/src/platform/node/id.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index db3b3f23a5d..f0c386783ff 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -32,8 +32,8 @@ export const randomSpanId = getIdGenerator(SPAN_ID_BYTES); const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); function getIdGenerator(bytes: number): () => string { return function generateId() { - for (let i = 0; i < bytes; i++) { - SHARED_BUFFER[i] = Math.floor(Math.random() * 256); + for (let i = 0; i < (bytes / 4); i++) { + SHARED_BUFFER.writeUInt32BE((Math.random() * 2**32) >>> 0, i * 4) } return SHARED_BUFFER.toString('hex', 0, bytes); }; From 86fe596d053350fa48b41a67325c5d77a0f8d444 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 28 Jul 2020 14:35:03 -0400 Subject: [PATCH 4/6] chore: lint --- packages/opentelemetry-core/src/platform/node/id.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index f0c386783ff..137cd189e14 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -32,8 +32,8 @@ export const randomSpanId = getIdGenerator(SPAN_ID_BYTES); const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); function getIdGenerator(bytes: number): () => string { return function generateId() { - for (let i = 0; i < (bytes / 4); i++) { - SHARED_BUFFER.writeUInt32BE((Math.random() * 2**32) >>> 0, i * 4) + for (let i = 0; i < bytes / 4; i++) { + SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4); } return SHARED_BUFFER.toString('hex', 0, bytes); }; From b6d533b66b181207966de6806c44df194a73914a Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 28 Jul 2020 16:38:29 -0400 Subject: [PATCH 5/6] chore: check if generated id is invalid (all 0) --- packages/opentelemetry-core/src/platform/node/id.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index 137cd189e14..57894acd2ad 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -33,8 +33,18 @@ const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); function getIdGenerator(bytes: number): () => string { return function generateId() { for (let i = 0; i < bytes / 4; i++) { - SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4); + SHARED_BUFFER.writeUInt32BE(Math.random() * 2 ** 32, i * 4); } + + // If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated + for (let i = 0; i < bytes; i++) { + if (SHARED_BUFFER[i] > 0) { + break; + } else if (i === bytes - 1) { + SHARED_BUFFER[bytes - 1] = 1; + } + } + return SHARED_BUFFER.toString('hex', 0, bytes); }; } From eecf2d5bd79e337fb6df4b898e35612b81058943 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 28 Jul 2020 17:11:08 -0400 Subject: [PATCH 6/6] chore: unsigned right shift required to guarantee function in range --- packages/opentelemetry-core/src/platform/node/id.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-core/src/platform/node/id.ts b/packages/opentelemetry-core/src/platform/node/id.ts index 57894acd2ad..8b6a0638b10 100644 --- a/packages/opentelemetry-core/src/platform/node/id.ts +++ b/packages/opentelemetry-core/src/platform/node/id.ts @@ -33,7 +33,9 @@ const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); function getIdGenerator(bytes: number): () => string { return function generateId() { for (let i = 0; i < bytes / 4; i++) { - SHARED_BUFFER.writeUInt32BE(Math.random() * 2 ** 32, i * 4); + // unsigned right shift drops decimal part of the number + // it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE + SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4); } // If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated