From 94b4ecbb18e28b5cc2eaaf88ee5b378f3f2d304f Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Wed, 5 Mar 2025 21:59:43 -0500 Subject: [PATCH 1/9] add new test data --- .../step_function_lambda_root_event.json | 24 +++++++++++++++++ ...tep_function_lambda_root_legacy_event.json | 26 +++++++++++++++++++ .../payloads/step_function_nested_event.json | 23 ++++++++++++++++ .../step_function_nested_legacy_event.json | 25 ++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 bottlecap/tests/payloads/step_function_lambda_root_event.json create mode 100644 bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json create mode 100644 bottlecap/tests/payloads/step_function_nested_event.json create mode 100644 bottlecap/tests/payloads/step_function_nested_legacy_event.json diff --git a/bottlecap/tests/payloads/step_function_lambda_root_event.json b/bottlecap/tests/payloads/step_function_lambda_root_event.json new file mode 100644 index 000000000..8d226474b --- /dev/null +++ b/bottlecap/tests/payloads/step_function_lambda_root_event.json @@ -0,0 +1,24 @@ +{ + "_datadog": { + "Execution": { + "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "invoker", + "EnteredTime": "2024-08-29T21:48:55.275Z", + "RetryCount": 0 + }, + "x-datadog-trace-id": "5821803790426892636", + "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=672a7cb100000000", + "serverless-version": "v1" + } + } diff --git a/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json b/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json new file mode 100644 index 000000000..263c27af4 --- /dev/null +++ b/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json @@ -0,0 +1,26 @@ +{ + "Payload": { + "_datadog": { + "Execution": { + "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "invoker", + "EnteredTime": "2024-08-29T21:48:55.275Z", + "RetryCount": 0 + }, + "x-datadog-trace-id": "5821803790426892636", + "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=672a7cb100000000", + "serverless-version": "v1" + } + } +} diff --git a/bottlecap/tests/payloads/step_function_nested_event.json b/bottlecap/tests/payloads/step_function_nested_event.json new file mode 100644 index 000000000..f8c9a95ca --- /dev/null +++ b/bottlecap/tests/payloads/step_function_nested_event.json @@ -0,0 +1,23 @@ +{ + "_datadog": { + "Execution": { + "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "invoker", + "EnteredTime": "2024-08-29T21:48:55.275Z", + "RetryCount": 0 + }, + "RootExecutionId": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad", + "serverless-version": "v1" + } + } diff --git a/bottlecap/tests/payloads/step_function_nested_legacy_event.json b/bottlecap/tests/payloads/step_function_nested_legacy_event.json new file mode 100644 index 000000000..3d3a92cb3 --- /dev/null +++ b/bottlecap/tests/payloads/step_function_nested_legacy_event.json @@ -0,0 +1,25 @@ +{ + "Payload": { + "_datadog": { + "Execution": { + "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "invoker", + "EnteredTime": "2024-08-29T21:48:55.275Z", + "RetryCount": 0 + }, + "RootExecutionId": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad", + "serverless-version": "v1" + } + } +} From c19e8c91df53d526cf632f1ea943392659fd3f42 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Thu, 6 Mar 2025 12:05:33 -0500 Subject: [PATCH 2/9] handling for retry_count + redrive_count and new optionals --- .../triggers/step_function_event.rs | 78 +++++++++++++------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index 6169be7d3..a50124d8a 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -14,13 +14,6 @@ use crate::{ }, }; -#[allow(clippy::module_name_repetitions)] -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct LegacyStepFunctionEvent { - #[serde(rename = "Payload")] - pub payload: StepFunctionEvent, -} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct StepFunctionEvent { #[serde(rename = "Execution")] @@ -29,12 +22,22 @@ pub struct StepFunctionEvent { pub state: State, #[serde(rename = "StateMachine")] pub state_machine: Option, + #[serde(rename = "x-datadog-trace-id")] + pub trace_id: Option, + #[serde(rename = "x-datadog-tags")] + pub trace_tags: Option, + #[serde(rename = "RootExecutionID")] + pub root_execution_id: Option, + #[serde(rename = "serverless-version")] + pub serverless_version: Option, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct Execution { #[serde(rename = "Id")] id: String, + #[serde(rename = "RedriveCount")] + redrive_count: u16, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -43,6 +46,8 @@ pub struct State { name: String, #[serde(rename = "EnteredTime")] entered_time: String, + #[serde(rename = "RetryCount")] + retry_count: u16, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -56,7 +61,11 @@ impl Trigger for StepFunctionEvent { where Self: Sized, { - let p = payload.get("Payload").unwrap_or(&payload); + let p = payload + .get("Payload") + .unwrap_or(&payload) + .get("_datadog") + .unwrap_or(payload.get("Payload").unwrap_or(&payload)); match serde_json::from_value::(p.clone()) { Ok(event) => Some(event), Err(e) => { @@ -70,8 +79,12 @@ impl Trigger for StepFunctionEvent { where Self: Sized, { - // Check first if the payload is a Legacy Step Function event - let p = payload.get("Payload").unwrap_or(payload); + // Check if the payload is a Legacy Step Function event and also a JSONata event + let p = payload + .get("Payload") + .unwrap_or(payload) + .get("_datadog") + .unwrap_or(payload.get("Payload").unwrap_or(payload)); let execution_id = p .get("Execution") @@ -128,6 +141,8 @@ impl StepFunctionEvent { self.execution.id.clone(), self.state.name.clone(), self.state.entered_time.clone(), + self.state.retry_count.clone(), + self.execution.redrive_count.clone(), ); SpanContext { @@ -145,14 +160,21 @@ impl StepFunctionEvent { } /// Generates a random 64 bit ID from the formatted hash of the - /// Step Function Execution ARN, the State Name, and the State Entered Time + /// Step Function context object. Omit retry_count and redrive_count + /// when both are 0 to maintain backwards compatibility. /// fn generate_parent_id( execution_id: String, state_name: String, state_entered_time: String, + retry_count: u16, + redrive_count: u16, ) -> u64 { - let unique_string = format!("{execution_id}#{state_name}#{state_entered_time}"); + let mut unique_string = format!("{execution_id}#{state_name}#{state_entered_time}"); + + if retry_count != 0 || redrive_count != 0 { + unique_string.push_str(&format!("#{retry_count}#{redrive_count}")); + } let hash = Sha256::digest(unique_string.as_bytes()); Self::get_positive_u64(&hash[0..8]) @@ -213,14 +235,20 @@ mod tests { let expected = StepFunctionEvent { execution: Execution { id: String::from("arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:bc9f281c-3daa-4e5a-9a60-471a3810bf44"), + redrive_count: 0, }, state: State { - name: String::from("agocsTest1"), + name: String::from("agocsTest1"), entered_time: String::from("2024-07-30T19:55:53.018Z"), + retry_count: 0, }, state_machine: Some(StateMachine { id: String::from("arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF"), }), + trace_id: None, + trace_tags: None, + root_execution_id: None, + serverless_version: None, }; assert_eq!(result, expected); @@ -235,14 +263,20 @@ mod tests { let expected = StepFunctionEvent { execution: Execution { id: String::from("arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:bc9f281c-3daa-4e5a-9a60-471a3810bf44"), + redrive_count: 0, }, state: State { - name: String::from("agocsTest1"), + name: String::from("agocsTest1"), entered_time: String::from("2024-07-30T19:55:53.018Z"), + retry_count: 0, }, state_machine: Some(StateMachine { id: String::from("arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF"), }), + trace_id: None, + trace_tags: None, + root_execution_id: None, + serverless_version: None, }; assert_eq!(result, expected); @@ -256,14 +290,6 @@ mod tests { assert!(StepFunctionEvent::is_match(&payload)); } - #[test] - fn test_is_match_legacy_event() { - let json = read_json_file("step_function_legacy_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize StepFunctionEvent"); - - assert!(StepFunctionEvent::is_match(&payload)); - } - #[test] fn test_is_not_match() { let json = read_json_file("sqs_event.json"); @@ -344,7 +370,9 @@ mod tests { let parent_id = StepFunctionEvent::generate_parent_id( String::from("arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d01111"), String::from("step-one"), - String::from("2022-12-08T21:08:19.224Z") + String::from("2022-12-08T21:08:19.224Z"), + 0, + 0, ); assert_eq!(parent_id, 4_340_734_536_022_949_921); @@ -352,7 +380,9 @@ mod tests { let parent_id = StepFunctionEvent::generate_parent_id( String::from("arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d01111"), String::from("step-one"), - String::from("2022-12-08T21:08:19.224Y") + String::from("2022-12-08T21:08:19.224Y"), + 0, + 0, ); assert_eq!(parent_id, 981_693_280_319_792_699); From cb10a31d4e0448868abc2522d8828271435eee88 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Thu, 6 Mar 2025 13:45:54 -0500 Subject: [PATCH 3/9] implement get_span_context for new cases --- .../triggers/step_function_event.rs | 365 +++++++++++++++--- .../step_function_lambda_root_event.json | 44 +-- ...tep_function_lambda_root_legacy_event.json | 8 +- .../payloads/step_function_nested_event.json | 42 +- .../step_function_nested_legacy_event.json | 8 +- 5 files changed, 357 insertions(+), 110 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index a50124d8a..589c8cfe3 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -26,7 +26,7 @@ pub struct StepFunctionEvent { pub trace_id: Option, #[serde(rename = "x-datadog-tags")] pub trace_tags: Option, - #[serde(rename = "RootExecutionID")] + #[serde(rename = "RootExecutionId")] pub root_execution_id: Option, #[serde(rename = "serverless-version")] pub serverless_version: Option, @@ -131,11 +131,29 @@ impl Trigger for StepFunctionEvent { impl StepFunctionEvent { #[must_use] pub fn get_span_context(&self) -> SpanContext { - let (lo_tid, hi_tid) = Self::generate_trace_id(self.execution.id.clone()); - let tags = HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - format!("{hi_tid:x}"), - )]); + let (lo_tid, tags) = + if let (Some(trace_id), Some(trace_tags)) = (&self.trace_id, &self.trace_tags) { + // Lambda Root + let lo_tid = trace_id.parse().expect("Failed to parse trace_id to u64"); + let tags = Self::extract_trace_tags(trace_tags); + (lo_tid, tags) + } else if let Some(root_execution_id) = &self.root_execution_id { + // Nested + let (lo_tid, hi_tid) = Self::generate_trace_id(root_execution_id.clone()); + let tags = HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + format!("{hi_tid:x}"), + )]); + (lo_tid, tags) + } else { + // Normal + let (lo_tid, hi_tid) = Self::generate_trace_id(self.execution.id.clone()); + let tags = HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + format!("{hi_tid:x}"), + )]); + (lo_tid, tags) + }; let parent_id = Self::generate_parent_id( self.execution.id.clone(), @@ -209,6 +227,19 @@ impl StepFunctionEvent { result } } + + /// Extracts the `_dd.p.tid` tag from a comma-separated trace tags string + /// + fn extract_trace_tags(trace_tags: &str) -> HashMap { + trace_tags + .split(',') + .filter_map(|s| s.split_once('=')) + .find_map(|(k, v)| { + (k == DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY) + .then(|| (k.to_string(), v.to_string())) + }) + .map_or_else(HashMap::new, |entry| HashMap::from([entry])) + } } impl ServiceNameResolver for StepFunctionEvent { @@ -227,10 +258,11 @@ mod tests { use crate::lifecycle::invocation::triggers::test_utils::read_json_file; #[test] - fn test_new() { - let json = read_json_file("step_function_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let result = StepFunctionEvent::new(payload).expect("Failed to deserialize into Event"); + fn test_new_event() { + let test_files = vec![ + "step_function_event.json", + "step_function_legacy_event.json", + ]; let expected = StepFunctionEvent { execution: Execution { @@ -251,23 +283,30 @@ mod tests { serverless_version: None, }; - assert_eq!(result, expected); + for file in test_files { + let json = read_json_file(file); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = StepFunctionEvent::new(payload).expect("Failed to deserialize into Event"); + + assert_eq!(result, expected); + } } #[test] - fn test_new_legacy_event() { - let json = read_json_file("step_function_legacy_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let result = StepFunctionEvent::new(payload).expect("Failed to deserialize into Event"); + fn test_new_nested_event() { + let test_files = vec![ + "step_function_nested_event.json", + "step_function_nested_legacy_event.json", + ]; let expected = StepFunctionEvent { execution: Execution { - id: String::from("arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:bc9f281c-3daa-4e5a-9a60-471a3810bf44"), + id: String::from("arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f"), redrive_count: 0, }, state: State { name: String::from("agocsTest1"), - entered_time: String::from("2024-07-30T19:55:53.018Z"), + entered_time: String::from("2024-07-30T20:46:20.824Z"), retry_count: 0, }, state_machine: Some(StateMachine { @@ -275,19 +314,78 @@ mod tests { }), trace_id: None, trace_tags: None, + root_execution_id: Some(String::from("arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad")), + serverless_version: Some(String::from("v1")), + }; + + for file in test_files { + let json = read_json_file(file); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = StepFunctionEvent::new(payload).expect("Failed to deserialize into Event"); + + assert_eq!(result, expected); + } + } + + #[test] + fn test_new_lambda_root_event() { + let test_files = vec![ + "step_function_lambda_root_event.json", + "step_function_lambda_root_legacy_event.json", + ]; + + let expected = StepFunctionEvent { + execution: Execution { + id: String::from("arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f"), + redrive_count: 0, + }, + state: State { + name: String::from("agocsTest1"), + entered_time: String::from("2024-07-30T20:46:20.824Z"), + retry_count: 0, + }, + state_machine: Some(StateMachine { + id: String::from("arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF"), + }), + trace_id: Some(String::from("5821803790426892636")), + trace_tags: Some(String::from("_dd.p.dm=-0,_dd.p.tid=672a7cb100000000")), root_execution_id: None, - serverless_version: None, + serverless_version: Some(String::from("v1")), }; - assert_eq!(result, expected); + for file in test_files { + let json = read_json_file(file); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = StepFunctionEvent::new(payload).expect("Failed to deserialize into Event"); + + assert_eq!(result, expected); + } } #[test] fn test_is_match() { - let json = read_json_file("step_function_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize StepFunctionEvent"); - - assert!(StepFunctionEvent::is_match(&payload)); + let test_files = vec![ + "step_function_event.json", + "step_function_legacy_event.json", + "step_function_nested_event.json", + "step_function_nested_legacy_event.json", + "step_function_lambda_root_event.json", + "step_function_lambda_root_legacy_event.json", + ]; + + for file in test_files { + let json = read_json_file(file); + let payload = serde_json::from_str(&json).expect(&format!( + "Failed to deserialize StepFunctionEvent from {}", + file + )); + + assert!( + StepFunctionEvent::is_match(&payload), + "StepFunctionEvent::is_match failed for {}", + file + ); + } } #[test] @@ -299,18 +397,34 @@ mod tests { #[test] fn test_get_tags() { - let json = read_json_file("step_function_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - StepFunctionEvent::new(payload).expect("Failed to deserialize StepFunctionEvent"); - let tags = event.get_tags(); - - let expected = HashMap::from([( - "function_trigger.event_source".to_string(), - "states".to_string(), - )]); - - assert_eq!(tags, expected); + let test_files = vec![ + "step_function_event.json", + "step_function_legacy_event.json", + "step_function_nested_event.json", + "step_function_nested_legacy_event.json", + "step_function_lambda_root_event.json", + "step_function_lambda_root_legacy_event.json", + ]; + + for file in &test_files { + let json = read_json_file(file); + let payload = serde_json::from_str(&json) + .expect(&format!("Failed to deserialize into Value from {}", file)); + + let event = StepFunctionEvent::new(payload).expect(&format!( + "Failed to deserialize StepFunctionEvent from {}", + file + )); + + let tags = event.get_tags(); + + let expected = HashMap::from([( + "function_trigger.event_source".to_string(), + "states".to_string(), + )]); + + assert_eq!(tags, expected, "get_tags() failed for {}", file); + } } #[test] @@ -339,30 +453,130 @@ mod tests { } #[test] - fn get_span_context() { - let json = read_json_file("step_function_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - StepFunctionEvent::new(payload).expect("Failed to deserialize StepFunctionEvent"); - - let span_context = event.get_span_context(); - - let expected = SpanContext { - trace_id: 5_744_042_798_732_701_615, - span_id: 2_902_498_116_043_018_663, - sampling: Some(Sampling { - priority: Some(1), - mechanism: None, - }), - origin: Some("states".to_string()), - tags: HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - "1914fe7789eb32be".to_string(), - )]), - links: vec![], - }; - - assert_eq!(span_context, expected); + fn test_get_span_context() { + let test_cases = vec![ + ( + "step_function_event.json", + SpanContext { + trace_id: 5_744_042_798_732_701_615, + span_id: 2_902_498_116_043_018_663, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "1914fe7789eb32be".to_string(), + )]), + links: vec![], + }, + ), + ( + "step_function_legacy_event.json", + SpanContext { + trace_id: 5_744_042_798_732_701_615, + span_id: 2_902_498_116_043_018_663, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "1914fe7789eb32be".to_string(), + )]), + links: vec![], + }, + ), + ( + "step_function_nested_event.json", + SpanContext { + trace_id: 1_322_229_001_489_018_110, + span_id: 8_947_638_978_974_359_093, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "579d19b3ee216ee9".to_string(), + )]), + links: vec![], + }, + ), + ( + "step_function_nested_legacy_event.json", + SpanContext { + trace_id: 1_322_229_001_489_018_110, + span_id: 8_947_638_978_974_359_093, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "579d19b3ee216ee9".to_string(), + )]), + links: vec![], + }, + ), + ( + "step_function_lambda_root_event.json", + SpanContext { + trace_id: 5_821_803_790_426_892_636, + span_id: 8_947_638_978_974_359_093, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "672a7cb100000000".to_string(), + )]), + links: vec![], + }, + ), + ( + "step_function_lambda_root_legacy_event.json", + SpanContext { + trace_id: 5_821_803_790_426_892_636, + span_id: 8_947_638_978_974_359_093, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("states".to_string()), + tags: HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "672a7cb100000000".to_string(), + )]), + links: vec![], + }, + ), + ]; + + for (file, expected) in test_cases { + let json = read_json_file(file); + let payload = serde_json::from_str(&json) + .expect(&format!("Failed to deserialize into Value from {}", file)); + + let event = StepFunctionEvent::new(payload).expect(&format!( + "Failed to deserialize StepFunctionEvent from {}", + file + )); + + let span_context = event.get_span_context(); + + assert_eq!( + span_context, expected, + "get_span_context() failed for {}", + file + ); + } } #[test] @@ -410,4 +624,37 @@ mod tests { assert_eq!(hex_tid, "1914fe7789eb32be"); } + + #[test] + fn test_extract_trace_tags() { + let test_cases = vec![ + ( + "valid_tid_at_start", + "_dd.p.tid=66bcb5eb00000000,_dd.p.dm=-0", + HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "66bcb5eb00000000".to_string(), + )]), + ), + ( + "valid_tid_at_end", + "_dd.p.dm=-0,_dd.p.tid=abcdef1234567890", + HashMap::from([( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "abcdef1234567890".to_string(), + )]), + ), + ("no_tid_present", "_dd.p.dm=-0", HashMap::new()), + ("empty_input", "", HashMap::new()), + ]; + + for (name, input, expected) in test_cases { + let result = StepFunctionEvent::extract_trace_tags(input); + assert_eq!( + result, expected, + "Test '{}' failed: input '{}'", + name, input + ); + } + } } diff --git a/bottlecap/tests/payloads/step_function_lambda_root_event.json b/bottlecap/tests/payloads/step_function_lambda_root_event.json index 8d226474b..75510b193 100644 --- a/bottlecap/tests/payloads/step_function_lambda_root_event.json +++ b/bottlecap/tests/payloads/step_function_lambda_root_event.json @@ -1,24 +1,24 @@ { - "_datadog": { - "Execution": { - "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", - "Input": {}, - "StartTime": "2024-08-29T21:48:55.187Z", - "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", - "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", - "RedriveCount": 0 - }, - "StateMachine": { - "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", - "Name": "invokeJavaLambda" - }, - "State": { - "Name": "invoker", - "EnteredTime": "2024-08-29T21:48:55.275Z", - "RetryCount": 0 - }, - "x-datadog-trace-id": "5821803790426892636", - "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=672a7cb100000000", - "serverless-version": "v1" - } + "_datadog": { + "Execution": { + "Id": "arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "agocsTest1", + "EnteredTime": "2024-07-30T20:46:20.824Z", + "RetryCount": 0 + }, + "x-datadog-trace-id": "5821803790426892636", + "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=672a7cb100000000", + "serverless-version": "v1" } +} diff --git a/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json b/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json index 263c27af4..55ab12b2e 100644 --- a/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json +++ b/bottlecap/tests/payloads/step_function_lambda_root_legacy_event.json @@ -2,7 +2,7 @@ "Payload": { "_datadog": { "Execution": { - "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Id": "arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f", "Input": {}, "StartTime": "2024-08-29T21:48:55.187Z", "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", @@ -10,12 +10,12 @@ "RedriveCount": 0 }, "StateMachine": { - "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Id": "arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF", "Name": "invokeJavaLambda" }, "State": { - "Name": "invoker", - "EnteredTime": "2024-08-29T21:48:55.275Z", + "Name": "agocsTest1", + "EnteredTime": "2024-07-30T20:46:20.824Z", "RetryCount": 0 }, "x-datadog-trace-id": "5821803790426892636", diff --git a/bottlecap/tests/payloads/step_function_nested_event.json b/bottlecap/tests/payloads/step_function_nested_event.json index f8c9a95ca..744324b74 100644 --- a/bottlecap/tests/payloads/step_function_nested_event.json +++ b/bottlecap/tests/payloads/step_function_nested_event.json @@ -1,23 +1,23 @@ { - "_datadog": { - "Execution": { - "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", - "Input": {}, - "StartTime": "2024-08-29T21:48:55.187Z", - "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", - "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", - "RedriveCount": 0 - }, - "StateMachine": { - "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", - "Name": "invokeJavaLambda" - }, - "State": { - "Name": "invoker", - "EnteredTime": "2024-08-29T21:48:55.275Z", - "RetryCount": 0 - }, - "RootExecutionId": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad", - "serverless-version": "v1" - } + "_datadog": { + "Execution": { + "Id": "arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f", + "Input": {}, + "StartTime": "2024-08-29T21:48:55.187Z", + "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "RoleArn": "arn:aws:iam::425362996713:role/new-extension-test-java-dev-InvokeJavaLambdaRole-LtJmnJReIOTS", + "RedriveCount": 0 + }, + "StateMachine": { + "Id": "arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF", + "Name": "invokeJavaLambda" + }, + "State": { + "Name": "agocsTest1", + "EnteredTime": "2024-07-30T20:46:20.824Z", + "RetryCount": 0 + }, + "RootExecutionId": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad", + "serverless-version": "v1" } +} diff --git a/bottlecap/tests/payloads/step_function_nested_legacy_event.json b/bottlecap/tests/payloads/step_function_nested_legacy_event.json index 3d3a92cb3..5e0dfbbe2 100644 --- a/bottlecap/tests/payloads/step_function_nested_legacy_event.json +++ b/bottlecap/tests/payloads/step_function_nested_legacy_event.json @@ -2,7 +2,7 @@ "Payload": { "_datadog": { "Execution": { - "Id": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", + "Id": "arn:aws:states:us-east-1:425362996713:execution:agocsTestSF:aa6c9316-713a-41d4-9c30-61131716744f", "Input": {}, "StartTime": "2024-08-29T21:48:55.187Z", "Name": "c0ca8d0f-a3af-4c42-bfd4-b3b100e77f01", @@ -10,12 +10,12 @@ "RedriveCount": 0 }, "StateMachine": { - "Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:invokeJavaLambda", + "Id": "arn:aws:states:us-east-1:425362996713:stateMachine:agocsTestSF", "Name": "invokeJavaLambda" }, "State": { - "Name": "invoker", - "EnteredTime": "2024-08-29T21:48:55.275Z", + "Name": "agocsTest1", + "EnteredTime": "2024-07-30T20:46:20.824Z", "RetryCount": 0 }, "RootExecutionId": "arn:aws:states:sa-east-1:425362996713:execution:invokeJavaLambda:4875aba4-ae31-4a4c-bf8a-63e9eee31dad", From 935f8e413a8bbca69bd3f7c614bd6d671a28afc4 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Thu, 6 Mar 2025 14:35:52 -0500 Subject: [PATCH 4/9] fix lint --- .../lifecycle/invocation/triggers/step_function_event.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index 589c8cfe3..b89438def 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -159,8 +159,8 @@ impl StepFunctionEvent { self.execution.id.clone(), self.state.name.clone(), self.state.entered_time.clone(), - self.state.retry_count.clone(), - self.execution.redrive_count.clone(), + self.state.retry_count, + self.execution.redrive_count, ); SpanContext { @@ -178,7 +178,7 @@ impl StepFunctionEvent { } /// Generates a random 64 bit ID from the formatted hash of the - /// Step Function context object. Omit retry_count and redrive_count + /// Step Function context object. We omit `retry_count` and `redrive_count` /// when both are 0 to maintain backwards compatibility. /// fn generate_parent_id( From 4b23b185e23ef71749f75292512443a3352a0218 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Fri, 7 Mar 2025 16:35:54 -0500 Subject: [PATCH 5/9] re-used constants and existing tag extractor --- .../triggers/step_function_event.rs | 123 +++++++----------- .../traces/propagation/text_map_propagator.rs | 2 +- 2 files changed, 51 insertions(+), 74 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index b89438def..a215fe539 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -10,10 +10,15 @@ use crate::{ }, traces::{ context::{Sampling, SpanContext}, - propagation::text_map_propagator::DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY, + propagation::text_map_propagator::{ + DatadogHeaderPropagator, DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY, + DATADOG_SAMPLING_DECISION_KEY, DATADOG_TAGS_KEY, + }, }, }; +pub const DATADOG_LEGACY_LAMBDA_PAYLOAD: &str = "Payload"; + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct StepFunctionEvent { #[serde(rename = "Execution")] @@ -62,10 +67,15 @@ impl Trigger for StepFunctionEvent { Self: Sized, { let p = payload - .get("Payload") + .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) .unwrap_or(&payload) - .get("_datadog") - .unwrap_or(payload.get("Payload").unwrap_or(&payload)); + .get(super::DATADOG_CARRIER_KEY) + .unwrap_or( + payload + .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) + .unwrap_or(&payload), + ); + match serde_json::from_value::(p.clone()) { Ok(event) => Some(event), Err(e) => { @@ -81,10 +91,14 @@ impl Trigger for StepFunctionEvent { { // Check if the payload is a Legacy Step Function event and also a JSONata event let p = payload - .get("Payload") + .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) .unwrap_or(payload) - .get("_datadog") - .unwrap_or(payload.get("Payload").unwrap_or(payload)); + .get(super::DATADOG_CARRIER_KEY) + .unwrap_or( + payload + .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) + .unwrap_or(payload), + ); let execution_id = p .get("Execution") @@ -134,20 +148,23 @@ impl StepFunctionEvent { let (lo_tid, tags) = if let (Some(trace_id), Some(trace_tags)) = (&self.trace_id, &self.trace_tags) { // Lambda Root - let lo_tid = trace_id.parse().expect("Failed to parse trace_id to u64"); - let tags = Self::extract_trace_tags(trace_tags); - (lo_tid, tags) - } else if let Some(root_execution_id) = &self.root_execution_id { - // Nested - let (lo_tid, hi_tid) = Self::generate_trace_id(root_execution_id.clone()); - let tags = HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - format!("{hi_tid:x}"), - )]); + let lo_tid = trace_id + .parse() + .unwrap_or(Self::generate_trace_id(self.execution.id.clone()).0); + + let tags = DatadogHeaderPropagator::extract_tags(&HashMap::from([( + DATADOG_TAGS_KEY.to_string(), + trace_tags.to_string(), + )])); + (lo_tid, tags) } else { - // Normal - let (lo_tid, hi_tid) = Self::generate_trace_id(self.execution.id.clone()); + // Nested or Normal, fetch correct ID + let execution_arn = self + .root_execution_id + .as_ref() + .unwrap_or(&self.execution.id); + let (lo_tid, hi_tid) = Self::generate_trace_id(execution_arn.clone()); let tags = HashMap::from([( DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), format!("{hi_tid:x}"), @@ -227,19 +244,6 @@ impl StepFunctionEvent { result } } - - /// Extracts the `_dd.p.tid` tag from a comma-separated trace tags string - /// - fn extract_trace_tags(trace_tags: &str) -> HashMap { - trace_tags - .split(',') - .filter_map(|s| s.split_once('=')) - .find_map(|(k, v)| { - (k == DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY) - .then(|| (k.to_string(), v.to_string())) - }) - .map_or_else(HashMap::new, |entry| HashMap::from([entry])) - } } impl ServiceNameResolver for StepFunctionEvent { @@ -533,10 +537,13 @@ mod tests { mechanism: None, }), origin: Some("states".to_string()), - tags: HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - "672a7cb100000000".to_string(), - )]), + tags: HashMap::from([ + ( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "672a7cb100000000".to_string(), + ), + (DATADOG_SAMPLING_DECISION_KEY.to_string(), "-0".to_string()), + ]), links: vec![], }, ), @@ -550,10 +557,13 @@ mod tests { mechanism: None, }), origin: Some("states".to_string()), - tags: HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - "672a7cb100000000".to_string(), - )]), + tags: HashMap::from([ + ( + DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), + "672a7cb100000000".to_string(), + ), + (DATADOG_SAMPLING_DECISION_KEY.to_string(), "-0".to_string()), + ]), links: vec![], }, ), @@ -624,37 +634,4 @@ mod tests { assert_eq!(hex_tid, "1914fe7789eb32be"); } - - #[test] - fn test_extract_trace_tags() { - let test_cases = vec![ - ( - "valid_tid_at_start", - "_dd.p.tid=66bcb5eb00000000,_dd.p.dm=-0", - HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - "66bcb5eb00000000".to_string(), - )]), - ), - ( - "valid_tid_at_end", - "_dd.p.dm=-0,_dd.p.tid=abcdef1234567890", - HashMap::from([( - DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY.to_string(), - "abcdef1234567890".to_string(), - )]), - ), - ("no_tid_present", "_dd.p.dm=-0", HashMap::new()), - ("empty_input", "", HashMap::new()), - ]; - - for (name, input, expected) in test_cases { - let result = StepFunctionEvent::extract_trace_tags(input); - assert_eq!( - result, expected, - "Test '{}' failed: input '{}'", - name, input - ); - } - } } diff --git a/bottlecap/src/traces/propagation/text_map_propagator.rs b/bottlecap/src/traces/propagation/text_map_propagator.rs index 520428630..67a88e166 100644 --- a/bottlecap/src/traces/propagation/text_map_propagator.rs +++ b/bottlecap/src/traces/propagation/text_map_propagator.rs @@ -22,7 +22,7 @@ pub const DATADOG_TAGS_KEY: &str = "x-datadog-tags"; pub const DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY: &str = "_dd.p.tid"; const DATADOG_PROPAGATION_ERROR_KEY: &str = "_dd.propagation_error"; pub const DATADOG_LAST_PARENT_ID_KEY: &str = "_dd.parent_id"; -const DATADOG_SAMPLING_DECISION_KEY: &str = "_dd.p.dm"; +pub const DATADOG_SAMPLING_DECISION_KEY: &str = "_dd.p.dm"; // Traceparent Keys const TRACEPARENT_KEY: &str = "traceparent"; From d53417e211613ec261b3f85cc7ff3835b963f0b3 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Fri, 7 Mar 2025 17:18:29 -0500 Subject: [PATCH 6/9] remove super call --- .../lifecycle/invocation/triggers/step_function_event.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index a215fe539..6f3c3cde7 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -17,6 +17,8 @@ use crate::{ }, }; +use super::DATADOG_CARRIER_KEY; + pub const DATADOG_LEGACY_LAMBDA_PAYLOAD: &str = "Payload"; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -69,7 +71,7 @@ impl Trigger for StepFunctionEvent { let p = payload .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) .unwrap_or(&payload) - .get(super::DATADOG_CARRIER_KEY) + .get(DATADOG_CARRIER_KEY) .unwrap_or( payload .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) @@ -93,7 +95,7 @@ impl Trigger for StepFunctionEvent { let p = payload .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) .unwrap_or(payload) - .get(super::DATADOG_CARRIER_KEY) + .get(DATADOG_CARRIER_KEY) .unwrap_or( payload .get(DATADOG_LEGACY_LAMBDA_PAYLOAD) From f50b5a8b9f965d6599816d2999e03e3fe14d88cb Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Mon, 10 Mar 2025 12:49:17 -0400 Subject: [PATCH 7/9] fix import --- .../src/lifecycle/invocation/triggers/step_function_event.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index 6f3c3cde7..8da71e965 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -11,8 +11,7 @@ use crate::{ traces::{ context::{Sampling, SpanContext}, propagation::text_map_propagator::{ - DatadogHeaderPropagator, DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY, - DATADOG_SAMPLING_DECISION_KEY, DATADOG_TAGS_KEY, + DatadogHeaderPropagator, DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY, DATADOG_TAGS_KEY, }, }, }; @@ -262,6 +261,7 @@ impl ServiceNameResolver for StepFunctionEvent { mod tests { use super::*; use crate::lifecycle::invocation::triggers::test_utils::read_json_file; + use crate::traces::propagation::text_map_propagator::DATADOG_SAMPLING_DECISION_KEY; #[test] fn test_new_event() { From df7af65b33fc8bdaa88da2ecdacc05bddc5e5299 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Mon, 10 Mar 2025 13:39:47 -0400 Subject: [PATCH 8/9] empty commit From e8cfc56491d1746cc4c18dee7ca5ec4719eabb38 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Mon, 10 Mar 2025 13:42:41 -0400 Subject: [PATCH 9/9] empty commit