mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
## Problem `TestDescCacheTimestampUpdate` was flaky on Windows CI because `time.Now()` has ~15.6ms resolution, causing consecutive calls to return identical timestamps. ## Solution Inject `quartz.Clock` into `MetricsAggregator` using an options pattern, making the test deterministic by using a mock clock with explicit time advancement. ### Changes - Add `clock quartz.Clock` field to `MetricsAggregator` struct - Add `WithClock()` option for dependency injection - Replace all `time.Now()` calls with `ma.clock.Now()` - Update test to use mock clock with `mClock.Advance(time.Second)` --- This PR was fully generated by [`mux`](https://github.com/coder/mux) using Claude Opus 4.5, and reviewed by me. Closes https://github.com/coder/internal/issues/1146
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package prometheusmetrics
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
|
"github.com/coder/coder/v2/coderd/agentmetrics"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
func TestDescCache_DescExpire(t *testing.T) {
|
|
const (
|
|
testWorkspaceName = "yogi-workspace"
|
|
testUsername = "yogi-bear"
|
|
testAgentName = "main-agent"
|
|
testTemplateName = "main-template"
|
|
)
|
|
|
|
testLabels := AgentMetricLabels{
|
|
Username: testUsername,
|
|
WorkspaceName: testWorkspaceName,
|
|
AgentName: testAgentName,
|
|
TemplateName: testTemplateName,
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
// given
|
|
registry := prometheus.NewRegistry()
|
|
ma, err := NewMetricsAggregator(slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}), registry, time.Millisecond, agentmetrics.LabelAll)
|
|
require.NoError(t, err)
|
|
|
|
given := []*agentproto.Stats_Metric{
|
|
{Name: "a_counter_one", Type: agentproto.Stats_Metric_COUNTER, Value: 1},
|
|
}
|
|
|
|
_, err = ma.asPrometheus(&annotatedMetric{
|
|
given[0],
|
|
testLabels.Username,
|
|
testLabels.WorkspaceName,
|
|
testLabels.AgentName,
|
|
testLabels.TemplateName,
|
|
// the rest doesn't matter for this test
|
|
time.Now(),
|
|
[]string{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.Eventually(t, func() bool {
|
|
ma.cleanupDescCache()
|
|
return len(ma.descCache) == 0
|
|
}, testutil.WaitShort, testutil.IntervalFast)
|
|
}
|
|
|
|
// TestDescCacheTimestampUpdate ensures that the timestamp update in getOrCreateDesc
|
|
// updates the map entry because d is a copy, not a pointer.
|
|
func TestDescCacheTimestampUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mClock := quartz.NewMock(t)
|
|
registry := prometheus.NewRegistry()
|
|
ma, err := NewMetricsAggregator(slogtest.Make(t, nil), registry, time.Hour, nil, WithClock(mClock))
|
|
require.NoError(t, err)
|
|
|
|
baseLabelNames := []string{"label1", "label2"}
|
|
extraLabels := []*agentproto.Stats_Metric_Label{
|
|
{Name: "extra1", Value: "value1"},
|
|
}
|
|
|
|
desc1 := ma.getOrCreateDesc("test_metric", "help text", baseLabelNames, extraLabels)
|
|
require.NotNil(t, desc1)
|
|
|
|
key := cacheKeyForDesc("test_metric", baseLabelNames, extraLabels)
|
|
initialEntry := ma.descCache[key]
|
|
initialTime := initialEntry.lastUsed
|
|
|
|
// Advance the mock clock to ensure a different timestamp
|
|
mClock.Advance(time.Second)
|
|
|
|
desc2 := ma.getOrCreateDesc("test_metric", "help text", baseLabelNames, extraLabels)
|
|
require.NotNil(t, desc2)
|
|
|
|
updatedEntry := ma.descCache[key]
|
|
updatedTime := updatedEntry.lastUsed
|
|
|
|
require.NotEqual(t, initialTime, updatedTime,
|
|
"Timestamp was NOT updated in map when accessing a metric description that should be cached")
|
|
}
|