-
Notifications
You must be signed in to change notification settings - Fork 894
feat: expose workspace statuses (with details) as a prometheus metric #12762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
843d650
2ed42a3
c31b498
2f5a948
fc61d37
6f95371
f42af07
2cb8ccc
b118044
acd104c
a333f98
c920508
cf14b9d
a94914f
8e6cde9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…levant detail Light refactoring Signed-off-by: Danny Kopping <danny@coder.com>
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,36 +72,42 @@ func ActiveUsers(ctx context.Context, registerer prometheus.Registerer, db datab | |
} | ||
|
||
// Workspaces tracks the total number of workspaces with labels on status. | ||
func Workspaces(ctx context.Context, registerer prometheus.Registerer, db database.Store, duration time.Duration) (func(), error) { | ||
func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.Registerer, db database.Store, duration time.Duration) (func(), error) { | ||
if duration == 0 { | ||
duration = 5 * time.Minute | ||
} | ||
|
||
gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{ | ||
workspacesByStatus := prometheus.NewGaugeVec(prometheus.GaugeOpts{ | ||
Namespace: "coderd", | ||
Subsystem: "api", | ||
Name: "workspace_latest_build_total", | ||
Help: "The latest workspace builds with a status.", | ||
Help: "The current number of workspace builds by status.", | ||
}, []string{"status"}) | ||
err := registerer.Register(gauge) | ||
if err != nil { | ||
if err := registerer.Register(workspacesByStatus); err != nil { | ||
return nil, err | ||
} | ||
|
||
workspacesDetail := prometheus.NewGaugeVec(prometheus.GaugeOpts{ | ||
Namespace: "coderd", | ||
Subsystem: "api", | ||
Name: "workspace_detail", | ||
Help: "The current workspace details by template, transition, owner, and status.", | ||
}, []string{"status", "template_name", "template_version", "workspace_name", "workspace_owner", "workspace_transition"}) | ||
if err := registerer.Register(workspacesDetail); err != nil { | ||
return nil, err | ||
} | ||
// This exists so the prometheus metric exports immediately when set. | ||
// It helps with tests so they don't have to wait for a tick. | ||
gauge.WithLabelValues("pending").Set(0) | ||
workspacesByStatus.WithLabelValues(string(database.ProvisionerJobStatusPending)).Set(0) | ||
workspacesDetail.WithLabelValues(string(database.ProvisionerJobStatusPending), "", "", "", "", "").Set(0) | ||
|
||
ctx, cancelFunc := context.WithCancel(ctx) | ||
done := make(chan struct{}) | ||
|
||
// Use time.Nanosecond to force an initial tick. It will be reset to the | ||
// correct duration after executing once. | ||
ticker := time.NewTicker(time.Nanosecond) | ||
doTick := func() { | ||
defer ticker.Reset(duration) | ||
|
||
updateWorkspacesByStatus := func() { | ||
builds, err := db.GetLatestWorkspaceBuilds(ctx) | ||
if err != nil { | ||
logger.Warn(ctx, "failed to load latest workspace builds", slog.Error(err)) | ||
return | ||
} | ||
jobIDs := make([]uuid.UUID, 0, len(builds)) | ||
|
@@ -110,16 +116,56 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa | |
} | ||
jobs, err := db.GetProvisionerJobsByIDs(ctx, jobIDs) | ||
if err != nil { | ||
ids := make([]string, 0, len(jobIDs)) | ||
for _, id := range jobIDs { | ||
ids = append(ids, id.String()) | ||
} | ||
|
||
logger.Warn(ctx, "failed to load provisioner jobs", slog.F("ids", ids), slog.Error(err)) | ||
return | ||
} | ||
|
||
gauge.Reset() | ||
workspacesByStatus.Reset() | ||
for _, job := range jobs { | ||
status := codersdk.ProvisionerJobStatus(job.JobStatus) | ||
gauge.WithLabelValues(string(status)).Add(1) | ||
workspacesByStatus.WithLabelValues(string(status)).Add(1) | ||
} | ||
} | ||
|
||
updateWorkspacesDetail := func() { | ||
ws, err := db.GetWorkspaces(ctx, database.GetWorkspacesParams{ | ||
Deleted: false, | ||
WithSummary: false, | ||
}) | ||
if err != nil { | ||
logger.Warn(ctx, "failed to load active workspaces", slog.Error(err)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't strictly call it an error because it could be an instance of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Will it also return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Believe so, yes. |
||
return | ||
} | ||
|
||
workspacesDetail.Reset() | ||
for _, w := range ws { | ||
// TODO: there may be a more elegant/idiomatic way to do this? | ||
buildStatus := string(database.ProvisionerJobStatusUnknown) | ||
if val, err := w.LatestBuildStatus.Value(); err == nil { | ||
if status, ok := val.(string); ok { | ||
buildStatus = status | ||
} | ||
} | ||
|
||
workspacesDetail.WithLabelValues(buildStatus, w.TemplateName, w.TemplateVersionName.String, w.Name, w.Username, string(w.LatestBuildTransition)).Set(1) | ||
} | ||
} | ||
|
||
// Use time.Nanosecond to force an initial tick. It will be reset to the | ||
// correct duration after executing once. | ||
ticker := time.NewTicker(time.Nanosecond) | ||
doTick := func() { | ||
defer ticker.Reset(duration) | ||
|
||
updateWorkspacesByStatus() | ||
updateWorkspacesDetail() | ||
} | ||
|
||
go func() { | ||
defer close(done) | ||
defer ticker.Stop() | ||
|
Uh oh!
There was an error while loading. Please reload this page.