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.