Thanks to visit codestin.com
Credit goes to github.com

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)