golang / vscode-go

Go extension for Visual Studio Code
https://marketplace.visualstudio.com/items?itemName=golang.Go
Other
3.89k stars 751 forks source link

Profiling CPU and Memory of Go programs #1685

Open jmaister opened 3 years ago

jmaister commented 3 years ago

Is your feature request related to a problem? Please describe. I'm always frustrated when I want to profile a Go program and I have to use several manual steps.

Describe the solution you'd like A way of profiling the execution of Go applications in terms of CPU and/or Memory usage.

Describe alternatives you've considered The way of doing it requires several manual steps with the command line. It requires adding code to the "func main()", then commands to parse the results to present it as text or PDF.

Additional context Go land docs about profiling: https://blog.golang.org/pprof

Simplest way of using pprof: https://golangdocs.com/profiling-in-golang

hyangah commented 3 years ago

For test profiling, I think we want it too. @firelizzard18 's Go test adapter extension already had a similar integration for VS code.

But I am not sure about this comment:

... It requires adding code to the "func main()", ....

Do you mean you want this extension to inject code to your program temporarily?

firelizzard18 commented 3 years ago

@jmaister, can you describe the user experience you want? Profiling tests and benchmarks is straightforward - I plan to migrate the test and benchmark profiling capability from my extension (which @hyangah mentioned) into this extension.

But it sounds like you want something different? If you have some existing way to generate profiles (cpu.prof/mem.prof), I can add a command to display those. But AFAIK there is no way to capture a pprof profile from an arbitrary program. Do you want to the extension to recompile your code with a generated main function? How would the extension know what to do in the main function? If your program embeds the pprof http handler and exposes an HTTP server, that can be used to capture traces, but again, that requires modifications to the program.

I'm always frustrated when I want to profile a Go program and I have to use several manual steps.

Can you describe the steps you're taking? If you give specifics, we can discuss how the extension might make it easier.

jmaister commented 3 years ago

Hi @firelizzard18 The experience that I would like to get is to select the main.go (or the main() function), right-click and select the option to "Run with profiler...". A screen for the options appears to select CPU, Memory or both and the folder/file to store the resulting files. Then, after the execution, a new tab for each type of profile opened to see the results. Depending on the type the CPU would show the list of methods, time, %, etc... and the Memory objects size, %, etc...

However, I've investigated a bit more and having to modify main.go to add the pprof package and the code could be quite difficult, also if the user already added it, could it be verified? Then I've found that the "go test" already has the profiler integrated.

With all this, the experience would be easier to implement by adding an option to run the tests with the profiler activated. Then open the tabs with the results as described above.

Additional note: go test already has the profiler activated. https://go.dev/blog/pprof "If the code used the Go testing package’s benchmarking support, we could use gotest’s standard -cpuprofile and -memprofile flags."

go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
firelizzard18 commented 3 years ago

With all this, the experience would be easier to implement by adding an option to run the tests with the profiler activated. Then open the tabs with the results as described above.

This is definitely easier, and is essentially what my extension does. I do plan on adding that capability to this extension.

How do you want to see the results? Do you want access to the files? My extension stores the files in a temporary directory and displays the profiles as a graph, which you can drag and zoom. go tool pprof can generate a variety of outputs. If you want to see more than just the graph, I can find a way to do that, but I'll stick to just the graph if that's sufficient.

I'm still open to the idea of profiling executables (main), but we need to figure out how to do that first.

jmaister commented 3 years ago

The graph will be good. I also found that you can generate the graph based on 2 pprof files to see the improvements/degradation.

$go tool pprof
...
-diff_base source     Source of base profile for comparison

That could be a nice feature too: "Compare with a previous run ...".

About running the main, could the main() function be run from a made-up test to get the results?

package main_test

import ...

func TestUserLogin(t *testing.T) {
    main.main()
} 
firelizzard18 commented 3 years ago

@jmaister If there are specific features you want implemented, I suggest you add a task list and/or open specific issues, e.g. "I want <this feature>". I am open to working on these features, but there are a lot of different things I want to do, and I'm going to forget details unless they are listed clearly somewhere easy to find. And a task list and/or separate issues would make it easier for the Go team to work on this too.

About running the main, could the main() function be run from a made-up test to get the results?

That didn't occur to me. It would have to be something like this:

package main

func TestMain(t *testing.T) { main() }

The actual test name isn't important, but main.main() won't work: cannot refer to unexported name main.main.

This is clearly possible. I need to think more - I'm not sure if it can be done without adding files to the user's workspace, and I'm not sure I find that acceptable. I could delete them afterwards, similar to how delve handles __debug_bin and friends, but that's still not great.

I also found that you can generate the graph based on 2 pprof files to see the improvements/degradation.

That's pretty cool. I'd like to see a UI with a list of profiles, including past runs, but it may be a while before that happens. So I suggest adding a separate issue for this, so it doesn't get lost or forgotten.

jmaister commented 3 years ago

Features:

firelizzard18 commented 3 years ago

@jmaister I created #1724 and #1725 to cover profile diffs and profiling main, respectively. At some point, I'll create a CL for showing graphical test/benchmark profiles and mark it as Fixes #1685.

firelizzard18 commented 3 years ago

@hyangah Do you have any ideas on a good way to persist profiles? Currently, I'm storing profiles in the per-session temp dir. As a user, it is frustrating that profiles go poof when I reload the window (since the extension uses a new temp dir for each activation). But on the other hand, it probably wouldn't be good UX to keep all of the profiles around indefinitely, not to mention consuming disk space. On reload, I could retain the last profile for each test (like VSCode does for test results AFAIK), but there could be hundreds or thousands of tests.

gopherbot commented 3 years ago

Change https://golang.org/cl/344149 mentions this issue: src/goTest: basic profiling support

firelizzard18 commented 3 years ago

The same for all the tests/benchmarks in a package

@jmaister Do you mean you want to be able to capture (and view) a profile for each test? Or do you mean you want to capture a profile that includes all tests and benchmarks in a package? In my personal experience, the latter isn't terribly useful, because the results from the various benchmarks are mixed up together. Driven by that experience, and some technical challenges, I decided to always capture a profile for a single benchmark or test at a time. If you use Go nightly and profile a package, it will create a separate profile for each test.

jmaister commented 3 years ago

@firelizzard18 I mean to capture the the profile data by test function or by benchmark function. I agree, it would not be useful to show the results for the whole package.