@@ -2,6 +2,7 @@ package prebuilds
22
33import (
44"context"
5+ "fmt"
56"sync/atomic"
67"time"
78
@@ -11,7 +12,6 @@ import (
1112"cdr.dev/slog"
1213
1314"github.com/coder/coder/v2/coderd/database"
14- "github.com/coder/coder/v2/coderd/database/dbauthz"
1515"github.com/coder/coder/v2/coderd/prebuilds"
1616)
1717
5757labels ,
5858nil ,
5959)
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+ )
6066)
6167
6268const (
@@ -91,18 +97,16 @@ func (*MetricsCollector) Describe(descCh chan<- *prometheus.Desc) {
9197descCh <- desiredPrebuildsDesc
9298descCh <- runningPrebuildsDesc
9399descCh <- eligiblePrebuildsDesc
100+ descCh <- lastUpdateDesc
94101}
95102
96103// Collect uses the cached state to set configured metrics.
97104// The state is cached because this function can be called multiple times per second and retrieving the current state
98105// is an expensive operation.
99106func (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 ())
102-
103- currentState := mc .latestState .Load ()
107+ currentState := mc .latestState .Load ()// Grab a copy; it's ok if it goes stale during the course of this func.
104108if currentState == nil {
105- mc .logger .Warn (ctx ,"failed to set prebuilds metrics; state not set" )
109+ mc .logger .Warn (context . Background () ,"failed to set prebuilds metrics; state not set" )
106110return
107111}
108112
@@ -119,7 +123,7 @@ func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
119123
120124presetSnapshot ,err := currentState .snapshot .FilterByPreset (preset .ID )
121125if err != nil {
122- mc .logger .Error (ctx ,"failed to filter by preset" ,slog .Error (err ))
126+ mc .logger .Error (context . Background () ,"failed to filter by preset" ,slog .Error (err ))
123127continue
124128}
125129state := presetSnapshot .CalculateState ()
@@ -128,11 +132,14 @@ func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
128132metricsCh <- prometheus .MustNewConstMetric (runningPrebuildsDesc ,prometheus .GaugeValue ,float64 (state .Actual ),preset .TemplateName ,preset .Name ,preset .OrganizationName )
129133metricsCh <- prometheus .MustNewConstMetric (eligiblePrebuildsDesc ,prometheus .GaugeValue ,float64 (state .Eligible ),preset .TemplateName ,preset .Name ,preset .OrganizationName )
130134}
135+
136+ metricsCh <- prometheus .MustNewConstMetric (lastUpdateDesc ,prometheus .GaugeValue ,float64 (currentState .createdAt .Unix ()))
131137}
132138
133139type state struct {
134140prebuildMetrics []database.GetPrebuildMetricsRow
135141snapshot * prebuilds.GlobalSnapshot
142+ createdAt time.Time
136143}
137144
138145// BackgroundFetch updates the metrics state every given interval.
@@ -157,6 +164,7 @@ func (mc *MetricsCollector) BackgroundFetch(ctx context.Context, updateInterval,
157164
158165// UpdateState builds the current metrics state.
159166func (mc * MetricsCollector )UpdateState (ctx context.Context ,timeout time.Duration )error {
167+ start := time .Now ()
160168mc .logger .Debug (ctx ,"fetching prebuilds metrics state" )
161169fetchCtx ,fetchCancel := context .WithTimeout (ctx ,timeout )
162170defer fetchCancel ()
@@ -170,11 +178,12 @@ func (mc *MetricsCollector) UpdateState(ctx context.Context, timeout time.Durati
170178if err != nil {
171179return xerrors .Errorf ("snapshot state: %w" ,err )
172180}
173- mc .logger .Debug (ctx ,"fetched prebuilds metrics state" )
181+ mc .logger .Debug (ctx ,"fetched prebuilds metrics state" , slog . F ( "duration_secs" , fmt . Sprintf ( "%.2f" , time . Since ( start ). Seconds ())) )
174182
175183mc .latestState .Store (& state {
176184prebuildMetrics :prebuildMetrics ,
177185snapshot :snapshot ,
186+ createdAt :time .Now (),
178187})
179188return nil
180189}