From 9e32748b0a32aa27f2db80c66fdeb174a8512f65 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 5 Sep 2019 20:30:55 +0100 Subject: [PATCH] o gopher2600 - debugger can now be run in a cpu profiling loop --- errors/categories.go | 2 +- errors/messages.go | 8 +++---- gopher2600.go | 26 +++++++++++++++++++---- performance/check.go | 37 ++++++++++++++++++++------------ performance/profiling.go | 46 +++++++++++++++++++--------------------- 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/errors/categories.go b/errors/categories.go index 3e8d4128..62986d0a 100644 --- a/errors/categories.go +++ b/errors/categories.go @@ -16,7 +16,7 @@ const ( PlayError DebuggerError DisasmError - FPSError + PerformanceError // debugger ParserError diff --git a/errors/messages.go b/errors/messages.go index 2a24b71d..24ba4085 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -12,10 +12,10 @@ var messages = map[Errno]string{ TVOutOfSpec: "tv out of spec: %s", // sentinal // program modes - PlayError: "error emulating vcs: %s", - DebuggerError: "error debugging vcs: %s", - FPSError: "error during fps profiling: %s", - DisasmError: "error during disassembly: %s", + PlayError: "error emulating vcs: %s", + DebuggerError: "error debugging vcs: %s", + PerformanceError: "error during performance profiling: %s", + DisasmError: "error during disassembly: %s", // debugger ParserError: "parser error: %s: %s (char %d)", // first placeholder is the command definition diff --git a/gopher2600.go b/gopher2600.go index 24ab060b..4b55c174 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -169,6 +169,7 @@ func main() { tvType := modeFlags.String("tv", "AUTO", "television specification: NTSC, PAL") termType := modeFlags.String("term", "COLOR", "terminal type to use in debug mode: COLOR, PLAIN") initScript := modeFlags.String("initscript", defaultInitScript, "terminal type to use in debug mode: COLOR, PLAIN") + profile := modeFlags.Bool("profile", false, "run debugger through cpu profiler") modeFlagsParse() dbg, err := debugger.NewDebugger(*tvType) @@ -195,10 +196,27 @@ func main() { // it's okay if DEBUG mode is started with no cartridges fallthrough case 1: - err := dbg.Start(term, *initScript, modeFlags.Arg(0)) - if err != nil { - fmt.Printf("* %s\n", err) - os.Exit(2) + runner := func() error { + err := dbg.Start(term, *initScript, modeFlags.Arg(0)) + if err != nil { + return errors.NewFormattedError(errors.PerformanceError, err) + } + return nil + } + + if *profile { + err := performance.ProfileCPU("debug.cpu.profile", runner) + if err != nil { + fmt.Printf("* %s\n", err) + os.Exit(2) + } + err = performance.ProfileMem("debug.mem.profile") + if err != nil { + fmt.Printf("* %s\n", err) + os.Exit(2) + } + } else { + runner() } default: fmt.Printf("* too many arguments for %s mode\n", mode) diff --git a/performance/check.go b/performance/check.go index 35915cc0..dc6d8092 100644 --- a/performance/check.go +++ b/performance/check.go @@ -21,46 +21,46 @@ func Check(output io.Writer, profile bool, cartridgeFile string, display bool, t if display { ftv, err = sdl.NewGUI(tvType, scaling, nil) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } err = ftv.(gui.GUI).SetFeature(gui.ReqSetVisibility, true) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } } else { ftv, err = television.NewStellaTelevision(tvType) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } } // create vcs using the tv created above vcs, err := hardware.NewVCS(ftv) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } // attach cartridge to te vcs err = vcs.AttachCartridge(cartridgeFile) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } // parse supplied duration duration, err := time.ParseDuration(runTime) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } // get starting frame number (should be 0) startFrame, err := ftv.GetState(television.ReqFramenum) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } // run for specified period of time - err = cpuProfile(profile, "cpu.profile", func() error { + runner := func() error { // setup trigger that expires when duration has elapsed timesUp := make(chan bool) @@ -85,23 +85,34 @@ func Check(output io.Writer, profile bool, cartridgeFile string, display bool, t } }) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } return nil - }) + } + + if profile { + err = ProfileCPU("cpu.profile", runner) + } else { + err = runner() + } + if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } // get ending frame number endFrame, err := vcs.TV.GetState(television.ReqFramenum) if err != nil { - return errors.NewFormattedError(errors.FPSError, err) + return errors.NewFormattedError(errors.PerformanceError, err) } numFrames := endFrame - startFrame fps, accuracy := CalcFPS(ftv, numFrames, duration.Seconds()) output.Write([]byte(fmt.Sprintf("%.2f fps (%d frames in %.2f seconds) %.1f%%\n", fps, numFrames, duration.Seconds(), accuracy))) - return memProfile(profile, "mem.profile") + if profile { + err = ProfileMem("mem.profile") + } + + return err } diff --git a/performance/profiling.go b/performance/profiling.go index 4abb1db1..eaad6192 100644 --- a/performance/profiling.go +++ b/performance/profiling.go @@ -7,36 +7,34 @@ import ( "runtime/pprof" ) -func cpuProfile(profile bool, outFile string, run func() error) error { - if profile { - // write cpu profile - f, err := os.Create(outFile) - if err != nil { - return errors.NewFormattedError(errors.FPSError, err) - } - err = pprof.StartCPUProfile(f) - if err != nil { - return errors.NewFormattedError(errors.FPSError, err) - } - defer pprof.StopCPUProfile() +// ProfileCPU runs supplied function "through" the pprof CPU profiler +func ProfileCPU(outFile string, run func() error) error { + // write cpu profile + f, err := os.Create(outFile) + if err != nil { + return errors.NewFormattedError(errors.PerformanceError, err) } + err = pprof.StartCPUProfile(f) + if err != nil { + return errors.NewFormattedError(errors.PerformanceError, err) + } + defer pprof.StopCPUProfile() return run() } -func memProfile(profile bool, outFile string) error { - if profile { - f, err := os.Create(outFile) - if err != nil { - return errors.NewFormattedError(errors.FPSError, err) - } - runtime.GC() - err = pprof.WriteHeapProfile(f) - if err != nil { - return errors.NewFormattedError(errors.FPSError, err) - } - f.Close() +// ProfileMem takes a snapshot of memory and writes to outFile +func ProfileMem(outFile string) error { + f, err := os.Create(outFile) + if err != nil { + return errors.NewFormattedError(errors.PerformanceError, err) } + runtime.GC() + err = pprof.WriteHeapProfile(f) + if err != nil { + return errors.NewFormattedError(errors.PerformanceError, err) + } + f.Close() return nil }