Documentation
¶
Overview ¶
Package symbolref implements symbol-aware tree references: a model.LocationRefNameTree node name can refer to either a resolved frame name or an unresolved native {buildID, address} location, sharing a single integer reference space (model.LocationRefName) alongside a side table (queryv1.SymbolRefTable). This lets a partial or merged tree carry unresolved locations through query-plan merges without leaving the native tree representation, deferring resolution and truncation until after the final merge.
Responsibility boundary:
Owns: the symbol-reference table (interning and deduplication), ref-space partitioning (resolved vs. unresolved vs. the truncation "other" sentinel), merge-time ref rebasing, marshal-time compaction and ordering, deferred-truncation bookkeeping, grouping unresolved references by binary for resolution, and rebuilding/truncating the final resolved tree.
Does NOT own: the generic tree node/marshal format (pkg/model's Tree, reused unchanged), building per-dataset trees from block data (pkg/phlaredb/symdb, pkg/querybackend), fetching or resolving debug info (pkg/symbolizer), or query orchestration (pkg/frontend/...).
Index ¶
- func FallbackSymbolName(binaryName string, addr uint64) string
- func Rebuild(treeBytes []byte, pb *queryv1.SymbolRefTable, ...) ([]byte, error)
- type Frame
- type ResultBuilder
- type Table
- func (t *Table) Add(pb *queryv1.SymbolRefTable) (func(model.LocationRefName) model.LocationRefName, error)
- func (t *Table) HasUnresolved() bool
- func (t *Table) InternName(name string) model.LocationRefName
- func (t *Table) InternUnresolved(buildID, binaryName string, addr uint64) model.LocationRefName
- func (t *Table) ResultBuilder() *ResultBuilder
- func (t *Table) UnresolvedCount() int
- type UnresolvedBinary
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FallbackSymbolName ¶
FallbackSymbolName renders the display name for an address that could not be resolved to a function name: "binary!0xaddr", or "unknown!0xaddr" when the binary name is empty.
func Rebuild ¶
func Rebuild( treeBytes []byte, pb *queryv1.SymbolRefTable, resolve func(buildID string, addr uint64) []Frame, maxNodes int64, ) ([]byte, error)
Rebuild expands every model.LocationRefName ref in treeBytes (a model.LocationRefNameTree marshal paired with pb) into resolved model.FunctionNames, rebuilds a plain tree from the expanded stacks, and truncates exactly once, via a single tree.Bytes(maxNodes, nil) call after every stack has been expanded and reinserted.
resolve returning nil or an empty slice for a given (buildID, addr) means "could not resolve"; Rebuild then synthesizes exactly one fallback frame named by FallbackSymbolName. A resolved chain must be root-first — outermost caller at index 0, innermost frame last, the reverse of lidia's and pprof Line order — and is spliced into the rebuilt stack unchanged, expanding one address into that many tree levels. Because every stack is reinserted from scratch, two refs that expand to the same name at the same position merge structurally, with no separate dedup pass.
Returns an error only for malformed input: an unmarshalable treeBytes, a structurally inconsistent pb, or a tree ref outside pb's valid range.
Types ¶
type Frame ¶
type Frame struct {
Name string
}
Frame is one resolved stack frame; deliberately minimal, with no lidia dependency.
type ResultBuilder ¶
type ResultBuilder struct {
// contains filtered or unexported fields
}
ResultBuilder encodes a Table's contents into a queryv1.SymbolRefTable at marshal time. It is built from a point-in-time snapshot of its Table's state, taken under the Table's lock (see tableCore.newResultBuilder); ResultBuilder itself holds no reference back to the Table and takes no lock of its own, so it is not safe for concurrent use — it is meant to be driven single-threaded, once, after every Add/Intern* call on its Table has completed.
func (*ResultBuilder) Build ¶
func (rb *ResultBuilder) Build(pb *queryv1.SymbolRefTable) *queryv1.SymbolRefTable
Build writes pb.Names, pb.BuildIds, pb.BinaryNames, pb.UnresolvedBuildId and pb.UnresolvedAddress from the snapshot and returns pb, allocating the returned queryv1.SymbolRefTable when pb == nil. Every interned name is written, in intern order, so resolved wire refs are the table's own name indices and len(pb.Names) always equals the offset KeepRef applies to unresolved refs; unresolved entries are sorted by (buildID, binaryName, address), which is what makes the unresolved side of the wire encoding independent of intern or merge arrival order. Equal binary rows form one contiguous run each under that order, so build_ids/binary_names carry exactly one row per distinct (build ID, binary name) pair referenced.
func (*ResultBuilder) KeepRef ¶
func (rb *ResultBuilder) KeepRef(ref model.LocationRefName) model.LocationRefName
KeepRef returns ref's wire encoding, for use as the keepName callback to Tree.Bytes/MarshalTruncate. ref is Table's internal encoding (resolved: >= 0; model.OtherLocationRef (-1): passed through unchanged; unresolved: <= -2, see tableCore.unresolvedRef).
Wire refs are assigned from the snapshot alone — resolved refs keep their table index, unresolved refs are offset by the snapshot's name count — and Build writes the full snapshot, so the encoding does not depend on which refs the marshaled tree keeps. Unresolved wire refs must be final the moment they are first returned, while the kept set is still unknown, so a kept-set-compacted encoding could not be assigned in this single pass without breaking the (buildID, binaryName, address) wire order.
func (*ResultBuilder) NameOf ¶
func (rb *ResultBuilder) NameOf(ref model.LocationRefName) string
NameOf returns ref's display name from the snapshot: the interned name of a resolved ref, or the FallbackSymbolName rendering of an unresolved entry's (binary name, address). Refs the snapshot does not describe — model.OtherLocationRef and out-of-range values — render as the empty string; a caller that special-cases the truncation sentinel must do so before calling. ref is Table's internal encoding, as with KeepRef.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table is a content-addressed intern table pairing a model.LocationRefNameTree with the wire-format queryv1.SymbolRefTable it represents. Safe for concurrent use: every method locks an internal mutex before touching its state.
func NewTable ¶
func NewTable() *Table
NewTable returns an empty table. Ref 0 is reserved as an unused sentinel: the tree-merge machinery invokes remap functions on synthetic zero-valued names (model.Tree.FormatNodeNames visits its virtual root node), and Add remaps refs its input does not describe to the reserved ref — so a real name must never land on ref 0. InternName's first call for real content returns 1, not 0.
func (*Table) Add ¶
func (t *Table) Add(pb *queryv1.SymbolRefTable) (func(model.LocationRefName) model.LocationRefName, error)
Add merges pb into t, returning a remap function from pb's ref space into t's ref space, suitable for model.WithTreeMergeFormatNodeNames. The returned remap passes model.OtherLocationRef through unchanged and maps every other ref pb does not describe — any other negative, or any non-negative when pb is nil or empty — to the reserved ref 0; err is non-nil only for structurally malformed input, never to signal "nothing to merge".
func (*Table) HasUnresolved ¶
HasUnresolved reports whether any unresolved entry has ever landed in t, whether via InternUnresolved directly or absorbed via Add.
func (*Table) InternName ¶
func (t *Table) InternName(name string) model.LocationRefName
InternName idempotently interns a resolved frame name: the same name always returns the same ref, and a name not seen before is assigned a new one.
func (*Table) InternUnresolved ¶
func (t *Table) InternUnresolved(buildID, binaryName string, addr uint64) model.LocationRefName
InternUnresolved idempotently interns an unresolved location, keyed on the (buildID, binaryName, addr) triple. The binary name is part of the key — the same build ID under two different names yields two distinct refs — so every name is retained exactly as stored and the table's content is independent of intern or merge arrival order.
func (*Table) ResultBuilder ¶
func (t *Table) ResultBuilder() *ResultBuilder
ResultBuilder returns a fresh ResultBuilder snapshotting t for one marshal pass.
func (*Table) UnresolvedCount ¶
UnresolvedCount reports the number of distinct unresolved locations interned in t, whether via InternUnresolved directly or absorbed via Add.
type UnresolvedBinary ¶
type UnresolvedBinary struct {
BuildID string
BinaryName string
Addresses []uint64 // sorted ascending, deduplicated
}
UnresolvedBinary is one binary's worth of unresolved addresses to resolve.
func UnresolvedBinaries ¶
func UnresolvedBinaries(pb *queryv1.SymbolRefTable) ([]UnresolvedBinary, error)
UnresolvedBinaries groups a table's unresolved references by binary row, one entry per distinct (build ID, binary name) row referenced; err is non-nil only for a structurally malformed pb. ResultBuilder.Build already sorts unresolved entries by (build ID, binary name, address), making contiguous-run grouping a single forward pass; a table from a different producer may not be grouped that way, so UnresolvedBinaries falls back to sorting first when it detects that.