OpenTelemetry#

OTelHook provides OpenTelemetry metrics integration for monitoring rate limiting events.

Installation#

Install throttled-py with OpenTelemetry support:

pip install 'throttled-py[otel]'

This installs opentelemetry-api only. throttled-py emits metrics in OpenTelemetry format - how you collect and export them is up to your application.

Quick Start#

from opentelemetry.metrics import get_meter
from throttled import Throttled
from throttled.contrib.otel import OTelHook

# Create OTelHook with a meter from the OTel API.
# The actual metrics backend (e.g., Prometheus, OTLP) is configured separately
# via opentelemetry-sdk or your framework's setup.
meter = get_meter("throttled-example")

# Create a rate limiter with OTelHook attached.
throttle = Throttled(key="/api/ping", quota="5/m", hooks=[OTelHook(meter)])


def main() -> None:
    # First 5 requests are allowed.
    for i in range(5):
        result = throttle.limit("/api/ping")
        print(f"Request {i + 1}: {'denied' if result.limited else 'allowed'}")

    # But 6th request is denied.
    result = throttle.limit("/api/ping")
    print(f"Request 6: {'denied' if result.limited else 'allowed'}")

    # πŸ“Š OTelHook records the following metrics:
    #   - throttled.requests (Counter): number of rate limit checks
    #   - throttled.duration (Histogram): latency of rate limit checks
    # Attributes: key, algorithm, store_type, result ("allowed" / "denied")


if __name__ == "__main__":
    main()
import asyncio

from opentelemetry.metrics import get_meter
from throttled.asyncio import Throttled
from throttled.asyncio.contrib.otel import OTelHook

# Create OTelHook with a meter from the OTel API.
# The actual metrics backend (e.g., Prometheus, OTLP) is configured separately
# via opentelemetry-sdk or your framework's setup.
meter = get_meter("throttled-example")

# Create an async rate limiter with OTelHook attached.
throttle = Throttled(key="/api/ping", quota="5/m", hooks=[OTelHook(meter)])


async def main() -> None:
    # First 5 requests are allowed.
    for i in range(5):
        result = await throttle.limit("/api/ping")
        print(f"Request {i + 1}: {'denied' if result.limited else 'allowed'}")

    # But 6th request is denied.
    result = await throttle.limit("/api/ping")
    print(f"Request 6: {'denied' if result.limited else 'allowed'}")

    # πŸ“Š OTelHook records the following metrics:
    #   - throttled.requests (Counter): number of rate limit checks
    #   - throttled.duration (Histogram): latency of rate limit checks
    # Attributes: key, algorithm, store_type, result ("allowed" / "denied")


if __name__ == "__main__":
    asyncio.run(main())

Metrics#

OTelHook records the following metrics:

Metric Name

Type

Description

throttled.requests

Counter

Number of rate limit checks (with result dimension)

throttled.duration

Histogram

Duration of rate limit checks in seconds

All metrics include these attributes:

Attribute

Description

key

The rate limit key (e.g., β€œ/api/users”, β€œuser:123”)

algorithm

Algorithm used (e.g., β€œtoken_bucket”, β€œfixed_window”)

store_type

Storage backend (e.g., β€œmemory”, β€œredis”)

result

Result of rate limit check: β€œallowed” or β€œdenied”

Configuration#

Both OTelHook (sync and async) require a Meter instance:

from opentelemetry import metrics
from throttled.contrib.otel import OTelHook

meter = metrics.get_meter("my-service", "1.0.0")
hook = OTelHook(meter)

The async OTelHook uses the same Meter instance and follows the same dependency injection pattern:

from opentelemetry import metrics
from throttled.asyncio.contrib.otel import OTelHook

meter = metrics.get_meter("my-service", "1.0.0")
hook = OTelHook(meter)

Architecture#

throttled-py depends only on opentelemetry-api, not the SDK:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    throttled-py                                     β”‚
β”‚   Dependency: opentelemetry-api (interface only)                    β”‚
β”‚   Output: counter.add(), histogram.record()                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                               v
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Your Application                                    β”‚
β”‚   You decide how to collect and export metrics:                     β”‚
β”‚   - Console, OTLP, Prometheus, Datadog, etc.                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This keeps the library lightweight. You have full control over how metrics are exported.

Exporter Examples#

Below are examples of how you might configure exporters in your application. These are just examples - use whatever setup fits your infrastructure.

Console (for debugging)#

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader

reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)

# Now get_meter() will use this provider
meter = metrics.get_meter("my-app")

OTLP#

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter

reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(endpoint="http://collector:4317")
)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)

Prometheus#

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from prometheus_client import start_http_server

start_http_server(port=8000)
reader = PrometheusMetricReader()
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)

# Metrics available at http://localhost:8000/metrics

Grafana Dashboard Example#

Example PromQL queries for Grafana:

# Request rate by key
sum(rate(throttled_requests_total[5m])) by (key)

# Denial rate
sum(rate(throttled_requests_total{result="denied"}[5m]))
/
sum(rate(throttled_requests_total[5m]))

# Allowed vs Denied comparison
sum(rate(throttled_requests_total{result="allowed"}[5m]))
sum(rate(throttled_requests_total{result="denied"}[5m]))

# P95 duration by algorithm
histogram_quantile(0.95, rate(throttled_duration_bucket[5m])) by (algorithm)