summaryrefslogtreecommitdiff
path: root/internal/parser/csv.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-07 16:32:10 +0200
committerPaul Buetow <paul@buetow.org>2026-02-07 16:32:10 +0200
commit3fd46f3977fb650974e5e936cba362c787c00637 (patch)
treeb49111ddd0b7af4a007bca6a304dba10efcd88ff /internal/parser/csv.go
reimport this PoC
Diffstat (limited to 'internal/parser/csv.go')
-rw-r--r--internal/parser/csv.go101
1 files changed, 101 insertions, 0 deletions
diff --git a/internal/parser/csv.go b/internal/parser/csv.go
new file mode 100644
index 0000000..64d16e5
--- /dev/null
+++ b/internal/parser/csv.go
@@ -0,0 +1,101 @@
+package parser
+
+import (
+ "context"
+ "encoding/csv"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+
+ "epimetheus/internal/metrics"
+)
+
+// CSVParser parses metrics from CSV format
+type CSVParser struct{}
+
+// NewCSVParser creates a new CSV parser
+func NewCSVParser() *CSVParser {
+ return &CSVParser{}
+}
+
+// Parse reads metrics from CSV format
+// Format: metric_name,label1=value1;label2=value2,value,timestamp_ms
+func (p *CSVParser) Parse(ctx context.Context, reader io.Reader) ([]metrics.Sample, error) {
+ var samples []metrics.Sample
+
+ csvReader := csv.NewReader(reader)
+ csvReader.Comment = '#'
+
+ lineNum := 0
+ for {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ record, err := csvReader.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, fmt.Errorf("line %d: %w", lineNum, err)
+ }
+ lineNum++
+
+ if len(record) < 3 {
+ continue // Skip invalid records
+ }
+
+ sample, err := p.parseRecord(record, lineNum)
+ if err != nil {
+ continue // Skip records with errors
+ }
+
+ samples = append(samples, sample)
+ }
+
+ return samples, nil
+}
+
+func (p *CSVParser) parseRecord(record []string, lineNum int) (metrics.Sample, error) {
+ metricName := strings.TrimSpace(record[0])
+ if metricName == "" {
+ return metrics.Sample{}, fmt.Errorf("empty metric name")
+ }
+
+ labels := parseLabels(record[1])
+
+ value, err := strconv.ParseFloat(strings.TrimSpace(record[2]), 64)
+ if err != nil {
+ return metrics.Sample{}, fmt.Errorf("invalid value: %w", err)
+ }
+
+ timestamp := time.Now()
+ if len(record) > 3 && record[3] != "" {
+ timestampMs, err := strconv.ParseInt(strings.TrimSpace(record[3]), 10, 64)
+ if err == nil {
+ timestamp = time.UnixMilli(timestampMs)
+ }
+ }
+
+ return metrics.NewSample(metricName, labels, value, timestamp), nil
+}
+
+func parseLabels(labelStr string) map[string]string {
+ labels := make(map[string]string)
+ if labelStr == "" {
+ return labels
+ }
+
+ labelPairs := strings.Split(labelStr, ";")
+ for _, pair := range labelPairs {
+ parts := strings.SplitN(pair, "=", 2)
+ if len(parts) == 2 {
+ labels[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
+ }
+ }
+ return labels
+}