Skip to content

Commit 6ffa6e4

Browse files
implement real-time UI for terminals
1 parent b19b613 commit 6ffa6e4

File tree

6 files changed

+665
-25
lines changed

6 files changed

+665
-25
lines changed

cmd/cmd.go

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
"time"
2828

2929
"github.com/bygui86/multi-profile/v2"
30+
tea "github.com/charmbracelet/bubbletea"
31+
"github.com/charmbracelet/x/term"
3032
"github.com/dustin/go-humanize"
3133
"github.com/felixge/fgprof"
3234
"github.com/minio/dperf/pkg/dperf"
@@ -125,15 +127,6 @@ $ dperf --sync -b 4KiB /mnt/drive1
125127
// Use sync mode if explicitly requested or if block size < 4KiB
126128
useSyncMode := syncMode || bs < alignSize
127129

128-
perf := &dperf.DrivePerf{
129-
Serial: serial,
130-
BlockSize: bs,
131-
FileSize: fs,
132-
Verbose: verbose,
133-
IOPerDrive: ioPerDrive,
134-
WriteOnly: writeOnly,
135-
SyncMode: useSyncMode,
136-
}
137130
paths := make([]string, 0, len(args))
138131
for _, arg := range args {
139132
if filepath.Clean(arg) == "" {
@@ -158,7 +151,8 @@ $ dperf --sync -b 4KiB /mnt/drive1
158151
paths = append(paths, filepath.Clean(arg))
159152
}
160153
defer startTraces()()
161-
return perf.RunAndRender(c.Context(), paths...)
154+
155+
return runWithUI(c.Context(), paths, serial, bs, fs, verbose, ioPerDrive, writeOnly, useSyncMode)
162156
},
163157
}
164158

