cost
Propagate measured runtime self cost along the CALLS graph into
inclusive cost — self plus everything a node transitively calls — and report
each node’s share of total. It is the causal counterpart to
hotspots: where hotspots --by self-time ranks by the
exclusive time spent in a node, cost answers the question the project was
built to produce —
This function is responsible for 40% of total measured time.
With no argument it ranks the whole graph. With a node id it breaks one node’s cost down causally: where its inclusive cost goes (callees) and who is responsible for it (callers).
Source: src/commands/cost_command.ts ·
queries: GraphQuery.costRanking / GraphQuery.costAttribution in
src/query/graph_query.ts
Synopsis
npx codespine cost [id] [options]Arguments
| Argument | Description |
|---|---|
[id] | Optional. A node id (from find) to break down causally. Omit it to rank the whole graph. |
Options
| Option | Default | Description |
|---|---|---|
-o, --output-folder <dir> | ./.codespine | Output folder; the Kùzu database is read from <dir>/graph.kuzu. |
--by <metric> | self-time | Cost metric to propagate: self-time (metadata.runtime.selfMs) or samples (metadata.runtime.samples). |
--edges <graph> | static | Call graph to propagate along: static (CALLS, weighted by call-site count) or runtime (CALLS_RUNTIME from enrich, weighted by sample flow). Falls back to static with a notice when the graph has no runtime call edges. |
--limit <n> | 20 | Maximum number of ranked nodes (ranking mode only). Clamped to 1–1000. |
--json | false | Emit the raw JSON report instead of the formatted output. |
The cost model
Each node has a measured self cost self(n) — the exclusive time
enrich attributed to it (0 when unmeasured). Each CALLS edge
carries a call-site count (the edge weight). Inclusive cost
propagates along those edges:
W_in(c) = Σ over callers p of count(p → c) (inbound call weight)
inclusive(n) = self(n) + Σ over callees c of inclusive(c) × count(n → c) / W_in(c)
shareOfTotal(n) = inclusive(n) / Σ over all nodes of selfA callee’s inclusive cost is partitioned among its callers in proportion to
how often each calls it. That normalization is what makes the model conserve
cost: a diamond (A → B → D, A → C → D) does not double-count D, and
shareOfTotal is a true fraction in [0, 1]. This is the standard gprof / pprof
attribution. (The narrative formula in
blog post 7
omits the / W_in(c) normalization for brevity; without it shares would not sum
to a meaningful total.)
Static or runtime call graph. By default the weights are static call-site
counts (--edges static). With --edges runtime the propagation runs over the
CALLS_RUNTIME edges enrich records, weighted by sample flow — a
callee’s cost is attributed to its callers by how much each actually drove it at
runtime, not by how many call sites appear in source, so a function called once
from a hot loop outweighs one called from many cold sites. It falls back to the
static graph (with a notice) when the graph carries no runtime call edges.
Cycles (recursion, mutual recursion) would make the recurrence circular, so
strongly-connected components are collapsed: their self costs are lumped, the
cycle is propagated as one unit, and every member is reported with the cycle’s
total inclusive cost and flagged cyclic (↺). Self-recursion (a node calling
itself) is captured by self cost and is not flagged.
Inclusive costs do not sum across nested nodes. A caller’s inclusive cost already contains its callees’. Only a cut of the graph (e.g. the root frontier) sums to the total —
shareOfTotalis a per-node attribution, not a partition of the whole.
Output — ranking (no id)
A header naming the metric, the total self cost (the shareOfTotal denominator),
and the coverage — what fraction of the profiled cost the join attributed to
graph nodes — then one ranked line per node: rank, inclusive cost, share,
kind, name (↺ when cyclic), location.
Inclusive cost by self-time (total self 17860.614 ms · coverage 62%)
1. 16906.578 ms 94.7% Function main src/main.ts:14
2. 12982.94 ms 72.7% Method headline src/report/text_report.ts:39
3. 9131.434 ms 51.1% Method titleCase src/utils/string_utils.ts:20
...
12 node(s) · share is of total self cost · ↺ = in a call cycle
coverage = 62% of profiled self-time attributed to the graph; 38% fell outside it (dropped by the join)main has almost no self cost, yet it is responsible for 94.7% of measured
time — the gap between exclusive (hotspots) and inclusive
(cost) ranking is exactly the leverage this query surfaces. Coverage keeps
it honest: at 62%, more than a third of the profiled time never landed on a graph
node, so the shares are of the attributed total, not wall-clock.
Output — attribution (with id)
The focal node’s self / inclusive / share of total, then two breakdowns:
callees (where the node’s inclusive cost goes, each carrying its subtree’s
contribution) and callers (how the node’s cost is attributed upward, by
call-count share).
titleCase Method · src/utils/string_utils.ts:20
self 5474.126 ms · inclusive 9131.434 ms · share of total 51.1%
graph coverage: 62% of profiled self-time is attributed to nodes
Cost flows into (callees)
-> 2705.166 ms 29.6% capitalize src/utils/string_utils.ts:7
-> 952.142 ms 10.4% normalizeWhitespace src/utils/string_utils.ts:15
Attributed to callers
<- 9131.434 ms 100.0% headline src/report/text_report.ts:39The callee shares plus the node’s own self share sum to 1 (where its inclusive cost is spent); the caller shares sum to 1 (who is responsible for it).
JSON (--json)
Ranking — nodes is CostRef[] (a SymbolRef plus selfCost,
inclusiveCost, shareOfTotal, cyclic, cycleSize); the envelope records the
metric, whether the graph is enriched, the totalSelf denominator, the count of
measuredNodes, and coverage (matched ÷ total profiled cost in the metric, or
null without an enrich manifest). Attribution — the same coverage plus node
(a CostRef, or null for an unknown id) and callees / callers arrays of
CostFlow (SymbolRef plus amount, share, callCount).
npx codespine cost --json
npx codespine cost <id> --jsonExamples
# rank the whole graph by inclusive cost / share of total
npx codespine cost
# the same, ranked by propagated profiler samples instead of self time
npx codespine cost --by samples --limit 10
# break one node down: where its cost goes, and who is responsible for it
npx codespine find titleCase --json # -> get its id
npx codespine cost <id>
# machine-readable — the shape the optimization agent consumes
npx codespine cost --jsonNotes and caveats
- Needs
enrich. Cost is inherently a runtime quantity — there is no self cost to propagate on an un-enriched graph, socostprints the notice! no runtime data in graph — run enrich first.and ranks empty (unlikehotspots, there is no static fallback). - Needs a
--semanticextraction. Propagation runs overCALLSedges, which only exist on a semantic graph. - Two denominators —
shareOfTotalvscoverage.shareOfTotalis a fraction oftotalSelf, the self costenrichattached to graph nodes (frames the join dropped are not in it).coveragereports how much of the profile that is —matched ÷ total, read from the manifestenrichrecords — so a node at 50% share on a graph with 62% coverage is ≈31% of wall-clock. Coverage isnull(“unknown”) when the graph carries no manifest — never enriched, enriched before manifests were recorded, or reloaded since (aloadclears it); re-runenrichto restore it. - Static call edges only. Dynamic dispatch is invisible to the graph, so cost
cannot flow across a call the extractor could not resolve — confirm a high-share
candidate with
who-calls/calls. - Re-extract before trusting it. The loader merges by id and does not remove
stale nodes; for a clean reading, delete the database, re-extract, reload, and
re-enrich — see
load.