@@ -38,7 +38,6 @@ import {
38
38
InstrumentationScope ,
39
39
isAttributeValue ,
40
40
isTimeInput ,
41
- isTimeInputHrTime ,
42
41
otperformance ,
43
42
sanitizeAttributes ,
44
43
} from '@opentelemetry/core' ;
@@ -106,9 +105,7 @@ export class SpanImpl implements Span {
106
105
private readonly _spanLimits : SpanLimits ;
107
106
private readonly _attributeValueLengthLimit : number ;
108
107
109
- private readonly _performanceStartTime : number ;
110
- private readonly _performanceOffset : number ;
111
- private readonly _startTimeProvided : boolean ;
108
+ private readonly _performance ?: { startTime : number } ;
112
109
113
110
/**
114
111
* Constructs a new SpanImpl instance.
@@ -117,10 +114,18 @@ export class SpanImpl implements Span {
117
114
const now = Date . now ( ) ;
118
115
119
116
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
+
124
129
this . _spanLimits = opts . spanLimits ;
125
130
this . _attributeValueLengthLimit =
126
131
this . _spanLimits . attributeValueLengthLimit || 0 ;
@@ -130,7 +135,6 @@ export class SpanImpl implements Span {
130
135
this . parentSpanContext = opts . parentSpanContext ;
131
136
this . kind = opts . kind ;
132
137
this . links = opts . links || [ ] ;
133
- this . startTime = this . _getTime ( opts . startTime ?? now ) ;
134
138
this . resource = opts . resource ;
135
139
this . instrumentationScope = opts . scope ;
136
140
@@ -223,7 +227,7 @@ export class SpanImpl implements Span {
223
227
this . events . push ( {
224
228
name,
225
229
attributes,
226
- time : this . _getTime ( timeStamp ) ,
230
+ time : this . _getHrTime ( timeStamp ) ,
227
231
droppedAttributesCount : 0 ,
228
232
} ) ;
229
233
return this ;
@@ -272,7 +276,8 @@ export class SpanImpl implements Span {
272
276
}
273
277
this . _ended = true ;
274
278
275
- this . endTime = this . _getTime ( endTime ) ;
279
+ this . endTime = this . _getHrTime ( endTime ) ;
280
+
276
281
this . _duration = hrTimeDuration ( this . startTime , this . endTime ) ;
277
282
278
283
if ( this . _duration [ 0 ] < 0 ) {
@@ -294,33 +299,42 @@ export class SpanImpl implements Span {
294
299
this . _spanProcessor . onEnd ( this ) ;
295
300
}
296
301
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
+ }
303
307
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
+ }
306
317
}
307
318
308
- if ( inp instanceof Date ) {
309
- return millisToHrTime ( inp . getTime ( ) ) ;
319
+ if ( input instanceof Date ) {
320
+ return millisToHrTime ( input . getTime ( ) ) ;
310
321
}
311
322
312
- if ( isTimeInputHrTime ( inp ) ) {
313
- return inp ;
314
- }
323
+ // It is already an HR time array
324
+ return input ;
325
+ }
315
326
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
319
331
return millisToHrTime ( Date . now ( ) ) ;
320
332
}
321
333
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 ) ) ;
324
338
}
325
339
326
340
isRecording ( ) : boolean {
0 commit comments