@@ -293,3 +287,96 @@ func init() {
293287
func Execute(ctx context.Context) error {
294288
return dperfCmd.ExecuteContext(ctx)
295289
}
290+
291+
// runWithUI runs the performance test with a real-time Bubble Tea UI
292+
// Falls back to traditional mode if TTY is not available
293+
func runWithUI(ctx context.Context, paths []string, serial bool, blockSize, fileSize uint64, verbose bool, ioPerDrive int, writeOnly, syncMode bool) error {
294+
// Check if we have a TTY - if not, fall back to traditional mode
295+
if !term.IsTerminal(os.Stdout.Fd()) || !term.IsTerminal(os.Stdin.Fd()) {
296+
// No TTY available, use traditional mode
297+
perf := &dperf.DrivePerf{
298+
Serial: serial,
299+
BlockSize: blockSize,
300+
FileSize: fileSize,
301+
Verbose: verbose,
302+
IOPerDrive: ioPerDrive,
303+
WriteOnly: writeOnly,
304+
SyncMode: syncMode,
305+
}
306+
return perf.RunAndRender(ctx, paths...)
307+
}
308+
309+
// Create the UI model
310+
model := newUIModel(paths, writeOnly, verbose)
311+
312+
// Create a channel to communicate with the Bubble Tea program
313+
progressChan := make(chan dperf.ProgressUpdate, 100)
314+
315+
// Create the performance test with a callback
316+
perf := &dperf.DrivePerf{
317+
Serial: serial,
318+
BlockSize: blockSize,
319+
FileSize: fileSize,
320+
Verbose: verbose,
321+
IOPerDrive: ioPerDrive,
322+
WriteOnly: writeOnly,
323+
SyncMode: syncMode,
324+
ProgressCallback: func(update dperf.ProgressUpdate) {
325+
select {
326+
case progressChan <- update:
327+
default:
328+
// Drop update if channel is full to avoid blocking
329+
}
330+
},
331+
}
332+
333+
// Start the Bubble Tea program
334+
p := tea.NewProgram(model, tea.WithAltScreen())
335+
336+
// Run the performance test in a goroutine
337+
go func() {
338+
results, err := perf.Run(ctx, paths...)
339+
if err != nil {
340+
// Send error through progress channel? Or handle differently
341+
// For now, just close the channel
342+
}
343+
344+
// Send completion message
345+
p.Send(completeMsg{Results: results})
346+
close(progressChan)
347+
}()
348+
349+
// Forward progress updates to the Bubble Tea program
350+
go func() {
351+
for update := range progressChan {
352+
p.Send(progressMsg(update))
353+
}
354+
}()
355+
356+
// Run the program and wait for it to finish
357+
finalModel, err := p.Run()
358+
if err != nil {
359+
return err
360+
}
361+
362+
// Print final results to regular terminal so they remain visible
363+
if m, ok := finalModel.(*uiModel); ok && m.Complete && m.Results != nil {
364+
fmt.Println(m.RenderFinalResults())
365+
}
366+
367+
return nil
368+
}
369+
370+
// newUIModel creates a new Bubble Tea UI model
371+
func newUIModel(paths []string, writeOnly, verbose bool) *uiModel {
372+
return dperf.NewUIModel(paths, writeOnly, verbose)
373+
}
374+
375+
// uiModel type alias for the UI model from dperf package
376+
type uiModel = dperf.UIModel
377+
378+
// progressMsg type alias
379+
type progressMsg = dperf.ProgressMsg
380+
381+
// completeMsg type alias
382+
type completeMsg = dperf.CompleteMsg

go.mod

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
module github.com/minio/dperf
22

3-
go 1.23
3+
go 1.24.0
4+
5+
toolchain go1.24.9
46

57
require (
68
github.com/bygui86/multi-profile/v2 v2.1.0
9+
github.com/charmbracelet/bubbles v0.21.0
10+
github.com/charmbracelet/bubbletea v1.3.10
11+
github.com/charmbracelet/lipgloss v1.1.0
12+
github.com/charmbracelet/x/term v0.2.1
713
github.com/dustin/go-humanize v1.0.1
814
github.com/fatih/color v1.18.0
915
github.com/felixge/fgprof v0.9.5
@@ -12,19 +18,32 @@ require (
1218
github.com/ncw/directio v1.0.5
1319
github.com/spf13/cobra v1.8.1
1420
github.com/spf13/viper v1.19.0
15-
golang.org/x/sys v0.29.0
21+
golang.org/x/sys v0.36.0
1622
)
1723

1824
require (
25+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
26+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
27+
github.com/charmbracelet/harmonica v0.2.0 // indirect
28+
github.com/charmbracelet/x/ansi v0.10.1 // indirect
29+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
30+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
1931
github.com/fsnotify/fsnotify v1.7.0 // indirect
2032
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
2133
github.com/hashicorp/hcl v1.0.0 // indirect
2234
github.com/inconshreveable/mousetrap v1.1.0 // indirect
35+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
2336
github.com/magiconair/properties v1.8.7 // indirect
2437
github.com/mattn/go-colorable v0.1.13 // indirect
2538
github.com/mattn/go-isatty v0.0.20 // indirect
39+
github.com/mattn/go-localereader v0.0.1 // indirect
40+
github.com/mattn/go-runewidth v0.0.16 // indirect
2641
github.com/mitchellh/mapstructure v1.5.0 // indirect
42+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
43+
github.com/muesli/cancelreader v0.2.2 // indirect
44+
github.com/muesli/termenv v0.16.0 // indirect
2745
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
46+
github.com/rivo/uniseg v0.4.7 // indirect
2847
github.com/rogpeppe/go-internal v1.9.1-0.20221123163938-fef05454be76 // indirect
2948
github.com/sagikazarmark/locafero v0.6.0 // indirect
3049
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -33,6 +52,7 @@ require (
3352
github.com/spf13/cast v1.7.0 // indirect
3453
github.com/spf13/pflag v1.0.5 // indirect
3554
github.com/subosito/gotenv v1.6.0 // indirect
55+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
3656
go.uber.org/multierr v1.11.0 // indirect
3757
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
3858
golang.org/x/text v0.21.0 // indirect

go.sum

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
1+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
2+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
13
github.com/bygui86/multi-profile/v2 v2.1.0 h1:x/jPqeL/6hJqLXoDI/H5zLPsSFbDR6IEbrBbFpkWQdw=
24
github.com/bygui86/multi-profile/v2 v2.1.0/go.mod h1:f4qCZiQo1nnJdwbPoADUtdDXg3hhnpfgZ9iq3/kW4BA=
5+
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
6+
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
7+
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
8+
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
9+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
10+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
11+
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
12+
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
13+
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
14+
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
15+
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
16+
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
17+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
18+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
19+
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
20+
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
321
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
422
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
523
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
@@ -13,6 +31,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
1331
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1432
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
1533
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
34+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
35+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
1636
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
1737
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
1838
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
@@ -44,6 +64,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
4464
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
4565
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
4666
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
67+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
68+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
4769
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
4870
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
4971
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@@ -52,10 +74,20 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
5274
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
5375
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
5476
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
77+
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
78+
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
79+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
80+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
5581
github.com/minio/pkg/v3 v3.0.28 h1:8tSuZnJbjc3C3DM2DEh4ZnSWjMZdccd679stk8sPD60=
5682
github.com/minio/pkg/v3 v3.0.28/go.mod h1:mIaN552nu0D2jiSk5BQC8LB25f44ytbOBJCuLtksX7Q=
5783
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
5884
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
85+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
86+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
87+
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
88+
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
89+
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
90+
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
5991
github.com/ncw/directio v1.0.5 h1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=
6092
github.com/ncw/directio v1.0.5/go.mod h1:rX/pKEYkOXBGOggmcyJeJGloCkleSvphPx2eV3t6ROk=
6193
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
@@ -64,6 +96,9 @@ github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xl
6496
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6597
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
6698
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
99+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
100+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
101+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
67102
github.com/rogpeppe/go-internal v1.9.1-0.20221123163938-fef05454be76 h1:h64Nf6capU/ZxzbIF+GFOTL4GYwodobYPdoosP4zggI=
68103
github.com/rogpeppe/go-internal v1.9.1-0.20221123163938-fef05454be76/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
69104
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -92,15 +127,18 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
92127
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
93128
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
94129
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
130+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
131+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
95132
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
96133
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
97134
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
98135
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
136+
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99137
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
100138
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
101139
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
102-
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
103-
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
140+
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
141+
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
104142
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
105143
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
106144
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

pkg/dperf/perf.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,31 @@ import (
2727
"github.com/google/uuid"
2828
)
2929

30+
// ProgressUpdate represents a real-time progress update for a drive test
31+
type ProgressUpdate struct {
32+
Path string
33+
Phase string // "write" or "read"
34+
BytesProcessed uint64
35+
TotalBytes uint64
36+
Throughput uint64 // bytes per second
37+
IOIndex int // which concurrent I/O operation (0 to IOPerDrive-1)
38+
Error error
39+
}
40+
41+
// ProgressCallback is called during testing to report progress updates
42+
// This is optional - if nil, no progress updates are sent (library mode)
43+
type ProgressCallback func(update ProgressUpdate)
44+
3045
// DrivePerf options
3146
type DrivePerf struct {
32-
Serial bool
33-
Verbose bool
34-
BlockSize uint64
35-
FileSize uint64
36-
IOPerDrive int
37-
WriteOnly bool
38-
SyncMode bool // Use O_DSYNC/O_SYNC instead of O_DIRECT
47+
Serial bool
48+
Verbose bool
49+
BlockSize uint64
50+
FileSize uint64
51+
IOPerDrive int
52+
WriteOnly bool
53+
SyncMode bool // Use O_DSYNC/O_SYNC instead of O_DIRECT
54+
ProgressCallback ProgressCallback // Optional callback for real-time progress updates
3955
}
4056

4157
// mustGetUUID - get a random UUID.
@@ -70,7 +86,7 @@ func (d *DrivePerf) runTests(ctx context.Context, path string, testUUID string)
7086
go func(idx int) {
7187
defer wg.Done()
7288
iopath := testPath + "-" + strconv.Itoa(idx)
73-
writeThroughput, err := d.runWriteTest(ctx, iopath, dataBuffers[idx])
89+
writeThroughput, err := d.runWriteTestWithIndex(ctx, iopath, dataBuffers[idx], idx)
7490
if err != nil {
7591
errs[idx] = err
7692
return
@@ -86,7 +102,7 @@ func (d *DrivePerf) runTests(ctx context.Context, path string, testUUID string)
86102
go func(idx int) {
87103
defer wg.Done()
88104
iopath := testPath + "-" + strconv.Itoa(idx)
89-
readThroughput, err := d.runReadTest(ctx, iopath, dataBuffers[idx])
105+
readThroughput, err := d.runReadTestWithIndex(ctx, iopath, dataBuffers[idx], idx)
90106
if err != nil {
91107
errs[idx] = err
92108
return
@@ -176,3 +192,8 @@ func (d *DrivePerf) RunAndRender(ctx context.Context, paths ...string) error {
176192
d.render(results)
177193
return nil
178194
}
195+
196+
// Render renders the results (exported for use by cmd package)
197+
func (d *DrivePerf) Render(results []*DrivePerfResult) {
198+
d.render(results)
199+
}

0 commit comments

Comments
 (0)