Skip to content

Commit 3e0be33

Browse files
lym953claude
andcommitted
test: add integration test for durable-function cold-start metric tag
Deploys a real durable-configured Lambda alongside a plain guard function and asserts the cold-start aws.lambda.enhanced.invocations metric carries durable_function:true only for the durable one, exercising the fix in a905347 end-to-end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent a905347 commit 3e0be33

4 files changed

Lines changed: 146 additions & 0 deletions

File tree

.gitlab/datasources/test-suites.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ test_suites:
77
- name: oom
88
- name: lmi-oom
99
- name: payload-size
10+
- name: durable-cold-start

integration-tests/bin/app.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Oom} from '../lib/stacks/oom';
1010
import {LmiOom} from '../lib/stacks/lmi-oom';
1111
import {CustomMetrics} from '../lib/stacks/custom-metrics';
1212
import {PayloadSize} from '../lib/stacks/payload-size';
13+
import {DurableColdStart} from '../lib/stacks/durable-cold-start';
1314
import {AuthRoleStack} from '../lib/auth-role';
1415
import {ACCOUNT, IDENTIFIER, REGION} from '../config';
1516
import {CapacityProviderStack} from "../lib/capacity-provider";
@@ -54,6 +55,9 @@ const stacks = [
5455
new PayloadSize(app, `${IDENTIFIER}-payload-size`, {
5556
env,
5657
}),
58+
new DurableColdStart(app, `${IDENTIFIER}-durable-cold-start`, {
59+
env,
60+
}),
5761
]
5862

