Scrape Pipeline
How Prometheus discovers targets, fetches metrics via HTTP, and delivers parsed samples to storage.
âķ Pipeline Flow
SD Providers (K8s, Consul, File, DNS, AWS, âĶ)
â
â []*targetgroup.Group (goroutine per provider)
âž
ââââââââââââââââââââââââââââââââââââââââââââ
â discovery.Manager â discovery/manager.go:170
â âĒ syncCh chan map[string][]*Group â
â âĒ ticker-based reconciliation â
ââââââââââââââââââââââŽââââââââââââââââââââââ
â map[jobName][]*targetgroup.Group
âž
ââââââââââââââââââââââââââââââââââââââââââââ
â scrape.Manager â scrape/manager.go:186
â âĒ one scrapePool per job â
â âĒ applies target relabeling â
â âĒ starts/stops scrapeLoop goroutines â
ââââââââââââââââââââââŽââââââââââââââââââââââ
â
(per target goroutine)
â
âž
ââââââââââââââââââââââââââââââââââââââââââââ
â scrapeLoop â scrape/scrape.go:1167
â âĒ ticker: every scrape_interval â
â âĒ HTTP GET target.URL â
â âĒ parse response (text or proto) â
â âĒ apply sample/label limits â
â âĒ call appender.Append() per sample â
â âĒ appender.Commit() â
ââââââââââââââââââââââŽââââââââââââââââââââââ
â samples, exemplars, histograms
âž
storage.Appender
(fanoutStorage â TSDB + Remote)
âķ Discovery Manager async
Each service discovery mechanism runs as a pluggable Discoverer. The manager aggregates their output and pushes batched updates downstream over a channel.
type Manager struct {
logger *slog.Logger
name string
httpClientOpts []config.HTTPClientOption
// Guards metrics and targetSets.
mtx sync.RWMutex
// Sync channel to the consumer (scrape/notify manager).
syncCh chan map[string][]*targetgroup.Group
// Providers holds all active SD providers.
providers []*Provider
// Target groups keyed by providerRef â jobName.
targetSets map[string]map[string]*targetgroup.Group
...
}
Each SD provider type has its own Discoverer implementation. When target groups change, the provider sends updates into the manager's internal channel, which the manager batches and forwards to syncCh after a configurable updatert delay (default 5 s).
syncCh is read by the Scrape Manager in a dedicated goroutine. The Notify Manager has its own separate Discovery Manager instance for Alertmanager endpoint discovery.
Supported SD Mechanisms
| Name | Package | Notes |
|---|---|---|
| kubernetes | discovery/kubernetes | Pods, services, endpoints, nodes |
| file | discovery/file | JSON/YAML; inotify watched |
| ec2 | discovery/aws | AWS EC2 instances via API |
| consul | discovery/consul | Consul catalog long-poll |
| dns | discovery/dns | SRV/A/AAAA record lookup |
| http | discovery/http | HTTP endpoint returning JSON target groups |
âķ Scrape Manager & scrapePool
type Manager struct {
opts *Options
logger *slog.Logger
append storage.Appendable
graceShut chan struct{}
offsetSeed uint64 // used to jitter scrape offsets
mtxScrape sync.Mutex // guards the maps below
scrapeConfigs map[string]*config.ScrapeConfig
scrapePools map[string]*scrapePool // one pool per job_name
...
}
When the Discovery Manager pushes a new set of target groups, Manager.reload() diffs the current and incoming pools:
- New jobs â create
scrapePool, callpool.Sync(tgroups) - Removed jobs â call
pool.stop()to drain loops - Changed targets within a job â
pool.Sync()starts/stops individualscrapeLoopgoroutines
Target Relabeling
Before a target is scraped, the scrape pool applies relabel.Process() using the job's relabel_configs. Labels prefixed with __ are internal and stripped before storage. Key labels:
| Label | Source | Purpose |
|---|---|---|
| __address__ | SD | host:port to scrape |
| __scheme__ | config | http or https |
| __metrics_path__ | config | defaults to /metrics |
| __scrape_interval__ | config | per-target override |
| instance | derived | set to __address__ after relabeling |
| job | config | job_name from scrape config |
âķ scrapeLoop â Per-Target Goroutine
func newScrapeLoop(opts scrapeLoopOptions) *scrapeLoop {
sl := &scrapeLoop{
target: opts.target,
scraper: opts.scraper, // HTTP client
buffers: pool.New(1e3, 1e6, 3, func(n int) interface{} {
return make([]byte, 0, n)
}),
sampleMutator: opts.sampleMutator, // relabeling
reportSampleMutator: opts.reportSampleMutator,
appender: opts.appender, // storage.Appendable
...
}
return sl
}
func (sl *scrapeLoop) run(errc chan<- error) {
// Align scrape to a fixed offset within the interval to spread load.
select {
case <-time.After(sl.scrapeOffset()):
case <-sl.ctx.Done():
return
}
ticker := time.NewTicker(sl.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// 1. HTTP GET target URL
// 2. Parse response into samples
// 3. Append to storage
// 4. Report scrape metadata (__scrape_duration_seconds__, etc.)
sl.scrapeAndReport(...)
case <-sl.ctx.Done():
return
}
}
}
Parse & Append Sequence
- HTTP GET
target.URL()withAccept: text/plain; version=0.0.4, application/openmetrics-text - Response body piped through
textparse.New()oropenmetricsparse.New() - Each metric family iterated; relabeling applied per sample
app.Append(ref, lset, ts, val)called for each float sampleapp.AppendHistogram()for native histogramsapp.AppendExemplar()for OpenMetrics exemplars- Sample/label limits checked; excess â
errSampleLimit app.Commit()â atomically writes all samples of this scrape- Stale markers written for series that disappeared since last scrape
Commit() fails the entire scrape is rolled back via app.Rollback() â scrape metrics are stored atomically or not at all.
âķ Scrape Config Parameters
| Parameter | Default | Effect |
|---|---|---|
| scrape_interval | 1m | How often to scrape |
| scrape_timeout | 10s | HTTP request deadline |
| sample_limit | 0 (off) | Max samples per scrape before error |
| label_limit | 0 (off) | Max labels per series |
| target_limit | 0 (off) | Max targets per job |
| honor_labels | false | Prefer target labels over server labels on conflict |
| honor_timestamps | true | Use timestamps from exposition if present |
| metrics_path | /metrics | Endpoint to scrape |
| scheme | http | http or https |
Config struct: config/config.go â ScrapeConfig
âķ Stale Markers
When a series disappears (target down or metric removed), Prometheus injects a special stale NaN value at the next scrape. This tells the query engine not to extrapolate across the gap.
// For each series seen in the previous scrape but absent now: app.Append(ref, lset, scrapeTimestamp, math.Float64frombits(value.StaleNaN))
value.StaleNaN is a special IEEE 754 NaN with the stale marker bit pattern (0x7ff0000000000002). The query engine treats it as end-of-series rather than a data point.