github.com/stealthrocket/wzprof@v0.2.1-0.20230830205924-5fa86be5e5b3/README.md (about) 1 [![Build](https://github.com/stealthrocket/wzprof/actions/workflows/build.yml/badge.svg)](https://github.com/stealthrocket/wzprof/actions/workflows/build.yml) 2 [![Go Report Card](https://goreportcard.com/badge/github.com/stealthrocket/wzprof)](https://goreportcard.com/report/github.com/stealthrocket/wzprof) 3 [![Go Reference](https://pkg.go.dev/badge/github.com/stealthrocket/wzprof.svg)](https://pkg.go.dev/github.com/stealthrocket/wzprof) 4 [![Apache 2 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) 5 6 # wzprof 7 8 `wzprof`, pronounced as you think it should, is a pprof based profiler for 9 WebAssembly built on top of [**Wazero**](https://github.com/tetratelabs/wazero). 10 It offers the ability to collect CPU and Memory profiles during the execution of 11 WebAssembly modules. 12 13 If you are interested in taking a deep-dive into how `wzprof` is built, 14 you might enjoy reading: 15 16 👉 [**Performance in the spotlight: WebAssembly profiling for everyone**](https://blog.stealthrocket.tech/performance-in-the-spotlight-webassembly-profiling-for-everyone) 17 18 ## Motivation 19 20 WebAssembly runtimes typically allow profiling guest code via an external 21 profiler such as `perf`, but in many cases the recording and analysis of 22 profiles remains a difficult task, especially due to features like JIT 23 compilation. 24 25 `pprof` is the de-facto standard profiling tool for Go programs, and offers 26 some of the simplest and quickest ways to gather insight into the performance 27 of an application. 28 29 `wzprof` aims to combine the capabilities and user experience of `pprof` 30 with a [**wazero.Runtime**](https://pkg.go.dev/github.com/tetratelabs/wazero#Runtime), 31 enabling the profiling of any application compiled to WebAssembly. 32 33 ## Features 34 35 `wzprof` mimics the approach and workflow popularized by Go pprof, and extends 36 it to collect profiles of WebAssembly programs compiled from any programming 37 language. The profiles produced are designed to be compatible with pprof, 38 allowing developers to use the classic `go tool pprof` workflow to analyize 39 application performance. 40 41 - CPU: calls sampling and on-CPU time. 42 - Memory: allocations (see below). 43 - DWARF support (demangling, source-level profiling). 44 - Integrated pprof server. 45 - Library and CLI interfaces. 46 47 ## Usage 48 49 You can either use `wzprof` as a CLI or as a library if you use the Wazero 50 runtime libraries. 51 52 To install the latest version of `wzprof`: 53 ```sh 54 go install github.com/stealthrocket/wzprof/cmd/wzprof@latest 55 ``` 56 To use the library as code in a Go program: 57 ```sh 58 go get github.com/stealthrocket/wzprof@latest 59 ``` 60 61 ### Sampling 62 63 By default, wzprof will sample calls with a ratio of 1/19. Sampling is used to 64 limit the overhead of the profilers but the default rate might not be suitable 65 in some cases. 66 For example, if your processes are short running and you don't see anything in the 67 profile, you might want to disable the sampling. To do so, use `-sample 1`. 68 69 ### Run program to completion with CPU or memory profiling 70 71 In those examples we set the sample rate to 1 to capture all samples because the 72 test programs complete quickly. 73 74 ```sh 75 wzprof -sample 1 -memprofile /tmp/profile ./testdata/c/simple.wasm 76 ``` 77 ```sh 78 wzprof -sample 1 -cpuprofile /tmp/profile ./testdata/c/crunch_numbers.wasm 79 ``` 80 ```sh 81 go tool pprof -http :4000 /tmp/profile 82 ``` 83 84 ### Connect to running pprof server 85 86 Similarly to [`net/http/pprof`](https://pkg.go.dev/net/http/pprof), `wzprof` 87 can expose a pprof-compatible http endpoint on behalf of the guest application: 88 89 ```sh 90 wzprof -pprof-addr :8080 ... 91 ``` 92 ```sh 93 go tool pprof -http :3030 'http://localhost:8080/debug/pprof/profile?seconds=5' 94 ``` 95 ```sh 96 go tool pprof -http :3030 'http://localhost:8080/debug/pprof/heap' 97 ``` 98 99 ## Profilers 100 101 ⚠️ The `wzprof` Go APIs depend on Wazero's `experimental` package which makes no 102 guarantees of backward compatilbity! 103 104 The following code snippet demonstrates how to integrate the profilers to a 105 Wazero runtime within a Go program: 106 107 ```go 108 sampleRate := 1.0 109 110 p := wzprof.ProfilingFor(wasmCode) 111 cpu := p.CPUProfiler() 112 mem := p.MemoryProfiler() 113 114 ctx := context.WithValue(context.Background(), 115 experimental.FunctionListenerFactoryKey{}, 116 experimental.MultiFunctionListenerFactory( 117 wzprof.Sample(sampleRate, cpu), 118 wzprof.Sample(sampleRate, mem), 119 ), 120 ) 121 122 runtime := wazero.NewRuntime(ctx) 123 defer runtime.Close(ctx) 124 125 compiledModule, err := runtime.CompileModule(ctx, wasmCode) 126 if err != nil { 127 log.Fatal("compiling wasm module:", err) 128 } 129 130 err = p.Prepare(compiledModule) 131 if err != nil { 132 return fmt.Errorf("preparing wasm module: %w", err) 133 } 134 135 // The CPU profiler collects records of module execution between two time 136 // points, the program drives where the profiler is active by calling 137 // StartProfile/StopProfile. 138 cpu.StartProfile() 139 140 moduleInstance, err := runtime.InstantiateModule(ctx, compiledModule, 141 wazero.NewModuleConfig(), 142 ) 143 if err != nil { 144 log.Fatal("instantiating wasm module:", err) 145 } 146 if err := moduleInstance.Close(ctx); err != nil { 147 log.Fatal("closing wasm module:", err) 148 } 149 150 cpuProfile := cpu.StopProfile(sampleRate, symbols) 151 memProfile := mem.NewProfile(sampleRate, symbols) 152 153 if err := wzprof.WriteProfile("cpu.pprof", cpuProfile); err != nil { 154 log.Fatal("writing CPU profile:", err) 155 } 156 if err := wzprof.WriteProfile("mem.pprof", memProfile); err != nil { 157 log.Fatal("writing memory profile:", err) 158 } 159 ``` 160 161 Note that the program must spearate the compilation and instantiation of 162 WebAssembly modules in order to use the profilers, because the module must be 163 compiled first in order to build the list of symbols from the DWARF sections. 164 165 ### Memory 166 167 Memory profiling works by tracing specific functions. Supported functions are: 168 169 - `malloc` 170 - `calloc` 171 - `realloc` 172 - `free` 173 - `runtime.mallocgc` 174 - `runtime.alloc` 175 176 Feel free to open a pull request to support more memory-allocating functions! 177 178 ### CPU 179 180 `wzprof` has two CPU profilers: CPU samples and CPU time. 181 182 The CPU samples profiler gives a repesentation of the guest execution by counting 183 the number of time it sees a unique stack trace. 184 185 The CPU time profiler measures the actual time spent on-CPU without taking into 186 account the off-CPU time (e.g waiting for I/O). For this profiler, all the 187 host-functions are considered off-CPU. 188 189 ## Language support 190 191 wzprof runs some heuristics to assess what the guest module is running to adapt 192 the way it symbolizes and walks the stack. In all other cases, it defaults to 193 inspecting the wasm stack and uses DWARF information if present in the module. 194 195 ### Golang 196 197 If the guest has been compiled by golang/go 1.21+, wzprof inspects the memory 198 to walk the Go stack, which provides full call stacks, instead of the shortened 199 versions you would get without this support. 200 201 In addition, wzprof parses pclntab to perform symbolization. This is the same 202 mechanism the Go runtime itself uses to display meaningful stack traces when a 203 panic occurs. 204 205 ### Python 3.11 206 207 If the guest is CPython 3.11 and has been compiled with debug symbols (such as 208 [timecraft's][timecraft-python]), wzprof walks the Python interpreter call 209 stack, not the C stack it would otherwise report. This provides more meaningful 210 profiling information on the script being executed. 211 212 At the moment it does not support merging the C extension calls into the Python 213 interpreter stack. 214 215 Note that a current limitation of the implementation is that unloading or 216 reloading modules may result in an incorrect profile. If that's a problem for 217 you please file an issue in the github tracker. 218 219 [timecraft-python]: https://docs.timecraft.dev/getting-started/prep-application/compiling-python#preparing-python 220 221 222 ### DWARF (C, Rust, Zig...) 223 224 As a fallback, if DWARF sections are available, wzprof symbolizes the wasm stack 225 trace using the DWARF symbols stored in custom sections of the module. For this 226 to work, you need to make sure your compiler generates those sections. For 227 example, use `-g` when compiling with clang: 228 229 ``` 230 clang code.c -o code.wasm -g -target wasm32 231 ``` 232 233 > **Warning** 234 > When using clang with any optimization level other than `-O0`, it will 235 > automatically run `wasm-opt` if that program is in your `PATH`. It makes DWARF 236 > information unusable by wzprof. Make sure clang can't find `wasm-opt` during 237 > compilation. See [llvm/llvm-project#55781][llvm-bug]. 238 239 [llvm-bug]: https://github.com/llvm/llvm-project/issues/55781 240 241 ## Contributing 242 243 Pull requests are welcome! Anything that is not a simple fix would probably 244 benefit from being discussed in an issue first. 245 246 Remember to be respectful and open minded!