Day 28 — Profiling
Learning Objectives
- Generate and read CPU and heap profiles
- Identify hot functions in a flame graph
- Know what to look for when engineers say "the WAF is slow"
Key Concepts
# Add pprof endpoints (one import)
import _ "net/http/pprof"
# Collect CPU profile (30 seconds)
curl -s http://localhost:8080/debug/pprof/profile?seconds=30 > cpu.prof
# Analyze
go tool pprof cpu.prof
(pprof) top10 # top 10 functions by CPU
(pprof) web # open flame graph in browser (requires graphviz)
(pprof) list regexp # source code for regexp package
# Heap profile
curl -s http://localhost:8080/debug/pprof/heap > heap.prof
go tool pprof heap.prof
(pprof) top # top allocators
(pprof) allocs # allocation profile
Exercise: Profile the WAF Pattern Matcher
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"regexp"
"time"
)
// Simulate WAF pattern matching under load
var wafPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(UNION|SELECT|INSERT|DELETE|DROP)\b`),
regexp.MustCompile(`(?i)<script[\s>]`),
regexp.MustCompile(`(?i)javascript:`),
regexp.MustCompile(`\.\./`),
regexp.MustCompile(`(?i)(cmd|exec|system|eval)\s*\(`),
}
func checkPayload(payload string) bool {
for _, re := range wafPatterns {
if re.MatchString(payload) {
return true // blocked
}
}
return false
}
func handler(w http.ResponseWriter, r *http.Request) {
payload := r.URL.Query().Get("q")
blocked := checkPayload(payload)
if blocked {
http.Error(w, "forbidden", 403)
return
}
fmt.Fprint(w, "ok")
}
func main() {
http.HandleFunc("/", handler)
// Load generator: 500 goroutines hammering for 30s
go func() {
time.Sleep(1 * time.Second)
fmt.Println("starting load test...")
payloads := []string{"normal search", "product name", "UNION SELECT", "<script>"}
var wg sync.WaitGroup
for i := 0; i < 500; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 100; j++ {
payload := payloads[j%len(payloads)]
http.Get(fmt.Sprintf("http://localhost:8080/?q=%s", url.QueryEscape(payload)))
}
}(i)
}
wg.Wait()
fmt.Println("load test complete. check: go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30")
}()
fmt.Println("profiling server on :8080")
http.ListenAndServe(":8080", nil)
}
Profile it:
go run . &
sleep 2
go tool pprof -http=:9090 http://localhost:8080/debug/pprof/profile?seconds=10
# Open http://localhost:9090 for flame graph
Real-World Context
When your WAF adds more latency than expected, profiling will show you whether it's regex, JSON parsing, or mutex contention. A flame graph makes the answer obvious in 30 seconds — no guessing. This is the skill that separates engineers who fix performance issues from ones who guess.