From e2ea31c73c93aa587941cb7ea9254f853786db26 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Thu, 8 Sep 2022 08:15:58 +0200 Subject: [PATCH] Missing unit fields for Android measurements (#2204) Co-authored-by: Alexander Dinauer Co-authored-by: Alexander Dinauer Co-authored-by: Sentry Github Bot --- CHANGELOG.md | 1 + .../android/core/ActivityFramesTracker.java | 8 +- .../PerformanceAndroidEventProcessor.java | 4 +- .../android/core/ActivityFramesTrackerTest.kt | 3 + .../PerformanceAndroidEventProcessorTest.kt | 17 +- sentry/api/sentry.api | 12 +- .../main/java/io/sentry/JsonObjectReader.java | 22 ++ .../io/sentry/protocol/MeasurementValue.java | 86 ++++++- .../io/sentry/protocol/SentryTransaction.java | 4 +- .../java/io/sentry/JsonObjectReaderTest.kt | 29 +++ .../MeasurementValueSerializationTest.kt | 2 +- .../SentryTransactionSerializationTest.kt | 11 +- .../resources/json/measurement_value.json | 4 +- .../resources/json/sentry_transaction.json | 10 +- ...sentry_transaction_legacy_date_format.json | 19 +- ...entry_transaction_no_measurement_unit.json | 222 ++++++++++++++++++ 16 files changed, 432 insertions(+), 22 deletions(-) create mode 100644 sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json diff --git a/CHANGELOG.md b/CHANGELOG.md index be73191013..7b6a105add 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Fixed AbstractMethodError when getting Lifecycle ([#2228](https://github.com/getsentry/sentry-java/pull/2228)) +- Missing unit fields for Android measurements ([#2204](https://github.com/getsentry/sentry-java/pull/2204)) - Avoid sending empty profiles ([#2232](https://github.com/getsentry/sentry-java/pull/2232)) ## 6.4.1 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java index d61a33e119..d71cab5213 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java @@ -1,5 +1,7 @@ package io.sentry.android.core; +import static io.sentry.protocol.MeasurementValue.NONE_UNIT; + import android.app.Activity; import android.util.SparseIntArray; import androidx.core.app.FrameMetricsAggregator; @@ -102,9 +104,9 @@ public synchronized void setMetrics( return; } - final MeasurementValue tfValues = new MeasurementValue(totalFrames); - final MeasurementValue sfValues = new MeasurementValue(slowFrames); - final MeasurementValue ffValues = new MeasurementValue(frozenFrames); + final MeasurementValue tfValues = new MeasurementValue(totalFrames, NONE_UNIT); + final MeasurementValue sfValues = new MeasurementValue(slowFrames, NONE_UNIT); + final MeasurementValue ffValues = new MeasurementValue(frozenFrames, NONE_UNIT); final Map measurements = new HashMap<>(); measurements.put("frames_total", tfValues); measurements.put("frames_slow", sfValues); 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 9c09d24f87..a3fca88156 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 @@ -3,6 +3,7 @@ import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_COLD; import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_WARM; import static io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP; +import static io.sentry.protocol.MeasurementValue.MILLISECOND_UNIT; import io.sentry.EventProcessor; import io.sentry.Hint; @@ -65,7 +66,8 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { final Long appStartUpInterval = AppStartState.getInstance().getAppStartInterval(); // if appStartUpInterval is null, metrics are not ready to be sent if (appStartUpInterval != null) { - final MeasurementValue value = new MeasurementValue((float) appStartUpInterval); + final MeasurementValue value = + new MeasurementValue((float) appStartUpInterval, MILLISECOND_UNIT); final String appStartKey = AppStartState.getInstance().isColdStart() ? "app_start_cold" : "app_start_warm"; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt index 1a8d2b5c2c..0e3b0e437b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt @@ -42,6 +42,7 @@ class ActivityFramesTrackerTest { val totalFrames = metrics!!["frames_total"] assertEquals(totalFrames!!.value, 1f) + assertEquals(totalFrames.unit, "none") } @Test @@ -57,6 +58,7 @@ class ActivityFramesTrackerTest { val frozenFrames = metrics!!["frames_frozen"] assertEquals(frozenFrames!!.value, 5f) + assertEquals(frozenFrames.unit, "none") } @Test @@ -72,6 +74,7 @@ class ActivityFramesTrackerTest { val slowFrames = metrics!!["frames_slow"] assertEquals(slowFrames!!.value, 5f) + assertEquals(slowFrames.unit, "none") } @Test 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 4250419e2b..4ebac94573 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 @@ -10,10 +10,12 @@ import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext import io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP import io.sentry.protocol.MeasurementValue +import io.sentry.protocol.MeasurementValue.MILLISECOND_UNIT import io.sentry.protocol.SentryTransaction import java.util.Date import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertTrue class PerformanceAndroidEventProcessorTest { @@ -64,6 +66,19 @@ class PerformanceAndroidEventProcessorTest { assertTrue(tr.measurements.containsKey("app_start_warm")) } + @Test + fun `set app cold start unit measurement`() { + val sut = fixture.getSut() + + var tr = getTransaction() + setAppStart() + + tr = sut.process(tr, Hint()) + + val measurement = tr.measurements["app_start_cold"] + assertEquals("millisecond", measurement?.unit) + } + @Test fun `do not add app start metric twice`() { val sut = fixture.getSut() @@ -140,7 +155,7 @@ class PerformanceAndroidEventProcessorTest { val tracer = SentryTracer(context, fixture.hub) var tr = SentryTransaction(tracer) - val metrics = mapOf("frames_total" to MeasurementValue(1f)) + val metrics = mapOf("frames_total" to MeasurementValue(1f, MILLISECOND_UNIT)) whenever(fixture.activityFramesTracker.takeMetrics(any())).thenReturn(metrics) tr = sut.process(tr, Hint()) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 97b91ab3a4..bf2009f6b5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -517,6 +517,7 @@ public final class io/sentry/JsonObjectReader : io/sentry/vendor/gson/stream/Jso public fun nextIntegerOrNull ()Ljava/lang/Integer; public fun nextList (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/List; public fun nextLongOrNull ()Ljava/lang/Long; + public fun nextMapOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/util/Map; public fun nextObjectOrNull ()Ljava/lang/Object; public fun nextOrNull (Lio/sentry/ILogger;Lio/sentry/JsonDeserializer;)Ljava/lang/Object; public fun nextStringOrNull ()Ljava/lang/String; @@ -2366,10 +2367,16 @@ public final class io/sentry/protocol/Gpu$JsonKeys { public fun ()V } -public final class io/sentry/protocol/MeasurementValue : io/sentry/JsonSerializable { - public fun (F)V +public final class io/sentry/protocol/MeasurementValue : io/sentry/JsonSerializable, io/sentry/JsonUnknown { + public static final field MILLISECOND_UNIT Ljava/lang/String; + public static final field NONE_UNIT Ljava/lang/String; + public fun (FLjava/lang/String;)V + public fun (FLjava/lang/String;Ljava/util/Map;)V + public fun getUnit ()Ljava/lang/String; + public fun getUnknown ()Ljava/util/Map; public fun getValue ()F public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun setUnknown (Ljava/util/Map;)V } public final class io/sentry/protocol/MeasurementValue$Deserializer : io/sentry/JsonDeserializer { @@ -2379,6 +2386,7 @@ public final class io/sentry/protocol/MeasurementValue$Deserializer : io/sentry/ } public final class io/sentry/protocol/MeasurementValue$JsonKeys { + public static final field UNIT Ljava/lang/String; public static final field VALUE Ljava/lang/String; public fun ()V } diff --git a/sentry/src/main/java/io/sentry/JsonObjectReader.java b/sentry/src/main/java/io/sentry/JsonObjectReader.java index 16bb008a11..bff626f3ee 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectReader.java +++ b/sentry/src/main/java/io/sentry/JsonObjectReader.java @@ -6,6 +6,7 @@ import java.io.Reader; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; @@ -99,6 +100,27 @@ public void nextUnknown(ILogger logger, Map unknown, String name return list; } + public @Nullable Map nextMapOrNull( + @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws IOException { + if (peek() == JsonToken.NULL) { + nextNull(); + return null; + } + beginObject(); + Map map = new HashMap<>(); + do { + try { + String key = nextName(); + map.put(key, deserializer.deserialize(this, logger)); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Failed to deserialize object in map.", e); + } + } while (peek() == JsonToken.BEGIN_OBJECT || peek() == JsonToken.NAME); + + endObject(); + return map; + } + public @Nullable T nextOrNull( @NotNull ILogger logger, @NotNull JsonDeserializer deserializer) throws Exception { if (peek() == JsonToken.NULL) { diff --git a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java index c8d757c22c..c80b3a47ac 100644 --- a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java +++ b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java @@ -5,18 +5,41 @@ import io.sentry.JsonObjectReader; import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; +import io.sentry.JsonUnknown; +import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @ApiStatus.Internal -public final class MeasurementValue implements JsonSerializable { +public final class MeasurementValue implements JsonUnknown, JsonSerializable { + + public static final @NotNull String NONE_UNIT = "none"; + public static final @NotNull String MILLISECOND_UNIT = "millisecond"; + @SuppressWarnings("UnusedVariable") private final float value; - public MeasurementValue(final float value) { + private final @Nullable String unit; + + /** the unknown fields of breadcrumbs, internal usage only */ + private @Nullable Map unknown; + + public MeasurementValue(final float value, final @Nullable String unit) { this.value = value; + this.unit = unit; + } + + @TestOnly + public MeasurementValue( + final float value, final @Nullable String unit, final @Nullable Map unknown) { + this.value = value; + this.unit = unit; + this.unknown = unknown; } @TestOnly @@ -24,10 +47,26 @@ public float getValue() { return value; } + public @Nullable String getUnit() { + return unit; + } + + @Nullable + @Override + public Map getUnknown() { + return unknown; + } + + @Override + public void setUnknown(@Nullable Map unknown) { + this.unknown = unknown; + } + // JsonSerializable public static final class JsonKeys { public static final String VALUE = "value"; + public static final String UNIT = "unit"; } @Override @@ -35,6 +74,19 @@ public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.VALUE).value(value); + + if (unit != null) { + writer.name(JsonKeys.UNIT).value(unit); + } + + if (unknown != null) { + for (final String key : unknown.keySet()) { + final Object value = unknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } + writer.endObject(); } @@ -43,10 +95,34 @@ public static final class Deserializer implements JsonDeserializer unknown = null; + + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.VALUE: + value = reader.nextFloat(); + break; + case JsonKeys.UNIT: + unit = reader.nextStringOrNull(); + break; + default: + if (unknown == null) { + unknown = new ConcurrentHashMap<>(); + } + reader.nextUnknown(logger, unknown, nextName); + break; + } + } + reader.endObject(); - return measurementValue; + final MeasurementValue measurement = new MeasurementValue(value, unit); + measurement.setUnknown(unknown); + + return measurement; } } } diff --git a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index 29e31981da..a1e96fa360 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -280,8 +280,8 @@ public static final class Deserializer implements JsonDeserializer deserializedMeasurements = - (Map) reader.nextObjectOrNull(); + Map deserializedMeasurements = + reader.nextMapOrNull(logger, new MeasurementValue.Deserializer()); if (deserializedMeasurements != null) { transaction.measurements.putAll(deserializedMeasurements); } diff --git a/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt b/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt index 40a1518f37..c470d9681f 100644 --- a/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt +++ b/sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt @@ -146,6 +146,35 @@ class JsonObjectReaderTest { assertEquals(expected, actual) } + // nextMap + + @Test + fun `returns null for null map`() { + val jsonString = "{\"key\": null}" + val reader = fixture.getSut(jsonString) + reader.beginObject() + reader.nextName() + + assertNull(reader.nextMapOrNull(fixture.logger, Deserializable.Deserializer())) + } + + @Test + fun `returns map of deserializables`() { + val deserializableA = "{\"foo\": \"foo\", \"bar\": \"bar\"}" + val deserializableB = "{\"foo\": \"fooo\", \"bar\": \"baar\"}" + val jsonString = "{\"deserializable\": { \"a\":$deserializableA,\"b\":$deserializableB}}" + val reader = fixture.getSut(jsonString) + reader.beginObject() + reader.nextName() + + val expected = mapOf( + "a" to Deserializable("foo", "bar"), + "b" to Deserializable("fooo", "baar") + ) + val actual = reader.nextMapOrNull(fixture.logger, Deserializable.Deserializer()) + assertEquals(expected, actual) + } + // nextDateOrNull @Test diff --git a/sentry/src/test/java/io/sentry/protocol/MeasurementValueSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/MeasurementValueSerializationTest.kt index 1ba1c4c61a..660a115411 100644 --- a/sentry/src/test/java/io/sentry/protocol/MeasurementValueSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/MeasurementValueSerializationTest.kt @@ -16,7 +16,7 @@ class MeasurementValueSerializationTest { class Fixture { val logger = mock() // float cannot represent 0.3 correctly https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html - fun getSut() = MeasurementValue(0.30000001192092896f) + fun getSut(value: Float = 0.30000001192092896f, unit: String = "test") = MeasurementValue(value, unit, mapOf("new_type" to "newtype")) } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt index ce84665833..51cbd5ce71 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt @@ -25,7 +25,8 @@ class SentryTransactionSerializationTest { SentrySpanSerializationTest.Fixture().getSut() ), mapOf( - "386384cb-1162-49e7-aea1-db913d4fca63" to MeasurementValueSerializationTest.Fixture().getSut() + "386384cb-1162-49e7-aea1-db913d4fca63" to MeasurementValueSerializationTest.Fixture().getSut(), + "186384cb-1162-49e7-aea1-db913d4fca63" to MeasurementValueSerializationTest.Fixture().getSut(0.4000000059604645f, "test2") ), TransactionInfo(TransactionNameSource.CUSTOM.apiName()) ).apply { @@ -49,6 +50,14 @@ class SentryTransactionSerializationTest { assertEquals(expectedJson, actualJson) } + @Test + fun `deserialize without measurement unit`() { + val expectedJson = sanitizedFile("json/sentry_transaction_no_measurement_unit.json") + val actual = deserialize(expectedJson) + val actualJson = serialize(actual) + assertEquals(expectedJson, actualJson) + } + @Test fun `deserialize legacy date format and missing transaction name source`() { val expectedJson = sanitizedFile("json/sentry_transaction_legacy_date_format.json") diff --git a/sentry/src/test/resources/json/measurement_value.json b/sentry/src/test/resources/json/measurement_value.json index 4ab01ea243..a4166ff551 100644 --- a/sentry/src/test/resources/json/measurement_value.json +++ b/sentry/src/test/resources/json/measurement_value.json @@ -1,3 +1,5 @@ { - "value": 0.30000001192092896 + "value": 0.30000001192092896, + "unit": "test", + "new_type": "newtype" } diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index ec911371b7..2765a15f26 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -28,7 +28,15 @@ { "386384cb-1162-49e7-aea1-db913d4fca63": { - "value": 0.30000001192092896 + "value": 0.30000001192092896, + "unit": "test", + "new_type": "newtype" + }, + "186384cb-1162-49e7-aea1-db913d4fca63": + { + "value": 0.4000000059604645, + "unit": "test2", + "new_type": "newtype" } }, "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 e157a35255..f3265e862c 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 @@ -26,10 +26,21 @@ "type": "transaction", "measurements": { - "386384cb-1162-49e7-aea1-db913d4fca63": - { - "value": 0.30000001192092896 - } + "386384cb-1162-49e7-aea1-db913d4fca63": + { + "value": 0.30000001192092896, + "unit": "test", + "new_type": "newtype" + }, + "186384cb-1162-49e7-aea1-db913d4fca63": + { + "value": 0.4000000059604645, + "unit": "test2", + "new_type": "newtype" + } + }, + "transaction_info": { + "source": "custom" }, "event_id": "afcb46b1140ade5187c4bbb5daa804df", "contexts": 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 new file mode 100644 index 0000000000..ec911371b7 --- /dev/null +++ b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json @@ -0,0 +1,222 @@ +{ + "transaction": "e54578ec-c9a8-4bce-8e3c-839e6c058fed", + "start_timestamp": 916613796.000000, + "timestamp": 919990124.000000, + "spans": + [ + { + "start_timestamp": 943149968.000000, + "timestamp": 923768643.000000, + "trace_id": "5b1f73d39486827b9e60ceb1fc23277a", + "span_id": "4584593a-5d9b-4a55-931f-cfe89c93907d", + "parent_span_id": "57518091-aed1-47a6-badf-11696035b5f4", + "op": "42e6bd1a-c45e-414d-8066-ed5196fbc686", + "description": "b026345e-ecd1-4555-8d6c-cd6d9f865c89", + "status": "already_exists", + "tags": + { + "f1333f3a-916a-47b7-8dd6-d6d15fa96e03": "d4a07684-5b3e-4d08-b605-f9364c398124" + }, + "data": + { + "518276a7-88d7-408f-ab36-af342f2d7715": "4a1c2d6c-3f49-41cc-b2ca-d1b36f7ea5a6" + } + } + ], + "type": "transaction", + "measurements": + { + "386384cb-1162-49e7-aea1-db913d4fca63": + { + "value": 0.30000001192092896 + } + }, + "transaction_info": { + "source": "custom" + }, + "event_id": "afcb46b1140ade5187c4bbb5daa804df", + "contexts": + { + "app": + { + "app_identifier": "3b7a3313-53b4-43f4-a6a1-7a7c36a9b0db", + "app_start_time": "1918-11-17T07:46:04.000Z", + "device_app_hash": "3d1fcf36-2c25-4378-bdf8-1e65239f1df4", + "build_type": "d78c56cd-eb0f-4213-8899-cd10ddf20763", + "app_name": "873656fd-f620-4edf-bb7a-a0d13325dba0", + "app_version": "801aab22-ad4b-44fb-995c-bacb5387e20c", + "app_build": "660f0cde-eedb-49dc-a973-8aa1c04f4a28", + "permissions": + { + "WRITE_EXTERNAL_STORAGE": "not_granted", + "CAMERA": "granted" + } + }, + "browser": + { + "name": "e1c723db-7408-4043-baa7-f4e96234e5dc", + "version": "724a48e9-2d35-416b-9f79-132beba2473a" + }, + "device": + { + "name": "83f1de77-fdb0-470e-8249-8f5c5d894ec4", + "manufacturer": "e21b2405-e378-4a0b-ad2c-4822d97cd38c", + "brand": "1abbd13e-d1ca-4d81-bd1b-24aa2c339cf9", + "family": "67a4b8ea-6c38-4c33-8579-7697f538685c", + "model": "d6ca2f35-bcc5-4dd3-ad64-7c3b585e02fd", + "model_id": "d3f133bd-b0a2-4aa4-9eed-875eba93652e", + "archs": + [ + "856e5da3-774c-4663-a830-d19f0b7dbb5b", + "b345bd5a-90a5-4301-a5a2-6c102d7589b6", + "fd7ed64e-a591-49e0-8dc1-578234356d23", + "8cec4101-0305-480b-91ee-f3c007f668c3", + "22583b9b-195e-49bf-bfe8-825ae3a346f2", + "8675b7aa-5b94-42d0-bc14-72ea1bb7112e" + ], + "battery_level": 0.45770407, + "charging": false, + "online": true, + "orientation": "portrait", + "simulator": true, + "memory_size": -6712323365568152393, + "free_memory": -953384122080236886, + "usable_memory": -8999512249221323968, + "low_memory": false, + "storage_size": -3227905175393990709, + "free_storage": -3749039933924297357, + "external_storage_size": -7739608324159255302, + "external_free_storage": -1562576688560812557, + "screen_width_pixels": 1101873181, + "screen_height_pixels": 1902392170, + "screen_density": 0.9829039, + "screen_dpi": -2092079070, + "boot_time": "2004-11-04T08:38:00.000Z", + "timezone": "Europe/Vienna", + "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", + "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", + "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", + "battery_temperature": 0.14775127 + }, + "gpu": + { + "name": "d623a6b5-e1ab-4402-931b-c06f5a43a5ae", + "id": -596576280, + "vendor_id": 1874778041, + "vendor_name": "d732cf76-07dc-48e2-8920-96d6bfc2439d", + "memory_size": -1484004451, + "api_type": "95dfc8bc-88ae-4d66-b85f-6c88ad45b80f", + "multi_threaded_rendering": true, + "version": "3f3f73c3-83a2-423a-8a6f-bb3de0d4a6ae", + "npot_support": "e06b074a-463c-45de-a959-cbabd461d99d" + }, + "os": + { + "name": "686a11a8-eae7-4393-aa10-a1368d523cb2", + "version": "3033f32d-6a27-4715-80c8-b232ce84ca61", + "raw_description": "eb2d0c5e-f5d4-49c7-b876-d8a654ee87cf", + "build": "bd197b97-eb68-49c3-9d07-ef789caf3069", + "kernel_version": "1df24aec-3a6f-49a9-8b50-69ae5f9dde08", + "rooted": true + }, + "runtime": + { + "name": "4ed019c4-9af9-43e0-830e-bfde9fe4461c", + "version": "16534f6b-1670-4bb8-aec2-647a1b97669b", + "raw_description": "773b5b05-a0f9-4ee6-9f3b-13155c37ad6e" + }, + "trace": + { + "trace_id": "afcb46b1140ade5187c4bbb5daa804df", + "span_id": "bf6b582d-8ce3-412b-a334-f4c5539b9602", + "parent_span_id": "c7500f2a-d4e6-4f5f-a0f4-6bb67e98d5a2", + "op": "e481581d-35a4-4e97-8a1c-b554bf49f23e", + "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", + "status": "resource_exhausted", + "tags": + { + "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", + "29106d7d-7fa4-444f-9d34-b9d7510c69ab": "218c23ea-694a-497e-bf6d-e5f26f1ad7bd", + "ba9ce913-269f-4c03-882d-8ca5e6991b14": "35a74e90-8db8-4610-a411-872cbc1030ac" + } + } + }, + "sdk": + { + "name": "3e934135-3f2b-49bc-8756-9f025b55143e", + "version": "3e31738e-4106-42d0-8be2-4a3a1bc648d3", + "packages": + [ + { + "name": "b59a1949-9950-4203-b394-ddd8d02c9633", + "version": "3d7790f3-7f32-43f7-b82f-9f5bc85205a8" + } + ], + "integrations": + [ + "daec50ae-8729-49b5-82f7-991446745cd5", + "8fc94968-3499-4a2c-b4d7-ecc058d9c1b0" + ] + }, + "request": + { + "url": "67369bc9-64d3-4d31-bfba-37393b145682", + "method": "8185abc3-5411-4041-a0d9-374180081044", + "query_string": "e3dc7659-f42e-413c-a07c-52b24bf9d60d", + "data": + { + "d9d709db-b666-40cc-bcbb-093bb12aad26": "1631d0e6-96b7-4632-85f8-ef69e8bcfb16" + }, + "cookies": "d84f4cfc-5310-4818-ad4f-3f8d22ceaca8", + "headers": + { + "c4991f66-9af9-4914-ac5e-e4854a5a4822": "37714d22-25a7-469b-b762-289b456fbec3" + }, + "env": + { + "6d569c89-5d5e-40e0-a4fc-109b20a53778": "ccadf763-44e4-475c-830c-de6ba0dbd202" + }, + "other": + { + "669ff1c1-517b-46dc-a889-131555364a56": "89043294-f6e1-4e2e-b152-1fdf9b1102fc" + } + }, + "tags": + { + "79ba41db-8dc6-4156-b53e-6cf6d742eb88": "690ce82f-4d5d-4d81-b467-461a41dd9419" + }, + "release": "be9b8133-72f5-497b-adeb-b0a245eebad6", + "environment": "89204175-e462-4628-8acb-3a7fa8d8da7d", + "platform": "38decc78-2711-4a6a-a0be-abb61bfa5a6e", + "user": + { + "email": "c4d61c1b-c144-431e-868f-37a46be5e5f2", + "id": "efb2084b-1871-4b59-8897-b4bd9f196a01", + "username": "60c05dff-7140-4d94-9a61-c9cdd9ca9b96", + "ip_address": "51d22b77-f663-4dbe-8103-8b749d1d9a48", + "other": + { + "dc2813d0-0f66-4a3f-a995-71268f61a8fa": "991659ad-7c59-4dd3-bb89-0bd5c74014bd" + } + }, + "server_name": "e6f0ae04-0f40-421b-aad1-f68c15117937", + "dist": "27022a08-aace-40c6-8d0a-358a27fcaa7a", + "breadcrumbs": + [ + { + "timestamp": "2009-11-16T01:08:47.000Z", + "message": "46f233c0-7c2d-488a-b05a-7be559173e16", + "type": "ace57e2e-305e-4048-abf0-6c8538ea7bf4", + "data": + { + "6607d106-d426-462b-af74-f29fce978e48": "149bb94a-1387-4484-90be-2df15d1322ab" + }, + "category": "b6eea851-5ae5-40ed-8fdd-5e1a655a879c", + "level": "debug" + } + ], + "extra": + { + "34a7d067-fad2-49d9-97b9-71eff243127b": "fe3dc1cf-4a99-4213-85bb-e0957b8349b8" + } +}