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.
This commit is contained in:
parent
dfb7796f10
commit
9f6e41b927
|
|
@ -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