5963
// Tag all stacks so we can easily clean them up
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import * as lambda from 'aws-cdk-lib/aws-lambda';
3+
import { Construct } from 'constructs';
4+
import {
5+
createLogGroup,
6+
defaultDatadogEnvVariables,
7+
defaultDatadogSecretPolicy,
8+
getExtensionLayer,
9+
getDefaultNodeLayer,
10+
defaultNodeRuntime,
11+
} from '../util';
12+
13+
// Durable-function cold-start metric tag test.
14+
// The Lambda Telemetry API only reports a `platform.initStart` runtimeVersion
15+
// containing "DurableFunction" for functions actually configured with
16+
// `durableConfig`, so this stack deploys one durable and one non-durable
17+
// function to exercise bottlecap's `durable_function:true` tagging on the
18+
// cold-start `aws.lambda.enhanced.invocations` metric end-to-end.
19+
export class DurableColdStart extends cdk.Stack {
20+
constructor(scope: Construct, id: string, props: cdk.StackProps) {
21+
super(scope, id, props);
22+
23+
const extensionLayer = getExtensionLayer(this);
24+
const nodeLayer = getDefaultNodeLayer(this);
25+
26+
const durableFunctionName = `${id}-durable-lambda`;
27+
const durableFunction = new lambda.Function(this, durableFunctionName, {
28+
runtime: defaultNodeRuntime,
29+
architecture: lambda.Architecture.ARM_64,
30+
handler: '/opt/nodejs/node_modules/datadog-lambda-js/handler.handler',
31+
code: lambda.Code.fromAsset('./lambda/default-node'),
32+
functionName: durableFunctionName,
33+
timeout: cdk.Duration.seconds(30),
34+
memorySize: 256,
35+
durableConfig: {
36+
executionTimeout: cdk.Duration.minutes(5),
37+
},
38+
environment: {
39+
...defaultDatadogEnvVariables,
40+
DD_SERVICE: durableFunctionName,
41+
DD_TRACE_ENABLED: 'true',
42+
DD_LAMBDA_HANDLER: 'index.handler',
43+
},
44+
logGroup: createLogGroup(this, durableFunctionName),
45+
});
46+
durableFunction.addToRolePolicy(defaultDatadogSecretPolicy);
47+
durableFunction.addLayers(extensionLayer);
48+
durableFunction.addLayers(nodeLayer);
49+
50+
const nonDurableFunctionName = `${id}-non-durable-lambda`;
51+
const nonDurableFunction = new lambda.Function(this, nonDurableFunctionName, {
52+
runtime: defaultNodeRuntime,
53+
architecture: lambda.Architecture.ARM_64,
54+
handler: '/opt/nodejs/node_modules/datadog-lambda-js/handler.handler',
55+
code: lambda.Code.fromAsset('./lambda/default-node'),
56+
functionName: nonDurableFunctionName,
57+
timeout: cdk.Duration.seconds(30),
58+
memorySize: 256,
59+
environment: {
60+
...defaultDatadogEnvVariables,
61+
DD_SERVICE: nonDurableFunctionName,
62+
DD_TRACE_ENABLED: 'true',
63+
DD_LAMBDA_HANDLER: 'index.handler',
64+
},
65+
logGroup: createLogGroup(this, nonDurableFunctionName),
66+
});
67+
nonDurableFunction.addToRolePolicy(defaultDatadogSecretPolicy);
68+
nonDurableFunction.addLayers(extensionLayer);
69+
nonDurableFunction.addLayers(nodeLayer);
70+
}
71+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Verifies bottlecap holds the cold-start `aws.lambda.enhanced.invocations`
2+
// metric until the Telemetry API's `platform.initStart` event reports the
3+
// runtime, so the metric carries `durable_function:true` for a real
4+
// durable-configured Lambda function (#1301). A plain (non-durable) function
5+
// is invoked alongside as a guard against the tag being set unconditionally.
6+
import { hasMetricWithTag } from './utils/datadog';
7+
import { forceColdStart, invokeLambda } from './utils/lambda';
8+
import { IDENTIFIER, DEFAULT_DATADOG_INDEXING_WAIT_MS } from '../config';
9+
10+
const stackName = `${IDENTIFIER}-durable-cold-start`;
11+
12+
const INVOCATIONS_METRIC = 'aws.lambda.enhanced.invocations';
13+
14+
describe('Durable Function Cold-Start Metric Tag Integration Tests', () => {
15+
let invocationStartTime: number;
16+
let metricsEndTime: number;
17+
18+
const durableFunctionName = `${stackName}-durable-lambda`;
19+
const nonDurableFunctionName = `${stackName}-non-durable-lambda`;
20+
21+
beforeAll(async () => {
22+
const functionNames = [durableFunctionName, nonDurableFunctionName];
23+
24+
await Promise.all(functionNames.map((fn) => forceColdStart(fn)));
25+
26+
// Back up the query window by 60s so the metric bucket (which Datadog
27+
// aligns to the rollup interval boundary, often before the invocation)
28+
// falls inside the range we pass to /api/v1/query.
29+
invocationStartTime = Date.now() - 60_000;
30+
31+
// Durable functions reject unqualified-ARN invokes ("You cannot invoke a
32+
// durable function using an unqualified ARN"); `:$LATEST` satisfies that
33+
// without publishing a version. Metric queries below use the base
34+
// (unqualified) function name, which is unaffected by this qualifier.
35+
await Promise.all([
36+
invokeLambda(`${durableFunctionName}:$LATEST`),
37+
invokeLambda(nonDurableFunctionName),
38+
]);
39+
40+
await new Promise((resolve) =>
41+
setTimeout(resolve, DEFAULT_DATADOG_INDEXING_WAIT_MS),
42+
);
43+
44+
metricsEndTime = Date.now();
45+
46+
console.log('Lambdas invoked and indexing wait complete');
47+
}, 1800000);
48+
49+
it('durable function cold-start invocation metric should have durable_function:true', async () => {
50+
const hasTag = await hasMetricWithTag(
51+
INVOCATIONS_METRIC,
52+
durableFunctionName,
53+
'durable_function:true',
54+
invocationStartTime,
55+
metricsEndTime,
56+
);
57+
expect(hasTag).toBe(true);
58+
});
59+
60+
it('non-durable function cold-start invocation metric should NOT have durable_function:true', async () => {
61+
const hasTag = await hasMetricWithTag(
62+
INVOCATIONS_METRIC,
63+
nonDurableFunctionName,
64+
'durable_function:true',
65+
invocationStartTime,
66+
metricsEndTime,
67+
);
68+
expect(hasTag).toBe(false);
69+
});
70+
});

0 commit comments

Comments
 (0)