Data model & metric types
|
This section documents the current Prometheus 3.x line (latest 3.14.0) as published at the Prometheus documentation and the current Grafana 13.x line (latest 13.2.1) as published at the Grafana documentation, which are the references these pages are written and verified against. No specific patch version is pinned. Some capabilities (Grafana Enterprise-only features, Grafana Cloud-hosted services, and long-term-storage systems like Thanos/Cortex/Mimir) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since both projects iterate quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Every value Prometheus stores is a time series: a stream of (timestamp, float64 value) samples
identified by a metric name plus a set of labels. This page covers that identification model,
where the job and instance labels come from, the four metric types client libraries expose
(Counter, Gauge, Histogram, Summary), the _bucket/_sum/_count suffixes a Histogram produces in
scraped output, and the newer native histogram representation. How an application actually emits
these types is covered in
Instrumenting Applications; querying them
is covered starting with
PromQL: Basics & Selectors.
Time series: metric name + labels
A time series is uniquely identified by its metric name together with an unordered set of key-value label pairs, conventionally written as:
metric_name{label1="value1", label2="value2", ...}
For example, http_requests_total{method="POST", handler="/messages", status="200"} and
http_requests_total{method="POST", handler="/messages", status="500"} are two distinct time
series — same metric name, different label value on status. Internally Prometheus treats the
metric name itself as just another label, name, so a metric name is really the special case of
one particular label matching. Metric names should describe what is being measured in general
(http_requests_total), and labels should carry the dimensions that vary (method, handler,
status) — never encode a dimension into the name itself (http_requests_post_total is an
anti-pattern). See
Data model for the full naming and labeling
conventions, including the reserved __-prefixed label namespace and the metric-name/label
character restrictions.
Every distinct combination of label values is a separate series stored and indexed independently, so an unbounded label value (a raw user ID, a full URL with a query string, a timestamp) multiplies storage and query cost unboundedly — this is the cardinality concern that comes up repeatedly across this section, in particular in Security & Production Operations.
Jobs and instances
A job is the set of targets configured under one scrape_configs entry — typically all the
replicas of one service. An instance is one specific target within that job, normally
<host>:<port>. Prometheus adds job and instance as labels automatically on every sample it
scrapes, so they need no instrumentation from the application:
scrape_configs:
- job_name: "orders-api"
static_configs:
- targets: ["10.0.1.11:9100", "10.0.1.12:9100"]
Scraping this config attaches job="orders-api" to every metric pulled from both targets, and
instance="10.0.1.11:9100" or instance="10.0.1.12:9100" depending on which replica answered. That
is enough to select one replica’s series (up{instance="10.0.1.11:9100"}) or aggregate across the
whole job (sum(rate(http_requests_total{job="orders-api"}[5m]))). Both labels can be renamed or
replaced before ingestion (for instance, deriving a friendlier instance from a service-discovery
label) — that mechanism belongs to
Service Discovery & Relabeling. See
Jobs & instances for the full scrape-target
model, including the synthetic up metric Prometheus generates for every target.
The four core metric types
Client libraries expose four metric types. The type only matters at instrumentation time (which
library call to use) and, for Histogram/Summary, at scrape time (which series it expands into) — on the wire, every type is still just plain samples: the type is metadata that shapes how PromQL
functions like rate() and histogram_quantile() are meant to be used against it. See
Metric types for the authoritative definitions.
Counter
A Counter is a cumulative value that only ever increases (or resets to zero on process restart).
It is for counting things: requests served, errors raised, tasks completed. Never use a Counter for
a value that can decrease — use a Gauge instead. Because a Counter only goes up, it is almost always
queried through rate() or increase() rather than read directly:
rate(http_requests_total{job="orders-api", status="500"}[5m])
By convention a Counter’s name ends in _total, and its underlying sample name in the exposition
format carries that suffix.
Gauge
A Gauge is a value that can go up or down: current memory usage, number of items in a queue,
temperature, the number of concurrently in-flight requests. A Gauge is read directly, or aggregated
with functions like avg(), min(), max() — not rate(), which is meaningless once a value can
decrease:
queue_size{job="orders-api"}
Histogram
A Histogram samples observations (request durations, response sizes) and counts them into configurable cumulative buckets, while also tracking their sum and total count. A single Histogram metric expands, at scrape time, into several underlying series:
curl -s http://localhost:9100/metrics | grep http_request_duration_seconds
# http_request_duration_seconds_bucket{le="0.1"} 24054
# http_request_duration_seconds_bucket{le="0.25"} 33444
# http_request_duration_seconds_bucket{le="0.5"} 33988
# http_request_duration_seconds_bucket{le="1"} 34009
# http_request_duration_seconds_bucket{le="+Inf"} 34011
# http_request_duration_seconds_sum 1863.31
# http_request_duration_seconds_count 34011
Each _bucket series is cumulative: le="0.25" (less-than-or-equal) counts every observation that
also falls into le="0.1", and the final le="+Inf" bucket always equals _count. _sum is the
running total of all observed values and _count the running total of observations — both are
themselves Counters, which is what lets histogram_quantile() and rate() work on a Histogram
across time windows. Because buckets are fixed at instrumentation time, a Histogram is cheap to
scrape and aggregate across instances, but its quantile estimate is only as accurate as the bucket
boundaries chosen. Bucket design and quantile queries are covered in
PromQL: Functions.
Summary
A Summary also samples observations and tracks a sum and count, but instead of (or in addition to) buckets it can calculate configurable quantiles (for example, the 0.5 and 0.99 quantile of request latency) directly on the client side, over a sliding time window:
curl -s http://localhost:9100/metrics | grep http_request_duration_seconds
# http_request_duration_seconds{quantile="0.5"} 0.042
# http_request_duration_seconds{quantile="0.9"} 0.183
# http_request_duration_seconds{quantile="0.99"} 0.512
# http_request_duration_seconds_sum 1863.31
# http_request_duration_seconds_count 34011
A client-side quantile cannot be aggregated across instances (the 99th percentile of several
machines' 99th percentiles is not the fleet’s 99th percentile), while a Histogram’s buckets can be
summed across instances and then have histogram_quantile() applied once, server-side. That
trade-off is why most instrumentation guidance defaults to Histogram and reserves Summary for cases
where only a single process’s exact quantile matters.
Native histograms
Native histograms are a newer, denser wire representation for histogram data: instead of a fixed,
manually chosen set of _bucket{le=…} series, a native histogram stores a single series carrying
a sparse, exponentially-scaled set of buckets at much higher resolution, computed automatically by
the client library. This removes the need to pick bucket boundaries up front, shrinks both scrape
payload size and stored series count (one series instead of one-per-bucket), and yields materially
more accurate quantiles. Native histograms require the remote-write/storage path and client library
support to be enabled explicitly, and are still evolving as a specification — see
Native histograms for the exposition format,
the exponential bucketing scheme, and current client/server support status. Native histograms are
also the representation OpenTelemetry’s exponential histogram metric maps onto most directly, which
matters when a service is instrumented through the OpenTelemetry SDK rather than a native Prometheus
client library, a topic picked up in
Instrumenting Applications.
Continue with Instrumenting Applications, or back up to Architecture & Ecosystem for how scraping, storage and these types fit into the overall system.