Introduction: Prometheus and the Scrape Format
Prometheus is a pull-based monitoring system. Instead of having services push metrics to a central collector, Prometheus periodically fetches (or scrapes) an HTTP endpoint (typically /metrics) from each monitored target. The response is a plain-text document in the scrape targets and forward metrics via the remote write protocol, which carries only sample data: it discards # HELP and # TYPE metadata. Once the data is in the storage backend, you lose the ability to filter or group by metric type, or to see the human-readable descriptions that often give the clearest clue about what a metric is and why its cardinality is high.
In both cases, working directly with the raw scrape text is the only option.
Counting Series with Shell Tools
Given a saved scrape, a first instinct is to reach for standard Unix tools. Here are the kinds of questions you might try to answer and how you would approach them.
How many lines does the scrape have?
wc -l < prometheus-scrape.txt
1069
Which metric families are present?
grep '^# TYPE' prometheus-scrape.txt
# TYPE go_gc_cycles_automatic_gc_cycles_total counter
# TYPE go_gc_cycles_forced_gc_cycles_total counter
# TYPE go_gc_cycles_total_gc_cycles_total counter
# TYPE go_gc_duration_seconds summary
# TYPE go_gc_gogc_percent gauge
# TYPE go_gc_gomemlimit_bytes gauge
# TYPE go_gc_heap_allocs_by_size_bytes histogram
...
# TYPE prometheus_http_requests_total counter
# TYPE prometheus_http_response_size_bytes histogram
# TYPE promhttp_metric_handler_requests_in_flight gauge
# TYPE promhttp_metric_handler_requests_total counter
How many series does each metric expose?
The idea is to count non-comment, non-empty lines per metric family. One approach: strip comment and blank lines, extract the metric name (the part before { or the first space), then count occurrences.
grep -v '^#' prometheus-scrape.txt \
| grep -v '^$' \
| sed 's/[{ ].*//' \
| sort \
| uniq -c \
| sort -rn \
| head -20
59 prometheus_http_requests_total
20 prometheus_engine_query_duration_histogram_seconds_bucket
18 prometheus_sd_kubernetes_events_total
15 prometheus_tsdb_compaction_duration_seconds_bucket
13 prometheus_tsdb_compaction_chunk_size_bytes_bucket
13 prometheus_tsdb_compaction_chunk_samples_bucket
12 prometheus_engine_query_duration_seconds
12 net_conntrack_dialer_conn_failed_total
12 go_gc_heap_frees_by_size_bytes_bucket
12 go_gc_heap_allocs_by_size_bytes_bucket
11 prometheus_tsdb_compaction_chunk_range_seconds_bucket
10 prometheus_http_request_duration_seconds_bucket
9 prometheus_http_response_size_bytes_bucket
8 prometheus_tsdb_sample_ooo_delta_bucket
8 go_sched_pauses_total_other_seconds_bucket
8 go_sched_pauses_total_gc_seconds_bucket
8 go_sched_pauses_stopping_other_seconds_bucket
8 go_sched_pauses_stopping_gc_seconds_bucket
8 go_sched_latencies_seconds_bucket
8 go_gc_pauses_seconds_bucket
This pipes the scrape through a sequence of filters: drop comment lines, drop blank lines, strip everything after the metric name, sort, count duplicates, and sort by count descending.
The Limits of This Approach
The pipeline above works for simple gauges and counters, but it breaks down for histograms and summaries. You can already see this in the output above: prometheus_engine_query_duration_histogram_seconds_bucket appears as its own entry with 20 lines, but the corresponding _sum and _count lines are counted separately and buried lower in the list. The sed pattern extracts different "names" for each suffix (_bucket, _sum, _count), so the count is fragmented across multiple rows rather than attributed to a single metric family. The true series count for that histogram is higher than any individual row suggests. Reassembling these correctly requires knowing the metric type, which means parsing the # TYPE lines and correlating them with the data lines. That turns a one-liner into a non-trivial script.
There are further edge cases: metrics with no labels, metric names that are prefixes of other metric names, and histograms where the bucket count varies across label dimensions. Shell pipelines are quick to write but fragile to maintain, and getting an accurate cardinality figure for a real-world scrape is harder than it looks.
Introducing scrapecli
.
Basic Usage
Pipe any Prometheus scrape into scrapecli:
curl -s localhost:9090/metrics | scrapecli
Or analyse a saved scrape file:
cat prometheus-scrape.txt | scrapecli
Running it against the same Prometheus scrape from the previous section produces:
. Have you run into oversized scrapes or runaway cardinality in your own setup? I'd love to hear about it in the comments: what caused it, how you found it, and how you fixed it.
SOCIAL SHARE CARD GENERATOR