Automatic hierarchical call tracing and performance profiling for Go tests and benchmarks in Panurus.
cd tools/profiler
./profile.sh BenchmarkValidatorTransfer
/tmp/profiler-<PID> (your files are never touched)token/ directorytoken/Safety: All work happens in /tmp. Your repository is never modified.
## Call Hierarchy
└── >>> (*Validator).VerifyTransfer [2.5s, 100.00%]
├── >>> (*Validator).verifyInputs [1.8s, 72.00%]
│ └── >>> (*Verifier).Verify x5 [1.5s, 60.00%]
└── (*Validator).verifyOutputs [0.7s, 28.00%]
## Top 20 Functions by Time
*Note: These functions are marked with `>>>` in the call tree above*
| Function | Total Time | % of Root |
|----------|------------|-----------|
| (*Validator).VerifyTransfer | 2.5s | 100.00% |
| (*Validator).verifyInputs | 1.8s | 72.00% |
| (*Verifier).Verify | 1.5s | 60.00% |
Reading the output:
>>> marks the top 20 functions by timex5 means called 5 times (total time shown)./profile.sh <test_or_benchmark_name> [options]
Options:
-d, --display <mode> Display: both, time, percent (default: both)
-f, --root-function <name> Start from this function (show subtree)
-m, --min-percent <n> Hide functions below this % (default: 0)
-h, --help Show help
Examples:
# Show only percentages
./profile.sh BenchmarkValidatorTransfer -d percent
# Profile a subtree
./profile.sh BenchmarkValidatorTransfer -f VerifyTransfer
# Hide functions using less than 1%
./profile.sh BenchmarkValidatorTransfer -m 1.0
# Profile a test
./profile.sh TestValidatorTransfer
Searches for tests/benchmarks in:
token/ directory and all subdirectoriesInstruments:
token/ directoryOutput:
<TestName>_profile.mdThe profile.sh script automatically instruments your code. However, you can also manually instrument code for profiling.
import "github.com/LFDT-Panurus/panurus/tools/profiler/tracer"
func BenchmarkValidatorTransfer(b *testing.B) {
// Enable tracer
tracer.Enable()
defer tracer.Disable()
// Setup code (not profiled)
validator := setupValidator()
request := generateRequest()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// This will be profiled if functions have tracer.Enter() calls
err := validator.VerifyTransfer(request)
if err != nil {
b.Fatal(err)
}
}
b.StopTimer()
// Print profile
tracer.PrintWithOptions(tracer.PrintOptions{
RootFunction: "VerifyTransfer",
ShowPercent: true,
ShowAbsolute: true,
AggregateLoops: true,
})
}
// Each function you want to profile needs this:
func (v *Validator) VerifyTransfer(req *Request) error {
defer tracer.Enter("(*Validator).VerifyTransfer")()
// ... function body ...
}
func TestValidatorTransfer(t *testing.T) {
// Enable tracer
tracer.Enable()
defer tracer.Disable()
// Run test
validator := setupValidator()
request := generateRequest()
err := validator.VerifyTransfer(request)
require.NoError(t, err)
// Print profile
tracer.PrintWithOptions(tracer.PrintOptions{
RootFunction: "VerifyTransfer",
ShowPercent: true,
ShowAbsolute: true,
AggregateLoops: true,
})
}
Note: Manual instrumentation requires adding defer tracer.Enter("FunctionName")() to every function you want to profile. The profile.sh script does this automatically by:
auto-instrument.go to add tracer callsFor most use cases, use profile.sh instead of manual instrumentation.
profile.sh - Automated profiling scriptauto-instrument.go - Adds tracer hooks to Go filesinject-tracer.go - Injects tracer into test/benchmarktracer/ - Runtime tracing libraryCan’t find test/benchmark:
token/ directoryEmpty output file:
Times don’t add up: