summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-24 17:27:10 +0200
committerPaul Buetow <paul@buetow.org>2026-02-24 17:27:10 +0200
commitda8ddf1cf415f1754c3fe71f3f342327ad00e91e (patch)
treeaf3ba70015cf7db4bfd3a2adcb9334417740e4f3
parent2ae0b33c9f196634eaa55bd6997d1feae9147385 (diff)
tui: add toggle to disable snapshot export file writes
-rw-r--r--internal/flags/flags.go11
-rw-r--r--internal/tui/common/keys.go15
-rw-r--r--internal/tui/tui.go13
-rw-r--r--internal/tui/tui_test.go37
4 files changed, 70 insertions, 6 deletions
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 729b1b6..87ece5d 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -14,7 +14,9 @@ import (
)
var (
- singleton Flags
+ singleton = Flags{
+ TUIExportEnable: true,
+ }
once sync.Once
pidFilter atomic.Int64
)
@@ -56,6 +58,7 @@ type Flags struct {
PlainMode bool
FlamegraphEnable bool
FlamegraphName string
+ TUIExportEnable bool
// To convert ior data into collapsed format
IorDataFile string
@@ -77,6 +80,11 @@ func SetPidFilter(pid int) {
pidFilter.Store(int64(pid))
}
+// SetTUIExportEnable toggles TUI snapshot export file writing.
+func SetTUIExportEnable(enabled bool) {
+ singleton.TUIExportEnable = enabled
+}
+
func Parse() {
once.Do(func() {
parse()
@@ -100,6 +108,7 @@ func parse() {
flag.BoolVar(&singleton.PlainMode, "plain", false, "Enable plain CSV output mode (disable TUI)")
flag.BoolVar(&singleton.FlamegraphEnable, "flamegraph", false, "Enable flamegraph builder")
flag.StringVar(&singleton.FlamegraphName, "name", "default", "Name of the flamegraph, used to generate the SVG file")
+ flag.BoolVar(&singleton.TUIExportEnable, "tuiExport", true, "Enable writing TUI snapshot export files")
flag.StringVar(&singleton.IorDataFile, "ior", "", "IOR data file to convert into collapsed format")
fields := flag.String("fields", "",
diff --git a/internal/tui/common/keys.go b/internal/tui/common/keys.go
index 0f9c54d..1111def 100644
--- a/internal/tui/common/keys.go
+++ b/internal/tui/common/keys.go
@@ -45,14 +45,25 @@ func DefaultKeyMap() KeyMap {
// DashboardShortHelp returns compact bindings for dashboard help bars.
func (k KeyMap) DashboardShortHelp() []key.Binding {
- return []key.Binding{k.Tab, k.ShiftTab, k.Export, k.Help, k.Quit}
+ bindings := []key.Binding{k.Tab, k.ShiftTab}
+ if help := k.Export.Help(); help.Key != "" || help.Desc != "" {
+ bindings = append(bindings, k.Export)
+ }
+ bindings = append(bindings, k.Help, k.Quit)
+ return bindings
}
// DashboardFullHelp returns grouped bindings for dashboard overlays.
func (k KeyMap) DashboardFullHelp() [][]key.Binding {
+ controls := []key.Binding{k.Tab, k.ShiftTab}
+ if help := k.Export.Help(); help.Key != "" || help.Desc != "" {
+ controls = append(controls, k.Export)
+ }
+ controls = append(controls, k.Refresh, k.Help, k.Quit)
+
return [][]key.Binding{
{k.One, k.Two, k.Three, k.Four, k.Five, k.Six},
- {k.Tab, k.ShiftTab, k.Export, k.Refresh, k.Help, k.Quit},
+ controls,
{
key.NewBinding(key.WithKeys("left/right"), key.WithHelp("left/right", "tab")),
key.NewBinding(key.WithKeys("h/l"), key.WithHelp("h/l", "tab")),
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
index 143cf02..4433172 100644
--- a/internal/tui/tui.go
+++ b/internal/tui/tui.go
@@ -99,13 +99,17 @@ func NewModel(initialPID int, startTrace TraceStarter) Model {
if startTrace == nil {
startTrace = defaultTraceStarter
}
+ keys := Keys
+ if !flags.Get().TUIExportEnable {
+ keys.Export = key.NewBinding()
+ }
model := Model{
screen: ScreenPIDPicker,
pidPicker: pidpicker.New(),
- dashboard: dashboardui.NewModel(lateBoundDashboardSource{}),
+ dashboard: dashboardui.NewModelWithConfig(lateBoundDashboardSource{}, 1000, keys),
exporter: tuiexport.NewModel(),
- keys: Keys,
+ keys: keys,
spin: spin,
startTrace: startTrace,
}
@@ -151,7 +155,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !m.exporter.Visible() && m.showHelp {
return m, nil
}
- if m.screen == ScreenDashboard && !m.attaching && m.lastErr == nil && key.Matches(msg, m.keys.Export) && !m.exporter.Visible() {
+ if flags.Get().TUIExportEnable && m.screen == ScreenDashboard && !m.attaching && m.lastErr == nil && key.Matches(msg, m.keys.Export) && !m.exporter.Visible() {
m.exporter = m.exporter.Open()
return m, nil
}
@@ -299,6 +303,9 @@ func (m Model) View() string {
func runExportCmd(option tuiexport.Option, snap *statsengine.Snapshot) tea.Cmd {
return func() tea.Msg {
+ if !flags.Get().TUIExportEnable {
+ return tuiexport.FailedMsg{Err: errors.New("tui export is disabled by -tuiExport=false")}
+ }
switch option {
case tuiexport.OptionCSV:
path, err := exportSnapshotCSV(snap)
diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go
index fd7adfa..e69ff9b 100644
--- a/internal/tui/tui_test.go
+++ b/internal/tui/tui_test.go
@@ -192,6 +192,9 @@ func TestDashboardRefreshPicksLateBoundSource(t *testing.T) {
}
func TestExportKeyOpensModalOnDashboard(t *testing.T) {
+ flags.SetTUIExportEnable(true)
+ t.Cleanup(func() { flags.SetTUIExportEnable(true) })
+
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.attaching = false
@@ -203,6 +206,21 @@ func TestExportKeyOpensModalOnDashboard(t *testing.T) {
}
}
+func TestExportKeyIgnoredWhenExportDisabled(t *testing.T) {
+ flags.SetTUIExportEnable(false)
+ t.Cleanup(func() { flags.SetTUIExportEnable(true) })
+
+ m := NewModel(-1, func(context.Context) error { return nil })
+ m.screen = ScreenDashboard
+ m.attaching = false
+
+ next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}})
+ updated := next.(Model)
+ if updated.exporter.Visible() {
+ t.Fatalf("expected export modal to remain closed when export is disabled")
+ }
+}
+
func TestRunExportCmdCSVWritesFile(t *testing.T) {
dir := t.TempDir()
prev, err := os.Getwd()
@@ -292,6 +310,9 @@ func TestHelpOverlayUsesPickerBindingsOnPickerScreen(t *testing.T) {
}
func TestHelpToggleDoesNotBreakExportModalInput(t *testing.T) {
+ flags.SetTUIExportEnable(true)
+ t.Cleanup(func() { flags.SetTUIExportEnable(true) })
+
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
@@ -314,6 +335,22 @@ func TestHelpToggleDoesNotBreakExportModalInput(t *testing.T) {
}
}
+func TestHelpOverlayHidesExportBindingWhenExportDisabled(t *testing.T) {
+ flags.SetTUIExportEnable(false)
+ t.Cleanup(func() { flags.SetTUIExportEnable(true) })
+
+ m := NewModel(-1, func(context.Context) error { return nil })
+ m.screen = ScreenDashboard
+ m.showHelp = true
+ m.width = 100
+ m.height = 30
+
+ out := m.View()
+ if strings.Contains(out, "e export") {
+ t.Fatalf("did not expect export shortcut in help overlay when export is disabled")
+ }
+}
+
func TestExportModalStillAllowsDashboardStatsUpdates(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard