diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-26 22:31:40 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-26 22:31:40 +0300 |
| commit | 6bfa0031cc7c903c16baaca2d0f504be26fb828c (patch) | |
| tree | 0d3c002eaed4c6e02f12cbffd7054bd07989e0fe | |
| parent | f42d4f4f0b9d3faf38d2f3c3a9753a03440cdd24 (diff) | |
flamegraph: add LiveTrie height metric ingestion (task qo)
| -rw-r--r-- | internal/bench_pipeline_test.go | 2 | ||||
| -rw-r--r-- | internal/flamegraph/livetrie.go | 109 | ||||
| -rw-r--r-- | internal/flamegraph/livetrie_test.go | 122 | ||||
| -rw-r--r-- | internal/runtime_builder.go | 2 | ||||
| -rw-r--r-- | internal/tui/dashboard/model_test.go | 10 | ||||
| -rw-r--r-- | internal/tui/flamegraph/async_refresh_test.go | 10 | ||||
| -rw-r--r-- | internal/tui/flamegraph/bench_test.go | 2 | ||||
| -rw-r--r-- | internal/tui/flamegraph/model_test.go | 24 | ||||
| -rw-r--r-- | internal/tui/flamegraph/stress_test.go | 4 | ||||
| -rw-r--r-- | internal/tui/flamegraph/testdata_test.go | 2 | ||||
| -rw-r--r-- | internal/tui/tui_test.go | 8 |
11 files changed, 212 insertions, 83 deletions
diff --git a/internal/bench_pipeline_test.go b/internal/bench_pipeline_test.go index bb6404a..2fd643c 100644 --- a/internal/bench_pipeline_test.go +++ b/internal/bench_pipeline_test.go @@ -200,7 +200,7 @@ func benchmarkPipelineTUIParquet(b *testing.B, mix benchutil.EventMix, events, n engine := statsengine.NewEngine(statsengine.DefaultTopN) streamBuf := streamrow.NewRingBuffer() streamSeq := streamrow.NewSequencer(0) - liveTrie := flamegraph.NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count") + liveTrie := flamegraph.NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count", "count") recorder := parquet.NewRecorder(parquet.RecorderConfig{}) path := filepath.Join(dir, fmt.Sprintf("tui-%d.parquet", i)) diff --git a/internal/flamegraph/livetrie.go b/internal/flamegraph/livetrie.go index a682a0a..a510a72 100644 --- a/internal/flamegraph/livetrie.go +++ b/internal/flamegraph/livetrie.go @@ -20,20 +20,22 @@ const ( ) type SnapshotNode struct { - Name string `json:"n"` - Value uint64 `json:"v"` - Total uint64 `json:"t"` - Children []*SnapshotNode `json:"c,omitempty"` + Name string `json:"n"` + Value uint64 `json:"v"` + Total uint64 `json:"t"` + HeightTotal uint64 `json:"ht,omitempty"` + Children []*SnapshotNode `json:"c,omitempty"` } // LiveTrie is a thread-safe, append-only trie used for live flamegraph snapshots. type LiveTrie struct { - mu sync.RWMutex - root *trieNode - maxDepth int - version atomic.Uint64 - fields []string - countField string + mu sync.RWMutex + root *trieNode + maxDepth int + version atomic.Uint64 + fields []string + countField string + heightField string // Snapshot cache avoids recomputing JSON when version is unchanged. cacheMu sync.Mutex @@ -48,22 +50,26 @@ type LiveTrie struct { treeCache *SnapshotNode } -// NewLiveTrie constructs an empty live trie with the configured frame/count fields. -func NewLiveTrie(fields []string, countField string) *LiveTrie { +// NewLiveTrie constructs an empty live trie with the configured frame/count/height fields. +func NewLiveTrie(fields []string, countField, heightField string) *LiveTrie { if !isLiveTrieCountField(countField) { countField = "count" } + if heightField != "" && !isLiveTrieCountField(heightField) { + heightField = "" + } return &LiveTrie{ root: &trieNode{ childMap: make(map[string]*trieNode), }, - fields: slices.Clone(fields), - countField: countField, + fields: slices.Clone(fields), + countField: countField, + heightField: heightField, } } -func (lt *LiveTrie) addLocked(frames []string, value uint64) { - insertTriePath(lt.root, frames, value, value) +func (lt *LiveTrie) addLocked(frames []string, value, heightValue uint64) { + insertTriePath(lt.root, frames, value, heightValue) if len(frames) > lt.maxDepth { lt.maxDepth = len(frames) } @@ -100,10 +106,17 @@ func (lt *LiveTrie) AddRecord(record IterRecord) { if err != nil { return } + heightValue := uint64(0) + if lt.heightField != "" { + heightValue, err = record.Cnt.ValueByName(lt.heightField) + if err != nil { + return + } + } lt.mu.Lock() frames := lt.buildFrames(record) - lt.addLocked(frames, value) + lt.addLocked(frames, value, heightValue) lt.version.Add(1) lt.mu.Unlock() } @@ -132,6 +145,14 @@ func (lt *LiveTrie) CountField() string { return field } +// HeightField returns the active metric used to aggregate node heights. +func (lt *LiveTrie) HeightField() string { + lt.mu.RLock() + field := lt.heightField + lt.mu.RUnlock() + return field +} + // SetCountField changes the active aggregation metric and starts a new baseline. func (lt *LiveTrie) SetCountField(countField string) error { field := strings.TrimSpace(countField) @@ -151,6 +172,25 @@ func (lt *LiveTrie) SetCountField(countField string) error { return nil } +// SetHeightField changes the active height metric and starts a new baseline. +func (lt *LiveTrie) SetHeightField(heightField string) error { + field := strings.TrimSpace(heightField) + if field != "" && !isLiveTrieCountField(field) { + return fmt.Errorf("invalid height field %q", heightField) + } + + lt.mu.Lock() + if lt.heightField == field { + lt.mu.Unlock() + return nil + } + lt.heightField = field + lt.resetLocked() + lt.mu.Unlock() + lt.invalidateCache() + return nil +} + // Reconfigure changes frame fields and clears accumulated data for a new baseline. func (lt *LiveTrie) Reconfigure(fields []string) error { normalized, err := normalizeLiveTrieFields(fields) @@ -303,18 +343,20 @@ func subtreeTotal(node *trieNode) uint64 { } func buildSnapshot(node *trieNode, depth int, minFraction float64, rootTotal uint64) *SnapshotNode { - snapshot, _ := buildSnapshotWithTotal(node, depth, minFraction, rootTotal, false) + snapshot, _, _ := buildSnapshotWithTotal(node, depth, minFraction, rootTotal, false) return snapshot } type childSnapshotState struct { - node *trieNode - snapshot *SnapshotNode - total uint64 + node *trieNode + snapshot *SnapshotNode + total uint64 + heightTotal uint64 } -func buildSnapshotWithTotal(node *trieNode, depth int, minFraction float64, rootTotal uint64, forceKeep bool) (*SnapshotNode, uint64) { +func buildSnapshotWithTotal(node *trieNode, depth int, minFraction float64, rootTotal uint64, forceKeep bool) (*SnapshotNode, uint64, uint64) { total := node.value + heightTotal := node.heightValue children := slices.Clone(node.children) slices.SortFunc(children, func(a, b *trieNode) int { return cmp.Compare(a.name, b.name) @@ -322,17 +364,19 @@ func buildSnapshotWithTotal(node *trieNode, depth int, minFraction float64, root childStates := make([]childSnapshotState, 0, len(children)) for _, child := range children { - childSnapshot, childTotal := buildSnapshotWithTotal(child, depth+1, minFraction, rootTotal, false) + childSnapshot, childTotal, childHeightTotal := buildSnapshotWithTotal(child, depth+1, minFraction, rootTotal, false) total += childTotal + heightTotal += childHeightTotal childStates = append(childStates, childSnapshotState{ - node: child, - snapshot: childSnapshot, - total: childTotal, + node: child, + snapshot: childSnapshot, + total: childTotal, + heightTotal: childHeightTotal, }) } if !forceKeep && depth > 0 && rootTotal > 0 && float64(total)/float64(rootTotal) < minFraction { - return nil, total + return nil, total, heightTotal } ensureFallbackVisibleChildren(childStates, depth, minFraction, rootTotal) @@ -344,14 +388,15 @@ func buildSnapshotWithTotal(node *trieNode, depth int, minFraction float64, root } snapshot := &SnapshotNode{ - Name: node.name, - Value: node.value, - Total: total, + Name: node.name, + Value: node.value, + Total: total, + HeightTotal: heightTotal, } if len(childSnapshots) > 0 { snapshot.Children = childSnapshots } - return snapshot, total + return snapshot, total, heightTotal } func ensureFallbackVisibleChildren(children []childSnapshotState, depth int, minFraction float64, rootTotal uint64) { @@ -389,7 +434,7 @@ func ensureFallbackVisibleChildren(children []childSnapshotState, depth int, min } for i := 0; i < limit; i++ { idx := candidates[i] - forced, _ := buildSnapshotWithTotal(children[idx].node, depth+1, minFraction, rootTotal, true) + forced, _, _ := buildSnapshotWithTotal(children[idx].node, depth+1, minFraction, rootTotal, true) children[idx].snapshot = forced } } diff --git a/internal/flamegraph/livetrie_test.go b/internal/flamegraph/livetrie_test.go index 6a825c0..32e2b40 100644 --- a/internal/flamegraph/livetrie_test.go +++ b/internal/flamegraph/livetrie_test.go @@ -17,7 +17,7 @@ import ( ) func TestLiveTrieIngestAndSnapshotRoundTrip(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "pid"}, "count") + lt := NewLiveTrie([]string{"comm", "pid"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 2, 3)) snap := decodeLiveSnapshot(t, lt) @@ -34,7 +34,7 @@ func TestLiveTrieIngestAndSnapshotRoundTrip(t *testing.T) { } func TestLiveTrieIngestIsAdditive(t *testing.T) { - lt := NewLiveTrie([]string{"path"}, "bytes") + lt := NewLiveTrie([]string{"path"}, "bytes", "bytes") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 2, 10)) lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 3, 4, 15)) @@ -49,7 +49,7 @@ func TestLiveTrieIngestIsAdditive(t *testing.T) { } func TestLiveTrieIngestCopiesBeforeRecycle(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "path"}, "count") + lt := NewLiveTrie([]string{"comm", "path"}, "count", "count") pair := newTestPair("svc", 42, 1001, "/tmp/a", 1, 2, 3) lt.Ingest(pair) @@ -66,7 +66,7 @@ func TestLiveTrieIngestCopiesBeforeRecycle(t *testing.T) { } func TestLiveTrieCommTracepointPathAggregatesSameSyscallAcrossPaths(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count") + lt := NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count", "count") lt.AddRecord(IterRecord{ Path: "/srv/a", TraceID: types.SYS_ENTER_READ, @@ -99,7 +99,7 @@ func TestLiveTrieCommTracepointPathAggregatesSameSyscallAcrossPaths(t *testing.T } func TestLiveTrieVersionIncrementsPerIngest(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") if got := lt.Version(); got != 0 { t.Fatalf("initial version = %d, want 0", got) } @@ -112,7 +112,7 @@ func TestLiveTrieVersionIncrementsPerIngest(t *testing.T) { } func TestLiveTrieAddRecordIncrementsVersion(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") lt.AddRecord(IterRecord{ Path: "/tmp/demo/read", TraceID: types.SYS_ENTER_READ, @@ -132,7 +132,7 @@ func TestLiveTrieAddRecordIncrementsVersion(t *testing.T) { } func TestSeedTestFlameDataBuildsStaticFixture(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") SeedTestFlameData(lt) if got := lt.Version(); got == 0 { @@ -151,7 +151,7 @@ func TestSeedTestFlameDataBuildsStaticFixture(t *testing.T) { } func TestSeedTestLiveFlameDataVariesByTick(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + lt := NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") SeedTestLiveFlameData(lt, 0) snapTick0 := decodeLiveSnapshot(t, lt) @@ -176,7 +176,7 @@ func TestSeedTestLiveFlameDataVariesByTick(t *testing.T) { } func TestLiveTrieResetClearsDataAndAdvancesVersion(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 1, 1)) lt.Ingest(newTestPair("svc", 42, 1002, "/tmp/b", 1, 1, 1)) @@ -203,7 +203,7 @@ func TestLiveTrieResetClearsDataAndAdvancesVersion(t *testing.T) { } func TestLiveTrieReconfigureChangesOrderAndResets(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "pid"}, "count") + lt := NewLiveTrie([]string{"comm", "pid"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 1, 1)) if err := lt.Reconfigure([]string{"path", "comm"}); err != nil { @@ -224,7 +224,7 @@ func TestLiveTrieReconfigureChangesOrderAndResets(t *testing.T) { } func TestLiveTrieReconfigureRejectsInvalidFields(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") cases := [][]string{ nil, @@ -241,7 +241,7 @@ func TestLiveTrieReconfigureRejectsInvalidFields(t *testing.T) { } func TestLiveTrieSetCountFieldSwitchesMetricAndResetsBaseline(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 10, 1, 64)) initial := decodeLiveSnapshot(t, lt) @@ -273,7 +273,7 @@ func TestLiveTrieSetCountFieldSwitchesMetricAndResetsBaseline(t *testing.T) { } func TestLiveTrieSetCountFieldRejectsInvalidValue(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 1, 1)) beforeVersion := lt.Version() @@ -288,8 +288,92 @@ func TestLiveTrieSetCountFieldRejectsInvalidValue(t *testing.T) { } } +func TestLiveTrieHeightFieldTracksIndependentMetric(t *testing.T) { + lt := NewLiveTrie([]string{"comm"}, "count", "bytes") + lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 10, 1, 64)) + lt.Ingest(newTestPair("svc", 42, 1002, "/tmp/b", 10, 1, 128)) + + snap := decodeLiveSnapshot(t, lt) + if got, want := snap.Total, uint64(2); got != want { + t.Fatalf("root total = %d, want %d", got, want) + } + if got, want := snap.HeightTotal, uint64(192); got != want { + t.Fatalf("root height total = %d, want %d", got, want) + } + leaf := findSnapshotPath(t, &snap, "svc") + if got, want := leaf.Total, uint64(2); got != want { + t.Fatalf("leaf total = %d, want %d", got, want) + } + if got, want := leaf.HeightTotal, uint64(192); got != want { + t.Fatalf("leaf height total = %d, want %d", got, want) + } +} + +func TestLiveTrieSetHeightFieldSwitchesMetricAndResetsBaseline(t *testing.T) { + lt := NewLiveTrie([]string{"comm"}, "count", "count") + lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 10, 1, 64)) + + initial := decodeLiveSnapshot(t, lt) + if got, want := initial.HeightTotal, uint64(1); got != want { + t.Fatalf("initial height total = %d, want %d", got, want) + } + + if err := lt.SetHeightField("bytes"); err != nil { + t.Fatalf("set height field: %v", err) + } + if got, want := lt.HeightField(), "bytes"; got != want { + t.Fatalf("height field = %q, want %q", got, want) + } + + empty := decodeLiveSnapshot(t, lt) + if got := empty.Total; got != 0 { + t.Fatalf("expected reset baseline after height metric switch, total=%d", got) + } + if got := empty.HeightTotal; got != 0 { + t.Fatalf("expected reset baseline after height metric switch, height total=%d", got) + } + + lt.Ingest(newTestPair("svc", 42, 1002, "/tmp/b", 10, 1, 64)) + next := decodeLiveSnapshot(t, lt) + if got, want := next.Total, uint64(1); got != want { + t.Fatalf("total after switch = %d, want %d", got, want) + } + if got, want := next.HeightTotal, uint64(64); got != want { + t.Fatalf("height total after switch = %d, want %d", got, want) + } +} + +func TestLiveTrieSetHeightFieldRejectsInvalidValue(t *testing.T) { + lt := NewLiveTrie([]string{"comm"}, "count", "count") + lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 1, 1)) + beforeVersion := lt.Version() + + if err := lt.SetHeightField("bogus"); err == nil { + t.Fatalf("expected invalid height field error") + } + if got, want := lt.HeightField(), "count"; got != want { + t.Fatalf("height field changed unexpectedly: got %q want %q", got, want) + } + if got := lt.Version(); got != beforeVersion { + t.Fatalf("version changed on invalid height field: got %d want %d", got, beforeVersion) + } +} + +func TestLiveTrieHeightFieldEmptyDisablesHeightTotals(t *testing.T) { + lt := NewLiveTrie([]string{"comm"}, "count", "") + lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 10, 1, 64)) + + snap := decodeLiveSnapshot(t, lt) + if got, want := snap.Total, uint64(1); got != want { + t.Fatalf("root total = %d, want %d", got, want) + } + if got := snap.HeightTotal; got != 0 { + t.Fatalf("root height total = %d, want 0 when height metric disabled", got) + } +} + func TestLiveTrieSnapshotJSONCaching(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") lt.Ingest(newTestPair("svc", 42, 1001, "/tmp/a", 1, 1, 1)) first, version1 := lt.SnapshotJSON() @@ -304,7 +388,7 @@ func TestLiveTrieSnapshotJSONCaching(t *testing.T) { } func TestLiveTrieSnapshotJSONPrunesTinyNodes(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") for i := 0; i < 2000; i++ { lt.Ingest(newTestPair("big", 42, uint32(1000+i), "/tmp/a", 1, 1, 1)) } @@ -320,7 +404,7 @@ func TestLiveTrieSnapshotJSONPrunesTinyNodes(t *testing.T) { } func TestLiveTrieSnapshotJSONKeepsFallbackChildrenWhenAllAreTinyAtRoot(t *testing.T) { - lt := NewLiveTrie([]string{"comm"}, "count") + lt := NewLiveTrie([]string{"comm"}, "count", "count") const total = 6000 for i := 0; i < total; i++ { comm := fmt.Sprintf("svc-%04d", i) @@ -337,7 +421,7 @@ func TestLiveTrieSnapshotJSONKeepsFallbackChildrenWhenAllAreTinyAtRoot(t *testin } func TestLiveTrieSnapshotJSONKeepsFallbackChildrenAtDepthOne(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "pid"}, "count") + lt := NewLiveTrie([]string{"comm", "pid"}, "count", "count") const total = 6000 for i := 0; i < total; i++ { pid := uint32(100000 + i) @@ -355,7 +439,7 @@ func TestLiveTrieSnapshotJSONKeepsFallbackChildrenAtDepthOne(t *testing.T) { } func TestLiveTrieConcurrentIngestAndSnapshot(t *testing.T) { - lt := NewLiveTrie([]string{"comm", "pid"}, "count") + lt := NewLiveTrie([]string{"comm", "pid"}, "count", "count") var wg sync.WaitGroup wg.Add(2) @@ -396,7 +480,7 @@ func TestLiveTrieStressHighRateConcurrentSnapshot(t *testing.T) { maxMemGrowth = 512 << 20 ) - lt := NewLiveTrie([]string{"path", "pid"}, "count") + lt := NewLiveTrie([]string{"path", "pid"}, "count", "count") var startMem runtime.MemStats runtime.ReadMemStats(&startMem) diff --git a/internal/runtime_builder.go b/internal/runtime_builder.go index 17a69c6..126495f 100644 --- a/internal/runtime_builder.go +++ b/internal/runtime_builder.go @@ -42,6 +42,6 @@ func (b RuntimeBuilder) Build() runtimeComponents { engine: statsengine.NewEngine(statsengine.DefaultTopN), streamBuf: streamrow.NewRingBuffer(), streamSeq: streamrow.NewSequencer(0), - liveTrie: flamegraph.NewLiveTrie(b.cfg.CollapsedFields, b.cfg.CountField), + liveTrie: flamegraph.NewLiveTrie(b.cfg.CollapsedFields, b.cfg.CountField, b.cfg.CountField), } } diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go index f758597..aa54e97 100644 --- a/internal/tui/dashboard/model_test.go +++ b/internal/tui/dashboard/model_test.go @@ -845,7 +845,7 @@ func TestStreamSpaceUnpauseSchedulesStreamTick(t *testing.T) { } func TestFlameTickRefreshesFlamegraphModel(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") liveTrie.Reset() m := NewModelWithConfig(nil, nil, 250, 200, common.DefaultKeyMap()) @@ -863,7 +863,7 @@ func TestFlameTickRefreshesFlamegraphModel(t *testing.T) { } func TestSetLiveTriePreloadsInitialSnapshotWithoutVersionChange(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModelWithConfig(nil, nil, 250, 200, common.DefaultKeyMap()) m.SetLiveTrie(liveTrie) @@ -880,7 +880,7 @@ func TestSetLiveTriePreloadsInitialSnapshotWithoutVersionChange(t *testing.T) { } func TestFlameTickPausedFreezesAfterInitialSnapshot(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModelWithConfig(nil, nil, 250, 200, common.DefaultKeyMap()) m.SetLiveTrie(liveTrie) m.activeTab = TabFlame @@ -905,7 +905,7 @@ func TestFlameTickPausedFreezesAfterInitialSnapshot(t *testing.T) { } func TestPausedFlameDashboardViewPreservesZoomedSelectedLine(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestFlameData(liveTrie) m := NewModelWithConfig(nil, nil, 250, 200, common.DefaultKeyMap()) @@ -1352,7 +1352,7 @@ func TestRefreshKeyResetsBaselineWhenSourceSupportsReset(t *testing.T) { } func TestRefreshKeyResetsLiveTrieOutsideFlameTab(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModelWithConfig(nil, nil, 250, 200, common.DefaultKeyMap()) m.SetLiveTrie(liveTrie) m.activeTab = TabSyscalls diff --git a/internal/tui/flamegraph/async_refresh_test.go b/internal/tui/flamegraph/async_refresh_test.go index d6027de..73c1889 100644 --- a/internal/tui/flamegraph/async_refresh_test.go +++ b/internal/tui/flamegraph/async_refresh_test.go @@ -31,7 +31,7 @@ func TestRefreshFromLiveTrieCmdNilWhenNoTrie(t *testing.T) { } func TestRefreshFromLiveTrieCmdProducesSnapshotReady(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") ingestTwoEventsForAsync(t, trie) m := NewModel(trie) m.width = 120 @@ -65,7 +65,7 @@ func TestRefreshFromLiveTrieCmdProducesSnapshotReady(t *testing.T) { } func TestRefreshFromLiveTrieCmdCoalescesInFlight(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") ingestTwoEventsForAsync(t, trie) m := NewModel(trie) m.width = 80 @@ -80,7 +80,7 @@ func TestRefreshFromLiveTrieCmdCoalescesInFlight(t *testing.T) { } func TestRefreshFromLiveTrieCmdSkippedWhileUserDrives(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") ingestTwoEventsForAsync(t, trie) m := NewModel(trie) m.width = 80 @@ -106,7 +106,7 @@ func TestRefreshFromLiveTrieCmdSkippedWhileUserDrives(t *testing.T) { } func TestSnapshotReadyHandlerSnapsToTargetWhileDriving(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") ingestTwoEventsForAsync(t, trie) m := NewModel(trie) m.width = 120 @@ -144,7 +144,7 @@ func TestSnapshotReadyHandlerSnapsToTargetWhileDriving(t *testing.T) { } func TestViewCacheReusesContentWhenStateUnchanged(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") ingestTwoEventsForAsync(t, trie) m := NewModel(trie) m.width = 120 diff --git a/internal/tui/flamegraph/bench_test.go b/internal/tui/flamegraph/bench_test.go index 33d77d1..d1d0955 100644 --- a/internal/tui/flamegraph/bench_test.go +++ b/internal/tui/flamegraph/bench_test.go @@ -284,7 +284,7 @@ func BenchmarkLiveTrieIngestAndSnapshot(b *testing.B) { b.Run(fmt.Sprintf("%d_events", count), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") for eventIdx := 0; eventIdx < count; eventIdx++ { traceID := types.SYS_ENTER_READ if eventIdx%2 == 0 { diff --git a/internal/tui/flamegraph/model_test.go b/internal/tui/flamegraph/model_test.go index e864e88..e9d16f7 100644 --- a/internal/tui/flamegraph/model_test.go +++ b/internal/tui/flamegraph/model_test.go @@ -42,7 +42,7 @@ func TestSetViewportAndDarkMode(t *testing.T) { } func TestRefreshFromLiveTrieTracksVersionAndSnapshot(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModel(trie) if changed := m.RefreshFromLiveTrie(); !changed { @@ -58,7 +58,7 @@ func TestRefreshFromLiveTrieTracksVersionAndSnapshot(t *testing.T) { } func TestRefreshFromLiveTrieAllowsInitialLoadWhilePaused(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModel(trie) m.paused = true @@ -74,7 +74,7 @@ func TestRefreshFromLiveTrieAllowsInitialLoadWhilePaused(t *testing.T) { } func TestRefreshFromLiveTriePausedBlocksAfterNavigableSnapshot(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModel(trie) m.paused = true m.snapshot = &snapshotNode{Name: "root", Total: 1} @@ -94,7 +94,7 @@ func TestRefreshFromLiveTriePausedBlocksAfterNavigableSnapshot(t *testing.T) { } func TestRefreshFromLiveTriePausedBlocksAfterAnySnapshot(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") m := NewModel(trie) m.paused = true m.snapshot = &snapshotNode{Name: "root", Total: 1} @@ -441,7 +441,7 @@ func TestMouseClickDirectDeepZoomUndoReturnsToRoot(t *testing.T) { } func TestStaticFixtureArrowTraversalVisitsAllFrames(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestFlameData(trie) m := NewModel(trie) @@ -472,7 +472,7 @@ func TestStaticFixtureArrowTraversalVisitsAllFrames(t *testing.T) { } func TestLiveFixtureArrowTraversalWhileStreamingVisitsAllFrames(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestLiveFlameData(trie, 0) m := NewModel(trie) @@ -531,7 +531,7 @@ func TestLiveFixtureArrowTraversalWhileStreamingVisitsAllFrames(t *testing.T) { } func TestSelectionRestoresByPathAcrossLiveRefresh(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestLiveFlameData(trie, 0) m := NewModel(trie) @@ -832,7 +832,7 @@ func TestControlPauseToggle(t *testing.T) { } func TestControlResetBaseline(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") m := NewModel(liveTrie) m.snapshot = &snapshotNode{Name: "root", Total: 10} m.frames = []tuiFrame{{Name: "root", Path: "root"}} @@ -953,7 +953,7 @@ func TestViewFilterSelectionStatusUsesFilteredTotalAndKeepsContextVisible(t *tes } func TestControlCycleFieldOrderReconfiguresLiveTrie(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") m := NewModel(liveTrie) initial := append([]string(nil), m.fieldPresets[m.fieldIndex]...) expectedNextIdx := (m.fieldIndex + 1) % len(m.fieldPresets) @@ -972,7 +972,7 @@ func TestControlCycleFieldOrderReconfiguresLiveTrie(t *testing.T) { } func TestControlMetricToggleReconfiguresLiveTrieCountField(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") m := NewModel(liveTrie) m = pressFlameKey(t, m, tea.KeyPressMsg{Code: []rune{'b'}[0], Text: "b"}) @@ -1004,7 +1004,7 @@ func TestControlMetricToggleReconfiguresLiveTrieCountField(t *testing.T) { } func TestNewModelAlignsPresetIndexToLiveTrieFields(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") m := NewModel(liveTrie) if got, want := m.fieldPresets[m.fieldIndex], []string{"comm", "path", "tracepoint"}; !reflect.DeepEqual(got, want) { t.Fatalf("expected model field preset to align with trie fields, got %v want %v", got, want) @@ -1012,7 +1012,7 @@ func TestNewModelAlignsPresetIndexToLiveTrieFields(t *testing.T) { } func TestNewModelAlignsCountFieldToLiveTrie(t *testing.T) { - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "bytes") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "bytes", "bytes") m := NewModel(liveTrie) if got, want := m.countField, "bytes"; got != want { t.Fatalf("expected model count field to align with trie field, got %q want %q", got, want) diff --git a/internal/tui/flamegraph/stress_test.go b/internal/tui/flamegraph/stress_test.go index e53e4d5..e40d4f1 100644 --- a/internal/tui/flamegraph/stress_test.go +++ b/internal/tui/flamegraph/stress_test.go @@ -26,7 +26,7 @@ func TestStressHighEventRate(t *testing.T) { ) allowedBudget := frameBudget * time.Duration(stressBudgetMultiplier()) - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") var ingestWG sync.WaitGroup type renderMetrics struct { @@ -149,7 +149,7 @@ func TestStressRapidResize(t *testing.T) { func TestStressZoomDuringRefresh(t *testing.T) { t.Parallel() - liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + liveTrie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") ingestStressEvents(liveTrie, 200, 0) model := NewModel(liveTrie) diff --git a/internal/tui/flamegraph/testdata_test.go b/internal/tui/flamegraph/testdata_test.go index c7d97b0..11fcb0c 100644 --- a/internal/tui/flamegraph/testdata_test.go +++ b/internal/tui/flamegraph/testdata_test.go @@ -29,7 +29,7 @@ const ( ) func generateTestTrie(depth, breadthPerLevel int) *coreflamegraph.LiveTrie { - lt := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + lt := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") comms := []string{"api", "db", "worker", "cache"} traceIDs := []types.TraceId{ types.SYS_ENTER_READ, diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go index 9e62c55..d6f0740 100644 --- a/internal/tui/tui_test.go +++ b/internal/tui/tui_test.go @@ -495,7 +495,7 @@ func TestDashboardRefreshPicksLateBoundSource(t *testing.T) { func TestRuntimeBindingsStoreAndExposeLiveTrie(t *testing.T) { runtime := newRuntimeBindings() - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path"}, "count", "count") runtime.SetLiveTrie(trie) if got := runtime.liveTrie(); got != trie { t.Fatalf("expected live trie to be stored and returned") @@ -684,7 +684,7 @@ func TestGlobalFilterApplyAdvancesRuntimeFilterEpochAndKeepsRecorder(t *testing. } func TestTracingStartedUsesCurrentViewportForFlameNavigationWithoutResize(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestFlameData(trie) m := NewModel(-1, func(context.Context) error { return nil }) @@ -727,7 +727,7 @@ func TestTracingStartedUsesCurrentViewportForFlameNavigationWithoutResize(t *tes } func TestTracingStartedAppliesViewportWhenModelSizeIsUnset(t *testing.T) { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "path", "tracepoint"}, "count", "count") coreflamegraph.SeedTestFlameData(trie) m := NewModel(-1, func(context.Context) error { return nil }) @@ -1945,7 +1945,7 @@ func aggregateTestSnapshot(syscall, path, comm, latencyLabel, gapLabel string) * } func aggregateTestTrie(comm, path string) *coreflamegraph.LiveTrie { - trie := coreflamegraph.NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count") + trie := coreflamegraph.NewLiveTrie([]string{"comm", "tracepoint", "path"}, "count", "count") trie.AddRecord(coreflamegraph.IterRecord{ Comm: comm, Path: path, |
