Skip to content

Commit 7bdb8ff

Browse files
authored
feat: Add workspace metrics export to Prometheus (#3421)
This adds workspace totals indexed by status. It could be any codersdk.ProvisionerJobStatus.
1 parent e62677e commit 7bdb8ff

File tree

10 files changed

+321
-23
lines changed

10 files changed

+321
-23
lines changed

cli/server.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,17 @@ func server() *cobra.Command {
402402
}
403403
if promEnabled {
404404
options.PrometheusRegistry = prometheus.NewRegistry()
405-
closeFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0)
405+
closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0)
406406
if err != nil {
407407
return xerrors.Errorf("register active users prometheus metric: %w", err)
408408
}
409-
defer closeFunc()
409+
defer closeUsersFunc()
410+
411+
closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.PrometheusRegistry, options.Database, 0)
412+
if err != nil {
413+
return xerrors.Errorf("register workspaces prometheus metric: %w", err)
414+
}
415+
defer closeWorkspacesFunc()
410416

411417
//nolint:revive
412418
defer serveHandler(ctx, logger, promhttp.InstrumentMetricHandler(

cli/server_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,16 +416,23 @@ func TestServer(t *testing.T) {
416416

417417
scanner := bufio.NewScanner(res.Body)
418418
hasActiveUsers := false
419+
hasWorkspaces := false
419420
for scanner.Scan() {
420421
// This metric is manually registered to be tracked in the server. That's
421422
// why we test it's tracked here.
422423
if strings.HasPrefix(scanner.Text(), "coderd_api_active_users_duration_hour") {
423424
hasActiveUsers = true
424425
continue
425426
}
427+
if strings.HasPrefix(scanner.Text(), "coderd_api_workspace_latest_build_total") {
428+
hasWorkspaces = true
429+
continue
430+
}
431+
t.Logf("scanned %s", scanner.Text())
426432
}
427433
require.NoError(t, scanner.Err())
428434
require.True(t, hasActiveUsers)
435+
require.True(t, hasWorkspaces)
429436
cancelFunc()
430437
<-serverErr
431438
})

coderd/database/databasefake/databasefake.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,32 @@ func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(_ context.Context, wo
600600
return row, nil
601601
}
602602

603+
func (q *fakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.WorkspaceBuild, error) {
604+
q.mutex.RLock()
605+
defer q.mutex.RUnlock()
606+
607+
builds := make(map[uuid.UUID]database.WorkspaceBuild)
608+
buildNumbers := make(map[uuid.UUID]int32)
609+
for _, workspaceBuild := range q.workspaceBuilds {
610+
id := workspaceBuild.WorkspaceID
611+
if workspaceBuild.BuildNumber > buildNumbers[id] {
612+
builds[id] = workspaceBuild
613+
buildNumbers[id] = workspaceBuild.BuildNumber
614+
}
615+
}
616+
var returnBuilds []database.WorkspaceBuild
617+
for i, n := range buildNumbers {
618+
if n > 0 {
619+
b := builds[i]
620+
returnBuilds = append(returnBuilds, b)
621+
}
622+
}
623+
if len(returnBuilds) == 0 {
624+
return nil, sql.ErrNoRows
625+
}
626+
return returnBuilds, nil
627+
}
628+
603629
func (q *fakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) {
604630
q.mutex.RLock()
605631
defer q.mutex.RUnlock()

coderd/database/modelmethods.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package database
22

3-
import "github.com/coder/coder/coderd/rbac"
3+
import (
4+
"github.com/coder/coder/coderd/rbac"
5+
)
46

57
func (t Template) RBACObject() rbac.Object {
68
return rbac.ResourceTemplate.InOrg(t.OrganizationID).WithID(t.ID.String())

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspacebuilds.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ JOIN
9999
workspace_builds wb
100100
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
101101

102+
-- name: GetLatestWorkspaceBuilds :many
103+
SELECT wb.*
104+
FROM (
105+
SELECT
106+
workspace_id, MAX(build_number) as max_build_number
107+
FROM
108+
workspace_builds
109+
GROUP BY
110+
workspace_id
111+
) m
112+
JOIN
113+
workspace_builds wb
114+
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
102115

103116
-- name: InsertWorkspaceBuild :one
104117
INSERT INTO

coderd/prometheusmetrics/prometheusmetrics.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/google/uuid"
88
"github.com/prometheus/client_golang/prometheus"
99

10+
"github.com/coder/coder/coderd"
1011
"github.com/coder/coder/coderd/database"
1112
)
1213

@@ -50,3 +51,56 @@ func ActiveUsers(ctx context.Context, registerer prometheus.Registerer, db datab
5051
}()
5152
return cancelFunc, nil
5253
}
54+
55+
// Workspaces tracks the total number of workspaces with labels on status.
56+
func Workspaces(ctx context.Context, registerer prometheus.Registerer, db database.Store, duration time.Duration) (context.CancelFunc, error) {
57+
if duration == 0 {
58+
duration = 5 * time.Minute
59+
}
60+
61+
gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
62+
Namespace: "coderd",
63+
Subsystem: "api",
64+
Name: "workspace_latest_build_total",
65+
Help: "The latest workspace builds with a status.",
66+
}, []string{"status"})
67+
err := registerer.Register(gauge)
68+
if err != nil {
69+
return nil, err
70+
}
71+
// This exists so the prometheus metric exports immediately when set.
72+
// It helps with tests so they don't have to wait for a tick.
73+
gauge.WithLabelValues("pending").Set(0)
74+
75+
ctx, cancelFunc := context.WithCancel(ctx)
76+
ticker := time.NewTicker(duration)
77+
go func() {
78+
defer ticker.Stop()
79+
for {
80+
select {
81+
case <-ctx.Done():
82+
return
83+
case <-ticker.C:
84+
}
85+
builds, err := db.GetLatestWorkspaceBuilds(ctx)
86+
if err != nil {
87+
continue
88+
}
89+
jobIDs := make([]uuid.UUID, 0, len(builds))
90+
for _, build := range builds {
91+
jobIDs = append(jobIDs, build.JobID)
92+
}
93+
jobs, err := db.GetProvisionerJobsByIDs(ctx, jobIDs)
94+
if err != nil {
95+
continue
96+
}
97+
98+
gauge.Reset()
99+
for _, job := range jobs {
100+
status := coderd.ConvertProvisionerJobStatus(job)
101+
gauge.WithLabelValues(string(status)).Add(1)
102+
}
103+
}
104+
}()
105+
return cancelFunc, nil
106+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy