From 6641771a42e0f3036974c719d29fbdfdd674b2ed Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 29 Mar 2023 08:25:29 +0200 Subject: [PATCH] Provide CPU count/frequency data as device context (#2622) --- CHANGELOG.md | 1 + .../core/DefaultAndroidEventProcessor.java | 11 ++++ .../core/internal/util/CpuInfoUtils.java | 8 ++- .../core/DefaultAndroidEventProcessorTest.kt | 24 +++++++ sentry/api/sentry.api | 9 +++ .../main/java/io/sentry/protocol/Device.java | 62 +++++++++++++++++++ .../protocol/DeviceSerializationTest.kt | 3 + .../java/io/sentry/protocol/DeviceTest.kt | 8 ++- sentry/src/test/resources/json/contexts.json | 5 +- sentry/src/test/resources/json/device.json | 5 +- .../resources/json/sentry_base_event.json | 5 +- .../src/test/resources/json/sentry_event.json | 5 +- .../resources/json/sentry_transaction.json | 5 +- ...sentry_transaction_legacy_date_format.json | 5 +- 14 files changed, 148 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f1ff45746..91d9e710d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611)) - Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598)) - Add Logcat adapter ([#2620](https://github.com/getsentry/sentry-java/pull/2620)) +- Provide CPU count/frequency data as device context ([#2622](https://github.com/getsentry/sentry-java/pull/2622)) ### Fixes diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 6a50b66577..a372012029 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -28,6 +28,7 @@ import io.sentry.SentryLevel; import io.sentry.android.core.internal.util.AndroidMainThreadChecker; import io.sentry.android.core.internal.util.ConnectivityChecker; +import io.sentry.android.core.internal.util.CpuInfoUtils; import io.sentry.android.core.internal.util.DeviceOrientations; import io.sentry.android.core.internal.util.RootChecker; import io.sentry.protocol.App; @@ -43,8 +44,10 @@ import java.io.FileReader; import java.io.IOException; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -96,6 +99,8 @@ public DefaultAndroidEventProcessor( // don't ref. to method reference, theres a bug on it //noinspection Convert2MethodRef contextData = executorService.submit(() -> loadContextData()); + // reading CPU info performs disk I/O, but it's result is cached, let's pre-cache it + executorService.submit(() -> CpuInfoUtils.getInstance().readMaxFrequencies()); executorService.shutdown(); } @@ -355,6 +360,12 @@ private void setArchitectures(final @NotNull Device device) { device.setLocale(locale.toString()); // eg en_US } + final @NotNull List cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies(); + if (!cpuFrequencies.isEmpty()) { + device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue()); + device.setProcessorCount(cpuFrequencies.size()); + } + return device; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java index 69c0a55766..8dcb994fbc 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java @@ -34,7 +34,7 @@ private CpuInfoUtils() {} * * @return A list with the frequency of each core of the cpu in Mhz */ - public @NotNull List readMaxFrequencies() { + public synchronized @NotNull List readMaxFrequencies() { if (!cpuMaxFrequenciesMhz.isEmpty()) { return cpuMaxFrequenciesMhz; } @@ -70,6 +70,12 @@ String getSystemCpuPath() { return SYSTEM_CPU_PATH; } + @TestOnly + public void setCpuMaxFrequencies(List frequencies) { + cpuMaxFrequenciesMhz.clear(); + cpuMaxFrequenciesMhz.addAll(frequencies); + } + @TestOnly final void clear() { cpuMaxFrequenciesMhz.clear(); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt index a1ef274a8f..421d5f5769 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt @@ -17,6 +17,7 @@ import io.sentry.android.core.DefaultAndroidEventProcessor.EMULATOR import io.sentry.android.core.DefaultAndroidEventProcessor.KERNEL_VERSION import io.sentry.android.core.DefaultAndroidEventProcessor.ROOTED import io.sentry.android.core.DefaultAndroidEventProcessor.SIDE_LOADED +import io.sentry.android.core.internal.util.CpuInfoUtils import io.sentry.protocol.OperatingSystem import io.sentry.protocol.SdkVersion import io.sentry.protocol.SentryThread @@ -526,6 +527,29 @@ class DefaultAndroidEventProcessorTest { } } + @Test + fun `Event sets no device cpu info when there is none provided`() { + val sut = fixture.getSut(context) + CpuInfoUtils.getInstance().setCpuMaxFrequencies(emptyList()) + assertNotNull(sut.process(SentryEvent(), Hint())) { + val device = it.contexts.device!! + assertNull(device.processorCount) + assertNull(device.processorFrequency) + } + } + + @Test + fun `Event sets rights device cpu info when there is one provided`() { + val sut = fixture.getSut(context) + CpuInfoUtils.getInstance().setCpuMaxFrequencies(listOf(800, 900)) + + assertNotNull(sut.process(SentryEvent(), Hint())) { + val device = it.contexts.device!! + assertEquals(2, device.processorCount) + assertEquals(900.0, device.processorFrequency) + } + } + @Test fun `Events from HybridSDKs don't set main thread and in foreground context`() { val sut = fixture.getSut(context) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a1698c1ae4..d343cecc90 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2752,6 +2752,7 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public fun getBootTime ()Ljava/util/Date; public fun getBrand ()Ljava/lang/String; public fun getConnectionType ()Ljava/lang/String; + public fun getCpuDescription ()Ljava/lang/String; public fun getExternalFreeStorage ()Ljava/lang/Long; public fun getExternalStorageSize ()Ljava/lang/Long; public fun getFamily ()Ljava/lang/String; @@ -2766,6 +2767,8 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public fun getModelId ()Ljava/lang/String; public fun getName ()Ljava/lang/String; public fun getOrientation ()Lio/sentry/protocol/Device$DeviceOrientation; + public fun getProcessorCount ()Ljava/lang/Integer; + public fun getProcessorFrequency ()Ljava/lang/Double; public fun getScreenDensity ()Ljava/lang/Float; public fun getScreenDpi ()Ljava/lang/Integer; public fun getScreenHeightPixels ()Ljava/lang/Integer; @@ -2786,6 +2789,7 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public fun setBrand (Ljava/lang/String;)V public fun setCharging (Ljava/lang/Boolean;)V public fun setConnectionType (Ljava/lang/String;)V + public fun setCpuDescription (Ljava/lang/String;)V public fun setExternalFreeStorage (Ljava/lang/Long;)V public fun setExternalStorageSize (Ljava/lang/Long;)V public fun setFamily (Ljava/lang/String;)V @@ -2802,6 +2806,8 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public fun setName (Ljava/lang/String;)V public fun setOnline (Ljava/lang/Boolean;)V public fun setOrientation (Lio/sentry/protocol/Device$DeviceOrientation;)V + public fun setProcessorCount (Ljava/lang/Integer;)V + public fun setProcessorFrequency (Ljava/lang/Double;)V public fun setScreenDensity (Ljava/lang/Float;)V public fun setScreenDpi (Ljava/lang/Integer;)V public fun setScreenHeightPixels (Ljava/lang/Integer;)V @@ -2841,6 +2847,7 @@ public final class io/sentry/protocol/Device$JsonKeys { public static final field BRAND Ljava/lang/String; public static final field CHARGING Ljava/lang/String; public static final field CONNECTION_TYPE Ljava/lang/String; + public static final field CPU_DESCRIPTION Ljava/lang/String; public static final field EXTERNAL_FREE_STORAGE Ljava/lang/String; public static final field EXTERNAL_STORAGE_SIZE Ljava/lang/String; public static final field FAMILY Ljava/lang/String; @@ -2857,6 +2864,8 @@ public final class io/sentry/protocol/Device$JsonKeys { public static final field NAME Ljava/lang/String; public static final field ONLINE Ljava/lang/String; public static final field ORIENTATION Ljava/lang/String; + public static final field PROCESSOR_COUNT Ljava/lang/String; + public static final field PROCESSOR_FREQUENCY Ljava/lang/String; public static final field SCREEN_DENSITY Ljava/lang/String; public static final field SCREEN_DPI Ljava/lang/String; public static final field SCREEN_HEIGHT_PIXELS Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/protocol/Device.java b/sentry/src/main/java/io/sentry/protocol/Device.java index 2ca24bc5f2..89787fd75f 100644 --- a/sentry/src/main/java/io/sentry/protocol/Device.java +++ b/sentry/src/main/java/io/sentry/protocol/Device.java @@ -117,6 +117,19 @@ public final class Device implements JsonUnknown, JsonSerializable { /** battery's temperature in celsius */ private @Nullable Float batteryTemperature; + /** Optional. Number of "logical processors". For example, 8. */ + private @Nullable Integer processorCount; + + /** + * Optional. Processor frequency in MHz. Note that the actual CPU frequency might vary depending + * on current load and power conditions, especially on low-powered devices like phones and + * laptops. + */ + private @Nullable Double processorFrequency; + + /** Optional. CPU description. For example, Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz. */ + private @Nullable String cpuDescription; + @SuppressWarnings("unused") private @Nullable Map unknown; @@ -158,6 +171,10 @@ public Device() {} final TimeZone timezoneRef = device.timezone; this.timezone = timezoneRef != null ? (TimeZone) timezoneRef.clone() : null; + this.processorCount = device.processorCount; + this.processorFrequency = device.processorFrequency; + this.cpuDescription = device.cpuDescription; + this.unknown = CollectionUtils.newConcurrentHashMap(device.unknown); } @@ -403,6 +420,30 @@ public void setBatteryTemperature(final @Nullable Float batteryTemperature) { this.batteryTemperature = batteryTemperature; } + public @Nullable Integer getProcessorCount() { + return processorCount; + } + + public void setProcessorCount(@Nullable final Integer processorCount) { + this.processorCount = processorCount; + } + + public @Nullable Double getProcessorFrequency() { + return processorFrequency; + } + + public void setProcessorFrequency(@Nullable final Double processorFrequency) { + this.processorFrequency = processorFrequency; + } + + public @Nullable String getCpuDescription() { + return cpuDescription; + } + + public void setCpuDescription(@Nullable final String cpuDescription) { + this.cpuDescription = cpuDescription; + } + public enum DeviceOrientation implements JsonSerializable { PORTRAIT, LANDSCAPE; @@ -460,6 +501,9 @@ public static final class JsonKeys { public static final String CONNECTION_TYPE = "connection_type"; public static final String BATTERY_TEMPERATURE = "battery_temperature"; public static final String LOCALE = "locale"; + public static final String PROCESSOR_COUNT = "processor_count"; + public static final String CPU_DESCRIPTION = "cpu_description"; + public static final String PROCESSOR_FREQUENCY = "processor_frequency"; } @Override @@ -559,6 +603,15 @@ public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) if (locale != null) { writer.name(JsonKeys.LOCALE).value(locale); } + if (processorCount != null) { + writer.name(JsonKeys.PROCESSOR_COUNT).value(processorCount); + } + if (processorFrequency != null) { + writer.name(JsonKeys.PROCESSOR_FREQUENCY).value(processorFrequency); + } + if (cpuDescription != null) { + writer.name(JsonKeys.CPU_DESCRIPTION).value(cpuDescription); + } if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -698,6 +751,15 @@ public static final class Deserializer implements JsonDeserializer { case JsonKeys.LOCALE: device.locale = reader.nextStringOrNull(); break; + case JsonKeys.PROCESSOR_COUNT: + device.processorCount = reader.nextIntegerOrNull(); + break; + case JsonKeys.PROCESSOR_FREQUENCY: + device.processorFrequency = reader.nextDoubleOrNull(); + break; + case JsonKeys.CPU_DESCRIPTION: + device.cpuDescription = reader.nextStringOrNull(); + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); diff --git a/sentry/src/test/java/io/sentry/protocol/DeviceSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/DeviceSerializationTest.kt index e6821e5176..d09bed0643 100644 --- a/sentry/src/test/java/io/sentry/protocol/DeviceSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/DeviceSerializationTest.kt @@ -56,6 +56,9 @@ class DeviceSerializationTest { language = "6dd45f60-111d-42d8-9204-0452cc836ad8" connectionType = "9ceb3a6c-5292-4ed9-8665-5732495e8ed4" batteryTemperature = 0.14775127f + cpuDescription = "cpu0" + processorCount = 4 + processorFrequency = 800.0 } } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt b/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt index 55736df07a..912f0d998f 100644 --- a/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt @@ -62,6 +62,9 @@ class DeviceTest { device.connectionType = "connection type" device.batteryTemperature = 30f device.locale = "en-US" + device.cpuDescription = "cpu0" + device.processorCount = 4 + device.processorFrequency = 800.0 val unknown = mapOf(Pair("unknown", "unknown")) device.setUnknown(unknown) @@ -99,7 +102,10 @@ class DeviceTest { assertEquals("language", clone.language) assertEquals("connection type", clone.connectionType) assertEquals(30f, clone.batteryTemperature) - assertEquals("en-US", clone.locale) + assertEquals("cpu0", clone.cpuDescription) + assertEquals(4, clone.processorCount) + assertEquals(800.0, clone.processorFrequency) + device.processorFrequency = 800.0 assertNotNull(clone.unknown) { assertEquals("unknown", it["unknown"]) } diff --git a/sentry/src/test/resources/json/contexts.json b/sentry/src/test/resources/json/contexts.json index 6f945a6834..ad7e052aac 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -59,7 +59,10 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" }, "gpu": { diff --git a/sentry/src/test/resources/json/device.json b/sentry/src/test/resources/json/device.json index 92a9904ef5..be4aabf38c 100644 --- a/sentry/src/test/resources/json/device.json +++ b/sentry/src/test/resources/json/device.json @@ -36,5 +36,8 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" } diff --git a/sentry/src/test/resources/json/sentry_base_event.json b/sentry/src/test/resources/json/sentry_base_event.json index 07d19c8885..86c5b49d36 100644 --- a/sentry/src/test/resources/json/sentry_base_event.json +++ b/sentry/src/test/resources/json/sentry_base_event.json @@ -62,7 +62,10 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" }, "gpu": { diff --git a/sentry/src/test/resources/json/sentry_event.json b/sentry/src/test/resources/json/sentry_event.json index 49cf07a7a6..55b057504b 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -186,7 +186,10 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" }, "gpu": { diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index 52f6746aa1..9651ec910c 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -105,7 +105,10 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" }, "gpu": { 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 7b0f6bb06a..ff6b17827a 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 @@ -105,7 +105,10 @@ "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", - "battery_temperature": 0.14775127 + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" }, "gpu": {