Skip to content

Commit 9c84977

Browse files
committed
fix(sdk-trace-base): nanosecond precision of span start time
1 parent cb42f7d commit 9c84977

File tree

1 file changed

+43
-29
lines changed
  • packages/opentelemetry-sdk-trace-base/src

1 file changed

+43
-29
lines changed

packages/opentelemetry-sdk-trace-base/src/Span.ts

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import {
3838
InstrumentationScope,
3939
isAttributeValue,
4040
isTimeInput,
41-
isTimeInputHrTime,
4241
otperformance,
4342
sanitizeAttributes,
4443
} from '@opentelemetry/core';
@@ -106,9 +105,7 @@ export class SpanImpl implements Span {
106105
private readonly _spanLimits: SpanLimits;
107106
private readonly _attributeValueLengthLimit: number;
108107

109-
private readonly _performanceStartTime: number;
110-
private readonly _performanceOffset: number;
111-
private readonly _startTimeProvided: boolean;
108+
private readonly _performance?: { startTime: number };
112109

113110
/**
114111
* Constructs a new SpanImpl instance.
@@ -117,10 +114,18 @@ export class SpanImpl implements Span {
117114
const now = Date.now();
118115

119116
this._spanContext = opts.spanContext;
120-
this._performanceStartTime = otperformance.now();
121-
this._performanceOffset =
122-
now - (this._performanceStartTime + getTimeOrigin());
123-
this._startTimeProvided = opts.startTime != null;
117+
118+
if (opts.startTime) {
119+
this.startTime = this._getHrTimeFromInput(opts.startTime);
120+
} else {
121+
const startTime = otperformance.now();
122+
const offsetFromDate = now - (startTime - getTimeOrigin());
123+
this.startTime = hrTime(startTime + offsetFromDate);
124+
// Store an HR start time, possibly drifted, to calculate the HR duration of the span.
125+
// Do not use this directly. Use _getDriftCorrectedHrTime() to get the current corrected HR time.
126+
this._performance = { startTime };
127+
}
128+
124129
this._spanLimits = opts.spanLimits;
125130
this._attributeValueLengthLimit =
126131
this._spanLimits.attributeValueLengthLimit || 0;
@@ -130,7 +135,6 @@ export class SpanImpl implements Span {
130135
this.parentSpanContext = opts.parentSpanContext;
131136
this.kind = opts.kind;
132137
this.links = opts.links || [];
133-
this.startTime = this._getTime(opts.startTime ?? now);
134138
this.resource = opts.resource;
135139
this.instrumentationScope = opts.scope;
136140

@@ -223,7 +227,7 @@ export class SpanImpl implements Span {
223227
this.events.push({
224228
name,
225229
attributes,
226-
time: this._getTime(timeStamp),
230+
time: this._getHrTime(timeStamp),
227231
droppedAttributesCount: 0,
228232
});
229233
return this;
@@ -272,7 +276,8 @@ export class SpanImpl implements Span {
272276
}
273277
this._ended = true;
274278

275-
this.endTime = this._getTime(endTime);
279+
this.endTime = this._getHrTime(endTime);
280+
276281
this._duration = hrTimeDuration(this.startTime, this.endTime);
277282

278283
if (this._duration[0] < 0) {
@@ -294,33 +299,42 @@ export class SpanImpl implements Span {
294299
this._spanProcessor.onEnd(this);
295300
}
296301

297-
private _getTime(inp?: TimeInput): HrTime {
298-
if (typeof inp === 'number' && inp <= otperformance.now()) {
299-
// must be a performance timestamp
300-
// apply correction and convert to hrtime
301-
return hrTime(inp + this._performanceOffset);
302-
}
302+
private _getHrTime(input?: TimeInput): HrTime {
303+
return input === undefined
304+
? this._getDriftCorrectedHrTime()
305+
: this._getHrTimeFromInput(input);
306+
}
303307

304-
if (typeof inp === 'number') {
305-
return millisToHrTime(inp);
308+
private _getHrTimeFromInput(input: TimeInput): HrTime {
309+
if (typeof input === 'number') {
310+
if (input <= otperformance.now()) {
311+
// must be the result of `performance.now`, which is the time since program start
312+
// translate it to an actual timestamp with hight resolution
313+
return hrTime(input);
314+
} else {
315+
return millisToHrTime(input);
316+
}
306317
}
307318

308-
if (inp instanceof Date) {
309-
return millisToHrTime(inp.getTime());
319+
if (input instanceof Date) {
320+
return millisToHrTime(input.getTime());
310321
}
311322

312-
if (isTimeInputHrTime(inp)) {
313-
return inp;
314-
}
323+
// It is already an HR time array
324+
return input;
325+
}
315326

316-
if (this._startTimeProvided) {
317-
// if user provided a time for the start manually
318-
// we can't use duration to calculate event/end times
327+
private _getDriftCorrectedHrTime(): HrTime {
328+
if (!this._performance) {
329+
// We can't apply drift correction, user provided the start time
330+
// so we don't have any baseline. Falling back to low precision but drift free Date.now
319331
return millisToHrTime(Date.now());
320332
}
321333

322-
const msDuration = otperformance.now() - this._performanceStartTime;
323-
return addHrTimes(this.startTime, millisToHrTime(msDuration));
334+
// Calculate actual time based on the elapsed time and the corrected start time.
335+
// This way, we avoid clock drifting of HR times.
336+
const duration = otperformance.now() - this._performance.startTime;
337+
return addHrTimes(this.startTime, millisToHrTime(duration));
324338
}
325339

326340
isRecording(): boolean {

0 commit comments

Comments
 (0)