ЁЯПл The SchoolтА║ЁЯй║ ObservabilityтА║ЁЯУК рдзрдбрд╛ 03 тАФ Metrics: рддрдкрд╛рд╕рдгреА рддрдХреНрддрд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУК рдзрдбрд╛ 03 тАФ Metrics: рддрдкрд╛рд╕рдгреА рддрдХреНрддрд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 03 ┬╖ рдорд╛рдЧреЗ: lesson-02-logs ┬╖ рдкреБрдвреЗ: lesson-04-percentiles


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ02, рдЖрдгрд┐ рджреБрд╕рд░рд╛ signal: metrics. рддреБрдореНрд╣реА metrics рдЪреЗ рддреАрди рдкреНрд░рдХрд╛рд░ рд╢рд┐рдХрддрд╛ тАФ counter, gauge, histogram тАФ рдЖрдгрд┐ labels, Prometheus рдЪрд╛ text format, рдЖрдгрд┐ cardinality рдЪрд╛ рд╕рд╛рдкрд│рд╛. Counter, Gauge, Histogram, exposition() рдЖрдгрд┐ cardinality() obs/signals.py рдордзреНрдпреЗ рдЖрд╣реЗрдд; obs/demo.py рдордзреАрд▓ metrics() рддреЗ print рдХрд░рддреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдЖрд░реЛрдЧреНрдп рдХрдХреНрд╖рд╛рдЪреНрдпрд╛ рднрд┐рдВрддреАрд╡рд░ рдХрддрд░рд┐рдирд╛рдЪрд╛ рддрдкрд╛рд╕рдгреА рддрдХреНрддрд╛ ЁЯУК рд▓рдЯрдХрд▓реЗрд▓рд╛ рдЖрд╣реЗ. рддреЛ рджреИрдирдВрджрд┐рдиреАрд╕рд╛рд░рдЦреНрдпрд╛ рдЧреЛрд╖реНрдЯреА рд╕рд╛рдВрдЧрдд рдирд╛рд╣реА. рддреНрдпрд╛рдд рд╡реЗрд│реЗрдиреБрд╕рд╛рд░ рдЖрдХрдбреЗ рдЕрд╕рддрд╛рдд. рддреНрдпрд╛рдЪреЗ рддреАрди рдкреНрд░рдХрд╛рд░ рдЖрд╣реЗрдд:

