Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(profiling): Update to new standard Profile format #504

Merged
merged 9 commits into from Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion sentry-core/Cargo.toml
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"]
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 }
chrono = { version = "0.4.22", features = ["serde"], 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 }
viglia marked this conversation as resolved.
Show resolved Hide resolved

[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
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().try_into().unwrap_or(0) }),
..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
140 changes: 85 additions & 55 deletions sentry-core/src/profiling.rs
@@ -1,12 +1,15 @@
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};

use chrono::DateTime;
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,104 @@ 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 frames: Vec<RustFrame> = Vec::new();
let mut address_to_frame_id: HashMap<String, u32> = HashMap::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 = format!("{:p}", frame.ip as *mut core::ffi::c_void);
#[cfg(not(feature = "frame-pointer"))]
instruction_addr: format!("{:p}", frame.ip()),
let instruction_addr = format!("{:p}", frame.ip());
*address_to_frame_id
.entry(instruction_addr.clone())
.or_insert_with(|| {
frames.push(RustFrame { instruction_addr });
(frames.len() - 1) as u32
})
viglia marked this conversation as resolved.
Show resolved Hide resolved
})
.collect();

stacks.push(stack);
viglia marked this conversation as resolved.
Show resolved Hide resolved
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
relative_timestamp_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(), // double check this
release: format!(
"{} ({})",
env!("CARGO_PKG_VERSION"),
build_id::get().to_simple(),
),
viglia marked this conversation as resolved.
Show resolved Hide resolved
timestamp: DateTime::from(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,
thread_metadata,
},
}
}
1 change: 1 addition & 0 deletions sentry-types/Cargo.toml
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"] }
viglia marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 4 additions & 4 deletions sentry-types/src/protocol/envelope.rs
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