Prometheus Architecture: How It Actually Works
How to read this page
Symvanta parsed this repository into a code graph: every function, class, and method is a node, and every call or import between them is an edge. Everything on this page is computed from that graph at the commit shown above. The terms:
- Module (or cluster)
- A group of symbols that call each other far more than they call anything else. An algorithm called Louvain community detection finds these groups from the call traffic alone; nobody draws them by hand.
- Modularity (the Q number)
- A 0-to-1 score of how cleanly those groups separate. Higher means more call traffic stays inside its own group; scores around 0.7 and above read as clean boundaries.
- Hub
- The most depended-upon symbol inside one module.
- Load-bearing symbols
- PageRank, the algorithm Google originally used to rank web pages, run over the call graph instead: it surfaces the functions the rest of the codebase leans on hardest.
- Arrows and their numbers
- How many calls cross from one module into another. A heavier arrow means tighter coupling between those two parts.
- Dependency cycle
- File A imports B, which imports A again, sometimes through a longer loop. Cycles are not bugs, but a change inside one tends to ripple around the whole loop.
- Mutually recursive symbols
- Functions that call each other, usually the natural shape of parsers and tree-walking code.
Prometheus is a single Go binary that scrapes targets, writes samples into a
local time series database, and answers PromQL queries against it. Symvanta's
graph of the repo at d8eeedd detects 83 functional modules (modularity
Q=0.79), and the weight sits in storage: the largest cluster holds 1,363
symbols around labels.FromStrings, and it sends 520 calls into the head
block and chunk pool cluster beside it and takes 744 back. The scrape loop,
service discovery, remote write, and the HTTP API all arrange themselves
around that pair.
The clearest structural signal is where the cycles are. All 8 dependency
cycles live in the web UI (web/ui/react-app, web/ui/mantine-ui, and the
codemirror-promql module). The Go packages that make up the server have none.
Module map
The diagram shows the 10 largest of the 83 detected modules, with edges
weighted by how many calls cross between them. The biggest holds 1,363 symbols
and its hub is labels.FromStrings, which puts label construction at the
center of the codebase: nothing reaches a block or a chunk without building a
label set first. Module names are checked by hand against the files their
members live in; the symbol counts, hubs, and edge weights are what the graph
computed.
Where to start reading
These are the most depended-upon symbols by PageRank over the call graph,
followed by the HTTP surface registered in web/web.go. Three of them are
one-word Go names that several types define independently, so each link points
at the one definition the ranking scored: Labels.Get in model/labels,
Head.MinTime in tsdb, and the bstream accessor in tsdb/chunkenc.
labels.FromStringslabels.Labels.Gettsdb.Head.MinTimelabels.EmptyLabelshistogram.FloatHistogram.UsesCustomBucketslabels.decodeSizestrutil.SanitizeLabelNamehistogram.IsCustomBucketsSchemalabels.NewMatcherv1.createYAMLNodechunkenc.bstream.byteslabels.NewFastRegexMatcherGET /graphGET /federateGET /consoles/*filepathGET /user/*filepathGET /-/healthyHEAD /-/healthyGET /-/readyHEAD /-/readyGET /-/quitGET /-/reload
Key subsystems
Label sets and TSDB database
The largest cluster in the repo. labels.FromStrings and the string-packed Labels representation in model/labels/labels_stringlabels.go sit with the database lifecycle in tsdb/db.go (DefaultOptions) and the compactor beside it (DefaultPostingsDecoderFactory in tsdb/compact.go). Every read and every write builds a label set before it opens a block, so label construction and database setup land in one cluster.
Scrape loop and appendables
The scrape loop and the appendable interfaces every scrape writes through, with staleness handling from model/value.IsStaleNaN. 1,018 symbols, and the ingest path shows in its edge weights: 129 calls into the label and database cluster, 89 into label string encoding, 62 into label access and config parsing.
Head block and chunk pool
The in-memory head block (DefaultHeadOptions in tsdb/head.go), the chunk pool that hands out and recycles chunk objects (chunkenc.NewPool), and label hashing (Labels.Hash). Its pair of edges with the label and database cluster is the heaviest coupling on the map: 744 calls out and 520 back.
Label access and config parsing
labels.EmptyLabels and the label accessors in model/labels, clustered with the loaders that read them: ScrapeConfig.Validate in config/config.go, rulefmt.ParseFile, and the PromQL parser constructor. Config and rule files are label-shaped, so the parsers land next to the accessors.
PromQL functions and native histograms
The PromQL function table in promql/functions.go (simpleFloatFunc) clustered with the native histogram arithmetic in model/histogram (FloatHistogram.UsesCustomBuckets, compactBuckets, floatBucketIterator). Histogram functions are where the query engine and the histogram model meet, 868 symbols in all.
Write-ahead log segments
tsdb/wlog: segment naming and listing (SegmentName, listSegments) alongside the label symbol table in model/labels. 691 symbols, and it calls into the head and chunk pool cluster 250 times, the third heaviest edge on the map.
Service discovery core
The discovery registry and metrics plumbing every service discovery provider registers through (NewMetricRegisterer in discovery/util.go, NewRefreshMetrics), plus strutil.SanitizeLabelName, which turns provider metadata into valid label names. 629 symbols, and its heaviest edges leave for the providers (6 into the AWS discovery cluster, 3 each into configuration management and the scrape loop). It reaches the label and database cluster once, which is how isolated this subsystem is from storage.
Canonical request flow
The sequence worth reading first is the database opening. Repair index files an
old release left behind, clear temporary directories and checkpoints, build the
chunk pool, take the directory lock, construct the compactor, open the
write-ahead log, then create the head block and replay the log into it. Every
step below is a call edge out of tsdb.open, in the order the work happens.
tsdb.Open(tsdb/db.go:902) is the public entry point: it validates the options, derives the block ranges from them, and hands the rest to the unexportedopen.tsdb.open(tsdb/db.go:986) runs every step below in one function and returns a*DBthat is ready to serve.tsdb.repairBadIndexVersion(tsdb/repair.go:30) rewrites the index andmeta.jsonversions on blocks written by Prometheus 2.1, before anything tries to read them.tsdbutil.RemoveTmpDirs(tsdb/tsdbutil/remove_tmp_dirs.go:27) deletes leftover temporary directories, once under the WAL directory and once under the data directory.wlog.DeleteTempCheckpoints(tsdb/wlog/checkpoint.go:89) removes checkpoint directories from a truncation the process died in the middle of.chunkenc.NewPool(tsdb/chunkenc/chunk.go:356) builds the pool that hands out and recycles chunk objects, and theDBstruct takes it at construction.tsdbutil.NewDirLocker(tsdb/tsdbutil/dir_locker.go:44) takes the lock file in the data directory so a second process cannot open the same database.tsdb.NewLeveledCompactorWithOptions(tsdb/compact.go:208) constructs the compactor over those block ranges and the chunk pool.wlog.NewSize(tsdb/wlog/wlog.go:300) opens the write-ahead log at the configured segment size, and opens a second log for out-of-order samples when that window is set or a WBL is already on disk.tsdb.NewHead(tsdb/head.go:284) creates the in-memory head block over both logs.tsdb.Head.Init(tsdb/head.go:729) replays the write-ahead log into the head. This is where a restart spends its time.wlog.WL.Repair(tsdb/wlog/wlog.go:400) runs only when that replay returns a read error: the log is truncated at the last good record, and the WBL takes the same path through its own error type.tsdb.DB.run(tsdb/db.go:1260) starts the background loop that reloads blocks, signals compaction, and mmaps head chunks for the life of the process.
Health signals
Symvanta detected 8 dependency cycles across 83 modules (modularity Q=0.79). The largest cycle spans 16 files in the graph area. 17 sets of mutually recursive symbols were also detected, the largest being parser (16 symbols). Every one of those cycles sits in the browser code: the React app under web/ui/react-app, the Mantine app under web/ui/mantine-ui, and the codemirror-promql editor module. The Go server packages carry none, and the recursion they do carry is the PromQL lexer and evaluator calling back into themselves, which is what a hand-written parser looks like in a graph.
Auto-generated by Symvanta from the public repo prometheus/prometheus at commit d8eeedd , licensed Apache-2.0 .
Machine-readable companion: data.json (module counts, subsystems, load-bearing symbols, health signals).