Skip to content

Commit d10d8ac

Browse files
feat(mobile): Synthesize device.class based on specs from device context (#1895)
Adds and uses `normalize_device_context` which creates a new device.class context value by checking a combination of the device family, processor_frequency, processor_count, and memory_size. The `device.class` context value will be used in the sentry issues, performance, and discover products for the device classification project (getsentry/sentry#44449).
1 parent a5d2593 commit d10d8ac

File tree

4 files changed

+182
-2
lines changed

4 files changed

+182
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Add Dotnet, Javascript and PHP support for profiling. ([#1871](https://github.com/getsentry/relay/pull/1871), [#1876](https://github.com/getsentry/relay/pull/1876), [#1885](https://github.com/getsentry/relay/pull/1885))
1111
- Initial support for the Crons beta. ([#1886](https://github.com/getsentry/relay/pull/1886))
1212
- Scrub `span.data.http.query` with default scrubbers. ([#1889](https://github.com/getsentry/relay/pull/1889))
13+
- Synthesize new class attribute in device context using specs found on the device, such as processor_count, memory_size, etc. ([#1895](https://github.com/getsentry/relay/pull/1895))
1314
- Add `thread.state` field to protocol. ([#1896](https://github.com/getsentry/relay/pull/1896))
1415

1516
**Bug Fixes**:
@@ -23,7 +24,6 @@
2324
- Deprecate fields on the profiling sample format. ([#1878](https://github.com/getsentry/relay/pull/1878))
2425
- Remove idle samples at the start and end of a profile and useless metadata. ([#1894](https://github.com/getsentry/relay/pull/1894))
2526

26-
2727
## 23.2.0
2828

2929
**Features**:

relay-general/src/protocol/contexts/device.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,29 @@ use crate::protocol::FromUserAgentInfo;
22
use crate::store::user_agent::is_known;
33
use crate::types::{Annotated, Object, Value};
44
use crate::user_agent::{parse_device, ClientHints};
5+
use serde::{Deserialize, Serialize};
6+
7+
#[derive(
8+
Clone,
9+
Copy,
10+
Debug,
11+
Deserialize,
12+
Eq,
13+
PartialEq,
14+
Serialize,
15+
Empty,
16+
FromValue,
17+
IntoValue,
18+
ProcessValue,
19+
)]
20+
#[cfg_attr(feature = "jsonschema", derive(JsonSchema))]
21+
pub struct DeviceClass(pub u64);
22+
23+
impl DeviceClass {
24+
pub const LOW: Self = Self(1);
25+
pub const MEDIUM: Self = Self(2);
26+
pub const HIGH: Self = Self(3);
27+
}
528

629
/// Device information.
730
///
@@ -160,6 +183,10 @@ pub struct DeviceContext {
160183
/// Whether location support is available on the device.
161184
pub supports_location_service: Annotated<bool>,
162185

186+
// The performance class of the device, stored as a number.
187+
// This value is synthesized from the device's specs in normalize_device_context.
188+
pub class: Annotated<DeviceClass>,
189+
163190
/// Additional arbitrary fields for forwards compatibility
164191
#[metastructure(additional_properties, retain = "true", pii = "maybe")]
165192
pub other: Object<Value>,
@@ -204,6 +231,7 @@ impl FromUserAgentInfo for DeviceContext {
204231

205232
#[cfg(test)]
206233
mod tests {
234+
use crate::protocol::contexts::device::DeviceClass;
207235
use crate::protocol::{DeviceContext, FromUserAgentInfo};
208236
use crate::protocol::{Headers, PairList};
209237
use crate::types::{Annotated, Object, Value};
@@ -316,6 +344,7 @@ mod tests {
316344
"supports_gyroscope": true,
317345
"supports_audio": true,
318346
"supports_location_service": true,
347+
"class": 1,
319348
"other": "value",
320349
"type": "device"
321350
}"#;
@@ -359,6 +388,7 @@ mod tests {
359388
supports_gyroscope: Annotated::new(true),
360389
supports_audio: Annotated::new(true),
361390
supports_location_service: Annotated::new(true),
391+
class: Annotated::new(DeviceClass::LOW),
362392
other: {
363393
let mut map = Object::new();
364394
map.insert(

relay-general/src/store/normalize/contexts.rs

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use once_cell::sync::Lazy;
22
use regex::Regex;
33

4-
use crate::protocol::{Context, OsContext, ResponseContext, RuntimeContext};
4+
use crate::protocol::{
5+
Context, DeviceClass, DeviceContext, OsContext, ResponseContext, RuntimeContext,
6+
};
57
use crate::types::{Annotated, Empty};
68

79
/// Environment.OSVersion (GetVersionEx) or RuntimeInformation.OSDescription on Windows
@@ -37,6 +39,8 @@ static OS_UNAME_REGEX: Lazy<Regex> = Lazy::new(|| {
3739
static RUNTIME_DOTNET_REGEX: Lazy<Regex> =
3840
Lazy::new(|| Regex::new(r#"^(?P<name>.*) (?P<version>\d+\.\d+(\.\d+){0,2}).*$"#).unwrap());
3941

42+
const GIB: u64 = 1024 * 1024 * 1024;
43+
4044
fn normalize_runtime_context(runtime: &mut RuntimeContext) {
4145
if runtime.name.value().is_empty() && runtime.version.value().is_empty() {
4246
if let Some(raw_description) = runtime.raw_description.as_str() {
@@ -199,11 +203,45 @@ fn normalize_response(response: &mut ResponseContext) {
199203
}
200204
}
201205

206+
// Reads device specs (family, memory, cpu, etc) and sets the device class to high, medium, or low.
207+
fn normalize_device_context(device: &mut DeviceContext) {
208+
// Unset device class if it is sent by the client, since this value should be computed by relay.
209+
if device.class.value().is_some() {
210+
device.class = Annotated::empty();
211+
}
212+
if let Some(family) = device.family.value() {
213+
if family == "iPhone" || family == "iOS" || family == "iOS-Device" {
214+
if let Some(processor_frequency) = device.processor_frequency.value() {
215+
if processor_frequency < &2000 {
216+
device.class = DeviceClass::LOW.into();
217+
} else if processor_frequency < &3000 {
218+
device.class = DeviceClass::MEDIUM.into();
219+
} else {
220+
device.class = DeviceClass::HIGH.into();
221+
}
222+
}
223+
} else if let (Some(&freq), Some(&proc), Some(&mem)) = (
224+
device.processor_frequency.value(),
225+
device.processor_count.value(),
226+
device.memory_size.value(),
227+
) {
228+
if freq < 2000 || proc < 8 || mem < 4 * GIB {
229+
device.class = DeviceClass::LOW.into();
230+
} else if freq < 2500 || mem < 6 * GIB {
231+
device.class = DeviceClass::MEDIUM.into();
232+
} else {
233+
device.class = DeviceClass::HIGH.into();
234+
}
235+
}
236+
}
237+
}
238+
202239
pub fn normalize_context(context: &mut Context) {
203240
match context {
204241
Context::Runtime(runtime) => normalize_runtime_context(runtime),
205242
Context::Os(os) => normalize_os_context(os),
206243
Context::Response(response) => normalize_response(response),
244+
Context::Device(device) => normalize_device_context(device),
207245
_ => (),
208246
}
209247
}
@@ -580,4 +618,96 @@ mod tests {
580618
assert_eq!(Some("15.0"), os.kernel_version.as_str());
581619
assert_eq!(None, os.build.value());
582620
}
621+
622+
#[test]
623+
fn test_apple_no_device_class() {
624+
let mut device = DeviceContext {
625+
family: "iPhone".to_string().into(),
626+
..DeviceContext::default()
627+
};
628+
normalize_device_context(&mut device);
629+
assert_eq!(None, device.class.value());
630+
}
631+
632+
#[test]
633+
fn test_apple_low_device_class() {
634+
let mut device = DeviceContext {
635+
family: "iPhone".to_string().into(),
636+
processor_frequency: 1000.into(),
637+
..DeviceContext::default()
638+
};
639+
normalize_device_context(&mut device);
640+
assert_eq!(DeviceClass::LOW, *device.class.value().unwrap());
641+
}
642+
643+
#[test]
644+
fn test_apple_medium_device_class() {
645+
let mut device = DeviceContext {
646+
family: "iPhone".to_string().into(),
647+
processor_frequency: 2000.into(),
648+
..DeviceContext::default()
649+
};
650+
normalize_device_context(&mut device);
651+
assert_eq!(DeviceClass::MEDIUM, *device.class.value().unwrap());
652+
}
653+
654+
#[test]
655+
fn test_apple_high_device_class() {
656+
let mut device = DeviceContext {
657+
family: "iPhone".to_string().into(),
658+
processor_frequency: 3000.into(),
659+
..DeviceContext::default()
660+
};
661+
normalize_device_context(&mut device);
662+
assert_eq!(DeviceClass::HIGH, *device.class.value().unwrap());
663+
}
664+
665+
#[test]
666+
fn test_android_no_device_class() {
667+
let mut device = DeviceContext {
668+
family: "android".to_string().into(),
669+
..DeviceContext::default()
670+
};
671+
normalize_device_context(&mut device);
672+
assert_eq!(None, device.class.value());
673+
}
674+
675+
#[test]
676+
fn test_android_low_device_class() {
677+
let mut device = DeviceContext {
678+
family: "android".to_string().into(),
679+
processor_frequency: 1000.into(),
680+
processor_count: 6.into(),
681+
memory_size: (2 * GIB).into(),
682+
..DeviceContext::default()
683+
};
684+
normalize_device_context(&mut device);
685+
assert_eq!(DeviceClass::LOW, *device.class.value().unwrap());
686+
}
687+
688+
#[test]
689+
fn test_android_medium_device_class() {
690+
let mut device = DeviceContext {
691+
family: "android".to_string().into(),
692+
processor_frequency: 2000.into(),
693+
processor_count: 8.into(),
694+
memory_size: (6 * GIB).into(),
695+
..DeviceContext::default()
696+
};
697+
normalize_device_context(&mut device);
698+
assert_eq!(DeviceClass::MEDIUM, *device.class.value().unwrap());
699+
}
700+
701+
#[test]
702+
fn test_android_high_device_class() {
703+
let mut device = DeviceContext {
704+
family: "android".to_string().into(),
705+
processor_frequency: 2500.into(),
706+
processor_count: 8.into(),
707+
memory_size: (6 * GIB).into(),
708+
..DeviceContext::default()
709+
};
710+
normalize_device_context(&mut device);
711+
assert_eq!(DeviceClass::HIGH, *device.class.value().unwrap());
712+
}
583713
}

relay-general/tests/snapshots/test_fixtures__event_schema.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,15 @@ expression: "relay_general::protocol::event_json_schema()"
10621062
}
10631063
]
10641064
},
1065+
"DeviceClass": {
1066+
"anyOf": [
1067+
{
1068+
"type": "integer",
1069+
"format": "uint64",
1070+
"minimum": 0.0
1071+
}
1072+
]
1073+
},
10651074
"DeviceContext": {
10661075
"description": " Device information.\n\n Device context describes the device that caused the event. This is most appropriate for mobile\n applications.",
10671076
"anyOf": [
@@ -1117,6 +1126,17 @@ expression: "relay_general::protocol::event_json_schema()"
11171126
"null"
11181127
]
11191128
},
1129+
"class": {
1130+
"default": null,
1131+
"anyOf": [
1132+
{
1133+
"$ref": "#/definitions/DeviceClass"
1134+
},
1135+
{
1136+
"type": "null"
1137+
}
1138+
]
1139+
},
11201140
"cpu_description": {
11211141
"description": " CPU description.\n\n For example, Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz.",
11221142
"default": null,

0 commit comments

Comments
 (0)