@@ -256,6 +256,9 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
256
256
if err != nil {
257
257
return xerrors .Errorf ("determine current snapshot: %w" ,err )
258
258
}
259
+
260
+ c .reportHardLimitedPresets (snapshot )
261
+
259
262
if len (snapshot .Presets )== 0 {
260
263
logger .Debug (ctx ,"no templates found with prebuilds configured" )
261
264
return nil
@@ -296,6 +299,56 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
296
299
return err
297
300
}
298
301
302
+ // Report a metric only if the preset uses the latest version of the template and the template is not deleted.
303
+ // This avoids conflicts between metrics from old and new template versions.
304
+ //
305
+ // NOTE: Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
306
+ // because templates can have multiple versions — or deleted templates can share the same name.
307
+ //
308
+ // The safest approach is to report the metric only for the latest version of the preset.
309
+ // When a new template version is released, the metric for the new preset should overwrite
310
+ // the old value in Prometheus.
311
+ //
312
+ // However, there’s one edge case: if an admin creates a template, it becomes hard-limited,
313
+ // then deletes the template and never creates another with the same name,
314
+ // the old preset will continue to be reported as hard-limited —
315
+ // even though it’s deleted. This will persist until `coderd` is restarted.
316
+
317
+ func (c * StoreReconciler )reportHardLimitedPresets (snapshot * prebuilds.GlobalSnapshot ) {
318
+ // presetsMap is a map from key (orgName:templateName:presetName) to list of corresponding presets.
319
+ // Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
320
+ // because templates can have multiple versions — or deleted templates can share the same name.
321
+ presetsMap := make (map [hardLimitedPresetKey ][]database.GetTemplatePresetsWithPrebuildsRow )
322
+ for _ ,preset := range snapshot .Presets {
323
+ key := hardLimitedPresetKey {
324
+ orgName :preset .OrganizationName ,
325
+ templateName :preset .TemplateName ,
326
+ presetName :preset .Name ,
327
+ }
328
+
329
+ presetsMap [key ]= append (presetsMap [key ],preset )
330
+ }
331
+
332
+ // Report a preset as hard-limited only if all the following conditions are met:
333
+ // - The preset is marked as hard-limited
334
+ // - The preset is using the active version of its template, and the template has not been deleted
335
+ //
336
+ // The second condition is important because a hard-limited preset that has become outdated is no longer relevant.
337
+ // Its associated prebuilt workspaces were likely deleted, and it's not meaningful to continue reporting it
338
+ // as hard-limited to the admin.
339
+ isPresetHardLimited := make (map [hardLimitedPresetKey ]bool )
340
+ for key ,presets := range presetsMap {
341
+ for _ ,preset := range presets {
342
+ if preset .UsingActiveVersion && ! preset .Deleted && snapshot .IsHardLimited (preset .ID ) {
343
+ isPresetHardLimited [key ]= true
344
+ break
345
+ }
346
+ }
347
+ }
348
+
349
+ c .metrics .registerHardLimitedPresets (isPresetHardLimited )
350
+ }
351
+
299
352
// SnapshotState captures the current state of all prebuilds across templates.
300
353
func (c * StoreReconciler )SnapshotState (ctx context.Context ,store database.Store ) (* prebuilds.GlobalSnapshot ,error ) {
301
354
if err := ctx .Err ();err != nil {
@@ -375,9 +428,9 @@ func (c *StoreReconciler) ReconcilePreset(ctx context.Context, ps prebuilds.Pres
375
428
// then deletes the template and never creates another with the same name,
376
429
// the old preset will continue to be reported as hard-limited —
377
430
// even though it’s deleted. This will persist until `coderd` is restarted.
378
- if ps .Preset .UsingActiveVersion && ! ps .Preset .Deleted {
379
- c .metrics .trackHardLimitedStatus (ps .Preset .OrganizationName ,ps .Preset .TemplateName ,ps .Preset .Name ,ps .IsHardLimited )
380
- }
431
+ // if ps.Preset.UsingActiveVersion && !ps.Preset.Deleted {
432
+ // c.metrics.trackHardLimitedStatus(ps.Preset.OrganizationName, ps.Preset.TemplateName, ps.Preset.Name, ps.IsHardLimited)
433
+ // }
381
434
382
435
// If the preset reached the hard failure limit for the first time during this iteration:
383
436
// - Mark it as hard-limited in the database