Skip to content

Commit

Permalink
Provide CPU count/frequency data as device context (#2622)
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi authored Mar 29, 2023
1 parent 17ab223 commit 6641771
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -355,6 +360,12 @@ private void setArchitectures(final @NotNull Device device) {
device.setLocale(locale.toString()); // eg en_US
}

final @NotNull List<Integer> cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies();
if (!cpuFrequencies.isEmpty()) {
device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue());
device.setProcessorCount(cpuFrequencies.size());
}

return device;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private CpuInfoUtils() {}
*
* @return A list with the frequency of each core of the cpu in Mhz
*/
public @NotNull List<Integer> readMaxFrequencies() {
public synchronized @NotNull List<Integer> readMaxFrequencies() {
if (!cpuMaxFrequenciesMhz.isEmpty()) {
return cpuMaxFrequenciesMhz;
}
Expand Down Expand Up @@ -70,6 +70,12 @@ String getSystemCpuPath() {
return SYSTEM_CPU_PATH;
}

@TestOnly
public void setCpuMaxFrequencies(List<Integer> frequencies) {
cpuMaxFrequenciesMhz.clear();
cpuMaxFrequenciesMhz.addAll(frequencies);
}

@TestOnly
final void clear() {
cpuMaxFrequenciesMhz.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
62 changes: 62 additions & 0 deletions sentry/src/main/java/io/sentry/protocol/Device.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, @NotNull Object> unknown;

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -698,6 +751,15 @@ public static final class Deserializer implements JsonDeserializer<Device> {
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<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 7 additions & 1 deletion sentry/src/test/java/io/sentry/protocol/DeviceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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"])
}
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/test/resources/json/contexts.json
Original file line number Diff line number Diff line change
Expand Up @@ -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":
{
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/test/resources/json/device.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 4 additions & 1 deletion sentry/src/test/resources/json/sentry_base_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -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":
{
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/test/resources/json/sentry_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -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":
{
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/test/resources/json/sentry_transaction.json
Original file line number Diff line number Diff line change
Expand Up @@ -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":
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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":
{
Expand Down

0 comments on commit 6641771

Please sign in to comment.