feat(profiling): Add CPU and MEM profiling
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(¤t)
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user