Skip to content

Commit b8d2a84

Browse files
authored
Merge branch 'main' into zarir/base_service
2 parents 5e75b5b + 1470194 commit b8d2a84

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

bottlecap/src/config/aws.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::env;
22
use tokio::time::Instant;
33

4+
use crate::tags::lambda::tags::SNAP_START_VALUE;
5+
46
const AWS_DEFAULT_REGION: &str = "AWS_DEFAULT_REGION";
57
const AWS_ACCESS_KEY_ID: &str = "AWS_ACCESS_KEY_ID";
68
const AWS_SECRET_ACCESS_KEY: &str = "AWS_SECRET_ACCESS_KEY";
@@ -46,6 +48,11 @@ impl AwsConfig {
4648
self.initialization_type
4749
.eq(LAMBDA_MANAGED_INSTANCES_INIT_TYPE)
4850
}
51+
52+
#[must_use]
53+
pub fn is_snapstart(&self) -> bool {
54+
self.initialization_type.eq(SNAP_START_VALUE)
55+
}
4956
}
5057

5158
#[allow(clippy::module_name_repetitions)]

bottlecap/src/lifecycle/invocation/processor.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ use crate::{
4949
pub const MS_TO_NS: f64 = 1_000_000.0;
5050
pub const S_TO_MS: u64 = 1_000;
5151
pub const S_TO_NS: f64 = 1_000_000_000.0;
52+
/// Threshold for classifying a Lambda cold start as proactive initialization.
53+
///
54+
/// Proactive initialization is a Lambda optimization where the runtime pre-initializes
55+
/// a sandbox before any invocation is scheduled, to reduce cold start latency for future
56+
/// requests.
5257
pub const PROACTIVE_INITIALIZATION_THRESHOLD_MS: u64 = 10_000;
5358

5459
pub const DATADOG_INVOCATION_ERROR_MESSAGE_KEY: &str = "x-datadog-invocation-error-msg";
@@ -96,6 +101,8 @@ pub struct Processor {
96101
/// logs agent. Decouples the trace agent from the logs agent: the trace agent sends spans
97102
/// to the lifecycle processor, which extracts durable context and relays it here.
98103
durable_context_tx: mpsc::Sender<DurableContextUpdate>,
104+
/// Time of the `SnapStart` restore event, set when `PlatformRestoreStart` is received.
105+
restore_time: Option<Instant>,
99106
}
100107

101108
impl Processor {
@@ -137,6 +144,7 @@ impl Processor {
137144
active_invocations: 0,
138145
awaiting_first_invocation: false,
139146
durable_context_tx,
147+
restore_time: None,
140148
}
141149
}
142150

@@ -252,12 +260,35 @@ impl Processor {
252260

253261
// If it's empty, then we are in a cold start
254262
if self.context_buffer.is_empty() {
255-
let now = Instant::now();
256-
let time_since_sandbox_init = now.duration_since(self.aws_config.sandbox_init_time);
257-
if time_since_sandbox_init.as_millis() > PROACTIVE_INITIALIZATION_THRESHOLD_MS.into() {
258-
proactive_initialization = true;
263+
if self.aws_config.is_snapstart() {
264+
match self.restore_time {
265+
None => {
266+
// PlatformRestoreStart hasn't arrived yet — restore and invoke
267+
// happened close together, so this is a cold start (not proactive).
268+
cold_start = true;
269+
}
270+
Some(restore_time) => {
271+
let now = Instant::now();
272+
let time_since_restore = now.duration_since(restore_time);
273+
if time_since_restore.as_millis()
274+
> PROACTIVE_INITIALIZATION_THRESHOLD_MS.into()
275+
{
276+
proactive_initialization = true;
277+
} else {
278+
cold_start = true;
279+
}
280+
}
281+
}
259282
} else {
260-
cold_start = true;
283+
let now = Instant::now();
284+
let time_since_sandbox_init = now.duration_since(self.aws_config.sandbox_init_time);
285+
if time_since_sandbox_init.as_millis()
286+
> PROACTIVE_INITIALIZATION_THRESHOLD_MS.into()
287+
{
288+
proactive_initialization = true;
289+
} else {
290+
cold_start = true;
291+
}
261292
}
262293

263294
// Resolve runtime only once
@@ -383,6 +414,8 @@ impl Processor {
383414
/// This is used to create a `snapstart_restore` span, since this telemetry event does not
384415
/// provide a `request_id`, we try to guess which invocation is the restore similar to init.
385416
pub fn on_platform_restore_start(&mut self, time: DateTime<Utc>) {
417+
self.restore_time = Some(Instant::now());
418+
386419
let start_time: i64 = SystemTime::from(time)
387420
.duration_since(UNIX_EPOCH)
388421
.expect("time went backwards")

integration-tests/tests/snapstart.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,23 @@ describe('Snapstart Integration Tests', () => {
101101
);
102102
expect(coldStartSpan).toBeUndefined();
103103
});
104+
105+
it('should have aws.lambda span with cold_start=true', () => {
106+
const result = getRestoreInvocation();
107+
expect(result).toBeDefined();
108+
const trace = result.traces![0];
109+
const awsLambdaSpan = trace.spans.find((span: any) =>
110+
span.attributes.operation_name === 'aws.lambda'
111+
);
112+
expect(awsLambdaSpan).toBeDefined();
113+
expect(awsLambdaSpan).toMatchObject({
114+
attributes: {
115+
custom: {
116+
cold_start: 'true'
117+
}
118+
}
119+
});
120+
});
104121
});
105122

106123
describe('second invocation (warm)', () => {
@@ -146,6 +163,23 @@ describe('Snapstart Integration Tests', () => {
146163
);
147164
expect(coldStartSpan).toBeUndefined();
148165
});
166+
167+
it('should have aws.lambda span with cold_start=false', () => {
168+
const result = getWarmInvocation();
169+
expect(result).toBeDefined();
170+
const trace = result.traces![0];
171+
const awsLambdaSpan = trace.spans.find((span: any) =>
172+
span.attributes.operation_name === 'aws.lambda'
173+
);
174+
expect(awsLambdaSpan).toBeDefined();
175+
expect(awsLambdaSpan).toMatchObject({
176+
attributes: {
177+
custom: {
178+
cold_start: 'false'
179+
}
180+
}
181+
});
182+
});
149183
});
150184

151185
describe('trace isolation', () => {

0 commit comments

Comments
 (0)