рдЖрдгрд┐ рдПрдХ рдЗрд╢рд╛рд░рд╛. рдХрддрд░рд┐рдирд╛ рдореЛрдЬрдгреА рд╡рд░реНрдЧрд╛рдиреБрд╕рд╛рд░ рд╡рд┐рднрд╛рдЧреВ рд╢рдХрддреЗ: 3A, 3B, 4A. рддреЗ рдареАрдХ рдЖрд╣реЗ тАФ рдХрд╛рд╣реА рд╕реНрддрдВрдн. рдкрдг рдЬрд░ рддреА рддреА рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рдЪреНрдпрд╛ рдирд╛рд╡рд╛рдиреБрд╕рд╛рд░ рд╡рд┐рднрд╛рдЧрд▓реА, рддрд░ рддрдХреНрддреНрдпрд╛рд▓рд╛ рд╢рд╛рд│реЗрддрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рд╕рд╛рдареА рдПрдХ рд╕реНрддрдВрдн рд▓рд╛рдЧреЗрд▓. рднрд┐рдВрдд рддреЗрд╡рдвреА рдореЛрдареА рдирд╛рд╣реА.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph types["ЁЯУК three metric types"]
      c["тЮХ counter<br/>http_requests_total<br/>only up ┬╖ resets to 0 on restart"]
      g["ЁЯМбя╕П gauge<br/>print_queue_length 137<br/>up and down"]
      h["ЁЯЧДя╕П histogram<br/>_bucket{le=тАж} cumulative<br/>+ _sum + _count"]
    end
    ex["ЁЯУД /metrics text format"]
    c --> ex
    g --> ex
    h --> ex
    lab["ЁЯП╖я╕П labels: route ├Ч status = 2 ├Ч 3 = 6 series<br/>+ user_id (50,000) = 300,000 series ЁЯТе"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/observability/lesson-diagrams.html#l03

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг metrics рд╕реНрд╡рд╕реНрдд рдЖрдгрд┐ рдЬрд▓рдж рдЕрд╕рддрд╛рдд. Traffic рдХрд┐рддреАрд╣реА рдЕрд╕реЛ, counter рдореНрд╣рдгрдЬреЗ рдкреНрд░рддреНрдпреЗрдХ series рд╕рд╛рдареА рдПрдХ рдЖрдХрдбрд╛: 10 requests рдЕрд╕реЛрдд рдХрд┐рдВрд╡рд╛ 1 рдХреЛрдЯреА, рд╕рд╛рдард╡рд╛рдпрд▓рд╛ рддреЗрд╡рдврд╛рдЪ рдЦрд░реНрдЪ. рддреНрдпрд╛рдореБрд│реЗ dashboards рдЖрдгрд┐ alerts рд╕рд╛рдареА metrics рд╣рд╛рдЪ рдпреЛрдЧреНрдп signal рдЖрд╣реЗ. рддреНрдпрд╛рдЪреА рдХрд┐рдВрдордд рдореНрд╣рдгрдЬреЗ рддрдкрд╢реАрд▓: metric рд╕рд╛рдВрдЧрддреЛ 20 requests рдЕрдпрд╢рд╕реНрд╡реА рдЭрд╛рд▓реНрдпрд╛, рдкрдг рдХреЛрдгрддреНрдпрд╛ рддреЗ рдирд╛рд╣реА. рдЖрдгрд┐ metrics рдорд╣рд╛рдЧ рдХрд░рдгреНрдпрд╛рдЪрд╛ рдПрдХрдореЗрд╡ рдорд╛рд░реНрдЧ рдореНрд╣рдгрдЬреЗ high cardinality.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

Counter.inc(labels, by) рдкреНрд░рддреНрдпреЗрдХ label рд╕рдВрдЪрд╛рдЪреНрдпрд╛ рдПрдХреВрдг рдЖрдХрдбреНрдпрд╛рдд рднрд░ рдШрд╛рд▓рддреЛ; Gauge.set(v) рдХрд┐рдВрдордд рдмрджрд▓рддреЛ; Histogram(name, help, buckets).observe_all(values) cumulative bucket counts рдЖрдгрд┐ sum рд╡ count рднрд░рддреЛ. exposition(*metrics) рддреЗ Prometheus /metrics endpoint рдкреНрд░рдорд╛рдгреЗ print рдХрд░рддреЛ. cardinality(**label_values) рдкреНрд░рддреНрдпреЗрдХ label рдЪреНрдпрд╛ рдХрд┐рдорддреАрдВрдЪреНрдпрд╛ рд╕рдВрдЦреНрдпрд╛рдВрдЪрд╛ рдЧреБрдгрд╛рдХрд╛рд░ рдХрд░рддреЛ. metrics() status рдиреБрд╕рд╛рд░ 9,500 requests рдореЛрдЬрддреЗ, print-queue gauge 137 рд╡рд░ рдареЗрд╡рддреЗ рдЖрдгрд┐ рджрд┐рд╡рд╕рд╛рдЪреНрдпрд╛ 20 рдирдореБрдирд╛ latencies рдкрд╛рдЪ buckets рдордзреНрдпреЗ рдЯрд╛рдХрддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

рдЖрд░реЛрдЧреНрдп рдХрдХреНрд╖рд╛рдЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ рддрдХреНрддрд╛ тАФ рдордЧ labels рдХрд╕реЗ рдЧреБрдгрд▓реЗ рдЬрд╛рддрд╛рдд рддреЗ рдкрд╛рд╣рд╛:

python3 obs/demo.py metrics
python3 - <<'EOF'
import sys; sys.path.insert(0, "obs"); from signals import Counter, Gauge, Histogram, exposition, cardinality
c = Counter("pupils_seen_total", "Pupils seen by the nurse")
c.inc((("reason", "fever"),), 3); c.inc((("reason", "scraped knee"),), 5); c.inc((("reason", "fever"),))
g = Gauge("beds_in_use", "Beds in use right now"); g.set(4); g.set(2)
h = Histogram("visit_minutes", "Minutes per visit", [5, 10, 30]).observe_all([3, 4, 8, 12, 25, 40])
print(exposition(c, g, h))
routes, status = ["/results", "/notices"], ["200", "404", "500"]
print("route ├Ч status                  тЖТ", cardinality(route=routes, status=status))
print("+ method (4)                    тЖТ", cardinality(route=routes, status=status, method=["GET", "POST", "PUT", "DELETE"]))
print("+ method (4) + pod (20)         тЖТ", f"{cardinality(route=routes, status=status, method=list('abcd'), pod=list(range(20))):,}")
print("+ pod (20) + user_id (50,000)   тЖТ", f"{cardinality(route=routes, status=status, pod=list(range(20)), user_id=list(range(50_000))):,}")
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

metrics рд╣реЗ print рдХрд░рддреЗ (рдХрд╛рд╣реА рднрд╛рдЧ):

   http_requests_total{route="/results",status="200"} 9450
   http_requests_total{route="/results",status="404"} 30
   http_requests_total{route="/results",status="500"} 20
   # HELP print_queue_length Certificates waiting
   # TYPE print_queue_length gauge
   print_queue_length 137
   http_request_duration_seconds_bucket{le="0.1"} 10
   http_request_duration_seconds_bucket{le="0.25"} 18
   http_request_duration_seconds_bucket{le="0.5"} 19
   http_request_duration_seconds_bucket{le="1.0"} 19
   http_request_duration_seconds_bucket{le="2.5"} 20
   http_request_duration_seconds_bucket{le="+Inf"} 20
   http_request_duration_seconds_sum 3.657
   http_request_duration_seconds_count 20
тФАтФА labels route, status тЖТ 6 time series
тФАтФА labels route, status, user_id тЖТ 300,000 time series

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

# HELP pupils_seen_total Pupils seen by the nurse
# TYPE pupils_seen_total counter
pupils_seen_total{reason="fever"} 4
pupils_seen_total{reason="scraped knee"} 5
# HELP beds_in_use Beds in use right now
# TYPE beds_in_use gauge
beds_in_use 2
# HELP visit_minutes Minutes per visit
# TYPE visit_minutes histogram
visit_minutes_bucket{le="5"} 2
visit_minutes_bucket{le="10"} 3
visit_minutes_bucket{le="30"} 5
visit_minutes_bucket{le="+Inf"} 6
visit_minutes_sum 92.0
visit_minutes_count 6
route ├Ч status                  тЖТ 6
+ method (4)                    тЖТ 24
+ method (4) + pod (20)         тЖТ 480
+ pod (20) + user_id (50,000)   тЖТ 6,000,000

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

Counter рдиреЗ рдлрдХреНрдд рдмреЗрд░реАрдЬ рдХреЗрд▓реА (3 + 1 рддрд╛рдк = 4). Gauge рдиреЗ рдлрдХреНрдд рддреНрдпрд╛рдЪреЗ рд╢реЗрд╡рдЯрдЪреЗ рд╡рд╛рдЪрди рдареЗрд╡рд▓реЗ (2, 4 рдирд╛рд╣реА). Histogram рдЪреЗ buckets cumulative рдЖрд╣реЗрдд: le="10" bucket рдордзреНрдпреЗ 3 рднреЗрдЯреА рдЖрд╣реЗрдд тАФ 3 рдЖрдгрд┐ 4 рдорд┐рдирд┐рдЯрд╛рдВрдЪреНрдпрд╛ рднреЗрдЯреА рддрд╕реЗрдЪ 8 рдорд┐рдирд┐рдЯрд╛рдВрдЪреА рднреЗрдЯ тАФ рдЖрдгрд┐ 40 рдорд┐рдирд┐рдЯрд╛рдВрдЪреА рднреЗрдЯ рдлрдХреНрдд +Inf рдордзреНрдпреЗ рджрд┐рд╕рддреЗ. рд╕рд░рд╛рд╕рд░реА рднреЗрдЯ _sum / _count = 92 / 6 тЙИ 15.3 рдорд┐рдирд┐рдЯреЗ. рдЖрдгрд┐ cardinality рд╣рд╛ рдЧреБрдгрд╛рдХрд╛рд░ рдЖрд╣реЗ: рдирд┐рд░реБрдкрджреНрд░рд╡реА labels (method, pod) рдиреА 6 series 480 рдкрд░реНрдпрдВрдд рдиреЗрд▓реНрдпрд╛; рдПрдХрд╛ user_id label рдиреЗ рддреНрдпрд╛ 60 рд▓рд╛рдЦ (6 million) рдкрд░реНрдпрдВрдд рдиреЗрд▓реНрдпрд╛.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

On a real account тАФ Python рдЪреА prometheus_client library, port 8000 рд╡рд░ /metrics рджреЗрдгрд╛рд░реА:

from prometheus_client import Counter, Gauge, Histogram, start_http_server

REQUESTS = Counter("http_requests_total", "Requests served", ["route", "status"])
QUEUE = Gauge("print_queue_length", "Certificates waiting")
LATENCY = Histogram("http_request_duration_seconds", "Request time", ["route"],
                    buckets=[0.05, 0.1, 0.25, 0.5, 1.0, 2.5])

start_http_server(8000)                     # Prometheus scrapes http://host:8000/metrics

def handle(route):                          # route = the TEMPLATE "/results/{cls}", not the full URL
    with LATENCY.labels(route=route).time():
        status = serve(route)
    REQUESTS.labels(route=route, status=str(status)).inc()
    QUEUE.set(print_queue.size())

Prometheus рдордзреНрдпреЗ рд╕рд░реНрд╡рд╛рдд рдЬрд╛рд╕реНрдд series рдЕрд╕рд▓реЗрд▓реЗ metrics рд╢реЛрдзрд╛ (Prometheus UI рдордзреНрдпреЗ рдЪрд╛рд▓рд╡рд╛):

topk(10, count by (__name__) ({__name__=~".+"}))

CloudWatch тАФ dimensions рд╕рд╣ рдПрдХ metric (рдкреНрд░рддреНрдпреЗрдХ dimension combination рд╣рд╛ рд╕реНрд╡рддрдВрддреНрд░ metric рдЕрд╕рддреЛ, рдЖрдгрд┐ custom metrics рдЪреЗ bill рдкреНрд░рддрд┐ metric рдпреЗрддреЗ):

aws cloudwatch put-metric-data --namespace School/ResultsApi \
    --metric-name Requests --dimensions Route=/results,Status=500 --value 1 --unit Count

Datadog рдордзреНрдпреЗ рд╣реАрдЪ рдХрд▓реНрдкрдирд╛ tags рдореНрд╣рдгреВрди: results.requests{route:/results,status:500}; рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрдЧрд│реЗ tag combination bill рд╡рд░ рдПрдХ custom metric рдЕрд╕рддреЗ.

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: label рдЬреЛрдбрдгреНрдпрд╛рдкреВрд░реНрд╡реА рдЧреБрдгрд╛рдХрд╛рд░ рдХрд░рд╛. рдХрд┐рдорддреАрдВрдЪреА рд╕рдВрдЦреНрдпрд╛ ├Ч рдЗрддрд░ рдкреНрд░рддреНрдпреЗрдХ label рдЪреНрдпрд╛ рдХрд┐рдорддреА ├Ч pods рдЪреА рд╕рдВрдЦреНрдпрд╛. рдЙрддреНрддрд░ рдХрд╛рд╣реА рд╣рдЬрд╛рд░ series рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдЖрд▓реЗ, рддрд░ рддреА рдХрд┐рдВрдордд рддреНрдпрд╛рдРрд╡рдЬреА log рдУрд│реАрдд рдХрд┐рдВрд╡рд╛ span attribute рдордзреНрдпреЗ рдЬрд╛рдпрд▓рд╛ рд╣рд╡реА.

тПня╕П рдкреБрдвреЗ

Histogram рдордзреНрдпреЗ buckets рдЖрд╣реЗрдд. Buckets рдЪреЗ "99% рдкрд╛рд▓рдХрд╛рдВрдиреА X рдкреЗрдХреНрд╖рд╛ рдХрдореА рд╡реЗрд│ рд╡рд╛рдЯ рдкрд╛рд╣рд┐рд▓реА" рдордзреНрдпреЗ рд░реВрдкрд╛рдВрддрд░ рдХрд╕реЗ рдХрд░рд╛рдпрдЪреЗ? рдЖрдгрд┐ рддреНрдпрд╛ рдЖрдХрдбреНрдпрд╛рдЪреА рд╕рд░рд╛рд╕рд░реА рдХрдзреАрдЪ рдХрд╛ рдХрд╛рдврддрд╛ рдпреЗрдд рдирд╛рд╣реА?

git checkout lesson-04-percentiles

ЁЯУК Lesson 03 тАФ Metrics: the vital-signs chart

ЁЯУН You are here: Lesson 03 of 12 ┬╖ Previous: lesson-02-logs ┬╖ Next: lesson-04-percentiles


ЁЯУж What's in this branch

Lessons 01тАУ02, plus the second signal: metrics. You learn the three metric types тАФ counter, gauge, histogram тАФ plus labels, the Prometheus text format, and the cardinality trap. Counter, Gauge, Histogram, exposition() and cardinality() live in obs/signals.py; metrics() in obs/demo.py prints them.

ЁЯзТ Explain like I'm 5

On the wall of the health room hangs Katrina's vital-signs chart ЁЯУК. It does not tell stories like the diary. It holds numbers over time. There are three kinds:

And a warning. Katrina can split the tally by class: 3A, 3B, 4A. That is fine тАФ a few columns. If she splits it by pupil name, the chart needs a column for every pupil in the school. The wall is not big enough.

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph types["ЁЯУК three metric types"]
      c["тЮХ counter<br/>http_requests_total<br/>only up ┬╖ resets to 0 on restart"]
      g["ЁЯМбя╕П gauge<br/>print_queue_length 137<br/>up and down"]
      h["ЁЯЧДя╕П histogram<br/>_bucket{le=тАж} cumulative<br/>+ _sum + _count"]
    end
    ex["ЁЯУД /metrics text format"]
    c --> ex
    g --> ex
    h --> ex
    lab["ЁЯП╖я╕П labels: route ├Ч status = 2 ├Ч 3 = 6 series<br/>+ user_id (50,000) = 300,000 series ЁЯТе"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/observability/lesson-diagrams.html#l03

тЭУ What

ЁЯдФ Why

Because metrics are cheap and fast. A counter is one number per series, whatever the traffic: 10 requests or 10 million cost the same to store. That makes metrics the right signal for dashboards and alerts. The price is detail: a metric says 20 requests failed, not which ones. And the one way to make metrics expensive is high cardinality.

ЁЯФз How (in this repo)

Counter.inc(labels, by) adds to a total per label set; Gauge.set(v) replaces the value; Histogram(name, help, buckets).observe_all(values) fills cumulative bucket counts plus sum and count. exposition(*metrics) prints them the way a Prometheus /metrics endpoint does. cardinality(**label_values) multiplies the number of values of each label. metrics() counts 9,500 requests by status, sets a print-queue gauge to 137 and puts the day's 20 sample latencies into five buckets.

ЁЯзк Try it

The health room's own chart тАФ then watch the labels multiply:

python3 obs/demo.py metrics
python3 - <<'EOF'
import sys; sys.path.insert(0, "obs"); from signals import Counter, Gauge, Histogram, exposition, cardinality
c = Counter("pupils_seen_total", "Pupils seen by the nurse")
c.inc((("reason", "fever"),), 3); c.inc((("reason", "scraped knee"),), 5); c.inc((("reason", "fever"),))
g = Gauge("beds_in_use", "Beds in use right now"); g.set(4); g.set(2)
h = Histogram("visit_minutes", "Minutes per visit", [5, 10, 30]).observe_all([3, 4, 8, 12, 25, 40])
print(exposition(c, g, h))
routes, status = ["/results", "/notices"], ["200", "404", "500"]
print("route ├Ч status                  тЖТ", cardinality(route=routes, status=status))
print("+ method (4)                    тЖТ", cardinality(route=routes, status=status, method=["GET", "POST", "PUT", "DELETE"]))
print("+ method (4) + pod (20)         тЖТ", f"{cardinality(route=routes, status=status, method=list('abcd'), pod=list(range(20))):,}")
print("+ pod (20) + user_id (50,000)   тЖТ", f"{cardinality(route=routes, status=status, pod=list(range(20)), user_id=list(range(50_000))):,}")
EOF

тЬЕ Verify тАФ what you should see

metrics prints (in part):

   http_requests_total{route="/results",status="200"} 9450
   http_requests_total{route="/results",status="404"} 30
   http_requests_total{route="/results",status="500"} 20
   # HELP print_queue_length Certificates waiting
   # TYPE print_queue_length gauge
   print_queue_length 137
   http_request_duration_seconds_bucket{le="0.1"} 10
   http_request_duration_seconds_bucket{le="0.25"} 18
   http_request_duration_seconds_bucket{le="0.5"} 19
   http_request_duration_seconds_bucket{le="1.0"} 19
   http_request_duration_seconds_bucket{le="2.5"} 20
   http_request_duration_seconds_bucket{le="+Inf"} 20
   http_request_duration_seconds_sum 3.657
   http_request_duration_seconds_count 20
тФАтФА labels route, status тЖТ 6 time series
тФАтФА labels route, status, user_id тЖТ 300,000 time series

Your snippet prints:

# HELP pupils_seen_total Pupils seen by the nurse
# TYPE pupils_seen_total counter
pupils_seen_total{reason="fever"} 4
pupils_seen_total{reason="scraped knee"} 5
# HELP beds_in_use Beds in use right now
# TYPE beds_in_use gauge
beds_in_use 2
# HELP visit_minutes Minutes per visit
# TYPE visit_minutes histogram
visit_minutes_bucket{le="5"} 2
visit_minutes_bucket{le="10"} 3
visit_minutes_bucket{le="30"} 5
visit_minutes_bucket{le="+Inf"} 6
visit_minutes_sum 92.0
visit_minutes_count 6
route ├Ч status                  тЖТ 6
+ method (4)                    тЖТ 24
+ method (4) + pod (20)         тЖТ 480
+ pod (20) + user_id (50,000)   тЖТ 6,000,000

ЁЯПБ What you just proved

The counter only added (3 + 1 fevers = 4). The gauge kept only its last reading (2, not 4). The histogram's buckets are cumulative: the le="10" bucket holds 3 visits тАФ the 3- and 4-minute ones as well as the 8-minute one тАФ and the 40-minute visit appears only in +Inf. The mean visit is _sum / _count = 92 / 6 тЙИ 15.3 minutes. And cardinality is a product: harmless labels (method, pod) took 6 series to 480; one user_id label took them to 6 million.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ the Python prometheus_client library, serving /metrics on port 8000:

from prometheus_client import Counter, Gauge, Histogram, start_http_server

REQUESTS = Counter("http_requests_total", "Requests served", ["route", "status"])
QUEUE = Gauge("print_queue_length", "Certificates waiting")
LATENCY = Histogram("http_request_duration_seconds", "Request time", ["route"],
                    buckets=[0.05, 0.1, 0.25, 0.5, 1.0, 2.5])

start_http_server(8000)                     # Prometheus scrapes http://host:8000/metrics

def handle(route):                          # route = the TEMPLATE "/results/{cls}", not the full URL
    with LATENCY.labels(route=route).time():
        status = serve(route)
    REQUESTS.labels(route=route, status=str(status)).inc()
    QUEUE.set(print_queue.size())

Find the metrics with the most series in Prometheus (run it in the Prometheus UI):

topk(10, count by (__name__) ({__name__=~".+"}))

CloudWatch тАФ one metric with dimensions (each dimension combination is its own metric, and custom metrics are billed per metric):

aws cloudwatch put-metric-data --namespace School/ResultsApi \
    --metric-name Requests --dimensions Route=/results,Status=500 --value 1 --unit Count

In Datadog, the same idea is tags: results.requests{route:/results,status:500}; each unique tag combination is a custom metric on the bill.

ЁЯПн Why this matters in production: before you add a label, multiply. Number of values ├Ч every other label's values ├Ч number of pods. If the answer has more than a few thousand series, the value belongs in a log line or a span attribute instead.

тПня╕П Next

The histogram has buckets. How do you turn buckets into "99% of parents waited less than X"? And why can you never average that number?

git checkout lesson-04-percentiles
тЖР PreviouslogsNext тЖТpercentiles

This page is the lesson's README from the lesson-03-metrics branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.