8000 [Prometheus] Add & instrument Lambda environment metrics (#94) · localstack/localstack-extensions@34b22f5 · GitHub
[go: up one dir, main page]

Skip to content
  • Commit 34b22f5

    Browse files
    authored
    [Prometheus] Add & instrument Lambda environment metrics (#94)
    1 parent 67ad5d4 commit 34b22f5

    File tree

    8 files changed

    +144
    -22
    lines changed

    8 files changed

    +144
    -22
    lines changed

    prometheus/localstack_prometheus/expose.py

    Lines changed: 1 addition & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -4,8 +4,6 @@
    44

    55
    def retrieve_metrics(request: http.Request):
    66
    """Expose the Prometheus metrics"""
    7-
    _generate_latest_metrics, content_type = choose_encoder(
    8-
    request.headers.get("Content-Type", "")
    9-
    )
    7+
    _generate_latest_metrics, content_type = choose_encoder(request.headers.get("Content-Type", ""))
    108
    data = _generate_latest_metrics()
    119
    return http.Response(response=data, status=200, mimetype=content_type)

    prometheus/localstack_prometheus/extension.py

    Lines changed: 5 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -9,7 +9,10 @@
    99

    1010
    from localstack_prometheus.expose import retrieve_metrics
    1111
    from localstack_prometheus.handler import RequestMetricsHandler, ResponseMetricsHandler
    12-
    from localstack_prometheus.instruments.patch import apply_poller_tracking_patches
    12+
    from localstack_prometheus.instruments.patch import (
    13+
    apply_lambda_tracking_patches,
    14+
    apply_poller_tracking_patches,
    15+
    )
    1316

    1417
    LOG = logging.getLogger(__name__)
    1518

    @@ -18,6 +21,7 @@ class PrometheusMetricsExtension(Extension):
    1821
    name = "prometheus"
    1922

    2023
    def on_extension_load(self):
    24+
    apply_lambda_tracking_patches()
    2125
    apply_poller_tracking_patches()
    2226
    LOG.debug("PrometheusMetricsExtension: extension is loaded")
    2327

    prometheus/localstack_prometheus/handler.py

    Lines changed: 5 additions & 13 deletions
    Original file line numberDiff line numberDiff line change
    @@ -6,7 +6,7 @@
    66
    from localstack.http import Response
    77

    88
    from localstack_prometheus.metrics.core import (
    9-
    LOCALSTACK_IN_FLIGHT_REQUESTS_GAUGE,
    9+
    LOCALSTACK_IN_FLIGHT_REQUESTS,
    1010
    LOCALSTACK_REQUEST_PROCESSING_DURATION_SECONDS,
    1111
    )
    1212

    @@ -22,9 +22,7 @@ class RequestMetricsHandler(Handler):
    2222
    Handler that records the start time of incoming requests
    2323
    """
    2424

    25-
    def __call__(
    26-
    self, chain: HandlerChain, context: TimedRequestContext, response: Response
    27-
    ):
    25+
    def __call__(self, chain: HandlerChain, context: TimedRequestContext, response: Response):
    2826
    # Record the start time
    2927
    context.start_time = time.perf_counter()
    3028

    @@ -33,27 +31,21 @@ def __call__(
    3331
    return
    3432

    3533
    service, operation = context.service_operation
    36-
    LOCALSTACK_IN_FLIGHT_REQUESTS_GAUGE.labels(
    37-
    service=service, operation=operation
    38-
    ).inc()
    34+
    LOCALSTACK_IN_FLIGHT_REQUESTS.labels(service=service, operation=operation).inc()
    3935

    4036

    4137
    class ResponseMetricsHandler(Handler):
    4238
    """
    4339
    Handler that records metrics when a response is ready
    4440
    """
    4541

    46-
    def __call__(
    47-
    self, chain: HandlerChain, context: TimedRequestContext, response: Response
    48-
    ):
    42+
    def __call__(self, chain: HandlerChain, context: TimedRequestContext, response: Response):
    4943
    # Do not record metrics if no service operation information is found
    5044
    if not context.service_operation:
    5145
    return
    5246

    5347
    service, operation = context.service_operation
    54-
    LOCALSTACK_IN_FLIGHT_REQUESTS_GAUGE.labels(
    55-
    service=service, operation=operation
    56-
    ).dec()
    48+
    LOCALSTACK_IN_FLIGHT_REQUESTS.labels(service=service, operation=operation).dec()
    5749

    5850
    # Do not record if response is None
    5951
    if response is None:
    Lines changed: 81 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,81 @@
    1+
    import contextlib
    2+
    from typing import ContextManager
    3+
    4+
    from localstack.services.lambda_.invocation.assignment import AssignmentService
    5+
    from localstack.services.lambda_.invocation.docker_runtime_executor import (
    6+
    DockerRuntimeExecutor,
    7+
    )
    8+
    from localstack.services.lambda_.invocation.execution_environment import (
    9+
    ExecutionEnvironment,
    10+
    )
    11+
    from localstack.services.lambda_.invocation.lambda_models import (
    12+
    FunctionVersion,
    13+
    InitializationType,
    14+
    )
    15+
    16+
    from localstack_prometheus.metrics.lambda_ import (
    17+
    LOCALSTACK_LAMBDA_ENVIRONMENT_ACTIVE,
    18+
    LOCALSTACK_LAMBDA_ENVIRONMENT_CONTAINERS_RUNNING,
    19+
    LOCALSTACK_LAMBDA_ENVIRONMENT_START_TOTAL,
    20+
    )
    21+
    22+
    23+
    def count_version_environments(
    24+
    assignment_service: AssignmentService, version_manager_id: str, prov_type: InitializationType
    25+
    ):
    26+
    """Count environments of a specific provisioning type for a specific version manager"""
    27+
    return sum(
    28+
    env.initialization_type == prov_type
    29+
    for env in assignment_service.environments.get(version_manager_id, {}).values()
    30+
    )
    31+
    32+
    33+
    def count_service_environments(
    34+
    assignment_service: AssignmentService, prov_type: InitializationType
    35+
    ):
    36+
    """Count environments of a specific provisioning type across all function versions"""
    37+
    return sum(
    38+
    count_version_environments(assignment_service, version_manager_id, prov_type)
    39+
    for version_manager_id in assignment_service.environments
    40+
    )
    41+
    42+
    43+
    def init_assignment_service_with_metrics(fn, self: AssignmentService):
    44+
    fn(self)
    45+
    # Initialise these once, with all subsequent calls being evaluated at collection time.
    46+
    LOCALSTACK_LAMBDA_ENVIRONMENT_ACTIVE.labels(
    47+
    provisioning_type="provisioned-concurrency"
    48+
    ).set_function(lambda: count_service_environments(self, "provisioned-concurrency"))
    49+
    50+
    LOCALSTACK_LAMBDA_ENVIRONMENT_ACTIVE.labels(provisioning_type="on-demand").set_function(
    51+
    lambda: count_service_environments(self, "on-demand")
    52+
    )
    53+
    54+
    55+
    def tracked_docker_start(fn, self: DockerRuntimeExecutor, env_vars: dict[str, str]):
    56+
    fn(self, env_vars)
    57+
    LOCALSTACK_LAMBDA_ENVIRONMENT_CONTAINERS_RUNNING.inc()
    58+
    59+
    60+
    def tracked_docker_stop(fn, self: DockerRuntimeExecutor):
    61+
    fn(self)
    62+
    LOCALSTACK_LAMBDA_ENVIRONMENT_CONTAINERS_RUNNING.dec()
    63+
    64+
    65+
    @contextlib.contextmanager
    66+
    def tracked_get_environment(
    67+ fn,
    68+
    self: AssignmentService,
    69+
    version_manager_id: str,
    70+
    function_version: FunctionVersion,
    71+
    provisioning_type: InitializationType,
    72+
    ) -> ContextManager[ExecutionEnvironment]:
    73+
    applicable_env_count = count_version_environments(self, version_manager_id, provisioning_type)
    74+
    # If there are no applicable environments, this will be a cold start.
    75+
    # Otherwise, it'll be warm.
    76+
    start_type = "warm" if applicable_env_count > 0 else "cold"
    77+
    LOCALSTACK_LAMBDA_ENVIRONMENT_START_TOTAL.labels(
    78+
    start_type=start_type, provisioning_type=provisioning_type
    79+
    ).inc()
    80+
    with fn(self, version_manager_id, function_version, provisioning_type) as execution_env:
    81+
    yield execution_env

    prometheus/localstack_prometheus/instruments/patch.py

    Lines changed: 32 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -12,8 +12,18 @@
    1212
    from localstack.services.lambda_.event_source_mapping.senders.lambda_sender import (
    1313
    LambdaSender,
    1414
    )
    15+
    from localstack.services.lambda_.invocation.assignment import AssignmentService
    16+
    from localstack.services.lambda_.invocation.docker_runtime_executor import (
    17+
    DockerRuntimeExecutor,
    18+
    )
    1519
    from localstack.utils.patch import Patch, Patches
    1620

    21+
    from localstack_prometheus.instruments.lambda_ import (
    22+
    init_assignment_service_with_metrics,
    23+
    tracked_docker_start,
    24+
    tracked_docker_stop,
    25+
    tracked_get_environment,
    26+
    )
    1727
    from localstack_prometheus.instruments.poller import tracked_poll_events
    1828
    from localstack_prometheus.instruments.sender import tracked_send_events
    1929
    from localstack_prometheus.instruments.sqs_poller import tracked_sqs_handle_messages
    @@ -22,6 +32,27 @@
    2232
    LOG = logging.getLogger(__name__)
    2333

    2434

    35+
    def apply_lambda_tracking_patches():
    36+
    """Apply all Lambda environment metrics tracking patches in one call"""
    37+
    patches = Patches(
    38+
    [
    39+
    # Track starting and stopping of containers function
    40+
    Patch.function(target=DockerRuntimeExecutor.start, fn=tracked_docker_start),
    41+
    Patch.function(target=DockerRuntimeExecutor.stop, fn=tracked_docker_stop),
    42+
    # Track cold and warm starts
    43+
    Patch.function(target=AssignmentService.get_environment, fn=tracked_get_environment),
    44+
    # Track and collect all environment
    45+
    Patch.function(
    46+
    target=AssignmentService.__init__, fn=init_assignment_service_with_metrics
    47+
    ),
    48+
    ]
    49+
    )
    50+
    51+
    patches.apply()
    52+
    LOG.debug("Applied all Lambda environment tracking patches")
    53+
    return patches
    54+
    55+
    2556
    def apply_poller_tracking_patches():
    2657
    """Apply all poller metrics tracking patches in one call"""
    2758
    patches = Patches(
    @@ -34,9 +65,7 @@ def apply_poller_tracking_patches():
    3465
    Patch.function(target=LambdaSender.send_events, fn=tracked_send_events),
    3566
    # TODO: Standardise a single abstract method that all Poller subclasses can use to fetch records
    3667
    # SQS-specific patches
    37-
    Patch.function(
    38-
    target=SqsPoller.handle_messages, fn=tracked_sqs_handle_messages
    39-
    ),
    68+
    Patch.function(target=SqsPoller.handle_messages, fn=tracked_sqs_handle_messages),
    4069
    # Stream-specific patches
    4170
    Patch.function(target=KinesisPoller.get_records, fn=tracked_get_records),
    4271
    Patch.function(target=DynamoDBPoller.get_records, fn=tracked_get_records),
    Lines changed: 0 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -1 +0,0 @@
    1-

    prometheus/localstack_prometheus/metrics/core.py

    Lines changed: 1 addition & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -8,7 +8,7 @@
    88
    buckets=[0.005, 0.05, 0.5, 5, 30, 60, 300, 900, 3600],
    99
    )
    1010

    11-
    LOCALSTACK_IN_FLIGHT_REQUESTS_GAUGE = Gauge(
    11+
    LOCALSTACK_IN_FLIGHT_REQUESTS = Gauge(
    1212
    "localstack_in_flight_requests",
    1313
    "Total number of currently in-flight requests",
    1414
    ["service", "operation"],
    Lines changed: 19 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,19 @@
    1+
    from prometheus_client import Counter, Gauge
    2+
    3+
    # Lambda environment metrics
    4+
    LOCALSTACK_LAMBDA_ENVIRONMENT_START_TOTAL = Counter(
    5+
    "localstack_lambda_environment_start_total",
    6+
    "Total count of all Lambda environment starts.",
    7+
    ["start_type", "provisioning_type"],
    8+
    )
    9+
    10+
    LOCALSTACK_LAMBDA_ENVIRONMENT_CONTAINERS_RUNNING = Gauge(
    11+
    "localstack_lambda_environment_containers_running",
    12+
    "Number of LocalStack Lambda Docker containers currently running.",
    13+
    )
    14+
    15+
    LOCALSTACK_LAMBDA_ENVIRONMENT_ACTIVE = Gauge(
    16+
    "localstack_lambda_environments_active",
    17+
    "Number of currently active LocalStack Lambda environments.",
    18+
    ["provisioning_type"],
    19+
    )

    0 commit comments

    Comments
     (0)
    0