Skip to content

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.

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)))
}
}

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)

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 call202.9ms
unsampled call1.1ms
overhead201.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.

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.

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. ~4ms means “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.
ApproachMethodDescription
All valuesTraceFunctionWithReturnsReturns []interface{} with all return values
First onlyTraceFunctionWithReturnReturns only the first return value
results := monigo.TraceFunctionWithReturns(ctx, myFunction, args...)
switch len(results) {
case 0:
// Function returns nothing
case 1:
value := results[0]
case 2:
value := results[0]
err := results[1].(error)
default:
// Handle many returns
}
  • 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.