zai-proxy/dashboard/collector/rate_test.go
jedarden 0f16d14e80 fix(bf-3ik4): replace zai-proxy.mcp namespace with devpod
Replace all occurrences of zai-proxy.mcp.svc.cluster.local with
zai-proxy.devpod.svc.cluster.local in documentation files.

Verification:
- No old namespace references remain (grep verified)
- DefaultConfig pattern is correct (shared parsing in config package)
- Dashboard rate tests pass
- Comma-splitting verified working

Fixed files:
- dashboard/README.md
- DEVELOPMENT.md
- docs/notes/CANARY_PROMOTION_CHECKLIST.md
- docs/notes/CANARY_PROMOTION_PROCEDURE.md
- docs/notes/CANARY_ROLLBACK_PROCEDURE.md
- docs/notes/DASHBOARD_API_REFERENCE.md
- docs/notes/DEPLOYMENT.md
- docs/notes/metrics.md
- docs/notes/TROUBLESHOOTING.md

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 19:25:33 -04:00

197 lines
5 KiB
Go

package collector
import (
"math"
"testing"
)
// TestRateComputation tests the rate computation logic.
// Since rate computation happens in the collector, we test it through
// buildSnapshot-like scenarios.
func TestRateComputation_FirstScrape(t *testing.T) {
// On the first scrape, rates should be 0
cur := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
prev := map[string][]MetricValue{}
elapsed := 5.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
if rate != 0 {
t.Errorf("first scrape rate should be 0, got %f", rate)
}
}
func TestRateComputation_NormalDelta(t *testing.T) {
cur := map[string][]MetricValue{
"test_counter": {{Value: 110, Labels: map[string]string{}}},
}
prev := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
elapsed := 5.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
expected := 10.0 / 5.0 // 2.0
if math.Abs(rate-expected) > 0.001 {
t.Errorf("rate should be %f, got %f", expected, rate)
}
}
func TestRateComputation_CounterReset(t *testing.T) {
// Counter reset: current < previous
cur := map[string][]MetricValue{
"test_counter": {{Value: 5, Labels: map[string]string{}}},
}
prev := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
elapsed := 5.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
// On counter reset, should use current value as delta
expected := 5.0 / 5.0 // 1.0
if math.Abs(rate-expected) > 0.001 {
t.Errorf("rate after counter reset should be %f, got %f", expected, rate)
}
}
func TestRateComputation_ZeroElapsed(t *testing.T) {
cur := map[string][]MetricValue{
"test_counter": {{Value: 110, Labels: map[string]string{}}},
}
prev := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
elapsed := 0.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
if rate != 0 {
t.Errorf("rate with zero elapsed should be 0, got %f", rate)
}
}
func TestRateComputation_NegativeElapsed(t *testing.T) {
cur := map[string][]MetricValue{
"test_counter": {{Value: 110, Labels: map[string]string{}}},
}
prev := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
elapsed := -1.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
if rate != 0 {
t.Errorf("rate with negative elapsed should be 0, got %f", rate)
}
}
func TestRateComputation_WithLabelFilter(t *testing.T) {
cur := map[string][]MetricValue{
"test_counter": {
{Value: 100, Labels: map[string]string{"direction": "input"}},
{Value: 50, Labels: map[string]string{"direction": "output"}},
},
}
prev := map[string][]MetricValue{
"test_counter": {
{Value: 90, Labels: map[string]string{"direction": "input"}},
{Value: 45, Labels: map[string]string{"direction": "output"}},
},
}
elapsed := 5.0
rate := computeRate(cur, prev, "test_counter", map[string]string{"direction": "input"}, elapsed)
expected := 10.0 / 5.0 // 2.0
if math.Abs(rate-expected) > 0.001 {
t.Errorf("filtered rate should be %f, got %f", expected, rate)
}
}
func TestRateComputation_MissingMetric(t *testing.T) {
cur := map[string][]MetricValue{}
prev := map[string][]MetricValue{
"test_counter": {{Value: 100, Labels: map[string]string{}}},
}
elapsed := 5.0
rate := computeRate(cur, prev, "test_counter", nil, elapsed)
// Missing metric should result in 0 rate
if rate != 0 {
t.Errorf("rate for missing metric should be 0, got %f", rate)
}
}
// computeRate is a helper that mimics the rate computation in the collector.
func computeRate(cur, prev map[string][]MetricValue, name string, filter map[string]string, elapsed float64) float64 {
if elapsed <= 0 || len(prev) == 0 {
return 0
}
curVal := sumValues(cur[name], filter)
prevVal := sumValues(prev[name], filter)
delta := curVal - prevVal
if delta < 0 {
delta = curVal // Counter reset
}
return delta / elapsed
}
func sumValues(values []MetricValue, filter map[string]string) float64 {
var total float64
for _, v := range values {
if matchesLabels(v.Labels, filter) {
total += v.Value
}
}
return total
}
// TestDeltaComputation tests delta computation for counters.
func TestDeltaComputation(t *testing.T) {
tests := []struct {
name string
curVal float64
prevVal float64
expected float64
}{
{
name: "normal increment",
curVal: 110,
prevVal: 100,
expected: 10,
},
{
name: "no change",
curVal: 100,
prevVal: 100,
expected: 0,
},
{
name: "counter reset",
curVal: 5,
prevVal: 100,
expected: 5, // Use current value
},
{
name: "large increment",
curVal: 1000000,
prevVal: 999000,
expected: 1000,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
delta := tt.curVal - tt.prevVal
if delta < 0 {
delta = tt.curVal
}
if delta != tt.expected {
t.Errorf("delta should be %f, got %f", tt.expected, delta)
}
})
}
}