From 68d0b5fe4d163c84ba05631869494df4ed76f938 Mon Sep 17 00:00:00 2001 From: afranco Date: Sun, 12 Oct 2025 10:39:00 +0100 Subject: [PATCH] feat(outputs): use only one output file, appending on every run --- internal/qol/capture/pcap.go | 31 +++++++++++++++++++++---------- internal/qol/results/writer.go | 34 +++++++++++++++++++++------------- internal/qol/utils.go | 19 ++++--------------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/internal/qol/capture/pcap.go b/internal/qol/capture/pcap.go index e342098..8254ae7 100644 --- a/internal/qol/capture/pcap.go +++ b/internal/qol/capture/pcap.go @@ -25,17 +25,28 @@ func NewPacketCapture(iface, outputPath string) (*PacketCapture, error) { return nil, fmt.Errorf("pcap open (try running as root): %w", err) } - file, err := os.Create(outputPath) + // Check if file exists + fileExists := false + if _, err := os.Stat(outputPath); err == nil { + fileExists = true + } + + // Open in append mode + file, err := os.OpenFile(outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { handle.Close() - return nil, fmt.Errorf("create pcap file: %w", err) + return nil, fmt.Errorf("create/open pcap file: %w", err) } writer := pcapgo.NewWriter(file) - if err := writer.WriteFileHeader(65535, handle.LinkType()); err != nil { - handle.Close() - file.Close() - return nil, fmt.Errorf("pcap header: %w", err) + + // Only write header if file is new + if !fileExists { + if err := writer.WriteFileHeader(65535, handle.LinkType()); err != nil { + handle.Close() + file.Close() + return nil, fmt.Errorf("pcap header: %w", err) + } } return &PacketCapture{ @@ -81,20 +92,20 @@ func (pc *PacketCapture) GetError() error { func (pc *PacketCapture) Close() error { var errs []error - + if pc.handle != nil { pc.handle.Close() } - + if pc.file != nil { if err := pc.file.Close(); err != nil { errs = append(errs, err) } } - + if len(errs) > 0 { return errs[0] } - + return nil } diff --git a/internal/qol/results/writer.go b/internal/qol/results/writer.go index 158d2ae..cad2109 100644 --- a/internal/qol/results/writer.go +++ b/internal/qol/results/writer.go @@ -31,27 +31,35 @@ type MetricsWriter struct { } func NewMetricsWriter(path string) (*MetricsWriter, error) { - file, err := os.Create(path) + // Check if file exists + fileExists := false + if _, err := os.Stat(path); err == nil { + fileExists = true + } + + // Open in append mode + file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return nil, fmt.Errorf("create csv output: %w", err) + return nil, fmt.Errorf("create/open csv output: %w", err) } writer := csv.NewWriter(file) - // Write CSV header - header := []string{ - "domain", "query_type", "protocol", "dnssec", "auth_dnssec", "keep_alive", - "dns_server", "timestamp", "duration_ns", "duration_ms", - "request_size_bytes", "response_size_bytes", "response_code", "error", - } + // Only write header if file is new + if !fileExists { + header := []string{ + "domain", "query_type", "protocol", "dnssec", "auth_dnssec", "keep_alive", + "dns_server", "timestamp", "duration_ns", "duration_ms", + "request_size_bytes", "response_size_bytes", "response_code", "error", "run_id", + } - if err := writer.Write(header); err != nil { - file.Close() - return nil, fmt.Errorf("write csv header: %w", err) + if err := writer.Write(header); err != nil { + file.Close() + return nil, fmt.Errorf("write csv header: %w", err) + } + writer.Flush() } - writer.Flush() - return &MetricsWriter{ writer: writer, file: file, diff --git a/internal/qol/utils.go b/internal/qol/utils.go index 6eb6ae3..df3ae4e 100644 --- a/internal/qol/utils.go +++ b/internal/qol/utils.go @@ -5,25 +5,17 @@ import ( "net/url" "path/filepath" "strings" - "time" ) func GenerateOutputPaths(outputDir, upstream string, dnssec, authDNSSEC, keepAlive bool) (csvPath, pcapPath string) { proto := DetectProtocol(upstream) cleanServer := cleanServerName(upstream) - // Create date-based subdirectory - date := time.Now().Format("2006-01-02") - timestamp := time.Now().Format("150405") + subDir := filepath.Join(outputDir, cleanServer) - // Organize hierarchically: resolver/date/filename - subDir := filepath.Join(outputDir, cleanServer, date) - - // Create simple filename base := proto - - // Add flags if enabled var flags []string + if dnssec { if authDNSSEC { flags = append(flags, "auth") @@ -39,11 +31,8 @@ func GenerateOutputPaths(outputDir, upstream string, dnssec, authDNSSEC, keepAli base = fmt.Sprintf("%s-%s", base, strings.Join(flags, "-")) } - // Add timestamp - filename := fmt.Sprintf("%s-%s", base, timestamp) - - return filepath.Join(subDir, filename+".csv"), - filepath.Join(subDir, filename+".pcap") + return filepath.Join(subDir, base+".csv"), + filepath.Join(subDir, base+".pcap") } func cleanServerName(server string) string {