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!