Function Tracing
MoniGo provides powerful function tracing capabilities to monitor the performance of your application functions. You can trace functions with any signature, including those with parameters and return values.
Available Tracing Methods
Section titled “Available Tracing Methods”1. TraceFunction (No Parameters)
Section titled “1. TraceFunction (No Parameters)”The simplest method for tracing functions without parameters:
func apiHandler(w http.ResponseWriter, r *http.Request) { monigo.TraceFunction(r.Context(), highMemoryUsage) w.Write([]byte("API1 response"))}
func highMemoryUsage() { largeSlice := make([]float64, 1e8) for i := 0; i < len(largeSlice); i++ { largeSlice[i] = float64(i) }}2. TraceFunctionWithArgs (With Parameters)
Section titled “2. TraceFunctionWithArgs (With Parameters)”Trace functions that take parameters:
func processUser(userID string, userName string) { time.Sleep(100 * time.Millisecond) _ = make([]byte, 1024*1024)}
func userHandler(w http.ResponseWriter, r *http.Request) { userID := r.URL.Query().Get("id") userName := r.URL.Query().Get("name")
monigo.TraceFunctionWithArgs(r.Context(), processUser, userID, userName) w.Write([]byte("User processed"))}3. TraceFunctionWithReturn (Single Return Value)
Section titled “3. TraceFunctionWithReturn (Single Return Value)”Trace functions that return a single value:
func calculateTotal(items []Item) float64 { var total float64 for _, item := range items { total += item.Price } return total}
func calculateHandler(w http.ResponseWriter, r *http.Request) { items := []Item{ {Name: "Laptop", Price: 999.99}, {Name: "Mouse", Price: 29.99}, }
total := monigo.TraceFunctionWithReturn(r.Context(), calculateTotal, items).(float64) w.Write([]byte(fmt.Sprintf("Total: $%.2f", total)))}4. TraceFunctionWithReturns (Multiple Return Values)
Section titled “4. TraceFunctionWithReturns (Multiple Return Values)”Trace functions that return multiple values:
func processData(data string) (Result, error) { if data == "error" { return Result{}, fmt.Errorf("processing error") } return Result{Success: true, Message: "OK"}, nil}
func processHandler(w http.ResponseWriter, r *http.Request) { results := monigo.TraceFunctionWithReturns(r.Context(), processData, "test-data")
if len(results) >= 2 { result := results[0].(Result) err := results[1].(error)
if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Error: %v", err))) return } w.Write([]byte(fmt.Sprintf("Result: %+v", result))) }}Adaptive Sampling
Section titled “Adaptive Sampling”MoniGo implements Adaptive Sampling to ensure production-grade performance. By default, heavy profiling (CPU and Heap profiles) is only performed for 1 in every 100 calls. Lightweight metrics (duration and concurrency) are still captured for every call.
Configure the sampling rate via the builder:
monigoInstance := monigo.NewBuilder(). WithSamplingRate(10). // Profile 1 in 10 calls Build()Or globally at runtime:
monigo.SetSamplingRate(1000)What a sampled call costs
Section titled “What a sampled call costs”Profiling a call is not cheap, and the cost does not scale with the work.
pprof.StopCPUProfile blocks while the runtime flushes its profile buffer,
which takes a roughly constant ~200ms regardless of how long the function
ran. Measured on a 1ms function:
| sampled call | 202.9ms |
| unsampled call | 1.1ms |
| overhead | 201.8ms |
At the default rate, a traced handler serving 100 rps stalls for ~200ms once
per second. ExecutionTime is captured before the stop, so the recorded metric
reads 1ms while the caller’s goroutine was blocked for 203ms.
Go’s CPU profiler also samples at 100 Hz, so a call shorter than ~10ms usually finishes between two samples and captures nothing. At the default settings the common case is to pay the 200ms and get an empty profile.
This closes off the obvious workaround: lowering the rate collects more profiles but stalls more often, and raising it makes profiles rarer without making them any less empty. Trace functions that do enough work to be worth sampling, and leave hot, short ones untraced.
Function Name Generation
Section titled “Function Name Generation”The enhanced tracing methods automatically generate descriptive function names that include:
- Function name
- Parameter types:
functionName(string,int) - Return types:
functionName(string,int)->(float64,error)
This makes it easier to identify and analyze specific function calls in the dashboard.
Call counts and latency percentiles
Section titled “Call counts and latency percentiles”The Functions page shows CALLS, P50 and P95 for every traced function,
alongside execution time and memory delta.
The distribution comes from a fixed-bucket histogram — 216 bytes per function regardless of call volume, against roughly 10 MB at the function cap if raw samples were kept. That has two consequences worth knowing:
- Values are bucket upper bounds, not interpolations.
~4msmeans “at most 4ms”, which is why the figures carry a~. - Below 20 calls the columns read
—. A p95 of three calls is the slowest of three, not a percentile, so it is not shown.
Handling Multiple Return Values
Section titled “Handling Multiple Return Values”| Approach | Method | Description |
|---|---|---|
| All values | TraceFunctionWithReturns | Returns []interface{} with all return values |
| First only | TraceFunctionWithReturn | Returns only the first return value |
Example: Switch on Return Count
Section titled “Example: Switch on Return Count”results := monigo.TraceFunctionWithReturns(ctx, myFunction, args...)
switch len(results) {case 0: // Function returns nothingcase 1: value := results[0]case 2: value := results[0] err := results[1].(error)default: // Handle many returns}Benefits
Section titled “Benefits”- Zero Overhead in Hot Paths - Non-sampled calls have sub-millisecond overhead.
- Cleaner Code - No need to wrap functions in anonymous functions.
- Better Function Identification - Actual function names appear in metrics.
- Type Safety - Compile-time checking of function signatures.
- Backward Compatibility - Existing code continues to work without changes.