Skip to content
This repository has been archived by the owner on Sep 13, 2022. It is now read-only.

Adding 128 bit traceId support #361

Merged
merged 20 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/reporters/udp_sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import dgram from 'dgram';
import fs from 'fs';
import path from 'path';
import { Thrift } from 'thriftrw';
import NullLogger from '../logger.js';
import SenderUtils from './sender_utils.js';
import NullLogger from '../logger';
import SenderUtils from './sender_utils';
import Utils from '../util';

const HOST = 'localhost';
const PORT = 6832;
Expand Down Expand Up @@ -128,7 +129,7 @@ export default class UDPSender {
}

const bufferLen = this._totalSpanBytes + this._emitSpanBatchOverhead;
const thriftBuffer = new Buffer(bufferLen);
const thriftBuffer = Utils.newBuffer(bufferLen);
const writeResult = this._agentThrift.Agent.emitBatch.argumentsMessageRW.writeInto(
this._convertBatchToThriftMessage(),
thriftBuffer,
Expand Down
14 changes: 13 additions & 1 deletion src/span_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,19 @@ export default class SpanContext {

get traceId(): any {
if (this._traceId == null && this._traceIdStr != null) {
this._traceId = Utils.encodeInt64(this._traceIdStr);
// make sure that the string is 32 or 16 characters long to generate the
// corresponding 64 or 128 bits buffer by padding the start with zeros if necessary
// https://github.com/jaegertracing/jaeger/issues/1657
// At the same time this will enforce that the HEX has an even number of digits, node is expecting 2 HEX characters per byte
// https://github.com/nodejs/node/issues/21242
const traceIdExactLength = this._traceIdStr.length > 16 ? 32 : 16;
if (this._traceIdStr.length === traceIdExactLength) {
this._traceId = Utils.newBufferFromHex(this._traceIdStr);
} else {
const padding = traceIdExactLength === 16 ? '0000000000000000' : '00000000000000000000000000000000';
const safeTraceIdStr = (padding + this._traceIdStr).slice(-traceIdExactLength);
this._traceId = Utils.newBufferFromHex(safeTraceIdStr);
}
}
return this._traceId;
}
Expand Down
26 changes: 21 additions & 5 deletions src/thrift.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class ThriftUtils {
source: fs.readFileSync(path.join(__dirname, './jaeger-idl/thrift/jaeger.thrift'), 'ascii'),
allowOptionalArguments: true,
});
static emptyBuffer: Buffer = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]);
static emptyBuffer: Buffer = Utils.newBuffer(8);

static getThriftTags(initialTags: Array<Tag>): Array<any> {
let thriftTags = [];
Expand Down Expand Up @@ -104,23 +104,39 @@ export default class ThriftUtils {

thriftRefs.push({
refType: refEnum,
traceIdLow: context.traceId,
traceIdHigh: ThriftUtils.emptyBuffer,
traceIdLow: ThriftUtils.getTraceIdLow(context.traceId),
traceIdHigh: ThriftUtils.getTraceIdHigh(context.traceId),
spanId: context.spanId,
});
}

return thriftRefs;
}

static getTraceIdLow(traceId: Buffer) {
if (traceId != null) {
return traceId.slice(-8);
}

return ThriftUtils.emptyBuffer;
}

static getTraceIdHigh(traceId: Buffer) {
if (traceId != null && traceId.length > 8) {
return traceId.slice(-16, -8);
}

return ThriftUtils.emptyBuffer;
}

