From c77981f5904529f769698d695d631ff013ed976d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serkan=20=C3=96ZAL?= Date: Mon, 26 Aug 2024 17:25:47 +0300 Subject: [PATCH 1/2] Record AWS Lambda coldstarts --- .../src/instrumentation.ts | 52 +++++++++- .../src/types.ts | 1 + .../test/integrations/lambda-handler.test.ts | 96 +++++++++++++++++++ 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts index 4aef1509ce..a705ca8b25 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts @@ -44,6 +44,7 @@ import { AWSXRayPropagator, } from '@opentelemetry/propagator-aws-xray'; import { + SEMATTRS_FAAS_COLDSTART, SEMATTRS_FAAS_EXECUTION, SEMRESATTRS_CLOUD_ACCOUNT_ID, SEMRESATTRS_FAAS_ID, @@ -72,6 +73,7 @@ const headerGetter: TextMapGetter = { }; export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID'; +export const lambdaMaxInitInMilliseconds = 10_000; export class AwsLambdaInstrumentation extends InstrumentationBase { private _traceForceFlusher?: () => Promise; @@ -135,6 +137,10 @@ export class AwsLambdaInstrumentation extends InstrumentationBase { @@ -164,16 +174,47 @@ export class AwsLambdaInstrumentation extends InstrumentationBase { - return this._getPatchHandler(original); + return this._getPatchHandler(original, handlerLoadStartTime); }; } - private _getPatchHandler(original: Handler) { + private _getPatchHandler(original: Handler, lambdaStartTime: number) { diag.debug('patch handler function'); const plugin = this; + let requestHandledBefore = false; + let requestIsColdStart = true; + + function _onRequest(): void { + if (requestHandledBefore) { + // Non-first requests cannot be coldstart. + requestIsColdStart = false; + } else { + if ( + process.env.AWS_LAMBDA_INITIALIZATION_TYPE === + 'provisioned-concurrency' + ) { + // If sandbox environment is initialized with provisioned concurrency, + // even the first requests should not be considered as coldstart. + requestIsColdStart = false; + } else { + // Check whether it is proactive initialization or not: + // https://aaronstuyvenberg.com/posts/understanding-proactive-initialization + const passedTimeSinceHandlerLoad: number = + Date.now() - lambdaStartTime; + const proactiveInitialization: boolean = + passedTimeSinceHandlerLoad > lambdaMaxInitInMilliseconds; + + // If sandbox has been initialized proactively before the actual request, + // even the first requests should not be considered as coldstart. + requestIsColdStart = !proactiveInitialization; + } + requestHandledBefore = true; + } + } + return function patchedHandler( this: never, // The event can be a user type, it truly is any. @@ -182,6 +223,8 @@ export class AwsLambdaInstrumentation extends InstrumentationBase { assert.strictEqual(span.parentSpanId, undefined); }); + it('should record coldstart', async () => { + initializeHandler('lambda-test/sync.handler'); + + const handlerModule = lambdaRequire('lambda-test/sync'); + + const result1 = await new Promise((resolve, reject) => { + handlerModule.handler('arg', ctx, (err: Error, res: any) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + + const result2 = await new Promise((resolve, reject) => { + handlerModule.handler('arg', ctx, (err: Error, res: any) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 2); + const [span1, span2] = spans; + + assert.strictEqual(result1, 'ok'); + assertSpanSuccess(span1); + assert.strictEqual(span1.parentSpanId, undefined); + assert.strictEqual(span1.attributes[SEMATTRS_FAAS_COLDSTART], true); + + assert.strictEqual(result2, 'ok'); + assertSpanSuccess(span2); + assert.strictEqual(span2.parentSpanId, undefined); + assert.strictEqual(span2.attributes[SEMATTRS_FAAS_COLDSTART], false); + }); + + it('should record coldstart with provisioned concurrency', async () => { + process.env.AWS_LAMBDA_INITIALIZATION_TYPE = 'provisioned-concurrency'; + + initializeHandler('lambda-test/sync.handler'); + + const result = await new Promise((resolve, reject) => { + lambdaRequire('lambda-test/sync').handler( + 'arg', + ctx, + (err: Error, res: any) => { + if (err) { + reject(err); + } else { + resolve(res); + } + } + ); + }); + assert.strictEqual(result, 'ok'); + const spans = memoryExporter.getFinishedSpans(); + const [span] = spans; + assert.strictEqual(spans.length, 1); + assertSpanSuccess(span); + assert.strictEqual(span.parentSpanId, undefined); + assert.strictEqual(span.attributes[SEMATTRS_FAAS_COLDSTART], false); + }); + + it('should record coldstart with proactive initialization', async () => { + initializeHandler('lambda-test/sync.handler', { + lambdaStartTime: Date.now() - 2 * lambdaMaxInitInMilliseconds, + }); + + const result = await new Promise((resolve, reject) => { + lambdaRequire('lambda-test/sync').handler( + 'arg', + ctx, + (err: Error, res: any) => { + if (err) { + reject(err); + } else { + resolve(res); + } + } + ); + }); + assert.strictEqual(result, 'ok'); + const spans = memoryExporter.getFinishedSpans(); + const [span] = spans; + assert.strictEqual(spans.length, 1); + assertSpanSuccess(span); + assert.strictEqual(span.parentSpanId, undefined); + assert.strictEqual(span.attributes[SEMATTRS_FAAS_COLDSTART], false); + }); + it('should record error', async () => { initializeHandler('lambda-test/sync.error'); From 8159ac7b5f52d97b264006dad6e404577d68380c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serkan=20=C3=96ZAL?= Date: Wed, 4 Sep 2024 22:55:09 +0300 Subject: [PATCH 2/2] Use FAAS coldstart attribute name from new ones --- .../src/instrumentation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts index a705ca8b25..276ef63518 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts @@ -44,11 +44,11 @@ import { AWSXRayPropagator, } from '@opentelemetry/propagator-aws-xray'; import { - SEMATTRS_FAAS_COLDSTART, SEMATTRS_FAAS_EXECUTION, SEMRESATTRS_CLOUD_ACCOUNT_ID, SEMRESATTRS_FAAS_ID, } from '@opentelemetry/semantic-conventions'; +import { ATTR_FAAS_COLDSTART } from '@opentelemetry/semantic-conventions/incubating'; import { APIGatewayProxyEventHeaders, @@ -246,7 +246,7 @@ export class AwsLambdaInstrumentation extends InstrumentationBase