Skip to Content

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

ArgumentDescription
[id]Optional. A node id (from find) to break down causally. Omit it to rank the whole graph.

Options

OptionDefaultDescription
-o, --output-folder <dir>./.codespineOutput folder; the Kùzu database is read from <dir>/graph.kuzu.
--by <metric>self-timeCost metric to propagate: self-time (metadata.runtime.selfMs) or samples (metadata.runtime.samples).
--edges <graph>staticCall 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>20Maximum number of ranked nodes (ranking mode only). Clamped to 11000.
--jsonfalseEmit 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 self

A 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 — shareOfTotal is 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:39

The 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> --json

Examples

# 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 --json

Notes and caveats

  • Needs enrich. Cost is inherently a runtime quantity — there is no self cost to propagate on an un-enriched graph, so cost prints the notice ! no runtime data in graph — run enrich first. and ranks empty (unlike hotspots, there is no static fallback).
  • Needs a --semantic extraction. Propagation runs over CALLS edges, which only exist on a semantic graph.
  • Two denominators — shareOfTotal vs coverage. shareOfTotal is a fraction of totalSelf, the self cost enrich attached to graph nodes (frames the join dropped are not in it). coverage reports how much of the profile that is — matched ÷ total, read from the manifest enrich records — so a node at 50% share on a graph with 62% coverage is ≈31% of wall-clock. Coverage is null (“unknown”) when the graph carries no manifest — never enriched, enriched before manifests were recorded, or reloaded since (a load clears it); re-run enrich to 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.

See also

  • hotspots — the exclusive counterpart: ranks by self time (time spent in a node) rather than propagated inclusive cost.
  • enrich — attaches the metadata.runtime self cost that cost propagates.
  • calls / who-calls — the raw edges the attribution breakdown is computed over.
Last updated on