Add benchmark history tracking and results browser
Extended the benchmark system to maintain a complete history of all benchmark runs (up to last 10) with full results for each encoder/preset combination tested. Features: - Stores complete benchmark run data including all test results - History browser UI to view past benchmark runs - Click any run to see detailed results for all encoders tested - Compare performance across different presets and encoders - Apply recommendations from past benchmarks - Automatic history limit (keeps last 10 runs) UI Changes: - Renamed "Benchmark" button to "Run Benchmark" - Added "View Results" button to main menu - New benchmark history view showing all past runs - Each run displays timestamp, recommended encoder, and test count - Clicking a run shows full results with all encoder/preset combinations Data Structure: - benchmarkRun: stores single test run with all results - benchmarkConfig: maintains array of benchmark runs - Saves to ~/.config/VideoTools/benchmark.json This allows users to review past benchmark results and make informed decisions about which encoder settings to use by comparing FPS across all available options on their hardware. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
87c2d28e9f
commit
4d99f6ec78
|
|
@ -305,3 +305,114 @@ func BuildBenchmarkResultsView(
|
|||
|
||||
return container.NewPadded(body)
|
||||
}
|
||||
|
||||
// BuildBenchmarkHistoryView creates the benchmark history browser UI
|
||||
func BuildBenchmarkHistoryView(
|
||||
history []BenchmarkHistoryRun,
|
||||
onSelectRun func(int),
|
||||
onClose func(),
|
||||
titleColor, bgColor, textColor color.Color,
|
||||
) fyne.CanvasObject {
|
||||
// Header
|
||||
title := canvas.NewText("BENCHMARK HISTORY", titleColor)
|
||||
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
title.TextSize = 24
|
||||
|
||||
closeBtn := widget.NewButton("← Back", onClose)
|
||||
closeBtn.Importance = widget.LowImportance
|
||||
|
||||
header := container.NewBorder(
|
||||
nil, nil,
|
||||
closeBtn,
|
||||
nil,
|
||||
container.NewCenter(title),
|
||||
)
|
||||
|
||||
if len(history) == 0 {
|
||||
emptyMsg := widget.NewLabel("No benchmark history yet.\n\nRun your first benchmark to see results here.")
|
||||
emptyMsg.Alignment = fyne.TextAlignCenter
|
||||
emptyMsg.Wrapping = fyne.TextWrapWord
|
||||
|
||||
body := container.NewBorder(
|
||||
header,
|
||||
nil, nil, nil,
|
||||
container.NewCenter(emptyMsg),
|
||||
)
|
||||
|
||||
return container.NewPadded(body)
|
||||
}
|
||||
|
||||
// Build list of benchmark runs
|
||||
var runItems []fyne.CanvasObject
|
||||
for i, run := range history {
|
||||
idx := i // Capture for closure
|
||||
runItems = append(runItems, buildHistoryRunItem(run, idx, onSelectRun, bgColor, textColor))
|
||||
}
|
||||
|
||||
runsList := container.NewVBox(runItems...)
|
||||
runsScroll := container.NewVScroll(runsList)
|
||||
runsScroll.SetMinSize(fyne.NewSize(0, 400))
|
||||
|
||||
infoLabel := widget.NewLabel("Click on a benchmark run to view detailed results")
|
||||
infoLabel.Alignment = fyne.TextAlignCenter
|
||||
infoLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
|
||||
body := container.NewBorder(
|
||||
header,
|
||||
container.NewVBox(widget.NewSeparator(), infoLabel),
|
||||
nil, nil,
|
||||
runsScroll,
|
||||
)
|
||||
|
||||
return container.NewPadded(body)
|
||||
}
|
||||
|
||||
// BenchmarkHistoryRun represents a benchmark run in the history view
|
||||
type BenchmarkHistoryRun struct {
|
||||
Timestamp string
|
||||
ResultCount int
|
||||
RecommendedEncoder string
|
||||
RecommendedPreset string
|
||||
RecommendedFPS float64
|
||||
}
|
||||
|
||||
func buildHistoryRunItem(
|
||||
run BenchmarkHistoryRun,
|
||||
index int,
|
||||
onSelect func(int),
|
||||
bgColor, textColor color.Color,
|
||||
) fyne.CanvasObject {
|
||||
// Timestamp label
|
||||
timeLabel := widget.NewLabel(run.Timestamp)
|
||||
timeLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
// Recommendation info
|
||||
recLabel := widget.NewLabel(fmt.Sprintf("Recommended: %s (%s) - %.1f FPS",
|
||||
run.RecommendedEncoder, run.RecommendedPreset, run.RecommendedFPS))
|
||||
|
||||
// Result count
|
||||
countLabel := widget.NewLabel(fmt.Sprintf("%d encoders tested", run.ResultCount))
|
||||
countLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
|
||||
// Content
|
||||
content := container.NewVBox(
|
||||
timeLabel,
|
||||
recLabel,
|
||||
countLabel,
|
||||
)
|
||||
|
||||
// Card background
|
||||
card := canvas.NewRectangle(bgColor)
|
||||
card.CornerRadius = 4
|
||||
|
||||
item := container.NewPadded(
|
||||
container.NewMax(card, content),
|
||||
)
|
||||
|
||||
// Make it tappable
|
||||
tappable := NewTappable(item, func() {
|
||||
onSelect(index)
|
||||
})
|
||||
|
||||
return tappable
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,20 +23,23 @@ type ModuleInfo struct {
|
|||
}
|
||||
|
||||
// BuildMainMenu creates the main menu view with module tiles grouped by category
|
||||
func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDrop func(string, []fyne.URI), onQueueClick func(), onLogsClick func(), onBenchmarkClick func(), titleColor, queueColor, textColor color.Color, queueCompleted, queueTotal int) fyne.CanvasObject {
|
||||
func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDrop func(string, []fyne.URI), onQueueClick func(), onLogsClick func(), onBenchmarkClick func(), onBenchmarkHistoryClick func(), titleColor, queueColor, textColor color.Color, queueCompleted, queueTotal int) fyne.CanvasObject {
|
||||
title := canvas.NewText("VIDEOTOOLS", titleColor)
|
||||
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
title.TextSize = 28
|
||||
|
||||
queueTile := buildQueueTile(queueCompleted, queueTotal, queueColor, textColor, onQueueClick)
|
||||
|
||||
benchmarkBtn := widget.NewButton("Benchmark", onBenchmarkClick)
|
||||
benchmarkBtn := widget.NewButton("Run Benchmark", onBenchmarkClick)
|
||||
benchmarkBtn.Importance = widget.LowImportance
|
||||
|
||||
viewResultsBtn := widget.NewButton("View Results", onBenchmarkHistoryClick)
|
||||
viewResultsBtn.Importance = widget.LowImportance
|
||||
|
||||
logsBtn := widget.NewButton("Logs", onLogsClick)
|
||||
logsBtn.Importance = widget.LowImportance
|
||||
|
||||
header := container.New(layout.NewHBoxLayout(), title, layout.NewSpacer(), benchmarkBtn, logsBtn, queueTile)
|
||||
header := container.New(layout.NewHBoxLayout(), title, layout.NewSpacer(), benchmarkBtn, viewResultsBtn, logsBtn, queueTile)
|
||||
|
||||
categorized := map[string][]fyne.CanvasObject{}
|
||||
for i := range modules {
|
||||
|
|
|
|||
156
main.go
156
main.go
|
|
@ -509,12 +509,19 @@ func savePersistedConvertConfig(cfg convertConfig) error {
|
|||
return os.WriteFile(path, data, 0o644)
|
||||
}
|
||||
|
||||
// benchmarkConfig holds benchmark results and recommendations
|
||||
// benchmarkRun represents a single benchmark test run
|
||||
type benchmarkRun struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Results []benchmark.Result `json:"results"`
|
||||
RecommendedEncoder string `json:"recommended_encoder"`
|
||||
RecommendedPreset string `json:"recommended_preset"`
|
||||
RecommendedHWAccel string `json:"recommended_hwaccel"`
|
||||
RecommendedFPS float64 `json:"recommended_fps"`
|
||||
}
|
||||
|
||||
// benchmarkConfig holds benchmark history
|
||||
type benchmarkConfig struct {
|
||||
RecommendedEncoder string `json:"recommended_encoder"`
|
||||
RecommendedPreset string `json:"recommended_preset"`
|
||||
RecommendedHWAccel string `json:"recommended_hwaccel"`
|
||||
LastBenchmarkTime time.Time `json:"last_benchmark_time"`
|
||||
History []benchmarkRun `json:"history"`
|
||||
}
|
||||
|
||||
func benchmarkConfigPath() string {
|
||||
|
|
@ -937,7 +944,7 @@ func (s *appState) showMainMenu() {
|
|||
viewAppLogBtn,
|
||||
)
|
||||
dialog.ShowCustom("Logs", "Close", logOptions, s.window)
|
||||
}, s.showBenchmark, titleColor, queueColor, textColor, queueCompleted, queueTotal)
|
||||
}, s.showBenchmark, s.showBenchmarkHistory, titleColor, queueColor, textColor, queueCompleted, queueTotal)
|
||||
|
||||
// Update stats bar
|
||||
s.updateStatsBar()
|
||||
|
|
@ -1293,14 +1300,20 @@ func (s *appState) showBenchmark() {
|
|||
|
||||
// Get recommendation
|
||||
encoder, preset, rec := suite.GetRecommendation()
|
||||
|
||||
// Save benchmark run to history
|
||||
if err := s.saveBenchmarkRun(suite.Results, encoder, preset, rec.FPS); err != nil {
|
||||
logging.Debug(logging.CatSystem, "failed to save benchmark run: %v", err)
|
||||
}
|
||||
|
||||
if encoder != "" {
|
||||
logging.Debug(logging.CatSystem, "benchmark recommendation: %s (preset: %s) - %.1f FPS", encoder, preset, rec.FPS)
|
||||
|
||||
// Show results dialog with option to apply
|
||||
go func() {
|
||||
topResults := suite.GetTopN(10)
|
||||
allResults := suite.Results // Show all results, not just top 10
|
||||
resultsView := ui.BuildBenchmarkResultsView(
|
||||
topResults,
|
||||
allResults,
|
||||
rec,
|
||||
func() {
|
||||
// Apply recommended settings
|
||||
|
|
@ -1351,7 +1364,7 @@ func (s *appState) detectHardwareEncoders() []string {
|
|||
return available
|
||||
}
|
||||
|
||||
func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
||||
func (s *appState) saveBenchmarkRun(results []benchmark.Result, encoder, preset string, fps float64) error {
|
||||
// Map encoder to hardware acceleration setting
|
||||
var hwAccel string
|
||||
switch {
|
||||
|
|
@ -1367,22 +1380,123 @@ func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
|||
hwAccel = "none"
|
||||
}
|
||||
|
||||
// Save benchmark recommendation
|
||||
cfg := benchmarkConfig{
|
||||
RecommendedEncoder: encoder,
|
||||
RecommendedPreset: preset,
|
||||
RecommendedHWAccel: hwAccel,
|
||||
LastBenchmarkTime: time.Now(),
|
||||
}
|
||||
if err := saveBenchmarkConfig(cfg); err != nil {
|
||||
logging.Debug(logging.CatSystem, "failed to save benchmark recommendation: %v", err)
|
||||
// Load existing config
|
||||
cfg, err := loadBenchmarkConfig()
|
||||
if err != nil {
|
||||
// Create new config if loading fails
|
||||
cfg = benchmarkConfig{History: []benchmarkRun{}}
|
||||
}
|
||||
|
||||
logging.Debug(logging.CatSystem, "applied benchmark recommendation: encoder=%s preset=%s hwaccel=%s", encoder, preset, hwAccel)
|
||||
// Create new benchmark run
|
||||
run := benchmarkRun{
|
||||
Timestamp: time.Now(),
|
||||
Results: results,
|
||||
RecommendedEncoder: encoder,
|
||||
RecommendedPreset: preset,
|
||||
RecommendedHWAccel: hwAccel,
|
||||
RecommendedFPS: fps,
|
||||
}
|
||||
|
||||
// Add to history (keep last 10 runs)
|
||||
cfg.History = append([]benchmarkRun{run}, cfg.History...)
|
||||
if len(cfg.History) > 10 {
|
||||
cfg.History = cfg.History[:10]
|
||||
}
|
||||
|
||||
// Save config
|
||||
if err := saveBenchmarkConfig(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logging.Debug(logging.CatSystem, "saved benchmark run: encoder=%s preset=%s fps=%.1f results=%d", encoder, preset, fps, len(results))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
||||
logging.Debug(logging.CatSystem, "applied benchmark recommendation: encoder=%s preset=%s", encoder, preset)
|
||||
|
||||
dialog.ShowInformation("Benchmark Settings Applied",
|
||||
fmt.Sprintf("Your system's optimal encoder settings have been saved:\n\nEncoder: %s\nPreset: %s\nHardware: %s\n\nThese are available for reference in the Convert module.",
|
||||
encoder, preset, hwAccel), s.window)
|
||||
fmt.Sprintf("Recommended encoder noted:\n\nEncoder: %s\nPreset: %s\n\nYou can reference these settings in the Convert module.",
|
||||
encoder, preset), s.window)
|
||||
}
|
||||
|
||||
func (s *appState) showBenchmarkHistory() {
|
||||
s.stopPreview()
|
||||
s.stopPlayer()
|
||||
s.active = "benchmark-history"
|
||||
|
||||
// Load benchmark history
|
||||
cfg, err := loadBenchmarkConfig()
|
||||
if err != nil || len(cfg.History) == 0 {
|
||||
// Show empty state
|
||||
view := ui.BuildBenchmarkHistoryView(
|
||||
[]ui.BenchmarkHistoryRun{},
|
||||
nil,
|
||||
s.showMainMenu,
|
||||
utils.MustHex("#4CE870"),
|
||||
utils.MustHex("#1E1E1E"),
|
||||
utils.MustHex("#FFFFFF"),
|
||||
)
|
||||
s.setContent(view)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert history to UI format
|
||||
var historyRuns []ui.BenchmarkHistoryRun
|
||||
for _, run := range cfg.History {
|
||||
historyRuns = append(historyRuns, ui.BenchmarkHistoryRun{
|
||||
Timestamp: run.Timestamp.Format("2006-01-02 15:04:05"),
|
||||
ResultCount: len(run.Results),
|
||||
RecommendedEncoder: run.RecommendedEncoder,
|
||||
RecommendedPreset: run.RecommendedPreset,
|
||||
RecommendedFPS: run.RecommendedFPS,
|
||||
})
|
||||
}
|
||||
|
||||
// Build history view
|
||||
view := ui.BuildBenchmarkHistoryView(
|
||||
historyRuns,
|
||||
func(index int) {
|
||||
// Show detailed results for this run
|
||||
if index < 0 || index >= len(cfg.History) {
|
||||
return
|
||||
}
|
||||
run := cfg.History[index]
|
||||
|
||||
// Create a fake recommendation result for the results view
|
||||
rec := benchmark.Result{
|
||||
Encoder: run.RecommendedEncoder,
|
||||
Preset: run.RecommendedPreset,
|
||||
FPS: run.RecommendedFPS,
|
||||
Score: run.RecommendedFPS,
|
||||
}
|
||||
|
||||
resultsView := ui.BuildBenchmarkResultsView(
|
||||
run.Results,
|
||||
rec,
|
||||
func() {
|
||||
// Apply this recommendation
|
||||
s.applyBenchmarkRecommendation(run.RecommendedEncoder, run.RecommendedPreset)
|
||||
s.showBenchmarkHistory()
|
||||
},
|
||||
func() {
|
||||
// Back to history
|
||||
s.showBenchmarkHistory()
|
||||
},
|
||||
utils.MustHex("#4CE870"),
|
||||
utils.MustHex("#1E1E1E"),
|
||||
utils.MustHex("#FFFFFF"),
|
||||
)
|
||||
|
||||
s.setContent(resultsView)
|
||||
},
|
||||
s.showMainMenu,
|
||||
utils.MustHex("#4CE870"),
|
||||
utils.MustHex("#1E1E1E"),
|
||||
utils.MustHex("#FFFFFF"),
|
||||
)
|
||||
|
||||
s.setContent(view)
|
||||
}
|
||||
|
||||
func (s *appState) showModule(id string) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user