Performance bottleneck analysis and flame graph generation based on pprof
A comprehensive guide on using Golang pprof
How to diagnose memory leak issues in programs written in C and Go. This article provides specific commands and practical examples for diagnosing memory leaks in C and Go programs, using system tools and language features for layered detection:
—
1. Memory Leak Diagnosis in C
1. Valgrind Tool Detection
# Add debug symbols during compilation
gcc -g -o program program.c
# Run detection (outputs locations of unreleased memory)
valgrind --leak-check=full --show-leak-kinds=all ./program
Output example:
==12345== 1,024 bytes in 1 blocks are definitely lost
==12345== at 0x4C2BBAF: malloc (vg_replace_malloc.c:381)
==12345== by 0x108ABC: create_object (program.c:42) # Locate the leak code line
2. AddressSanitizer (ASAN)
# Enable ASAN during compilation
gcc -fsanitize=address -g -o program program.c
./program # Outputs leak stack when the program crashes
Output characteristics:
===================================================================
45678==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 1024 byte(s) in 1 object(s) allocated from:
#0 0x7f2a5b in malloc (/lib64/libasan.so.6+0xab5b)
#1 0x55d678 in main program.c:42
3. Core Dump Analysis
ulimit -c unlimited # Allow core file generation
./program & # Run the program
kill -SIGSEGV $(pgrep program) # Trigger core dump
gdb ./program core # Analyze core file
(gdb) info malloc-history # View memory allocation history (must compile with -g)
—
2. Memory Leak Diagnosis in Go
1. pprof Memory Analysis Steps:
Integrate pprof in the code:
import _ "net/http/pprof"
func main() {
go func() { http.ListenAndServe(":6060", nil) }()
// Business logic...
}
2. Obtain Memory Snapshot:
# Real-time analysis (replace PORT)
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
Web interface operations:
– Click Top to view the functions with the highest memory usage
– Use Flame Graph to locate the call chain (example: `main.processData` occupies 70% of memory)
2. Goroutine Leak Detection
# Check if the number of goroutines is continuously increasing
curl http://localhost:6060/debug/pprof/goroutine?debug=2 > goroutines.txt
# Analyze blocked goroutines
go tool pprof http://localhost:6060/debug/pprof/goroutine
(pprof) top -cum
Common leak scenarios:
// Example: Goroutine without exit condition
go func() {
for { /* No exit condition */ }
// Leak!
}()
3. Runtime Monitoring
# Observe GC behavior (whether memory is reclaimed)
GODEBUG='gctrace=1' ./program
Output interpretation:
gc 1 @0.023s: 4->4->0 MB, 0.5 ms # Memory did not decrease after reclamation → Possible leak
—
3. Key Points for Mixed Programming (CGO) Leak Diagnosis:
– Memory allocated in C must be manually released:
/* #include <stdlib.h> */
import "C"
func main() {
ptr := C.malloc(1024)
defer C.free(ptr) // Must be explicitly released!
}
– Cross-language toolchain:
# Use eBPF to detect cgo leaks (requires Linux 4.15+)
sudo memleak-bpfcc -p $(pgrep program)
Output:
Top 5 stacks with outstanding allocations:
1024 bytes in 1 allocations from
malloc+0x10 [libc.so.6]
main._Cfunc_createObject+0x20 [program]
—
4. Practical Recommendations:
1. Layered Diagnosis:
– First confirm the memory growth trend of the process using `top/htop`
– C: Prioritize using `valgrind` or `ASAN`
– Go: Combine `pprof` + `GODEBUG=gctrace=1`
2. Preventive Measures:
– C: Add comments to each `malloc` indicating the responsibility for release
– Go: Use `defer` to release resources (files, locks, network connections)
3. Container Environment:
# Obtain pprof data inside the container
docker exec -it <container> curl http://localhost:6060/debug/pprof/heap > heap.out
go tool pprof heap.out # Local analysis
Through the combination of the above tools, 90% of memory leak scenarios can be covered. If kernel-level leaks are involved (such as unclosed sockets), deep analysis with `lsof -p PID` or `bpftrace` is required.
For the issue of continuous memory growth in the `stmanager` process written in C and Go, a combination of system tools, language features, and code logic should be used for diagnosis. Below is a step-by-step diagnosis plan:
—
1. Preliminary Diagnosis: System-level Monitoring and Process Analysis
1. Monitor Memory Changes
Use `top` or `htop` to observe in real-time whether the memory usage of `stmanager` (RES/physical memory) is continuously increasing:
top -p $(pgrep stmanager)
– If memory fluctuates periodically, it may be normal GC or caching mechanism; if it increases unidirectionally, there is a risk of leakage.
2. Locate Memory Allocation Sources
Use `pmap` to view the process memory mapping and identify memory segments with abnormal growth:
pmap -x $(pgrep stmanager)
– Focus on the growth trend of `[heap]` (heap memory) or `[anon]` (anonymous memory).
—
2. C Language Part: Manual Memory Management Diagnosis
1. Check for Memory Leaks
– Tool Detection:
Use `valgrind` to detect unreleased memory blocks:
valgrind --leak-check=full --show-leak-kinds=all ./stmanager
– Output example:
==1234== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1234== at 0x4C2BBAF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1234== by 0x108745: create_struct (example.c:42)
– Focus on checking `malloc`/`calloc` locations that are not matched with `free`.
– AddressSanitizer:
Enable `-fsanitize=address` during compilation, run the program to directly locate leak points:
gcc -fsanitize=address -g structmanager.c -o stmanager
./stmanager
2. Analyze Core Dump Files
– Generate core dump:
ulimit -c unlimited # Allow large file generation
kill -SIGSEGV $(pgrep stmanager) # Trigger crash to generate core file
– Use `gdb` for analysis:
gdb ./stmanager core
(gdb) info malloc-history # View memory allocation history (must compile with -g)
—
3. Go Language Part: Garbage Collection and Object Management
1. Enable pprof Performance Analysis
– Integrate `net/http/pprof` in Go code:
import _ "net/http/pprof"
func main() {
go func() {
http.ListenAndServe(":6060", nil) // Start pprof HTTP service
}()
// Other logic...
}
– Obtain memory snapshot through `pprof`:
go tool pprof http://localhost:6060/debug/pprof/heap
– Analyze memory hotspots:
In the `pprof` interactive interface, enter `top` to view the function call stacks that occupy the most memory:
(pprof) top
1.23GB of 1.50GB total (82%)
Dropped 12 nodes (cum <= 0.01GB)
flat flat% sum% cum cum%
0.80GB 53.33% 53.33% 0.80GB 53.33% main.processStruct (example.go:30)
0.43GB 28.67% 82.00% 0.43GB 28.67% runtime.mallocgc
2. Check for Goroutine Leaks
– Use `pprof` to analyze goroutines:
go tool pprof http://localhost:6060/debug/pprof/goroutine
– If the number of goroutines increases abnormally, check for `goroutine`s that do not exit:
go func() {
for {
// Loop without exit condition may cause leak
}
}()
3. Check Structure References and Caches
– Unreleased structure references:
Check if global or cache variables continuously hold structure pointers, for example:
var cache = make(map[string]*Struct)
// Must regularly clean up expired entries, otherwise memory will continue to grow
– Slice/Map Expansion:
When frequently adding elements to a `slice`, if the capacity is not limited or the underlying array is not reused, it may lead to memory bloat:
data := make([]byte, 0, 1024) // Pre-allocate capacity to avoid frequent expansion
—
4. Mixed Language Interaction Diagnosis (C and Go)
1. Memory Management in CGO Calls
– If Go calls C code via `cgo`, ensure that memory allocated in C is explicitly released:
/*
#include <stdlib.h>
*/
import "C"
func createCStruct() unsafe.Pointer {
ptr := C.malloc(C.size_t(1024))
return ptr
}
// Must manually release after use
C.free(ptr)
2. Cross-language Cache Sharing
– Check shared cache structures between C and Go (such as shared memory or global variables) to avoid memory accumulation due to one side not releasing.
—
5. Code-Level Optimization Recommendations
| Problem Type | C Language Solution | Go Language Solution |
| Memory Leak | Use `valgrind` or `AddressSanitizer` to locate unreleased memory | Use `pprof` to analyze memory allocation hotspots |
| Structure Not Released | Check `free()` call logic | Clean up global cache, nullify unused structure references |
| Frequent Allocation/Release | Use Memory Pool to reuse memory blocks | Pre-allocate `slice` capacity, reuse objects (sync.Pool) |
| Goroutine Leak | – | Add context cancellation mechanism (`context.Context`) |
—
6. Summary Steps
1. System Monitoring: Confirm memory growth trends and sources.
2. Language-Specific Diagnosis:
– C: Use `valgrind`/`gdb` to detect leaks.
– Go: Use `pprof` to analyze memory and goroutines.
3. Check Interaction Logic: Ensure memory from CGO calls is correctly released.
4. Code Optimization: Limit cache size, reuse memory, clean up references.
Through the above methods, the root cause of the memory growth in `stmanager` can be accurately located, whether it is a memory leak in C or an unreleased structure reference issue in Go.