@@ -72,36 +72,42 @@ func ActiveUsers(ctx context.Context, registerer prometheus.Registerer, db datab
72
72
}
73
73
74
74
// Workspaces tracks the total number of workspaces with labels on status.
75
- func Workspaces (ctx context.Context , registerer prometheus.Registerer , db database.Store , duration time.Duration ) (func (), error ) {
75
+ func Workspaces (ctx context.Context , logger slog. Logger , registerer prometheus.Registerer , db database.Store , duration time.Duration ) (func (), error ) {
76
76
if duration == 0 {
77
77
duration = 5 * time .Minute
78
78
}
79
79
80
- gauge := prometheus .NewGaugeVec (prometheus.GaugeOpts {
80
+ workspacesByStatus := prometheus .NewGaugeVec (prometheus.GaugeOpts {
81
81
Namespace : "coderd" ,
82
82
Subsystem : "api" ,
83
83
Name : "workspace_latest_build_total" ,
84
- Help : "The latest workspace builds with a status." ,
84
+ Help : "The current number of workspace builds by status." ,
85
85
}, []string {"status" })
86
- err := registerer .Register (gauge )
87
- if err != nil {
86
+ if err := registerer .Register (workspacesByStatus ); err != nil {
87
+ return nil , err
88
+ }
89
+
90
+ workspacesDetail := prometheus .NewGaugeVec (prometheus.GaugeOpts {
91
+ Namespace : "coderd" ,
92
+ Subsystem : "api" ,
93
+ Name : "workspace_detail" ,
94
+ Help : "The current workspace details by template, transition, owner, and status." ,
95
+ }, []string {"status" , "template_name" , "template_version" , "workspace_name" , "workspace_owner" , "workspace_transition" })
96
+ if err := registerer .Register (workspacesDetail ); err != nil {
88
97
return nil , err
89
98
}
90
99
// This exists so the prometheus metric exports immediately when set.
91
100
// It helps with tests so they don't have to wait for a tick.
92
- gauge .WithLabelValues ("pending" ).Set (0 )
101
+ workspacesByStatus .WithLabelValues (string (database .ProvisionerJobStatusPending )).Set (0 )
102
+ workspacesDetail .WithLabelValues (string (database .ProvisionerJobStatusPending ), "" , "" , "" , "" , "" ).Set (0 )
93
103
94
104
ctx , cancelFunc := context .WithCancel (ctx )
95
105
done := make (chan struct {})
96
106
97
- // Use time.Nanosecond to force an initial tick. It will be reset to the
98
- // correct duration after executing once.
99
- ticker := time .NewTicker (time .Nanosecond )
100
- doTick := func () {
101
- defer ticker .Reset (duration )
102
-
107
+ updateWorkspacesByStatus := func () {
103
108
builds , err := db .GetLatestWorkspaceBuilds (ctx )
104
109
if err != nil {
110
+ logger .Warn (ctx , "failed to load latest workspace builds" , slog .Error (err ))
105
111
return
106
112
}
107
113
jobIDs := make ([]uuid.UUID , 0 , len (builds ))
@@ -110,16 +116,56 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa
110
116
}
111
117
jobs , err := db .GetProvisionerJobsByIDs (ctx , jobIDs )
112
118
if err != nil {
119
+ ids := make ([]string , 0 , len (jobIDs ))
120
+ for _ , id := range jobIDs {
121
+ ids = append (ids , id .String ())
122
+ }
123
+
124
+ logger .Warn (ctx , "failed to load provisioner jobs" , slog .F ("ids" , ids ), slog .Error (err ))
113
125
return
114
126
}
115
127
116
- gauge .Reset ()
128
+ workspacesByStatus .Reset ()
117
129
for _ , job := range jobs {
118
130
status := codersdk .ProvisionerJobStatus (job .JobStatus )
119
- gauge .WithLabelValues (string (status )).Add (1 )
131
+ workspacesByStatus .WithLabelValues (string (status )).Add (1 )
120
132
}
121
133
}
122
134
135
+ updateWorkspacesDetail := func () {
136
+ ws , err := db .GetWorkspaces (ctx , database.GetWorkspacesParams {
137
+ Deleted : false ,
138
+ WithSummary : false ,
139
+ })
140
+ if err != nil {
141
+ logger .Warn (ctx , "failed to load active workspaces" , slog .Error (err ))
142
+ return
143
+ }
144
+
145
+ workspacesDetail .Reset ()
146
+ for _ , w := range ws {
147
+ // TODO: there may be a more elegant/idiomatic way to do this?
148
+ buildStatus := string (database .ProvisionerJobStatusUnknown )
149
+ if val , err := w .LatestBuildStatus .Value (); err == nil {
150
+ if status , ok := val .(string ); ok {
151
+ buildStatus = status
152
+ }
153
+ }
154
+
155
+ workspacesDetail .WithLabelValues (buildStatus , w .TemplateName , w .TemplateVersionName .String , w .Name , w .Username , string (w .LatestBuildTransition )).Set (1 )
156
+ }
157
+ }
158
+
159
+ // Use time.Nanosecond to force an initial tick. It will be reset to the
160
+ // correct duration after executing once.
161
+ ticker := time .NewTicker (time .Nanosecond )
162
+ doTick := func () {
163
+ defer ticker .Reset (duration )
164
+
165
+ updateWorkspacesByStatus ()
166
+ updateWorkspacesDetail ()
167
+ }
168
+
123
169
go func () {
124
170
defer close (done )
125
171
defer ticker .Stop ()
0 commit comments