Skip to content

Commit

Permalink
Merge branch 'dav1d/remove-metric-meta' into dav1d/sypc
Browse files Browse the repository at this point in the history
  • Loading branch information
Dav1dde authored Oct 30, 2024
2 parents 4fd4839 + 3360a91 commit a468ea2
Show file tree
Hide file tree
Showing 43 changed files with 23,096 additions and 195 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
- Removes support for metric meta envelope items. ([#4152](https://github.com/getsentry/relay/pull/4152))
- Removes support for the project cache endpoint version 2 and before. ([#4147](https://github.com/getsentry/relay/pull/4147))

**Bug Fixes:**

- Allow profile chunks without release. ([#4155](https://github.com/getsentry/relay/pull/4155))
- Add validation for timestamps sent from the future. ([#4163](https://github.com/getsentry/relay/pull/4163))

**Internal:**

- Add a metric that counts span volume in the root project for dynamic sampling (`c:spans/count_per_root_project@none`). ([#4134](https://github.com/getsentry/relay/pull/4134))
- Add a tag `target_project_id` to both root project metrics for dynamic sampling (`c:transactions/count_per_root_project@none` and `c:spans/count_per_root_project@none`) which shows the flow trace traffic from root to target projects. ([#4170](https://github.com/getsentry/relay/pull/4170))

## 24.10.0

**Breaking Changes**:
Expand All @@ -27,6 +37,7 @@
- Add user geo information to Replays. ([#4088](https://github.com/getsentry/relay/pull/4088))
- Configurable span.op inference. ([#4056](https://github.com/getsentry/relay/pull/4056))
- Enable support for zstd `Content-Encoding`. ([#4089](https://github.com/getsentry/relay/pull/4089))
- Accept profile chunks from Android. ([#4104](https://github.com/getsentry/relay/pull/4104))
- Add support for creating User from LoginId in Unreal Crash Context. ([#4093](https://github.com/getsentry/relay/pull/4093))
- Add multi-write Redis client. ([#4064](https://github.com/getsentry/relay/pull/4064))

Expand Down
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions gocd/templates/pipelines/pops.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ local canary_region_pops = {
'us-pop-regional-2',
'us-pop-regional-3',
'us-pop-regional-4',
'us-pop-1',
'us-pop-2',
],
};

Expand All @@ -24,6 +26,8 @@ local region_pops = {
'us-pop-regional-2',
'us-pop-regional-3',
'us-pop-regional-4',
'us-pop-1',
'us-pop-2',
],
s4s: [],
};
Expand Down
49 changes: 48 additions & 1 deletion relay-event-normalization/src/replay.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Validation and normalization of [`Replay`] events.
use chrono::Utc;
use std::net::IpAddr as StdIpAddr;

use relay_event_schema::processor::{self, ProcessingState, Processor};
Expand Down Expand Up @@ -35,6 +36,10 @@ pub enum ReplayError {
/// This erorr is usually returned when the PII configuration fails to parse.
#[error("failed to scrub PII: {0}")]
CouldNotScrub(String),

/// Date in the future.
#[error("date was from the future")]
DateInTheFuture,
}

/// Checks if the Replay event is structurally valid.
Expand Down Expand Up @@ -76,6 +81,14 @@ pub fn validate(replay: &Replay) -> Result<(), ReplayError> {
));
}

if replay
.timestamp
.value()
.map_or(false, |v| v.into_inner() > Utc::now())
{
return Err(ReplayError::DateInTheFuture);
}

Ok(())
}

Expand Down Expand Up @@ -171,7 +184,7 @@ fn normalize_type(replay: &mut Replay) {
mod tests {
use std::net::{IpAddr, Ipv4Addr};

use chrono::{TimeZone, Utc};
use chrono::{Duration, TimeZone, Utc};
use insta::assert_json_snapshot;
use relay_protocol::{assert_annotated_snapshot, get_value, SerializableAnnotated};
use uuid::Uuid;
Expand Down Expand Up @@ -439,6 +452,40 @@ mod tests {
assert!(validation_result.is_err());
}

#[test]
fn test_future_timestamp_validation() {
let future_time = Utc::now() + Duration::hours(1);
let json = format!(
r#"{{
"event_id": "52df9022835246eeb317dbd739ccd059",
"replay_id": "52df9022835246eeb317dbd739ccd059",
"segment_id": 0,
"replay_type": "session",
"error_sample_rate": 0.5,
"session_sample_rate": 0.5,
"timestamp": {},
"replay_start_timestamp": 946684800.0,
"urls": ["localhost:9000"],
"error_ids": ["test"],
"trace_ids": [],
"platform": "myplatform",
"release": "myrelease",
"dist": "mydist",
"environment": "myenv",
"tags": [
[
"tag",
"value"
]
]
}}"#,
future_time.timestamp()
);
let mut replay = Annotated::<Replay>::from_json(&json).unwrap();
let validation_result = validate(replay.value_mut().as_mut().unwrap());
assert!(validation_result.is_err());
}

#[test]
fn test_trace_id_validation() {
// NOTE: Interfaces will be tested separately.
Expand Down
169 changes: 169 additions & 0 deletions relay-profiling/src/android/chunk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! Android Format
//!
//! Relay is expecting a JSON object with some mandatory metadata and a `sampled_profile` key
//! containing the raw Android profile.
//!
//! `android` has a specific binary representation of its profile and Relay is responsible to
//! unpack it before it's forwarded down the line.
//!
use std::collections::HashMap;
use std::time::Duration;

use android_trace_log::chrono::Utc;
use android_trace_log::{AndroidTraceLog, Clock, Vm};
use data_encoding::BASE64_NOPAD;
use relay_event_schema::protocol::EventId;
use serde::{Deserialize, Serialize};

use crate::measurements::ChunkMeasurement;
use crate::sample::v2::ProfileData;
use crate::types::{ClientSdk, DebugMeta};
use crate::{ProfileError, MAX_PROFILE_DURATION};

#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
#[serde(default, skip_serializing_if = "String::is_empty")]
build_id: String,
chunk_id: EventId,
profiler_id: EventId,

client_sdk: ClientSdk,

#[serde(default, skip_serializing_if = "String::is_empty")]
environment: String,
platform: String,
release: String,

#[serde(skip_serializing_if = "Option::is_none")]
debug_meta: Option<DebugMeta>,

#[serde(default)]
duration_ns: u64,
#[serde(default)]
timestamp: f64,
}

#[derive(Debug, Serialize, Deserialize)]
struct Chunk {
#[serde(flatten)]
metadata: Metadata,

#[serde(default, skip_serializing)]
sampled_profile: String,

#[serde(default, skip_serializing_if = "Option::is_none")]
js_profile: Option<ProfileData>,

#[serde(default = "Chunk::default")]
profile: AndroidTraceLog,

#[serde(default, skip_serializing_if = "Option::is_none")]
measurements: Option<HashMap<String, ChunkMeasurement>>,
}

impl Chunk {
fn default() -> AndroidTraceLog {
AndroidTraceLog {
data_file_overflow: Default::default(),
clock: Clock::Global,
elapsed_time: Default::default(),
total_method_calls: Default::default(),
clock_call_overhead: Default::default(),
vm: Vm::Dalvik,
start_time: Utc::now(),
pid: Default::default(),
gc_trace: Default::default(),
threads: Default::default(),
methods: Default::default(),
events: Default::default(),
}
}

fn parse(&mut self) -> Result<(), ProfileError> {
let profile_bytes = match BASE64_NOPAD.decode(self.sampled_profile.as_bytes()) {
Ok(profile) => profile,
Err(_) => return Err(ProfileError::InvalidBase64Value),
};
self.profile = match android_trace_log::parse(&profile_bytes) {
Ok(profile) => profile,
Err(_) => return Err(ProfileError::InvalidSampledProfile),
};
Ok(())
}
}

fn parse_chunk(payload: &[u8]) -> Result<Chunk, ProfileError> {
let d = &mut serde_json::Deserializer::from_slice(payload);
let mut profile: Chunk =
serde_path_to_error::deserialize(d).map_err(ProfileError::InvalidJson)?;

if let Some(ref mut js_profile) = profile.js_profile {
js_profile.normalize(profile.metadata.platform.as_str())?;
}

if !profile.sampled_profile.is_empty() {
profile.parse()?;
}

if profile.profile.events.is_empty() {
return Err(ProfileError::NotEnoughSamples);
}

if profile.profile.elapsed_time > MAX_PROFILE_DURATION {
return Err(ProfileError::DurationIsTooLong);
}

if profile.profile.elapsed_time.is_zero() {
return Err(ProfileError::DurationIsZero);
}

// Use duration given by the profiler and not reported by the SDK.
profile.metadata.duration_ns = profile.profile.elapsed_time.as_nanos() as u64;
profile.metadata.timestamp = Duration::from_nanos(
profile
.profile
.start_time
.timestamp_nanos_opt()
.unwrap_or_default() as u64,
)
.as_secs_f64();

Ok(profile)
}

pub fn parse(payload: &[u8]) -> Result<Vec<u8>, ProfileError> {
let profile = parse_chunk(payload)?;

serde_json::to_vec(&profile).map_err(|_| ProfileError::CannotSerializePayload)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_roundtrip() {
let payload = include_bytes!("../../tests/fixtures/android/chunk/valid.json");
let profile = parse_chunk(payload);
assert!(profile.is_ok());
let data = serde_json::to_vec(&profile.unwrap());
assert!(parse_chunk(&(data.unwrap())[..]).is_ok());
}

#[test]
fn test_roundtrip_react_native() {
let payload = include_bytes!("../../tests/fixtures/android/chunk/valid-rn.json");
let profile = parse_chunk(payload);
assert!(profile.is_ok());
let data = serde_json::to_vec(&profile.unwrap());
assert!(parse_chunk(&(data.unwrap())[..]).is_ok());
}

#[test]
fn test_remove_invalid_events() {
let payload =
include_bytes!("../../tests/fixtures/android/chunk/remove_invalid_events.json");
let data = parse(payload);
assert!(data.is_err());
}
}
Loading

0 comments on commit a468ea2

Please sign in to comment.