package main import ( "sort" "sync" "time" ) // Metrics collects performance data during load tests. type Metrics struct { mu sync.Mutex // Write metrics writeLatencies []time.Duration writeErrors int writeCount int // Read metrics readLatencies []time.Duration readErrors int readCount int // Timing startTime time.Time endTime time.Time // Degradation tracking baselineP99 time.Duration loadedP99 time.Duration degradationPct float64 readerCount int backgroundWrite int } // NewMetrics creates a new Metrics collector. func NewMetrics() *Metrics { return &Metrics{ startTime: time.Now(), } } // RecordWrite records a write operation latency and error status. func (m *Metrics) RecordWrite(latency time.Duration, err error) { m.mu.Lock() defer m.mu.Unlock() m.writeLatencies = append(m.writeLatencies, latency) m.writeCount++ if err != nil { m.writeErrors++ } } // RecordRead records a read operation latency and error status. func (m *Metrics) RecordRead(latency time.Duration, err error) { m.mu.Lock() defer m.mu.Unlock() m.readLatencies = append(m.readLatencies, latency) m.readCount++ if err != nil { m.readErrors++ } } // Stop marks the end of the test. func (m *Metrics) Stop() { m.mu.Lock() defer m.mu.Unlock() m.endTime = time.Now() } // Duration returns the test duration. func (m *Metrics) Duration() time.Duration { m.mu.Lock() defer m.mu.Unlock() end := m.endTime if end.IsZero() { end = time.Now() } return end.Sub(m.startTime) } // WriteCount returns the total number of write operations. func (m *Metrics) WriteCount() int { m.mu.Lock() defer m.mu.Unlock() return m.writeCount } // WriteErrors returns the number of failed write operations. func (m *Metrics) WriteErrors() int { m.mu.Lock() defer m.mu.Unlock() return m.writeErrors } // WriteErrorRate returns the write error rate as a percentage. func (m *Metrics) WriteErrorRate() float64 { m.mu.Lock() defer m.mu.Unlock() if m.writeCount == 0 { return 0 } return float64(m.writeErrors) / float64(m.writeCount) * 100 } // WriteThroughput returns the average writes per second. func (m *Metrics) WriteThroughput() float64 { m.mu.Lock() defer m.mu.Unlock() var duration time.Duration if m.endTime.IsZero() { duration = time.Since(m.startTime) } else { duration = m.endTime.Sub(m.startTime) } if duration <= 0 { return 0 } return float64(m.writeCount) / duration.Seconds() } // WriteP50 returns the 50th percentile write latency. func (m *Metrics) WriteP50() time.Duration { return m.writePercentile(0.50) } // WriteP99 returns the 99th percentile write latency. func (m *Metrics) WriteP99() time.Duration { return m.writePercentile(0.99) } // WriteMax returns the maximum write latency. func (m *Metrics) WriteMax() time.Duration { m.mu.Lock() defer m.mu.Unlock() if len(m.writeLatencies) == 0 { return 0 } sorted := make([]time.Duration, len(m.writeLatencies)) copy(sorted, m.writeLatencies) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted[len(sorted)-1] } func (m *Metrics) writePercentile(p float64) time.Duration { m.mu.Lock() defer m.mu.Unlock() if len(m.writeLatencies) == 0 { return 0 } sorted := make([]time.Duration, len(m.writeLatencies)) copy(sorted, m.writeLatencies) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) idx := int(float64(len(sorted)-1) * p) return sorted[idx] } // ReadCount returns the total number of read operations. func (m *Metrics) ReadCount() int { m.mu.Lock() defer m.mu.Unlock() return m.readCount } // ReadErrors returns the number of failed read operations. func (m *Metrics) ReadErrors() int { m.mu.Lock() defer m.mu.Unlock() return m.readErrors } // ReadP50 returns the 50th percentile read latency. func (m *Metrics) ReadP50() time.Duration { return m.readPercentile(0.50) } // ReadP99 returns the 99th percentile read latency. func (m *Metrics) ReadP99() time.Duration { return m.readPercentile(0.99) } func (m *Metrics) readPercentile(p float64) time.Duration { m.mu.Lock() defer m.mu.Unlock() if len(m.readLatencies) == 0 { return 0 } sorted := make([]time.Duration, len(m.readLatencies)) copy(sorted, m.readLatencies) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) idx := int(float64(len(sorted)-1) * p) return sorted[idx] } // SetBaselineP99 records the baseline read P99 latency (no concurrent load). func (m *Metrics) SetBaselineP99(latency time.Duration) { m.mu.Lock() defer m.mu.Unlock() m.baselineP99 = latency } // SetLoadedP99 records the P99 read latency under concurrent load. func (m *Metrics) SetLoadedP99(latency time.Duration) { m.mu.Lock() defer m.mu.Unlock() m.loadedP99 = latency } // SetDegradation records the degradation percentage and test parameters. func (m *Metrics) SetDegradation(degradationPct float64, readers, backgroundWriteRPS int) { m.mu.Lock() defer m.mu.Unlock() m.degradationPct = degradationPct m.readerCount = readers m.backgroundWrite = backgroundWriteRPS } // BaselineP99 returns the baseline P99 latency. func (m *Metrics) BaselineP99() time.Duration { m.mu.Lock() defer m.mu.Unlock() return m.baselineP99 } // LoadedP99 returns the P99 latency under load. func (m *Metrics) LoadedP99() time.Duration { m.mu.Lock() defer m.mu.Unlock() return m.loadedP99 } // DegradationPct returns the latency degradation percentage. func (m *Metrics) DegradationPct() float64 { m.mu.Lock() defer m.mu.Unlock() return m.degradationPct } // ReaderCount returns the number of concurrent readers tested. func (m *Metrics) ReaderCount() int { m.mu.Lock() defer m.mu.Unlock() return m.readerCount }