Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix timing metric value different from span duration #3368

Merged
merged 2 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368))

## 7.8.0

### Features
Expand Down
3 changes: 0 additions & 3 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,6 @@ public abstract interface class io/sentry/IMetricsAggregator : java/io/Closeable
public abstract fun increment (Ljava/lang/String;DLio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
public abstract fun set (Ljava/lang/String;ILio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
public abstract fun set (Ljava/lang/String;Ljava/lang/String;Lio/sentry/MeasurementUnit;Ljava/util/Map;JLio/sentry/metrics/LocalMetricsAggregator;)V
public abstract fun timing (Ljava/lang/String;Ljava/lang/Runnable;Lio/sentry/MeasurementUnit$Duration;Ljava/util/Map;Lio/sentry/metrics/LocalMetricsAggregator;)V
}

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

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

public final class io/sentry/metrics/SetMetric : io/sentry/metrics/Metric {
Expand Down
16 changes: 0 additions & 16 deletions sentry/src/main/java/io/sentry/IMetricsAggregator.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,5 @@ void set(
final long timestampMs,
final @Nullable LocalMetricsAggregator localMetricsAggregator);

/**
* Emits a distribution with the time it takes to run a given code block.
*
* @param key A unique key identifying the metric
* @param callback The code block to measure
* @param unit An optional unit, see {@link MeasurementUnit.Duration}, defaults to seconds
* @param tags Optional Tags to associate with the metric
* @param localMetricsAggregator The local metrics aggregator for creating span summaries
*/
void timing(
final @NotNull String key,
final @NotNull Runnable callback,
final @NotNull MeasurementUnit.Duration unit,
final @Nullable Map<String, String> tags,
final @Nullable LocalMetricsAggregator localMetricsAggregator);

void flush(boolean force);
}
18 changes: 0 additions & 18 deletions sentry/src/main/java/io/sentry/MetricsAggregator.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,24 +141,6 @@ public void set(
add(MetricType.Set, key, intValue, unit, tags, timestampMs, localMetricsAggregator);
}

@Override
public void timing(
final @NotNull String key,
final @NotNull Runnable callback,
final @NotNull MeasurementUnit.Duration unit,
final @Nullable Map<String, String> tags,
final @Nullable LocalMetricsAggregator localMetricsAggregator) {
final long startMs = nowMillis();
final long startNanos = System.nanoTime();
try {
callback.run();
} finally {
final long durationNanos = (System.nanoTime() - startNanos);
final double value = MetricsHelper.convertNanosTo(unit, durationNanos);
add(MetricType.Distribution, key, value, unit, tags, startMs, localMetricsAggregator);
}
}

@SuppressWarnings({"FutureReturnValueIgnored", "UnusedVariable"})
private void add(
final @NotNull MetricType type,
Expand Down
27 changes: 22 additions & 5 deletions sentry/src/main/java/io/sentry/metrics/MetricsApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import io.sentry.IMetricsAggregator;
import io.sentry.ISpan;
import io.sentry.MeasurementUnit;
import io.sentry.SentryDate;
import io.sentry.SentryNanotimeDate;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -425,23 +427,38 @@ public void timing(
unit != null ? unit : MeasurementUnit.Duration.SECOND;
final @NotNull Map<String, String> enrichedTags =
MetricsHelper.mergeTags(tags, aggregator.getDefaultTagsForMetrics());
final @Nullable LocalMetricsAggregator localMetricsAggregator =
aggregator.getLocalMetricsAggregator();
final @Nullable LocalMetricsAggregator localMetricsAggregator;

final @Nullable ISpan span = aggregator.startSpanForMetric("metric.timing", key);
// If the span was started, we take its local aggregator, otherwise we request another one.
localMetricsAggregator =
span != null ? span.getLocalMetricsAggregator() : aggregator.getLocalMetricsAggregator();

if (span != null && tags != null) {
for (final @NotNull Map.Entry<String, String> entry : tags.entrySet()) {
span.setTag(entry.getKey(), entry.getValue());
}
}
final long timestamp = System.currentTimeMillis();
final long startNanos = System.nanoTime();
try {
aggregator
.getMetricsAggregator()
.timing(key, callback, durationUnit, enrichedTags, localMetricsAggregator);
callback.run();
} finally {
// If we have a span, the duration we emit is the same of the span, otherwise calculate it.
final long durationNanos;
if (span != null) {
span.finish();
// We finish the span, so we should have a finish date, but it's still nullable.
final @NotNull SentryDate spanFinishDate =
span.getFinishDate() != null ? span.getFinishDate() : new SentryNanotimeDate();
durationNanos = spanFinishDate.diff(span.getStartDate());
} else {
durationNanos = System.nanoTime() - startNanos;
}
final double value = MetricsHelper.convertNanosTo(durationUnit, durationNanos);
aggregator
.getMetricsAggregator()
.distribution(key, value, durationUnit, enrichedTags, timestamp, localMetricsAggregator);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,6 @@ public void set(
// no-op
}

@Override
public void timing(
final @NotNull String key,
final @NotNull Runnable callback,
final @NotNull MeasurementUnit.Duration unit,
final @Nullable Map<String, String> tags,
final @Nullable LocalMetricsAggregator localMetricsAggregator) {
callback.run();
}

@Override
public void flush(final boolean force) {
// no-op
Expand Down
11 changes: 1 addition & 10 deletions sentry/src/test/java/io/sentry/MetricsAggregatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -268,21 +268,12 @@ class MetricsAggregatorTest {
20_001,
null
)
aggregator.timing(
"name0",
{
Thread.sleep(2)
},
MeasurementUnit.Duration.SECOND,
mapOf("key0" to "value0"),
null
)

aggregator.flush(true)
verify(fixture.client).captureMetrics(
check {
val metrics = MetricsHelperTest.parseMetrics(it.encodeToStatsd())
assertEquals(6, metrics.size)
assertEquals(5, metrics.size)
}
)
}
Expand Down
53 changes: 42 additions & 11 deletions sentry/src/test/java/io/sentry/metrics/MetricsApiTest.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package io.sentry.metrics

import io.sentry.DateUtils
import io.sentry.IMetricsAggregator
import io.sentry.ISpan
import io.sentry.MeasurementUnit
import io.sentry.SentryNanotimeDate
import io.sentry.metrics.MetricsApi.IMetricsInterface
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import kotlin.test.Test
import kotlin.test.assertEquals

Expand All @@ -25,7 +28,12 @@ class MetricsApiTest {

fun getSut(
defaultTags: Map<String, String> = emptyMap(),
spanProvider: () -> ISpan? = { mock<ISpan>() }
spanProvider: () -> ISpan? = {
val span = mock<ISpan>()
val date = SentryNanotimeDate()
whenever(span.startDate).thenReturn(date)
span
}
): MetricsApi {
val localAggregator = localMetricsAggregator

Expand Down Expand Up @@ -280,12 +288,14 @@ class MetricsApiTest {
api.timing("timing") {
// no-op
}
verify(fixture.aggregator).timing(
val spanLocalAggregator = fixture.lastSpan!!.localMetricsAggregator
verify(fixture.aggregator).distribution(
anyOrNull(),
anyOrNull(),
anyOrNull(),
anyOrNull(),
eq(fixture.localMetricsAggregator)
anyOrNull(),
eq(spanLocalAggregator)
)
}

Expand All @@ -304,16 +314,37 @@ class MetricsApiTest {
}

@Test
fun `timing applies metric tags as span tags`() {
fun `timing value equals span duration`() {
val startDate = SentryNanotimeDate()
val finishNanos = startDate.nanoTimestamp() + 10000
val finishDate = SentryNanotimeDate(DateUtils.nanosToDate(finishNanos), finishNanos)
val localAggregator = mock<LocalMetricsAggregator>()
val span = mock<ISpan>()
val api = fixture.getSut(
spanProvider = {
span
},
defaultTags = mapOf(
"release" to "1.0"
)
whenever(span.startDate).thenReturn(startDate)
whenever(span.finishDate).thenReturn(finishDate)
whenever(span.localMetricsAggregator).thenReturn(localAggregator)

val api = fixture.getSut(spanProvider = { span })

api.timing("key") {
// no-op
}

assertEquals("metric.timing", fixture.lastOp)
assertEquals("key", fixture.lastDescription)
verify(fixture.aggregator).distribution(
eq("key"),
eq((finishDate.diff(startDate)) / 1000000000.0),
eq(MeasurementUnit.Duration.SECOND),
anyOrNull(),
anyOrNull(),
eq(localAggregator)
)
}

@Test
fun `timing applies metric tags as span tags`() {
val api = fixture.getSut(defaultTags = mapOf("release" to "1.0"))
// when timing is called
api.timing("key", {
// no-op
Expand Down
Loading