Skip to content

Commit

Permalink
feat(profiling): Update to new standard Profile format (#504)
Browse files Browse the repository at this point in the history
* Update to new standard Profile format
  • Loading branch information
viglia authored Oct 7, 2022
1 parent a433f63 commit af1b255
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 119 deletions.
5 changes: 4 additions & 1 deletion sentry-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ client = ["rand"]
# and macros actually expand features (and extern crate) where they are used!
debug-logs = ["log_"]
test = ["client"]
profiling = ["pprof", "build_id", "uuid", "sys-info", "findshlibs"]
profiling = ["pprof", "build_id", "uuid", "sys-info", "findshlibs", "rustc_version_runtime", "libc", "indexmap"]
frame-pointer = ["pprof?/frame-pointer"]

[dependencies]
Expand All @@ -40,9 +40,12 @@ uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true }
sys-info = { version = "0.9.1", optional = true }
build_id = { version = "0.2.1", optional = true }
findshlibs = { version = "=0.10.2", optional = true }
rustc_version_runtime = { version = "0.2.1", optional = true }
indexmap = { version = "1.9.1", optional = true}

[target.'cfg(target_family = "unix")'.dependencies]
pprof = { version = "0.10.1", optional = true, default-features = false}
libc = { version = "^0.2.66", optional = true }

