Bug:
The OpenTelemetry spec states:
For delta aggregations, the start timestamp MUST equal the previous collection interval's timestamp, or the creation time of the instrument if this is the first collection interval for the instrument.
The C++ SDK uses MeterContext::GetSDKStartTime() (the MeterProvider creation time) as start_ts for the first delta collection interval of all instruments, rather than the creation time of each individual instrument. This affects both the fast path and slow path in TemporalMetricStorage::buildMetrics.
Call chain:
MeterContext constructor:
sdk_start_ts_{std::chrono::system_clock::now()} // captured once
Meter::Collect():
metric_storage.second->Collect(collector, ctx->GetCollectors(), ctx->GetSDKStartTime(), ...)
SyncMetricStorage::Collect():
temporal_metric_storage_.buildMetrics(collector, collectors, sdk_start_ts, ...)
TemporalMetricStorage::buildMetrics():
// slow path, first interval:
opentelemetry::common::SystemTimestamp last_collection_ts = sdk_start_ts; // should be instrument creation time
All instruments receive the same sdk_start_ts regardless of when they were created.
Expected behavior:
If an instrument is created at T=30s and the MeterProvider was created at T=0, the first delta data point for that instrument should have start_time_unix_nano = T=30s.
Actual behavior:
The first delta data point has start_time_unix_nano = T=0 (MeterProvider creation time) regardless of when the instrument was created.
Impact:
Backends that use start_time_unix_nano for data point placement (e.g., New Relic maps it to the timestamp field) will show the first data point at the wrong time in charts. For instruments created well after SDK initialization, the first delta interval spans an artificially long window.
Reference implementation:
The Java SDK handles this correctly in DeltaSynchronousMetricStorage:
// Constructor stores per-instrument creation time
this.instrumentCreationEpochNanos = clock.now();
// Collection uses it as the fallback for the first interval
long startEpochNanos =
registeredReader.getLastCollectEpochNanosOrDefault(instrumentCreationEpochNanos);
Source: DefaultSynchronousMetricStorage.java
Suggested fix:
Store a creation timestamp in TemporalMetricStorage (or SyncMetricStorage) initialized to std::chrono::system_clock::now() at construction time. Use this instead of the passed-in sdk_start_ts for the first collection interval's start_ts.
Related: #4038 (fast path always using sdk_start_ts, even on subsequent exports)
Bug:
The OpenTelemetry spec states:
For delta aggregations, the start timestamp MUST equal the previous collection interval's timestamp, or the creation time of the instrument if this is the first collection interval for the instrument.
The C++ SDK uses
MeterContext::GetSDKStartTime()(the MeterProvider creation time) asstart_tsfor the first delta collection interval of all instruments, rather than the creation time of each individual instrument. This affects both the fast path and slow path inTemporalMetricStorage::buildMetrics.Call chain:
MeterContext constructor:
All instruments receive the same
sdk_start_tsregardless of when they were created.Expected behavior:
If an instrument is created at T=30s and the MeterProvider was created at T=0, the first delta data point for that instrument should have
start_time_unix_nano = T=30s.Actual behavior:
The first delta data point has
start_time_unix_nano = T=0(MeterProvider creation time) regardless of when the instrument was created.Impact:
Backends that use
start_time_unix_nanofor data point placement (e.g., New Relic maps it to thetimestampfield) will show the first data point at the wrong time in charts. For instruments created well after SDK initialization, the first delta interval spans an artificially long window.Reference implementation:
The Java SDK handles this correctly in
DeltaSynchronousMetricStorage:Source: DefaultSynchronousMetricStorage.java
Suggested fix:
Store a creation timestamp in
TemporalMetricStorage(orSyncMetricStorage) initialized tostd::chrono::system_clock::now()at construction time. Use this instead of the passed-insdk_start_tsfor the first collection interval'sstart_ts.Related: #4038 (fast path always using
sdk_start_ts, even on subsequent exports)