@@ -2,7 +2,8 @@ package prebuilds
2
2
3
3
import (
4
4
"context"
5
- "sync/atomic"
5
+ "fmt"
6
+ "sync"
6
7
"time"
7
8
8
9
"github.com/prometheus/client_golang/prometheus"
@@ -11,7 +12,6 @@ import (
11
12
"cdr.dev/slog"
12
13
13
14
"github.com/coder/coder/v2/coderd/database"
14
- "github.com/coder/coder/v2/coderd/database/dbauthz"
15
15
"github.com/coder/coder/v2/coderd/prebuilds"
16
16
)
17
17
57
57
labels ,
58
58
nil ,
59
59
)
60
+ lastUpdateDesc = prometheus .NewDesc (
61
+ "coderd_prebuilt_workspaces_metrics_last_updated" ,
62
+ "The unix timestamp when the metrics related to prebuilt workspaces were last updated; these metrics are cached." ,
63
+ []string {},
64
+ nil ,
65
+ )
60
66
)
61
67
62
68
const (
@@ -69,7 +75,9 @@ type MetricsCollector struct {
69
75
logger slog.Logger
70
76
snapshotter prebuilds.StateSnapshotter
71
77
72
- latestState atomic.Pointer [state ]
78
+ latestState * state
79
+ latestStateUpdatedAt time.Time
80
+ latestStateMu sync.Mutex
73
81
}
74
82
75
83
var _ prometheus.Collector = new (MetricsCollector )
@@ -91,35 +99,36 @@ func (*MetricsCollector) Describe(descCh chan<- *prometheus.Desc) {
91
99
descCh <- desiredPrebuildsDesc
92
100
descCh <- runningPrebuildsDesc
93
101
descCh <- eligiblePrebuildsDesc
102
+ descCh <- lastUpdateDesc
94
103
}
95
104
96
105
// Collect uses the cached state to set configured metrics.
97
106
// The state is cached because this function can be called multiple times per second and retrieving the current state
98
107
// is an expensive operation.
99
108
func (mc * MetricsCollector ) Collect (metricsCh chan <- prometheus.Metric ) {
100
- // nolint:gocritic // We need to set an authz context to read metrics from the db.
101
- ctx := dbauthz .AsPrebuildsOrchestrator (context .Background ())
109
+ // Prevent the state from changing while a collection is occurring.
110
+ mc .latestStateMu .Lock ()
111
+ defer mc .latestStateMu .Unlock ()
102
112
103
- currentState := mc .latestState .Load ()
104
- if currentState == nil {
105
- mc .logger .Warn (ctx , "failed to set prebuilds metrics; state not set" )
113
+ if mc .latestState == nil {
114
+ mc .logger .Warn (context .Background (), "failed to set prebuilds metrics; state not set" )
106
115
return
107
116
}
108
117
109
- for _ , metric := range currentState .prebuildMetrics {
118
+ for _ , metric := range mc . latestState .prebuildMetrics {
110
119
metricsCh <- prometheus .MustNewConstMetric (createdPrebuildsDesc , prometheus .CounterValue , float64 (metric .CreatedCount ), metric .TemplateName , metric .PresetName , metric .OrganizationName )
111
120
metricsCh <- prometheus .MustNewConstMetric (failedPrebuildsDesc , prometheus .CounterValue , float64 (metric .FailedCount ), metric .TemplateName , metric .PresetName , metric .OrganizationName )
112
121
metricsCh <- prometheus .MustNewConstMetric (claimedPrebuildsDesc , prometheus .CounterValue , float64 (metric .ClaimedCount ), metric .TemplateName , metric .PresetName , metric .OrganizationName )
113
122
}
114
123
115
- for _ , preset := range currentState .snapshot .Presets {
124
+ for _ , preset := range mc . latestState .snapshot .Presets {
116
125
if ! preset .UsingActiveVersion {
117
126
continue
118
127
}
119
128
120
- presetSnapshot , err := currentState .snapshot .FilterByPreset (preset .ID )
129
+ presetSnapshot , err := mc . latestState .snapshot .FilterByPreset (preset .ID )
121
130
if err != nil {
122
- mc .logger .Error (ctx , "failed to filter by preset" , slog .Error (err ))
131
+ mc .logger .Error (context . Background () , "failed to filter by preset" , slog .Error (err ))
123
132
continue
124
133
}
125
134
state := presetSnapshot .CalculateState ()
@@ -128,6 +137,8 @@ func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
128
137
metricsCh <- prometheus .MustNewConstMetric (runningPrebuildsDesc , prometheus .GaugeValue , float64 (state .Actual ), preset .TemplateName , preset .Name , preset .OrganizationName )
129
138
metricsCh <- prometheus .MustNewConstMetric (eligiblePrebuildsDesc , prometheus .GaugeValue , float64 (state .Eligible ), preset .TemplateName , preset .Name , preset .OrganizationName )
130
139
}
140
+
141
+ metricsCh <- prometheus .MustNewConstMetric (lastUpdateDesc , prometheus .GaugeValue , float64 (mc .latestStateUpdatedAt .Unix ()))
131
142
}
132
143
133
144
type state struct {
@@ -157,6 +168,11 @@ func (mc *MetricsCollector) BackgroundFetch(ctx context.Context, updateInterval,
157
168
158
169
// UpdateState builds the current metrics state.
159
170
func (mc * MetricsCollector ) UpdateState (ctx context.Context , timeout time.Duration ) error {
171
+ // Prevent collection from occurring while state is updating.
172
+ mc .latestStateMu .Lock ()
173
+ defer mc .latestStateMu .Unlock ()
174
+
175
+ start := time .Now ()
160
176
mc .logger .Debug (ctx , "fetching prebuilds metrics state" )
161
177
fetchCtx , fetchCancel := context .WithTimeout (ctx , timeout )
162
178
defer fetchCancel ()
@@ -170,11 +186,12 @@ func (mc *MetricsCollector) UpdateState(ctx context.Context, timeout time.Durati
170
186
if err != nil {
171
187
return xerrors .Errorf ("snapshot state: %w" , err )
172
188
}
173
- mc .logger .Debug (ctx , "fetched prebuilds metrics state" )
189
+ mc .logger .Debug (ctx , "fetched prebuilds metrics state" , slog . F ( "duration_secs" , fmt . Sprintf ( "%.2f" , time . Since ( start ). Seconds ())) )
174
190
175
- mc .latestState . Store ( & state {
191
+ mc .latestState = & state {
176
192
prebuildMetrics : prebuildMetrics ,
177
193
snapshot : snapshot ,
178
- })
194
+ }
195
+ mc .latestStateUpdatedAt = time .Now ()
179
196
return nil
180
197
}
0 commit comments