diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae2f17083..705faeaa3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add support for measurements at span level ([#3219](https://github.com/getsentry/sentry-java/pull/3219)) - Add `enableScopePersistence` option to disable `PersistingScopeObserver` used for ANR reporting which may increase performance overhead. Defaults to `true` ([#3218](https://github.com/getsentry/sentry-java/pull/3218)) - When disabled, the SDK will not enrich ANRv2 events with scope data (e.g. breadcrumbs, user, tags, etc.) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java index 00653b622e..4f442f566b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -248,7 +249,8 @@ private static SentrySpan timeSpanToSentrySpan( span.getDescription(), SpanStatus.OK, APP_METRICS_ORIGIN, - new HashMap<>(), + new ConcurrentHashMap<>(), + new ConcurrentHashMap<>(), defaultSpanData); } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt index 9aaeb04770..3f2c659e45 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt @@ -242,6 +242,7 @@ class PerformanceAndroidEventProcessorTest { SpanStatus.OK, null, emptyMap(), + emptyMap(), null ) tr.spans.add(appStartSpan) @@ -337,6 +338,7 @@ class PerformanceAndroidEventProcessorTest { SpanStatus.OK, null, emptyMap(), + emptyMap(), null ) tr.spans.add(appStartSpan) @@ -386,6 +388,7 @@ class PerformanceAndroidEventProcessorTest { SpanStatus.OK, null, emptyMap(), + emptyMap(), null ) tr.spans.add(appStartSpan) @@ -431,6 +434,7 @@ class PerformanceAndroidEventProcessorTest { SpanStatus.OK, null, emptyMap(), + emptyMap(), null ) tr.spans.add(appStartSpan) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index 93d859449c..da52c72a68 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -267,7 +267,10 @@ protected void onResume() { screenLoadCount++; final ISpan span = Sentry.getSpan(); if (span != null) { - span.setMeasurement("screen_load_count", screenLoadCount, new MeasurementUnit.Custom("test")); + ISpan measurementSpan = span.startChild("screen_load_measurement", "test measurement"); + measurementSpan.setMeasurement( + "screen_load_count", screenLoadCount, new MeasurementUnit.Custom("test")); + measurementSpan.finish(); } Sentry.reportFullyDisplayed(); } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 6f79266d2d..1a6056fcd0 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2501,6 +2501,8 @@ public final class io/sentry/SentryTracer : io/sentry/ITransaction { public fun setDescription (Ljava/lang/String;)V public fun setMeasurement (Ljava/lang/String;Ljava/lang/Number;)V public fun setMeasurement (Ljava/lang/String;Ljava/lang/Number;Lio/sentry/MeasurementUnit;)V + public fun setMeasurementFromChild (Ljava/lang/String;Ljava/lang/Number;)V + public fun setMeasurementFromChild (Ljava/lang/String;Ljava/lang/Number;Lio/sentry/MeasurementUnit;)V public fun setName (Ljava/lang/String;)V public fun setName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V public fun setOperation (Ljava/lang/String;)V @@ -2605,6 +2607,7 @@ public final class io/sentry/Span : io/sentry/ISpan { public fun getData (Ljava/lang/String;)Ljava/lang/Object; public fun getDescription ()Ljava/lang/String; public fun getFinishDate ()Lio/sentry/SentryDate; + public fun getMeasurements ()Ljava/util/Map; public fun getOperation ()Ljava/lang/String; public fun getParentSpanId ()Lio/sentry/SpanId; public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision; @@ -4388,9 +4391,10 @@ public final class io/sentry/protocol/SentryRuntime$JsonKeys { public final class io/sentry/protocol/SentrySpan : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun (Lio/sentry/Span;)V public fun (Lio/sentry/Span;Ljava/util/Map;)V - public fun (Ljava/lang/Double;Ljava/lang/Double;Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Ljava/lang/String;Ljava/lang/String;Lio/sentry/SpanStatus;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V + public fun (Ljava/lang/Double;Ljava/lang/Double;Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Ljava/lang/String;Ljava/lang/String;Lio/sentry/SpanStatus;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V public fun getData ()Ljava/util/Map; public fun getDescription ()Ljava/lang/String; + public fun getMeasurements ()Ljava/util/Map; public fun getOp ()Ljava/lang/String; public fun getOrigin ()Ljava/lang/String; public fun getParentSpanId ()Lio/sentry/SpanId; @@ -4415,6 +4419,7 @@ public final class io/sentry/protocol/SentrySpan$Deserializer : io/sentry/JsonDe public final class io/sentry/protocol/SentrySpan$JsonKeys { public static final field DATA Ljava/lang/String; public static final field DESCRIPTION Ljava/lang/String; + public static final field MEASUREMENTS Ljava/lang/String; public static final field OP Ljava/lang/String; public static final field ORIGIN Ljava/lang/String; public static final field PARENT_SPAN_ID Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 8c1536cbbf..bcf7b016c5 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -1,7 +1,6 @@ package io.sentry; import io.sentry.protocol.Contexts; -import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.TransactionNameSource; @@ -13,7 +12,6 @@ import java.util.Map; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -48,7 +46,6 @@ public final class SentryTracer implements ITransaction { private final @NotNull Baggage baggage; private @NotNull TransactionNameSource transactionNameSource; - private final @NotNull Map measurements; private final @NotNull Instrumenter instrumenter; private final @NotNull Contexts contexts = new Contexts(); private final @Nullable TransactionPerformanceCollector transactionPerformanceCollector; @@ -72,7 +69,6 @@ public SentryTracer( final @Nullable TransactionPerformanceCollector transactionPerformanceCollector) { Objects.requireNonNull(context, "context is required"); Objects.requireNonNull(hub, "hub is required"); - this.measurements = new ConcurrentHashMap<>(); this.root = new Span(context, this, hub, transactionOptions.getStartTimestamp(), transactionOptions); @@ -263,7 +259,7 @@ public void finish( return; } - transaction.getMeasurements().putAll(measurements); + transaction.getMeasurements().putAll(root.getMeasurements()); hub.captureTransaction(transaction, traceContext(), hint, profilingTraceData); } } @@ -619,6 +615,12 @@ private boolean hasAllChildrenFinished() { @Override public void setOperation(final @NotNull String operation) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The transaction is already finished. Operation %s cannot be set", + operation); return; } @@ -633,6 +635,12 @@ public void setOperation(final @NotNull String operation) { @Override public void setDescription(final @Nullable String description) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The transaction is already finished. Description %s cannot be set", + description); return; } @@ -647,6 +655,12 @@ public void setDescription(final @Nullable String description) { @Override public void setStatus(final @Nullable SpanStatus status) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The transaction is already finished. Status %s cannot be set", + status == null ? "null" : status.name()); return; } @@ -661,6 +675,9 @@ public void setStatus(final @Nullable SpanStatus status) { @Override public void setThrowable(final @Nullable Throwable throwable) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log(SentryLevel.DEBUG, "The transaction is already finished. Throwable cannot be set"); return; } @@ -680,6 +697,9 @@ public void setThrowable(final @Nullable Throwable throwable) { @Override public void setTag(final @NotNull String key, final @NotNull String value) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log(SentryLevel.DEBUG, "The transaction is already finished. Tag %s cannot be set", key); return; } @@ -699,6 +719,10 @@ public boolean isFinished() { @Override public void setData(@NotNull String key, @NotNull Object value) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, "The transaction is already finished. Data %s cannot be set", key); return; } @@ -710,13 +734,28 @@ public void setData(@NotNull String key, @NotNull Object value) { return this.root.getData(key); } - @Override - public void setMeasurement(final @NotNull String name, final @NotNull Number value) { - if (root.isFinished()) { - return; + @ApiStatus.Internal + public void setMeasurementFromChild(final @NotNull String name, final @NotNull Number value) { + // We don't want to overwrite the root span measurement, if it comes from a child. + if (!root.getMeasurements().containsKey(name)) { + setMeasurement(name, value); } + } - this.measurements.put(name, new MeasurementValue(value, null)); + @ApiStatus.Internal + public void setMeasurementFromChild( + final @NotNull String name, + final @NotNull Number value, + final @NotNull MeasurementUnit unit) { + // We don't want to overwrite the root span measurement, if it comes from a child. + if (!root.getMeasurements().containsKey(name)) { + setMeasurement(name, value, unit); + } + } + + @Override + public void setMeasurement(final @NotNull String name, final @NotNull Number value) { + root.setMeasurement(name, value); } @Override @@ -724,11 +763,7 @@ public void setMeasurement( final @NotNull String name, final @NotNull Number value, final @NotNull MeasurementUnit unit) { - if (root.isFinished()) { - return; - } - - this.measurements.put(name, new MeasurementValue(value, unit.apiName())); + root.setMeasurement(name, value, unit); } public @Nullable Map getData() { @@ -759,6 +794,12 @@ public void setName(@NotNull String name) { @Override public void setName(@NotNull String name, @NotNull TransactionNameSource transactionNameSource) { if (root.isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The transaction is already finished. Name %s cannot be set", + name); return; } @@ -834,12 +875,6 @@ AtomicBoolean isDeadlineTimerRunning() { return isDeadlineTimerRunning; } - @TestOnly - @NotNull - Map getMeasurements() { - return measurements; - } - @ApiStatus.Internal @Override public void setContext(final @NotNull String key, final @NotNull Object context) { diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index 6ab9e3417c..24e803158e 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; import io.sentry.util.Objects; import java.util.ArrayList; @@ -41,6 +42,7 @@ public final class Span implements ISpan { private @Nullable SpanFinishedCallback spanFinishedCallback; private final @NotNull Map data = new ConcurrentHashMap<>(); + private final @NotNull Map measurements = new ConcurrentHashMap<>(); Span( final @NotNull SentryId traceId, @@ -323,24 +325,59 @@ public Map getTags() { } @Override - public void setData(@NotNull String key, @NotNull Object value) { + public void setData(final @NotNull String key, final @NotNull Object value) { data.put(key, value); } @Override - public @Nullable Object getData(@NotNull String key) { + public @Nullable Object getData(final @NotNull String key) { return data.get(key); } @Override - public void setMeasurement(@NotNull String name, @NotNull Number value) { - this.transaction.setMeasurement(name, value); + public void setMeasurement(final @NotNull String name, final @NotNull Number value) { + if (isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The span is already finished. Measurement %s cannot be set", + name); + return; + } + this.measurements.put(name, new MeasurementValue(value, null)); + // We set the measurement in the transaction, too, but we have to check if this is the root span + // of the transaction, to avoid an infinite recursion + if (transaction.getRoot() != this) { + transaction.setMeasurementFromChild(name, value); + } } @Override public void setMeasurement( - @NotNull String name, @NotNull Number value, @NotNull MeasurementUnit unit) { - this.transaction.setMeasurement(name, value, unit); + final @NotNull String name, + final @NotNull Number value, + final @NotNull MeasurementUnit unit) { + if (isFinished()) { + hub.getOptions() + .getLogger() + .log( + SentryLevel.DEBUG, + "The span is already finished. Measurement %s cannot be set", + name); + return; + } + this.measurements.put(name, new MeasurementValue(value, unit.apiName())); + // We set the measurement in the transaction, too, but we have to check if this is the root span + // of the transaction, to avoid an infinite recursion + if (transaction.getRoot() != this) { + transaction.setMeasurementFromChild(name, value, unit); + } + } + + @NotNull + public Map getMeasurements() { + return measurements; } @Override diff --git a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java index b627aa4c9f..4acce5d45e 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java +++ b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java @@ -40,6 +40,8 @@ public final class SentrySpan implements JsonUnknown, JsonSerializable { private final @NotNull Map tags; private final @Nullable Map data; + private @NotNull final Map measurements; + @SuppressWarnings("unused") private @Nullable Map unknown; @@ -59,6 +61,9 @@ public SentrySpan(final @NotNull Span span, final @Nullable Map this.origin = span.getSpanContext().getOrigin(); final Map tagsCopy = CollectionUtils.newConcurrentHashMap(span.getTags()); this.tags = tagsCopy != null ? tagsCopy : new ConcurrentHashMap<>(); + final Map measurementsCopy = + CollectionUtils.newConcurrentHashMap(span.getMeasurements()); + this.measurements = measurementsCopy != null ? measurementsCopy : new ConcurrentHashMap<>(); // we lose precision here, from potential nanosecond precision down to 10 microsecond precision this.timestamp = span.getFinishDate() == null @@ -82,6 +87,7 @@ public SentrySpan( @Nullable SpanStatus status, @Nullable String origin, @NotNull Map tags, + @NotNull Map measurements, @Nullable Map data) { this.startTimestamp = startTimestamp; this.timestamp = timestamp; @@ -93,6 +99,7 @@ public SentrySpan( this.status = status; this.tags = tags; this.data = data; + this.measurements = measurements; this.origin = origin; } @@ -144,6 +151,10 @@ public boolean isFinished() { return origin; } + public @NotNull Map getMeasurements() { + return measurements; + } + // JsonSerializable public static final class JsonKeys { @@ -158,6 +169,7 @@ public static final class JsonKeys { public static final String ORIGIN = "origin"; public static final String TAGS = "tags"; public static final String DATA = "data"; + public static final String MEASUREMENTS = "measurements"; } @Override @@ -189,6 +201,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (data != null) { writer.name(JsonKeys.DATA).value(logger, data); } + if (!measurements.isEmpty()) { + writer.name(JsonKeys.MEASUREMENTS).value(logger, measurements); + } if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -233,6 +248,7 @@ public static final class Deserializer implements JsonDeserializer { String origin = null; Map tags = null; Map data = null; + Map measurements = null; Map unknown = null; while (reader.peek() == JsonToken.NAME) { @@ -281,6 +297,9 @@ public static final class Deserializer implements JsonDeserializer { case JsonKeys.DATA: data = (Map) reader.nextObjectOrNull(); break; + case JsonKeys.MEASUREMENTS: + measurements = reader.nextMapOrNull(logger, new MeasurementValue.Deserializer()); + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); @@ -304,6 +323,9 @@ public static final class Deserializer implements JsonDeserializer { if (tags == null) { tags = new HashMap<>(); } + if (measurements == null) { + measurements = new HashMap<>(); + } SentrySpan sentrySpan = new SentrySpan( startTimestamp, @@ -316,6 +338,7 @@ public static final class Deserializer implements JsonDeserializer { status, origin, tags, + measurements, data); sentrySpan.setUnknown(unknown); reader.endObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index d0c9271a26..f7bde300a4 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -115,6 +115,9 @@ public SentryTransaction( this.timestamp = timestamp; this.spans.addAll(spans); this.measurements.putAll(measurements); + for (SentrySpan span : spans) { + this.measurements.putAll(span.getMeasurements()); + } this.transactionInfo = transactionInfo; } diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index a894fcfff3..8dc4f804bc 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1019,6 +1019,8 @@ class JsonSerializerTest { assertEquals("value1", it) } } + assertEquals(1, deserialized?.measurements?.get("test_measurement")?.value) + assertEquals("test", deserialized?.measurements?.get("test_measurement")?.unit) } @Test @@ -1300,6 +1302,7 @@ class JsonSerializerTest { } val tracer = SentryTracer(trace, fixture.hub) val span = tracer.startChild("child") + span.setMeasurement("test_measurement", 1, MeasurementUnit.Custom("test")) span.finish(SpanStatus.OK) tracer.finish() return span diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 37a3d09cca..f92e90799c 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -465,7 +465,7 @@ class SentryTracerTest { assertEquals("desc", transaction.description) assertEquals("myValue", transaction.getTag("myTag")) assertEquals("myValue", transaction.getData("myData")) - assertEquals(1.0f, transaction.measurements["myMetric"]!!.value) + assertEquals(1.0f, transaction.root.measurements["myMetric"]!!.value) assertEquals("name", transaction.name) assertEquals(ex, transaction.throwable) } @@ -986,6 +986,24 @@ class SentryTracerTest { ) } + @Test + fun `setting the same measurement multiple times from a child only keeps first value`() { + val transaction = fixture.getSut() + transaction.setMeasurementFromChild("metric1", 1.0f) + transaction.setMeasurementFromChild("metric1", 2, MeasurementUnit.Duration.DAY) + transaction.finish() + + verify(fixture.hub).captureTransaction( + check { + assertEquals(1.0f, it.measurements["metric1"]!!.value) + assertNull(it.measurements["metric1"]!!.unit) + }, + anyOrNull(), + anyOrNull(), + anyOrNull() + ) + } + @Test fun `when transaction is created, but not profiled, transactionPerformanceCollector is not started`() { val transaction = fixture.getSut() diff --git a/sentry/src/test/java/io/sentry/SpanTest.kt b/sentry/src/test/java/io/sentry/SpanTest.kt index e74c3feb89..bad7d85afe 100644 --- a/sentry/src/test/java/io/sentry/SpanTest.kt +++ b/sentry/src/test/java/io/sentry/SpanTest.kt @@ -1,8 +1,12 @@ package io.sentry import io.sentry.protocol.SentryId +import io.sentry.test.injectForField import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.test.Test @@ -439,6 +443,49 @@ class SpanTest { assertEquals(endDate, span.finishDate) } + @Test + fun `setMeasurement sets a measurement`() { + val span = fixture.getSut() + span.setMeasurement("test", 1) + assertNotNull(span.measurements["test"]) + assertEquals(1, span.measurements["test"]!!.value) + } + + @Test + fun `setMeasurement does not set a measurement if a span is finished`() { + val span = fixture.getSut() + span.finish() + span.setMeasurement("test", 1) + assertTrue(span.measurements.isEmpty()) + } + + @Test + fun `setMeasurement also set a measurement to the transaction root span`() { + val transaction = spy(getTransaction()) + val span = transaction.startChild("op") as Span + // We need to inject the mock, otherwise the span calls the real transaction object + span.injectForField("transaction", transaction) + span.setMeasurement("test", 1) + verify(transaction).setMeasurementFromChild(eq("test"), eq(1)) + verify(transaction).setMeasurement(eq("test"), eq(1)) + assertNotNull(span.measurements["test"]) + assertEquals(1, span.measurements["test"]!!.value) + assertNotNull(transaction.root.measurements["test"]) + assertEquals(1, transaction.root.measurements["test"]!!.value) + } + + @Test + fun `setMeasurement on transaction root span does not call transaction setMeasurement to avoid infinite recursion`() { + val transaction = spy(getTransaction()) + // We need to inject the mock, otherwise the span calls the real transaction object + transaction.root.injectForField("transaction", transaction) + transaction.root.setMeasurement("test", 1) + verify(transaction, never()).setMeasurementFromChild(any(), any()) + verify(transaction, never()).setMeasurementFromChild(any(), any(), any()) + assertNotNull(transaction.root.measurements["test"]) + assertEquals(1, transaction.root.measurements["test"]!!.value) + } + private fun getTransaction(transactionContext: TransactionContext = TransactionContext("name", "op")): SentryTracer { return SentryTracer(transactionContext, fixture.hub) } diff --git a/sentry/src/test/java/io/sentry/protocol/SentrySpanSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentrySpanSerializationTest.kt index a92d322eba..1612a993d4 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentrySpanSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentrySpanSerializationTest.kt @@ -29,6 +29,7 @@ class SentrySpanSerializationTest { SpanStatus.ALREADY_EXISTS, "auto.test.unit.span", mapOf("f1333f3a-916a-47b7-8dd6-d6d15fa96e03" to "d4a07684-5b3e-4d08-b605-f9364c398124"), + mapOf("test_measurement" to MeasurementValue(1, "test")), mapOf("518276a7-88d7-408f-ab36-af342f2d7715" to "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6") ) } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt index ed21426a57..cf9463191a 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt @@ -53,6 +53,8 @@ class SentryTransactionSerializationTest { val expected = sanitizedFile("json/sentry_transaction.json") val actual = serialize(fixture.getSut()) assertEquals(expected, actual) + // There are 1 measurement from the span and 2 from the transaction + assertEquals(3, fixture.getSut().measurements.size) } @Test diff --git a/sentry/src/test/resources/json/sentry_span.json b/sentry/src/test/resources/json/sentry_span.json index 1b0cc97092..85343ed442 100644 --- a/sentry/src/test/resources/json/sentry_span.json +++ b/sentry/src/test/resources/json/sentry_span.json @@ -15,6 +15,12 @@ "data": { "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + }, + "measurements": { + "test_measurement": { + "value": 1, + "unit": "test" + } } } diff --git a/sentry/src/test/resources/json/sentry_span_legacy_date_format.json b/sentry/src/test/resources/json/sentry_span_legacy_date_format.json index 9122eb6abf..b9a3edcb85 100644 --- a/sentry/src/test/resources/json/sentry_span_legacy_date_format.json +++ b/sentry/src/test/resources/json/sentry_span_legacy_date_format.json @@ -15,5 +15,11 @@ "data": { "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + }, + "measurements": { + "test_measurement": { + "value": 1, + "unit": "test" + } } } diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index 5b0f0af221..281eb05831 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -21,6 +21,14 @@ "data": { "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + }, + "measurements": + { + "test_measurement": + { + "value": 1, + "unit": "test" + } } } ], @@ -38,6 +46,11 @@ "value": 0.4000000059604645, "unit": "test2", "new_type": "newtype" + }, + "test_measurement": + { + "value": 1, + "unit": "test" } }, "transaction_info": { diff --git a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json index 679b3a624b..f562369259 100644 --- a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json +++ b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json @@ -21,6 +21,14 @@ "data": { "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + }, + "measurements": + { + "test_measurement": + { + "value": 1, + "unit": "test" + } } } ], @@ -38,6 +46,11 @@ "value": 0.4000000059604645, "unit": "test2", "new_type": "newtype" + }, + "test_measurement": + { + "value": 1, + "unit": "test" } }, "transaction_info": { diff --git a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json index c67f0d1b4f..48588534ae 100644 --- a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json +++ b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json @@ -20,6 +20,14 @@ "data": { "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + }, + "measurements": + { + "test_measurement": + { + "value": 1, + "unit": "test" + } } } ], @@ -29,6 +37,11 @@ "386384cb-1162-49e7-aea1-db913d4fca63": { "value": 0.30000001192092896 + }, + "test_measurement": + { + "value": 1, + "unit": "test" } }, "transaction_info": {