HrTime is the interface used to store timestamps in SpanImpl, metric Aggregators and Instruments, and LogRecords. It can also be used as the time input for starting and ending spans, metric times, and log times. We use it because in node, process.hrtime() has nanosecond precision which is better than the microsecond precision of performance.now(). process.hrtime() is no longer used. The only reference to process.hrtime in this repo is a comment in time.ts. Both Node and browser platforms use performance.now(). The nanosecond precision HrTime was designed for is never actually captured.
The use of HrTime as our internal time format has caused it to spread all over our code including into browser code where it was never meant to live. The worst example of this is in the fetch instrumentation which filters entries from the performance resource timing API by timestamp. In order to do that it creates timestamps with Date.now, converts them to HrTime, and calls @opentelemetry/sdk-trace-web#filterResourcesForSpan. That function converts them back to number nanoseconds before comparing them to the DOMHighResTimestamps used by the Performance Resource Timing API, including converting those to nanoseconds as well. HrTime is then used in the SpanImpl and passed to exporters where they are converted yet again to a nanosecond timestamp for export.
Proposal
- Use millisecond
number types for internal timestamps, which matches Date.now and performance.now. In 2026, the timeOrigin in milliseconds is roughly 1.76e12. In the IEEE 754 floating point format used by JS the mantissa is 53 bits. It takes around 41 bits to encode the integer part of timeOrigin, leaving 12 bits for the sub-millisecond part. This means the maximum resolution for a timestamp in millisecond number format is about 0.000244 milliseconds or 244 nanoseconds. Notably, this is well within the 5-100 microsecond jitter introduced by browsers to prevent timing attacks. This jitter is not introduced by node, but the performance.now() used by span start and end is only precise to 1 microsecond anyway, which is still 4x larger than 244 nanoseconds.
- Deprecate
HrTime as a time input format. We will have to keep it around as long as the API accepts it (which may be forever), but we can discourage its use and educate our users about its drawbacks.
Long term
- We can consider if using something like
Temporal is a good fit once it gains wider acceptance.
- We can consider allowing custom clock implementations that can implement their own performance/precision tradeoffs. One thing to note is that a custom clock needs some interface with the exporters which is ideally lossless, but if we use
BigInt that could nullify any performance benefit of using custom clocks. Maybe they need a toProtobuf and toStringNanosecond interface? A question for another time
HrTimeis the interface used to store timestamps inSpanImpl, metricAggregatorsandInstruments, andLogRecords. It can also be used as the time input for starting and ending spans, metric times, and log times. We use it because in node,process.hrtime()has nanosecond precision which is better than the microsecond precision ofperformance.now().process.hrtime()is no longer used. The only reference toprocess.hrtimein this repo is a comment intime.ts. Both Node and browser platforms useperformance.now(). The nanosecond precisionHrTimewas designed for is never actually captured.The use of
HrTimeas our internal time format has caused it to spread all over our code including into browser code where it was never meant to live. The worst example of this is in thefetchinstrumentation which filters entries from the performance resource timing API by timestamp. In order to do that it creates timestamps withDate.now, converts them toHrTime, and calls@opentelemetry/sdk-trace-web#filterResourcesForSpan. That function converts them back tonumbernanoseconds before comparing them to theDOMHighResTimestampsused by the Performance Resource Timing API, including converting those to nanoseconds as well.HrTimeis then used in theSpanImpland passed to exporters where they are converted yet again to a nanosecond timestamp for export.Proposal
numbertypes for internal timestamps, which matchesDate.nowandperformance.now. In 2026, thetimeOriginin milliseconds is roughly 1.76e12. In the IEEE 754 floating point format used by JS the mantissa is 53 bits. It takes around 41 bits to encode the integer part oftimeOrigin, leaving 12 bits for the sub-millisecond part. This means the maximum resolution for a timestamp in millisecondnumberformat is about 0.000244 milliseconds or 244 nanoseconds. Notably, this is well within the 5-100 microsecond jitter introduced by browsers to prevent timing attacks. This jitter is not introduced by node, but theperformance.now()used by span start and end is only precise to 1 microsecond anyway, which is still 4x larger than 244 nanoseconds.HrTimeas a time input format. We will have to keep it around as long as the API accepts it (which may be forever), but we can discourage its use and educate our users about its drawbacks.Long term
Temporalis a good fit once it gains wider acceptance.BigIntthat could nullify any performance benefit of using custom clocks. Maybe they need atoProtobufandtoStringNanosecondinterface? A question for another time