static spanToThrift(span: Span): any {
let tags = ThriftUtils.getThriftTags(span._tags);
let logs = ThriftUtils.getThriftLogs(span._logs);
let unsigned = true;

return {
traceIdLow: span._spanContext.traceId,
traceIdHigh: ThriftUtils.emptyBuffer, // TODO(oibe) implement 128 bit ids
traceIdLow: ThriftUtils.getTraceIdLow(span._spanContext.traceId),
traceIdHigh: ThriftUtils.getTraceIdHigh(span._spanContext.traceId),
spanId: span._spanContext.spanId,
parentSpanId: span._spanContext.parentId || ThriftUtils.emptyBuffer,
operationName: span._operationName,
Expand Down
13 changes: 11 additions & 2 deletions src/tracer.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class Tracer {
_baggageSetter: BaggageSetter;
_debugThrottler: Throttler & ProcessSetter;
_process: Process;
_traceId128bit: boolean;

/**
* @param {String} [serviceName] - name of the current service or application.
Expand All @@ -57,6 +58,7 @@ export default class Tracer {
* @param {Object} [options.baggageRestrictionManager] - a baggageRestrictionManager matching
* @param {Object} [options.contextKey] - a name of the key to extract/inject context from headers
* @param {Object} [options.baggagePrefix] - a name of the context baggage key prefix
* @param {boolean} [options.traceId128bit] - generate root span with a 128bit traceId.
* BaggageRestrictionManager API from ./baggage.js.
*/
constructor(
Expand Down Expand Up @@ -117,6 +119,8 @@ export default class Tracer {
this._debugThrottler.setProcess(this._process);
// TODO update reporter to implement ProcessSetter
this._reporter.setProcess(this._process.serviceName, this._process.tags);

this._traceId128bit = options.traceId128bit;
}

_startInternalSpan(
Expand Down Expand Up @@ -230,8 +234,13 @@ export default class Tracer {
let internalTags: any = {};
let hasValidParent = false;
if (!parent || !parent.isValid) {
let randomId = Utils.getRandom64();
ctx = new SpanContext(randomId, randomId);
if (this._traceId128bit) {
let randomId = Utils.getRandom128();
ctx = new SpanContext(randomId, randomId.slice(-8));
} else {
let randomId = Utils.getRandom64();
ctx = new SpanContext(randomId, randomId);
}
if (parent) {
// fake parent, doesn't contain a parent trace-id, but may contain debug-id/baggage
if (parent.isDebugIDContainerOnly() && this._isDebugAllowed(operationName)) {
Expand Down
48 changes: 46 additions & 2 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,36 @@ export default class Utils {
}

/**
* Determines whether a string contains a given prefix.
* Get a random buffer representing a random 64 bit.
*
* @return {Buffer} - returns a buffer representing a random 64 bit
* number.
**/
static getRandom64(): Buffer {
let randint = xorshift.randomint();
let buf = new Buffer(8);
let buf = this.newBuffer(8);
buf.writeUInt32BE(randint[0], 0);
buf.writeUInt32BE(randint[1], 4);
return buf;
}

/**
* Get a random buffer representing a random 128 bit.
*
* @return {Buffer} - returns a buffer representing a random 128 bit
* number.
**/
static getRandom128(): Buffer {
let randint1 = xorshift.randomint();
let randint2 = xorshift.randomint();
let buf = this.newBuffer(16);
buf.writeUInt32BE(randint1[0], 0);
buf.writeUInt32BE(randint1[1], 4);
buf.writeUInt32BE(randint2[0], 8);
buf.writeUInt32BE(randint2[1], 12);
return buf;
}

/**
* @param {string|number} numberValue - a string or number to be encoded
* as a 64 bit byte array.
Expand Down Expand Up @@ -149,6 +166,33 @@ export default class Utils {
});
}

/**
* @param {string|number} input - a hex encoded string to store in the buffer.
* @return {Buffer} - returns a buffer representing the hex encoded string.
**/
static newBufferFromHex(input: string): Buffer {
const encoding = 'hex';
// check that 'Buffer.from' exists based on node's documentation
// https://nodejs.org/en/docs/guides/buffer-constructor-deprecation/#variant-3
if (Buffer.from && Buffer.from !== Uint8Array.from) {
PaulMiami marked this conversation as resolved.
Show resolved Hide resolved
return Buffer.from(input, encoding);
}
return new Buffer(input, encoding);
}

/**
* @param {number} input - a number of octets to allocate.
* @return {Buffer} - returns an empty buffer.
**/
static newBuffer(size: number): Buffer {
if (Buffer.alloc) {
return Buffer.alloc(size);
}
const buffer = new Buffer(size);
buffer.fill(0);
return buffer;
}

/**
* Creates a callback function that only delegates to passed <code>callback</code>
* after <code>limit</code> invocations. Useful in types like CompositeReporter that
Expand Down
2 changes: 1 addition & 1 deletion test/http_sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('http sender', () => {
expect(err).to.have.string('error sending spans over HTTP: Error: getaddrinfo ENOTFOUND');
tracer.close(done);
});
});
}).timeout(5000);

it('should handle HTTPS collectors', done => {
// Make it ignore the fact that our cert isn't valid.
Expand Down
35 changes: 35 additions & 0 deletions test/span_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ describe('SpanContext', () => {
assert.equal(flags, context.flags);
});

it('should return given values as they were set 128 bit', () => {
let traceId = Buffer.concat([Utils.encodeInt64(2), Utils.encodeInt64(1)]);
let spanId = Utils.encodeInt64(3);
let parentId = Utils.encodeInt64(4);
let flags = 1;

let context = SpanContext.withBinaryIds(traceId, spanId, parentId, flags);

assert.deepEqual(traceId, context.traceId);
assert.deepEqual('20000000000000001', context.traceIdStr);
assert.deepEqual(spanId, context.spanId);
assert.deepEqual(parentId, context.parentId);
assert.equal(flags, context.flags);
});

it('should expose IsSampled properly', () => {
let context = SpanContext.withBinaryIds(
Utils.encodeInt64(1),
Expand Down Expand Up @@ -80,6 +95,7 @@ describe('SpanContext', () => {
let context = SpanContext.fromString('100:7f:0:1');

assert.deepEqual('100', context.traceIdStr);
assert.deepEqual(Utils.encodeInt64(0x100), context.traceId);
assert.deepEqual(Utils.encodeInt64(0x7f), context.spanId);
assert.equal(null, context.parentId);
assert.equal(1, context.flags);
Expand All @@ -94,6 +110,25 @@ describe('SpanContext', () => {
assert.equal(context.flags, 0x1);
});

it('should turn properly formatted strings into correct span contexts 128 bit', () => {
let context = SpanContext.fromString('20000000000000100:7f:0:1');

assert.deepEqual('20000000000000100', context.traceIdStr);
assert.deepEqual(Buffer.concat([Utils.encodeInt64(0x2), Utils.encodeInt64(0x100)]), context.traceId);
assert.deepEqual(Utils.encodeInt64(0x7f), context.spanId);
assert.equal(null, context.parentId);
assert.equal(1, context.flags);

// test large numbers
context = SpanContext.fromString('ffffffffffffffffffffffffffffffff:ffffffffffffffff:5:1');
assert.equal('ffffffffffffffffffffffffffffffff', context.traceIdStr);
assert.equal('ffffffffffffffff', context.spanIdStr);
assert.deepEqual(Buffer.concat([LARGEST_64_BUFFER, LARGEST_64_BUFFER]), context.traceId);
assert.deepEqual(LARGEST_64_BUFFER, context.spanId);
assert.deepEqual(Utils.encodeInt64(0x5), context.parentId);
assert.equal(context.flags, 0x1);
});

it('should return null on malformed traces', () => {
assert.equal(SpanContext.fromString('bad value'), null);
assert.equal(SpanContext.fromString('1:1:1:1:1'), null, 'Too many colons');
Expand Down
31 changes: 31 additions & 0 deletions test/thrift.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// the License.

import { assert } from 'chai';
import opentracing from 'opentracing';
import ConstSampler from '../src/samplers/const_sampler.js';
import InMemoryReporter from '../src/reporters/in_memory_reporter.js';
import Tracer from '../src/tracer.js';
Expand Down Expand Up @@ -64,4 +65,34 @@ describe('ThriftUtils', () => {
assert.deepEqual(tSpan.duration, Utils.encodeInt64((123.678 - 123.456) * 1000));
assert.deepEqual(tSpan.logs[0].timestamp, Utils.encodeInt64(123567));
});

it('should convert span with 128 bit traceId', () => {
let reporter = new InMemoryReporter();
let tracer = new Tracer('test-service-name', reporter, new ConstSampler(true), { traceId128bit: true });
let span = tracer.startSpan('some operation');
let childOfRef = new opentracing.Reference(opentracing.REFERENCE_CHILD_OF, span.context());
let childSpan = tracer.startSpan('some child operation', { references: [childOfRef] });
childSpan.finish();
span.finish();
tracer.close();
let tSpan = ThriftUtils.spanToThrift(childSpan);
assert.deepEqual(tSpan.traceIdLow, childSpan.context().traceId.slice(-8));
assert.deepEqual(tSpan.traceIdHigh, childSpan.context().traceId.slice(-16, -8));
assert.deepEqual(tSpan.spanId, childSpan.context().spanId);
assert.deepEqual(tSpan.references[0].traceIdLow, span.context().traceId.slice(-8));
assert.deepEqual(tSpan.references[0].traceIdHigh, span.context().traceId.slice(-16, -8));
assert.deepEqual(tSpan.references[0].spanId, span.context().spanId);
});

it('should convert extract traceIdLow and traceIdHigh', () => {
let traceIdLow = Utils.encodeInt64(1);
let traceIdHigh = Utils.encodeInt64(2);
let traceId = Buffer.concat([traceIdHigh, traceIdLow]);

assert.deepEqual(ThriftUtils.getTraceIdLow(traceId), traceIdLow);
assert.deepEqual(ThriftUtils.getTraceIdHigh(traceId), traceIdHigh);

assert.deepEqual(ThriftUtils.getTraceIdLow(null), ThriftUtils.emptyBuffer);
assert.deepEqual(ThriftUtils.getTraceIdHigh(null), ThriftUtils.emptyBuffer);
});
});
61 changes: 61 additions & 0 deletions test/tracer.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,38 @@ describe('tracer should', () => {
assertByFormat(opentracing.FORMAT_HTTP_HEADERS);
});

it('inject plain text headers into carrier, and extract span context with the same value 128bits', () => {
let keyOne = 'keyOne';
let keyTwo = 'keyTwo';
let baggage = {
keyOne: 'leela',
keyTwo: 'bender',
};
let savedContext = SpanContext.withBinaryIds(
Buffer.concat([Utils.encodeInt64(1), Utils.encodeInt64(2)]),
Utils.encodeInt64(2),
Utils.encodeInt64(3),
constants.SAMPLED_MASK,
baggage
);

let assertByFormat = format => {
let carrier = {};
tracer.inject(savedContext, format, carrier);
let extractedContext = tracer.extract(format, carrier);

assert.deepEqual(savedContext.traceId, extractedContext.traceId);
assert.deepEqual(savedContext.spanId, extractedContext.spanId);
assert.deepEqual(savedContext.parentId, extractedContext.parentId);
assert.equal(savedContext.flags, extractedContext.flags);
assert.equal(savedContext.baggage[keyOne], extractedContext.baggage[keyOne]);
assert.equal(savedContext.baggage[keyTwo], extractedContext.baggage[keyTwo]);
};

assertByFormat(opentracing.FORMAT_TEXT_MAP);
assertByFormat(opentracing.FORMAT_HTTP_HEADERS);
});

it('inject url encoded values into headers', () => {
let baggage = {
keyOne: 'Leela vs. Bender',
Expand Down Expand Up @@ -430,6 +462,35 @@ describe('tracer should', () => {
});
});

it('start a root span with 128 bit traceId', () => {
tracer = new Tracer('test-service-name', reporter, new ConstSampler(true), { traceId128bit: true });
let span = tracer.startSpan('test-name');

assert.deepEqual(span.context().traceId.slice(-8), span.context().spanId);
assert.equal(16, span.context().traceId.length);
});

it('preserve 64bit traceId even when in 128bit mode', () => {
// NB: because we currently trim leading zeros, this test is not as effective as it could be.
// But once https://github.com/jaegertracing/jaeger-client-node/issues/391 is fixed, this test
// will be more useful as it can catch regression.
tracer = new Tracer('test-service-name', reporter, new ConstSampler(true), { traceId128bit: true });
let span = tracer.startSpan('test-name');
assert.equal(16, span.context().traceId.length, 'new traces use 128bit IDs');

let parent = SpanContext.fromString('100:7f:0:1');
assert.equal(8, parent.traceId.length, 'respect 64bit length');

let child = tracer.startSpan('test-name', { childOf: parent });
assert.equal(8, child.context().traceId.length, 'preserve 64bit length');

let carrier = {};
tracer.inject(child.context(), opentracing.FORMAT_TEXT_MAP, carrier);
// Once https://github.com/jaegertracing/jaeger-client-node/issues/391 is fixed, the following
// asset will fail and will need to be changed to compare against '0000000000000100' string.
assert.equal('100:', carrier['uber-trace-id'].substring(0, 4), 'preserve 64bit length');
});

it('should NOT mutate tags', () => {
const tags = {
robot: 'bender',
Expand Down
2 changes: 1 addition & 1 deletion test/udp_sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,5 +272,5 @@ describe('udp sender', () => {
tracer.close(done);
}
});
});
}).timeout(5000);
});
Loading