[dev-dependencies]
# Because we re-export all the public API in `sentry`, we actually run all the
Expand Down
10 changes: 6 additions & 4 deletions sentry-core/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ impl Transaction {
}),
Some(protocol::Transaction {
name: Some(ctx.name),
#[cfg(all(feature = "profiling", target_family = "unix"))]
active_thread_id: Some(unsafe { libc::pthread_self() as u64 }),
..Default::default()
}),
),
Expand Down Expand Up @@ -424,17 +426,17 @@ impl Transaction {
// if the profiler is running for the given transaction
// then call finish_profiling to return the profile
#[cfg(all(feature = "profiling", target_family = "unix"))]
let profile = inner.profiler_guard.take().and_then(|profiler_guard| {
let sample_profile = inner.profiler_guard.take().and_then(|profiler_guard| {
profiling::finish_profiling(&transaction, profiler_guard, inner.context.trace_id)
});

let mut envelope = protocol::Envelope::new();
envelope.add_item(transaction);

#[cfg(all(feature = "profiling", target_family = "unix"))]
if let Some(profile) = profile {
if !profile.sampled_profile.samples.is_empty(){
envelope.add_item(profile);
if let Some(sample_profile) = sample_profile {
if !sample_profile.profile.samples.is_empty(){
envelope.add_item(sample_profile);
}
else {
sentry_debug!("the profile is being dropped because it contains no samples");
Expand Down
138 changes: 83 additions & 55 deletions sentry-core/src/profiling.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use indexmap::set::IndexSet;
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};

use findshlibs::{SharedLibrary, SharedLibraryId, TargetSharedLibrary, TARGET_SUPPORTED};

use sentry_types::protocol::v7::Profile;
use sentry_types::protocol::v7::{
DebugImage, DebugMeta, RustFrame, Sample, SampledProfile, SymbolicDebugImage, TraceId,
Transaction,
DebugImage, DebugMeta, DeviceMetadata, OSMetadata, RuntimeMetadata, RustFrame, Sample,
SampleProfile, SymbolicDebugImage, ThreadMetadata, TraceId, Transaction, TransactionMetadata,
Version,
};
use sentry_types::{CodeId, DebugId, Uuid};

Expand Down Expand Up @@ -60,14 +63,9 @@ pub(crate) fn finish_profiling(
transaction: &Transaction,
profiler_guard: ProfilerGuard,
trace_id: TraceId,
) -> Option<Profile> {
let profile = match profiler_guard.0.report().build_unresolved() {
Ok(report) => Some(get_profile_from_report(
&report,
trace_id,
transaction.event_id,
transaction.name.as_ref().unwrap().clone(),
)),
) -> Option<SampleProfile> {
let sample_profile = match profiler_guard.0.report().build_unresolved() {
Ok(report) => Some(get_profile_from_report(&report, trace_id, transaction)),
Err(err) => {
sentry_debug!(
"could not build the profile result due to the error: {}",
Expand All @@ -78,7 +76,7 @@ pub(crate) fn finish_profiling(
};

PROFILER_RUNNING.store(false, Ordering::SeqCst);
profile
sample_profile
}

/// Converts an ELF object identifier into a `DebugId`.
Expand Down Expand Up @@ -171,72 +169,102 @@ pub fn debug_images() -> Vec<DebugImage> {
fn get_profile_from_report(
rep: &pprof::UnresolvedReport,
trace_id: TraceId,
transaction_id: sentry_types::Uuid,
transaction_name: String,
) -> Profile {
transaction: &Transaction,
) -> SampleProfile {
use std::time::SystemTime;

let mut samples: Vec<Sample> = Vec::new();
let mut samples: Vec<Sample> = Vec::with_capacity(rep.data.len());
let mut stacks: Vec<Vec<u32>> = Vec::with_capacity(rep.data.len());
let mut address_to_frame_idx: IndexSet<_> = IndexSet::new();
let mut thread_metadata: HashMap<String, ThreadMetadata> = HashMap::new();

for sample in rep.data.keys() {
let frames = sample
let stack = sample
.frames
.iter()
.map(|frame| RustFrame {
.map(|frame| {
#[cfg(feature = "frame-pointer")]
instruction_addr: format!("{:p}", frame.ip as *mut core::ffi::c_void),
let instruction_addr = frame.ip as *mut core::ffi::c_void;
#[cfg(not(feature = "frame-pointer"))]
instruction_addr: format!("{:p}", frame.ip()),
let instruction_addr = frame.ip();

address_to_frame_idx.insert_full(instruction_addr).0 as u32
})
.collect();

stacks.push(stack);
samples.push(Sample {
frames,
thread_name: String::from_utf8_lossy(&sample.thread_name[0..sample.thread_name_length])
.into_owned(),
stack_id: (stacks.len() - 1) as u32,
thread_id: sample.thread_id,
nanos_relative_to_start: sample
elapsed_since_start_ns: sample
.sample_timestamp
.duration_since(rep.timing.start_time)
.unwrap()
.as_nanos() as u64,
});

thread_metadata
.entry(sample.thread_id.to_string())
.or_insert(ThreadMetadata {
name: Some(
String::from_utf8_lossy(&sample.thread_name[0..sample.thread_name_length])
.into_owned(),
),
});
}
let sampled_profile = SampledProfile {
start_time_nanos: rep
.timing
.start_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64,
start_time_secs: rep
.timing
.start_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
duration_nanos: rep.timing.duration.as_nanos() as u64,
samples,
};

let profile: Profile = Profile {
duration_ns: sampled_profile.duration_nanos,
debug_meta: DebugMeta {
SampleProfile {
version: Version::V1,
debug_meta: Some(DebugMeta {
sdk_info: None,
images: debug_images(),
}),
device: DeviceMetadata {
architecture: Some(std::env::consts::ARCH.to_string()),
},
os: OSMetadata {
name: sys_info::os_type().unwrap(),
version: sys_info::os_release().unwrap(),
build_number: None,
},
runtime: Some(RuntimeMetadata {
name: "rustc".to_string(),
version: rustc_version_runtime::version().to_string(),
}),
environment: match &transaction.environment {
Some(env) => env.to_string(),
_ => "".to_string(),
},
platform: "rust".to_string(),
architecture: Some(std::env::consts::ARCH.to_string()),
trace_id,
transaction_name,
transaction_id,
profile_id: uuid::Uuid::new_v4(),
sampled_profile,
os_name: sys_info::os_type().unwrap(),
os_version: sys_info::os_release().unwrap(),
version_name: env!("CARGO_PKG_VERSION").to_string(),
version_code: build_id::get().to_simple().to_string(),
};

profile
event_id: uuid::Uuid::new_v4(),
release: transaction.release.clone().unwrap_or_default().into(),
timestamp: rep.timing.start_time,
transactions: vec![TransactionMetadata {
id: transaction.event_id,
name: transaction.name.clone().unwrap_or_else(|| "".to_string()),
trace_id,
relative_start_ns: 0,
relative_end_ns: transaction
.timestamp
.unwrap_or_else(SystemTime::now)
.duration_since(rep.timing.start_time)
.unwrap()
.as_nanos() as u64,
active_thread_id: transaction.active_thread_id.unwrap_or(0),
}],
platform: "rust".to_string(),
profile: Profile {
samples,
stacks,
frames: address_to_frame_idx
.into_iter()
.map(|address| -> RustFrame {
RustFrame {
instruction_addr: format!("{:p}", address),
}
})
.collect(),
thread_metadata,
},
}
}
1 change: 1 addition & 0 deletions sentry-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ thiserror = "1.0.15"
time = { version = "0.3.5", features = ["formatting", "parsing"] }
url = { version = "2.1.1", features = ["serde"] }
uuid = { version = "1.0.0", features = ["v4", "serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
2 changes: 1 addition & 1 deletion sentry-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ mod auth;
mod dsn;
mod project_id;
pub mod protocol;
mod utils;
pub(crate) mod utils;

pub use crate::auth::*;
pub use crate::dsn::*;
Expand Down
8 changes: 4 additions & 4 deletions sentry-types/src/protocol/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use uuid::Uuid;

use super::{
attachment::AttachmentType,
v7::{Attachment, Event, Profile, SessionAggregates, SessionUpdate, Transaction},
v7::{Attachment, Event, SampleProfile, SessionAggregates, SessionUpdate, Transaction},
};

/// Raised if a envelope cannot be parsed from a given input.
Expand Down Expand Up @@ -109,7 +109,7 @@ pub enum EnvelopeItem {
Attachment(Attachment),
/// An Profile Item.
///
Profile(Profile),
Profile(SampleProfile),
// TODO:
// etc…
}
Expand Down Expand Up @@ -144,8 +144,8 @@ impl From<Attachment> for EnvelopeItem {
}
}

impl From<Profile> for EnvelopeItem {
fn from(profile: Profile) -> Self {
impl From<SampleProfile> for EnvelopeItem {
fn from(profile: SampleProfile) -> Self {
EnvelopeItem::Profile(profile)
}
}
Expand Down
Loading

0 comments on commit af1b255

Please sign in to comment.