Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.

Commit 1224e92

Browse files
authored
Add telemetry for Lambda Managed Instances (#13466)
1 parent 67ee6bc commit 1224e92

5 files changed

Lines changed: 45 additions & 14 deletions

File tree

localstack-core/localstack/services/lambda_/analytics.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
"status",
1515
"runtime",
1616
"package_type",
17-
# only for operation "invoke"
18-
"invocation_type",
17+
"invocation_type", # only for operation "invoke", otherwise "n/a"
18+
"initialization_type",
1919
],
20+
schema_version=2,
2021
)
2122

2223

@@ -38,6 +39,14 @@ class FunctionStatus(StrEnum):
3839
invocation_error = "invocation_error"
3940

4041

42+
class FunctionInitializationType(StrEnum):
43+
# Maps to the Lambda environment variable AWS_LAMBDA_INITIALIZATION_TYPE
44+
on_demand = "on-demand"
45+
lambda_managed_instances = "lambda-managed-instances"
46+
# Only applies to the operation "invoke" because provisioned concurrency is not configured on "create"
47+
provisioned_concurrency = "provisioned-concurrency"
48+
49+
4150
esm_counter = LabeledCounter(namespace=NAMESPACE, name="esm", labels=["source", "status"])
4251

4352

localstack-core/localstack/services/lambda_/invocation/event_manager.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from localstack import config
1414
from localstack.aws.api.lambda_ import InvocationType, TooManyRequestsException
1515
from localstack.services.lambda_.analytics import (
16+
FunctionInitializationType,
1617
FunctionOperation,
1718
FunctionStatus,
1819
function_counter,
@@ -198,22 +199,22 @@ def stop(self):
198199
def handle_message(self, message: dict) -> None:
199200
failure_cause = None
200201
qualifier = self.version_manager.function_version.id.qualifier
202+
function_config = self.version_manager.function_version.config
201203
event_invoke_config = self.version_manager.function.event_invoke_configs.get(qualifier)
202204
runtime = None
203205
status = None
206+
# TODO: handle initialization_type provisioned-concurrency, which requires enriching invocation_result
207+
initialization_type = (
208+
FunctionInitializationType.lambda_managed_instances
209+
if function_config.CapacityProviderConfig
210+
else FunctionInitializationType.on_demand
211+
)
204212
try:
205213
sqs_invocation = SQSInvocation.decode(message["Body"])
206214
invocation = sqs_invocation.invocation
207215
try:
208216
invocation_result = self.version_manager.invoke(invocation=invocation)
209-
function_config = self.version_manager.function_version.config
210-
function_counter.labels(
211-
operation=FunctionOperation.invoke,
212-
runtime=function_config.runtime or "n/a",
213-
status=FunctionStatus.success,
214-
invocation_type=InvocationType.Event,
215-
package_type=function_config.package_type,
216-
).increment()
217+
status = FunctionStatus.success
217218
except Exception as e:
218219
# Reserved concurrency == 0
219220
if self.version_manager.function.reserved_concurrent_executions == 0:
@@ -223,6 +224,7 @@ def handle_message(self, message: dict) -> None:
223224
elif not has_enough_time_for_retry(sqs_invocation, event_invoke_config):
224225
failure_cause = "EventAgeExceeded"
225226
status = FunctionStatus.event_age_exceeded_error
227+
226228
if failure_cause:
227229
invocation_result = InvocationResult(
228230
is_error=True, request_id=invocation.request_id, payload=None, logs=None
@@ -240,14 +242,14 @@ def handle_message(self, message: dict) -> None:
240242
sqs_client.delete_message(
241243
QueueUrl=self.event_queue_url, ReceiptHandle=message["ReceiptHandle"]
242244
)
243-
# status MUST be set before returning
244-
package_type = self.version_manager.function_version.config.package_type
245+
assert status, "status MUST be set before returning"
245246
function_counter.labels(
246247
operation=FunctionOperation.invoke,
247248
runtime=runtime or "n/a",
248249
status=status,
249250
invocation_type=InvocationType.Event,
250-
package_type=package_type,
251+
package_type=function_config.package_type,
252+
initialization_type=initialization_type,
251253
).increment()
252254

253255
# Good summary blogpost: https://haithai91.medium.com/aws-lambdas-retry-behaviors-edff90e1cf1b
@@ -257,6 +259,8 @@ def handle_message(self, message: dict) -> None:
257259
if event_invoke_config and event_invoke_config.maximum_retry_attempts is not None:
258260
max_retry_attempts = event_invoke_config.maximum_retry_attempts
259261

262+
assert invocation_result, "Invocation result MUST exist if we are not returning before"
263+
260264
# An invocation error either leads to a terminal failure or to a scheduled retry
261265
if invocation_result.is_error: # invocation error
262266
failure_cause = None

localstack-core/localstack/services/lambda_/invocation/lambda_service.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from localstack.constants import AWS_REGION_US_EAST_1
3030
from localstack.services.lambda_ import hooks as lambda_hooks
3131
from localstack.services.lambda_.analytics import (
32+
FunctionInitializationType,
3233
FunctionOperation,
3334
FunctionStatus,
3435
function_counter,
@@ -313,6 +314,12 @@ def invoke(
313314
raise ResourceNotFoundException(f"Function not found: {invoked_arn}", Type="User")
314315
runtime = version.config.runtime or "n/a"
315316
package_type = version.config.package_type
317+
# Not considering provisioned concurrency for such early errors
318+
initialization_type = (
319+
FunctionInitializationType.lambda_managed_instances
320+
if version.config.CapacityProviderConfig
321+
else FunctionInitializationType.on_demand
322+
)
316323
if version.config.CapacityProviderConfig and qualifier == "$LATEST":
317324
if function.versions.get("$LATEST.PUBLISHED"):
318325
raise InvalidParameterValueException(
@@ -355,6 +362,7 @@ def invoke(
355362
status=status,
356363
invocation_type=invocation_type,
357364
package_type=package_type,
365+
initialization_type=initialization_type,
358366
).increment()
359367
raise ResourceConflictException(
360368
f"The operation cannot be performed at this time. The function is currently in the following state: {state}"
@@ -373,6 +381,7 @@ def invoke(
373381
status=FunctionStatus.invalid_payload_error,
374382
invocation_type=invocation_type,
375383
package_type=package_type,
384+
initialization_type=initialization_type,
376385
).increment()
377386
# MAYBE: improve parity of detailed exception message (quite cumbersome)
378387
raise InvalidRequestContentException(
@@ -417,12 +426,14 @@ def invoke(
417426
if invocation_result.is_error
418427
else FunctionStatus.success
419428
)
429+
# TODO: handle initialization_type provisioned-concurrency, requires enriching invocation_result
420430
function_counter.labels(
421431
operation=FunctionOperation.invoke,
422432
runtime=runtime,
423433
status=status,
424434
invocation_type=invocation_type,
425435
package_type=package_type,
436+
initialization_type=initialization_type,
426437
).increment()
427438
return invocation_result
428439

localstack-core/localstack/services/lambda_/provider.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
from localstack.services.lambda_ import api_utils
159159
from localstack.services.lambda_ import hooks as lambda_hooks
160160
from localstack.services.lambda_.analytics import (
161+
FunctionInitializationType,
161162
FunctionOperation,
162163
FunctionStatus,
163164
function_counter,
@@ -1169,12 +1170,18 @@ def create_function(
11691170
)
11701171
fn.versions["$LATEST"] = version_post_response or version
11711172
state.functions[function_name] = fn
1173+
initialization_type = (
1174+
FunctionInitializationType.lambda_managed_instances
1175+
if capacity_provider_config
1176+
else FunctionInitializationType.on_demand
1177+
)
11721178
function_counter.labels(
11731179
operation=FunctionOperation.create,
11741180
runtime=runtime or "n/a",
11751181
status=FunctionStatus.success,
11761182
invocation_type="n/a",
11771183
package_type=package_type,
1184+
initialization_type=initialization_type,
11781185
)
11791186
# TODO: consider potential other side effects of not having a function version for $LATEST
11801187
# Provisioning happens upon publishing for functions using a capacity provider

localstack-core/localstack/testing/aws/lambda_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def _concurrency_update_done():
261261

262262
def get_invoke_init_type(
263263
client, function_name, qualifier
264-
) -> Literal["on-demand", "provisioned-concurrency"]:
264+
) -> Literal["on-demand", "provisioned-concurrency", "lambda-managed-instances"]:
265265
"""check the environment in the lambda for AWS_LAMBDA_INITIALIZATION_TYPE indicating ondemand/provisioned"""
266266
invoke_result = client.invoke(FunctionName=function_name, Qualifier=qualifier)
267267
return json.load(invoke_result["Payload"])

0 commit comments

Comments
 (0)