feat(profiling): Add CPU and MEM profiling

This commit is contained in:
2026-04-06 13:15:09 +01:00
parent 0dc77c4583
commit cd8a6c5433
17 changed files with 631 additions and 2740 deletions
+16 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/afonsofrancof/sdns-proxy/client"
"github.com/afonsofrancof/sdns-proxy/internal/qol/capture"
"github.com/afonsofrancof/sdns-proxy/internal/qol/results"
"github.com/afonsofrancof/sdns-proxy/internal/qol/stats"
"github.com/google/gopacket/pcap"
"github.com/miekg/dns"
)
@@ -86,13 +87,16 @@ func (r *MeasurementRunner) runPerUpstream(upstream string, domains []string, qT
defer dnsClient.Close()
// Setup output files
csvPath, pcapPath := GenerateOutputPaths(r.config.OutputDir, upstream, r.config.DNSSEC, r.config.AuthoritativeDNSSEC, r.config.KeepAlive)
csvPath, pcapPath, memPath := GenerateOutputPaths(r.config.OutputDir, upstream, r.config.DNSSEC, r.config.AuthoritativeDNSSEC, r.config.KeepAlive)
// Create directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(csvPath), 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Initialize runtime collector with memPath
runtimeCollector := stats.NewRuntimeCollector(memPath)
keepAliveStr := ""
if r.config.KeepAlive {
keepAliveStr = " (keep-alive)"
@@ -118,7 +122,17 @@ func (r *MeasurementRunner) runPerUpstream(upstream string, domains []string, qT
time.Sleep(time.Second)
// Run measurements
return r.runQueries(dnsClient, upstream, domains, qType, writer, packetCapture)
err = r.runQueries(dnsClient, upstream, domains, qType, writer, packetCapture)
if err != nil {
return err
}
// Write summed mem stats for the entire run
if err := runtimeCollector.WriteStats(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to write mem stats to %s: %v\n", memPath, err)
}
return nil
}
func (r *MeasurementRunner) runQueries(dnsClient client.DNSClient, upstream string,
+95
View File
@@ -0,0 +1,95 @@
package stats
import (
"encoding/csv"
"fmt"
"os"
"runtime"
"time"
)
type RuntimeStats struct {
TotalAlloc uint64
Mallocs uint64
NumGC uint32
AllocDelta uint64
MallocsDelta uint64
GCDelta uint32
}
type RuntimeCollector struct {
startStats runtime.MemStats
memPath string
}
func NewRuntimeCollector(memPath string) *RuntimeCollector {
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
return &RuntimeCollector{
startStats: stats,
memPath: memPath,
}
}
func (rc *RuntimeCollector) Collect() RuntimeStats {
var current runtime.MemStats
runtime.ReadMemStats(&current)
return RuntimeStats{
TotalAlloc: current.TotalAlloc,
Mallocs: current.Mallocs,
NumGC: current.NumGC,
AllocDelta: current.TotalAlloc - rc.startStats.TotalAlloc,
MallocsDelta: current.Mallocs - rc.startStats.Mallocs,
GCDelta: current.NumGC - rc.startStats.NumGC,
}
}
func (rc *RuntimeCollector) WriteStats() error {
stats := rc.Collect()
timestamp := time.Now().Format(time.RFC3339Nano)
// Check if file exists
fileExists := false
if _, err := os.Stat(rc.memPath); err == nil {
fileExists = true
}
// Open in append mode
file, err := os.OpenFile(rc.memPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open mem.csv: %w", err)
}
defer file.Close()
writer := csv.NewWriter(file)
// Write header if new file
if !fileExists {
header := []string{
"timestamp", "total_alloc_bytes", "mallocs", "gc_cycles",
"alloc_delta", "mallocs_delta", "gc_delta",
}
if err := writer.Write(header); err != nil {
return fmt.Errorf("failed to write mem.csv header: %w", err)
}
}
// Write data row
row := []string{
timestamp,
fmt.Sprintf("%d", stats.TotalAlloc),
fmt.Sprintf("%d", stats.Mallocs),
fmt.Sprintf("%d", stats.NumGC),
fmt.Sprintf("%d", stats.AllocDelta),
fmt.Sprintf("%d", stats.MallocsDelta),
fmt.Sprintf("%d", stats.GCDelta),
}
if err := writer.Write(row); err != nil {
return fmt.Errorf("failed to write mem.csv row: %w", err)
}
writer.Flush()
return writer.Error()
}
+5 -4
View File
@@ -1,4 +1,3 @@
// ./internal/qol/utils.go
package qol
import (
@@ -8,7 +7,7 @@ import (
"strings"
)
func GenerateOutputPaths(outputDir, upstream string, dnssec, authDNSSEC, keepAlive bool) (csvPath, pcapPath string) {
func GenerateOutputPaths(outputDir, upstream string, dnssec, authDNSSEC, keepAlive bool) (csvPath, pcapPath, memPath string) {
proto := DetectProtocol(upstream)
cleanServer := cleanServerName(upstream)
@@ -32,8 +31,10 @@ func GenerateOutputPaths(outputDir, upstream string, dnssec, authDNSSEC, keepAli
base = fmt.Sprintf("%s-%s", base, strings.Join(flags, "-"))
}
return filepath.Join(subDir, base+".csv"),
filepath.Join(subDir, base+".pcap")
csvPath = filepath.Join(subDir, base+".csv")
pcapPath = filepath.Join(subDir, base+".pcap")
memPath = filepath.Join(subDir, base+".mem.csv")
return
}
func cleanServerName(server string) string {