-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ref(profiling): Refactor profile processing into its own crate (#1340)
- Loading branch information
Showing
21 changed files
with
22,284 additions
and
497 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "relay-profiling" | ||
authors = ["Sentry <oss@sentry.io>"] | ||
description = "Profiling processing for Relay" | ||
homepage = "https://getsentry.github.io/relay/" | ||
repository = "https://github.com/getsentry/relay" | ||
version = "22.6.0" | ||
edition = "2018" | ||
license-file = "../LICENSE" | ||
publish = false | ||
|
||
[dependencies] | ||
failure = "0.1.8" | ||
android_trace_log = { version = "0.2.0", features = ["serde"] } | ||
base64 = "0.10.1" | ||
bytes = { version = "0.4.12", features = ["serde"] } | ||
relay-general = { path = "../relay-general" } | ||
serde = { version = "1.0.114", features = ["derive"] } | ||
serde_json = "1.0.55" | ||
|
||
[dev-dependencies] | ||
serde_test = "1.0.125" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
use android_trace_log::AndroidTraceLog; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use relay_general::protocol::EventId; | ||
|
||
use crate::utils::deserialize_number_from_string; | ||
use crate::ProfileError; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct AndroidProfile { | ||
android_api_level: u16, | ||
|
||
#[serde(default, skip_serializing_if = "String::is_empty")] | ||
build_id: String, | ||
|
||
device_cpu_frequencies: Vec<u32>, | ||
device_is_emulator: bool, | ||
device_locale: String, | ||
device_manufacturer: String, | ||
device_model: String, | ||
device_os_name: String, | ||
device_os_version: String, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
device_physical_memory_bytes: u64, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
duration_ns: u64, | ||
|
||
#[serde(default, skip_serializing_if = "String::is_empty")] | ||
environment: String, | ||
|
||
platform: String, | ||
profile_id: EventId, | ||
trace_id: EventId, | ||
transaction_id: EventId, | ||
transaction_name: String, | ||
version_code: String, | ||
version_name: String, | ||
|
||
#[serde(default, skip_serializing)] | ||
sampled_profile: String, | ||
|
||
#[serde(default)] | ||
profile: Option<AndroidTraceLog>, | ||
} | ||
|
||
impl AndroidProfile { | ||
fn parse(&mut self) -> Result<(), ProfileError> { | ||
let profile_bytes = match base64::decode(&self.sampled_profile) { | ||
Ok(profile) => profile, | ||
Err(_) => return Err(ProfileError::InvalidBase64Value), | ||
}; | ||
self.profile = match android_trace_log::parse(&profile_bytes) { | ||
Ok(profile) => Some(profile), | ||
Err(_) => return Err(ProfileError::InvalidSampledProfile), | ||
}; | ||
Ok(()) | ||
} | ||
} | ||
|
||
pub fn parse_android_profile(payload: &[u8]) -> Result<Vec<u8>, ProfileError> { | ||
let mut profile: AndroidProfile = | ||
serde_json::from_slice(payload).map_err(ProfileError::InvalidJson)?; | ||
|
||
if profile.sampled_profile.is_empty() { | ||
return Ok(Vec::new()); | ||
} | ||
|
||
profile.parse()?; | ||
|
||
if profile.profile.as_ref().unwrap().events.len() < 2 { | ||
return Err(ProfileError::NotEnoughSamples); | ||
} | ||
|
||
match serde_json::to_vec(&profile) { | ||
Ok(payload) => Ok(payload), | ||
Err(_) => Err(ProfileError::CannotSerializePayload), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_roundtrip_android() { | ||
let payload = include_bytes!("../tests/fixtures/profiles/android.json"); | ||
let data = parse_android_profile(payload); | ||
assert!(data.is_ok()); | ||
assert!(parse_android_profile(&(data.unwrap())[..]).is_ok()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use std::collections::HashMap; | ||
|
||
use serde::{de, Deserialize, Serialize}; | ||
|
||
use relay_general::protocol::{Addr, DebugId, EventId, NativeImagePath}; | ||
|
||
use crate::utils::deserialize_number_from_string; | ||
use crate::ProfileError; | ||
|
||
fn strip_pointer_authentication_code<'de, D>(deserializer: D) -> Result<Addr, D::Error> | ||
where | ||
D: de::Deserializer<'de>, | ||
{ | ||
let addr: Addr = Deserialize::deserialize(deserializer)?; | ||
// https://github.com/microsoft/plcrashreporter/blob/748087386cfc517936315c107f722b146b0ad1ab/Source/PLCrashAsyncThread_arm.c#L84 | ||
Ok(Addr(addr.0 & 0x0000000FFFFFFFFF)) | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct Frame { | ||
#[serde(deserialize_with = "strip_pointer_authentication_code")] | ||
instruction_addr: Addr, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct Sample { | ||
frames: Vec<Frame>, | ||
|
||
#[serde(default, skip_serializing_if = "String::is_empty")] | ||
queue_address: String, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
relative_timestamp_ns: u64, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
thread_id: u64, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct ThreadMetadata { | ||
#[serde(default)] | ||
is_main_thread: bool, | ||
|
||
#[serde(default, skip_serializing_if = "String::is_empty")] | ||
name: String, | ||
priority: u32, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct QueueMetadata { | ||
label: String, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct SampledProfile { | ||
samples: Vec<Sample>, | ||
|
||
#[serde(default)] | ||
thread_metadata: HashMap<String, ThreadMetadata>, | ||
|
||
#[serde(default)] | ||
queue_metadata: HashMap<String, QueueMetadata>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, PartialEq)] | ||
#[serde(rename_all = "lowercase")] | ||
enum ImageType { | ||
MachO, | ||
Symbolic, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, PartialEq)] | ||
pub struct NativeDebugImage { | ||
#[serde(alias = "name")] | ||
code_file: NativeImagePath, | ||
#[serde(alias = "id")] | ||
debug_id: DebugId, | ||
image_addr: Addr, | ||
|
||
#[serde(default)] | ||
image_vmaddr: Addr, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
image_size: u64, | ||
|
||
#[serde(rename = "type")] | ||
image_type: ImageType, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct DebugMeta { | ||
images: Vec<NativeDebugImage>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct CocoaProfile { | ||
debug_meta: DebugMeta, | ||
device_is_emulator: bool, | ||
device_locale: String, | ||
device_manufacturer: String, | ||
device_model: String, | ||
device_os_build_number: String, | ||
device_os_name: String, | ||
device_os_version: String, | ||
|
||
#[serde(deserialize_with = "deserialize_number_from_string")] | ||
duration_ns: u64, | ||
|
||
#[serde(default, skip_serializing_if = "String::is_empty")] | ||
environment: String, | ||
|
||
platform: String, | ||
profile_id: EventId, | ||
sampled_profile: SampledProfile, | ||
trace_id: EventId, | ||
transaction_id: EventId, | ||
transaction_name: String, | ||
version_code: String, | ||
version_name: String, | ||
} | ||
|
||
pub fn parse_cocoa_profile(payload: &[u8]) -> Result<Vec<u8>, ProfileError> { | ||
let profile: CocoaProfile = | ||
serde_json::from_slice(payload).map_err(ProfileError::InvalidJson)?; | ||
|
||
if profile.sampled_profile.samples.len() < 2 { | ||
return Err(ProfileError::NotEnoughSamples); | ||
} | ||
|
||
match serde_json::to_vec(&profile) { | ||
Ok(payload) => Ok(payload), | ||
Err(_) => Err(ProfileError::CannotSerializePayload), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
use relay_general::protocol::{DebugImage, NativeDebugImage as RelayNativeDebugImage}; | ||
use relay_general::types::{Annotated, Map}; | ||
|
||
#[test] | ||
fn test_roundtrip_cocoa() { | ||
let payload = include_bytes!("../tests/fixtures/profiles/cocoa.json"); | ||
let data = parse_cocoa_profile(payload); | ||
assert!(data.is_ok()); | ||
assert!(parse_cocoa_profile(&data.unwrap()[..]).is_ok()); | ||
} | ||
|
||
#[test] | ||
fn test_ios_debug_image_compatibility() { | ||
let image_json = r#"{"debug_id":"32420279-25E2-34E6-8BC7-8A006A8F2425","image_addr":"0x000000010258c000","code_file":"/private/var/containers/Bundle/Application/C3511752-DD67-4FE8-9DA2-ACE18ADFAA61/TrendingMovies.app/TrendingMovies","type":"macho","image_size":1720320,"image_vmaddr":"0x0000000100000000"}"#; | ||
let image: NativeDebugImage = serde_json::from_str(image_json).unwrap(); | ||
let json = serde_json::to_string(&image).unwrap(); | ||
let annotated = Annotated::from_json(&json[..]).unwrap(); | ||
assert_eq!( | ||
Annotated::new(DebugImage::MachO(Box::new(RelayNativeDebugImage { | ||
arch: Annotated::empty(), | ||
code_file: Annotated::new("/private/var/containers/Bundle/Application/C3511752-DD67-4FE8-9DA2-ACE18ADFAA61/TrendingMovies.app/TrendingMovies".into()), | ||
code_id: Annotated::empty(), | ||
debug_file: Annotated::empty(), | ||
debug_id: Annotated::new("32420279-25E2-34E6-8BC7-8A006A8F2425".parse().unwrap()), | ||
image_addr: Annotated::new(Addr(4334338048)), | ||
image_size: Annotated::new(1720320), | ||
image_vmaddr: Annotated::new(Addr(4294967296)), | ||
other: Map::new(), | ||
}))), annotated); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use failure::Fail; | ||
|
||
#[derive(Debug, Fail)] | ||
pub enum ProfileError { | ||
#[fail(display = "invalid json in profile")] | ||
InvalidJson(#[cause] serde_json::Error), | ||
#[fail(display = "invalid base64 value")] | ||
InvalidBase64Value, | ||
#[fail(display = "invalid sampled profile")] | ||
InvalidSampledProfile, | ||
#[fail(display = "cannot serialize payload")] | ||
CannotSerializePayload, | ||
#[fail(display = "not enough samples")] | ||
NotEnoughSamples, | ||
#[fail(display = "platform not supported")] | ||
PlatformNotSupported, | ||
} |
Oops, something went wrong.