Skip to main content
In this lesson we instrument a Flask application with a histogram metric to measure request latency. A histogram groups observations into buckets so you can see how many requests fall into each latency range (for example: under 50ms, under 100ms, under 1s, over 2s). This helps detect latency regressions and identify when requests negatively impact user experience. Keywords: OpenTelemetry, histogram, Flask, request latency, metrics, buckets, instrumentation
All timings and bucket boundaries in this article are expressed in milliseconds (ms). Python’s time.time() returns seconds — multiply by 1000 to get milliseconds before recording to a histogram that uses "ms" as its unit.

What we’ll add

  • A MeterProvider and Meter configured with a ConsoleMetricExporter (for local testing).
  • Instruments:
    • a counter for total requests,
    • an up-down counter for concurrent in-flight requests,
    • a histogram to record request durations.
  • A minimal Flask app that records durations in after_request and updates the concurrent counter in before_request.
  • How to customize histogram bucket boundaries using a View and ExplicitBucketHistogramAggregation.

Basic meter and instruments

Create the meter and instruments used by the Flask app. Save this as metrics_setup.py.
Table: Instruments and use cases
InstrumentUse caseExample (attributes or recorded value)
CounterTrack total number of requestsrequests_counter.add(1, {"route": "/products", "method": request.method})
UpDownCounterTrack number of requests currently in-flightconcurrent_requests.add(1) and concurrent_requests.add(-1)
HistogramRecord request duration in millisecondstotal_request_duration.record(latency_ms, {"route": request.path})
You can activate your virtual environment and exercise the API with curl:

Calculating per-request latency in Flask

Use Flask’s before_request to capture the request start time and increment the concurrent counter. Use after_request to compute the latency, record it to the histogram, and decrement the concurrent counter. Note: store the start time with time.time() (seconds) and convert to milliseconds when recording to the histogram (since the histogram’s unit is "ms").
A few practical notes:
  • Restart the application and send multiple requests so the histogram sees a variety of latencies:
  • The ConsoleMetricExporter will periodically print the aggregated metrics (here every 5 seconds). The histogram export contains count, sum, bucket_counts, and explicit_bounds.
Example console histogram output (trimmed):
Interpretation:
  • count is the total number of recorded observations.
  • sum is the sum of all latencies in milliseconds.
  • bucket_counts is cumulative: each element is the count of observations <= corresponding bound in explicit_bounds.
    • For example, the count for a 75ms bucket includes observations from the 0, 5, 10, 25, 50, and 75ms buckets.
  • Use min/max to quickly spot extreme values.
Make sure request.start_time is set before after_request tries to compute latency. If your app raises an exception and after_request doesn’t run, consider registering teardown_request or an exception handler to ensure counters are adjusted and latencies are recorded or cleaned up appropriately.

Customizing histogram buckets

Default SDK histogram buckets may not match your application’s latency profile. You can provide explicit bucket boundaries using a View and ExplicitBucketHistogramAggregation. This example shows how to configure a MeterProvider with custom buckets (in milliseconds). Save as metrics_setup_with_buckets.py.
Restart the app with the custom view, generate requests, and the console will show explicit_bounds equal to the buckets you provided:
Table: Example bucket sets and when to use them
Bucket set (ms)Typical use case
[0, 5, 10, 25, 50, 75, 100, 250]Low-latency services where most requests are under 250ms
[0, 20, 30, 60, 100, 250, 2000]Services with mixed latencies (quick endpoints and occasional long ops)
[0, 100, 300, 1000, 3000, 10000]Latency-sensitive background jobs or APIs with long tails
Choose bucket boundaries that give you actionable visibility into the distribution of latency for your traffic patterns.
When recording histogram values, ensure the recorded unit matches the histogram unit. This example records latencies in milliseconds because the histogram’s unit is set to "ms".

Quick troubleshooting & tips

  • If you see all observations falling into a single bucket, your boundaries likely don’t match your observed latencies — widen or shift the buckets.
  • Use attributes (e.g., route, method) to filter or split the histogram for more granular insights. For example:
    • total_request_duration.record(latency_ms, {"route": request.path, "method": request.method})
  • For production, replace ConsoleMetricExporter with a metrics backend exporter (Prometheus, OTLP, etc.) appropriate to your observability stack.
This guide gives you a straightforward way to add histograms to your Flask app, select bucket boundaries that align with your service’s performance, and surface useful latency distributions for troubleshooting and alerting.

Watch Video