Skip to content

Commit 61981dc

Browse files
authored
Fix timing metric value different from span duration (#3368)
* removed timing from IMetricsAggregator * duration of timing metric of MetricsApi now equals span duration * local metrics aggregator is fetch directly from the span in the timing api
1 parent 7b7964f commit 61981dc

File tree

8 files changed

+71
-73
lines changed

8 files changed

+71
-73
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368))
8+
39
## 7.8.0
410

511
### Features

sentry/api/sentry.api

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,6 @@ public abstract interface class io/sentry/IMetricsAggregator : java/io/Closeable
634634
public abstract fun increment (Ljava/lang/String;DLio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
635635
public abstract fun set (Ljava/lang/String;ILio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
636636
public abstract fun set (Ljava/lang/String;Ljava/lang/String;Lio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
637-
public abstract fun timing (Ljava/lang/String;Ljava/lang/Runnable;Lio/sentry/MeasurementUnit$Duration;Ljava/util/Map;Lio/sentry/metrics/LocalMetricsAggregator;)V
638637
}
639638

640639
public abstract interface class io/sentry/IOptionsObserver {
@@ -1039,7 +1038,6 @@ public final class io/sentry/MetricsAggregator : io/sentry/IMetricsAggregator, j
10391038
public fun run ()V
10401039
public fun set (Ljava/lang/String;ILio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
10411040
public fun set (Ljava/lang/String;Ljava/lang/String;Lio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
1042-
public fun timing (Ljava/lang/String;Ljava/lang/Runnable;Lio/sentry/MeasurementUnit$Duration;Ljava/util/Map;Lio/sentry/metrics/LocalMetricsAggregator;)V
10431041
}
10441042

10451043
public final class io/sentry/MonitorConfig : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
@@ -3561,7 +3559,6 @@ public final class io/sentry/metrics/NoopMetricsAggregator : io/sentry/IMetricsA
35613559
public fun set (Ljava/lang/String;ILio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
35623560
public fun set (Ljava/lang/String;Ljava/lang/String;Lio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
35633561
public fun startSpanForMetric (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ISpan;
3564-
public fun timing (Ljava/lang/String;Ljava/lang/Runnable;Lio/sentry/MeasurementUnit$Duration;Ljava/util/Map;Lio/sentry/metrics/LocalMetricsAggregator;)V
35653562
}
35663563

35673564
public final class io/sentry/metrics/SetMetric : io/sentry/metrics/Metric {

sentry/src/main/java/io/sentry/IMetricsAggregator.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,21 +103,5 @@ void set(
103103
final long timestampMs,
104104
final @Nullable LocalMetricsAggregator localMetricsAggregator);
105105

106-
/**
107-
* Emits a distribution with the time it takes to run a given code block.
108-
*
109-
* @param key A unique key identifying the metric
110-
* @param callback The code block to measure
111-
* @param unit An optional unit, see {@link MeasurementUnit.Duration}, defaults to seconds
112-
* @param tags Optional Tags to associate with the metric
113-
* @param localMetricsAggregator The local metrics aggregator for creating span summaries
114-
*/
115-
void timing(
116-
final @NotNull String key,
117-
final @NotNull Runnable callback,
118-
final @NotNull MeasurementUnit.Duration unit,
119-
final @Nullable Map<String, String> tags,
120-
final @Nullable LocalMetricsAggregator localMetricsAggregator);
121-
122106
void flush(boolean force);
123107
}

sentry/src/main/java/io/sentry/MetricsAggregator.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,24 +141,6 @@ public void set(
141141
add(MetricType.Set, key, intValue, unit, tags, timestampMs, localMetricsAggregator);
142142
}
143143

144-
@Override
145-
public void timing(
146-
final @NotNull String key,
147-
final @NotNull Runnable callback,
148-
final @NotNull MeasurementUnit.Duration unit,
149-
final @Nullable Map<String, String> tags,
150-
final @Nullable LocalMetricsAggregator localMetricsAggregator) {
151-
final long startMs = nowMillis();
152-
final long startNanos = System.nanoTime();
153-
try {
154-
callback.run();
155-
} finally {
156-
final long durationNanos = (System.nanoTime() - startNanos);
157-
final double value = MetricsHelper.convertNanosTo(unit, durationNanos);
158-
add(MetricType.Distribution, key, value, unit, tags, startMs, localMetricsAggregator);
159-
}
160-
}
161-
162144
@SuppressWarnings({"FutureReturnValueIgnored", "UnusedVariable"})
163145
private void add(
164146
final @NotNull MetricType type,

sentry/src/main/java/io/sentry/metrics/MetricsApi.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.sentry.IMetricsAggregator;
44
import io.sentry.ISpan;
55
import io.sentry.MeasurementUnit;
6+
import io.sentry.SentryDate;
7+
import io.sentry.SentryNanotimeDate;
68
import java.util.Map;
79
import org.jetbrains.annotations.ApiStatus;
810
import org.jetbrains.annotations.NotNull;
@@ -425,23 +427,38 @@ public void timing(
425427
unit != null ? unit : MeasurementUnit.Duration.SECOND;
426428
final @NotNull Map<String, String> enrichedTags =
427429
MetricsHelper.mergeTags(tags, aggregator.getDefaultTagsForMetrics());
428-
final @Nullable LocalMetricsAggregator localMetricsAggregator =
429-
aggregator.getLocalMetricsAggregator();
430+
final @Nullable LocalMetricsAggregator localMetricsAggregator;
430431

431432
final @Nullable ISpan span = aggregator.startSpanForMetric("metric.timing", key);
433+
// If the span was started, we take its local aggregator, otherwise we request another one.
434+
localMetricsAggregator =
435+
span != null ? span.getLocalMetricsAggregator() : aggregator.getLocalMetricsAggregator();
436+
432437
if (span != null && tags != null) {
433438
for (final @NotNull Map.Entry<String, String> entry : tags.entrySet()) {
434439
span.setTag(entry.getKey(), entry.getValue());
435440
}
436441
}
442+
final long timestamp = System.currentTimeMillis();
443+
final long startNanos = System.nanoTime();
437444
try {
438-
aggregator
439-
.getMetricsAggregator()
440-
.timing(key, callback, durationUnit, enrichedTags, localMetricsAggregator);
445+
callback.run();
441446
} finally {
447+
// If we have a span, the duration we emit is the same of the span, otherwise calculate it.
448+
final long durationNanos;
442449
if (span != null) {
443450
span.finish();
451+
// We finish the span, so we should have a finish date, but it's still nullable.
452+
final @NotNull SentryDate spanFinishDate =
453+
span.getFinishDate() != null ? span.getFinishDate() : new SentryNanotimeDate();
454+
durationNanos = spanFinishDate.diff(span.getStartDate());
455+
} else {
456+
durationNanos = System.nanoTime() - startNanos;
444457
}
458+
final double value = MetricsHelper.convertNanosTo(durationUnit, durationNanos);
459+
aggregator
460+
.getMetricsAggregator()
461+
.distribution(key, value, durationUnit, enrichedTags, timestamp, localMetricsAggregator);
445462
}
446463
}
447464
}

sentry/src/main/java/io/sentry/metrics/NoopMetricsAggregator.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,6 @@ public void set(
7575
// no-op
7676
}
7777

78-
@Override
79-
public void timing(
80-
final @NotNull String key,
81-
final @NotNull Runnable callback,
82-
final @NotNull MeasurementUnit.Duration unit,
83-
final @Nullable Map<String, String> tags,
84-
final @Nullable LocalMetricsAggregator localMetricsAggregator) {
85-
callback.run();
86-
}
87-
8878
@Override
8979
public void flush(final boolean force) {
9080
// no-op

sentry/src/test/java/io/sentry/MetricsAggregatorTest.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -268,21 +268,12 @@ class MetricsAggregatorTest {
268268
20_001,
269269
null
270270
)
271-
aggregator.timing(
272-
"name0",
273-
{
274-
Thread.sleep(2)
275-
},
276-
MeasurementUnit.Duration.SECOND,
277-
mapOf("key0" to "value0"),
278-
null
279-
)
280271

281272
aggregator.flush(true)
282273
verify(fixture.client).captureMetrics(
283274
check {
284275
val metrics = MetricsHelperTest.parseMetrics(it.encodeToStatsd())
285-
assertEquals(6, metrics.size)
276+
assertEquals(5, metrics.size)
286277
}
287278
)
288279
}

sentry/src/test/java/io/sentry/metrics/MetricsApiTest.kt

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package io.sentry.metrics
22

3+
import io.sentry.DateUtils
34
import io.sentry.IMetricsAggregator
45
import io.sentry.ISpan
56
import io.sentry.MeasurementUnit
7+
import io.sentry.SentryNanotimeDate
68
import io.sentry.metrics.MetricsApi.IMetricsInterface
79
import org.mockito.kotlin.any
810
import org.mockito.kotlin.anyOrNull
911
import org.mockito.kotlin.eq
1012
import org.mockito.kotlin.mock
1113
import org.mockito.kotlin.never
1214
import org.mockito.kotlin.verify
15+
import org.mockito.kotlin.whenever
1316
import kotlin.test.Test
1417
import kotlin.test.assertEquals
1518

@@ -25,7 +28,12 @@ class MetricsApiTest {
2528

2629
fun getSut(
2730
defaultTags: Map<String, String> = emptyMap(),
28-
spanProvider: () -> ISpan? = { mock<ISpan>() }
31+
spanProvider: () -> ISpan? = {
32+
val span = mock<ISpan>()
33+
val date = SentryNanotimeDate()
34+
whenever(span.startDate).thenReturn(date)
35+
span
36+
}
2937
): MetricsApi {
3038
val localAggregator = localMetricsAggregator
3139

@@ -280,12 +288,14 @@ class MetricsApiTest {
280288
api.timing("timing") {
281289
// no-op
282290
}
283-
verify(fixture.aggregator).timing(
291+
val spanLocalAggregator = fixture.lastSpan!!.localMetricsAggregator
292+
verify(fixture.aggregator).distribution(
284293
anyOrNull(),
285294
anyOrNull(),
286295
anyOrNull(),
287296
anyOrNull(),
288-
eq(fixture.localMetricsAggregator)
297+
anyOrNull(),
298+
eq(spanLocalAggregator)
289299
)
290300
}
291301

@@ -304,16 +314,37 @@ class MetricsApiTest {
304314
}
305315

306316
@Test
307-
fun `timing applies metric tags as span tags`() {
317+
fun `timing value equals span duration`() {
318+
val startDate = SentryNanotimeDate()
319+
val finishNanos = startDate.nanoTimestamp() + 10000
320+
val finishDate = SentryNanotimeDate(DateUtils.nanosToDate(finishNanos), finishNanos)
321+
val localAggregator = mock<LocalMetricsAggregator>()
308322
val span = mock<ISpan>()
309-
val api = fixture.getSut(
310-
spanProvider = {
311-
span
312-
},
313-
defaultTags = mapOf(
314-
"release" to "1.0"
315-
)
323+
whenever(span.startDate).thenReturn(startDate)
324+
whenever(span.finishDate).thenReturn(finishDate)
325+
whenever(span.localMetricsAggregator).thenReturn(localAggregator)
326+
327+
val api = fixture.getSut(spanProvider = { span })
328+
329+
api.timing("key") {
330+
// no-op
331+
}
332+
333+
assertEquals("metric.timing", fixture.lastOp)
334+
assertEquals("key", fixture.lastDescription)
335+
verify(fixture.aggregator).distribution(
336+
eq("key"),
337+
eq((finishDate.diff(startDate)) / 1000000000.0),
338+
eq(MeasurementUnit.Duration.SECOND),
339+
anyOrNull(),
340+
anyOrNull(),
341+
eq(localAggregator)
316342
)
343+
}
344+
345+
@Test
346+
fun `timing applies metric tags as span tags`() {
347+
val api = fixture.getSut(defaultTags = mapOf("release" to "1.0"))
317348
// when timing is called
318349
api.timing("key", {
319350
// no-op

0 commit comments

Comments
 (0)