Skip to content

Commit 0da2940

Browse files
committed
Implement stats as a Prometheus collector
1 parent f7617dd commit 0da2940

File tree

4 files changed

+245
-295
lines changed

4 files changed

+245
-295
lines changed

coderd/monitoring/collector.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package monitoring
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"sync"
7+
8+
"github.com/prometheus/client_golang/prometheus"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/coderd/database"
12+
)
13+
14+
// Collector implements prometheus.Collector and collects statistics from the
15+
// provided database.
16+
type Collector struct {
17+
ctx context.Context
18+
db database.Store
19+
users *prometheus.Desc
20+
workspaces *prometheus.Desc
21+
workspaceResources *prometheus.Desc
22+
}
23+
24+
func NewCollector(ctx context.Context, db database.Store) *Collector {
25+
return &Collector{
26+
ctx: ctx,
27+
db: db,
28+
users: prometheus.NewDesc(
29+
"coder_users",
30+
"The users in a Coder deployment.",
31+
nil,
32+
nil,
33+
),
34+
workspaces: prometheus.NewDesc(
35+
"coder_workspaces",
36+
"The workspaces in a Coder deployment.",
37+
nil,
38+
nil,
39+
),
40+
workspaceResources: prometheus.NewDesc(
41+
"coder_workspace_resources",
42+
"The workspace resources in a Coder deployment.",
43+
[]string{
44+
"workspace_resource_type",
45+
},
46+
nil,
47+
),
48+
}
49+
}
50+
51+
// Describe implements prometheus.Collector.
52+
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
53+
ch <- c.users
54+
ch <- c.workspaces
55+
ch <- c.workspaceResources
56+
}
57+
58+
// Collect implements prometheus.Collector.
59+
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
60+
var wg sync.WaitGroup
61+
62+
wg.Add(1)
63+
go func() {
64+
defer wg.Done()
65+
66+
dbUsers, err := c.db.GetUsers(c.ctx, database.GetUsersParams{})
67+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
68+
ch <- prometheus.NewInvalidMetric(c.users, err)
69+
return
70+
}
71+
72+
ch <- prometheus.MustNewConstMetric(
73+
c.users,
74+
prometheus.GaugeValue,
75+
float64(len(dbUsers)),
76+
)
77+
}()
78+
79+
wg.Add(1)
80+
go func() {
81+
defer wg.Done()
82+
83+
dbWorkspaces, err := c.db.GetWorkspaces(c.ctx, false)
84+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
85+
ch <- prometheus.NewInvalidMetric(c.workspaces, err)
86+
return
87+
}
88+
89+
ch <- prometheus.MustNewConstMetric(
90+
c.workspaces,
91+
prometheus.GaugeValue,
92+
float64(len(dbWorkspaces)),
93+
)
94+
}()
95+
96+
wg.Add(1)
97+
go func() {
98+
defer wg.Done()
99+
100+
dbWorkspaceResources, err := c.db.GetWorkspaceResources(c.ctx)
101+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
102+
ch <- prometheus.NewInvalidMetric(c.workspaceResources, err)
103+
return
104+
}
105+
106+
resourcesByType := map[string][]database.WorkspaceResource{}
107+
for _, dbwr := range dbWorkspaceResources {
108+
resourcesByType[dbwr.Type] = append(resourcesByType[dbwr.Type], dbwr)
109+
}
110+
111+
for resourceType, resources := range resourcesByType {
112+
ch <- prometheus.MustNewConstMetric(
113+
c.workspaceResources,
114+
prometheus.GaugeValue,
115+
float64(len(resources)),
116+
resourceType,
117+
)
118+
}
119+
}()
120+
121+
wg.Wait()
122+
}

coderd/monitoring/collector_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package monitoring_test
2+
3+
import (
4+
"context"
5+
"strings"
6+
"testing"
7+
8+
"github.com/coder/coder/coderd/database"
9+
"github.com/coder/coder/coderd/database/databasefake"
10+
"github.com/coder/coder/coderd/monitoring"
11+
"github.com/google/uuid"
12+
"github.com/prometheus/client_golang/prometheus/testutil"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestCollector(t *testing.T) {
17+
t.Parallel()
18+
19+
ctx, cancel := context.WithCancel(context.Background())
20+
defer cancel()
21+
db := databasefake.New()
22+
populateDB(ctx, db)
23+
24+
collector := monitoring.NewCollector(ctx, db)
25+
expected := `
26+
# HELP coder_users The users in a Coder deployment.
27+
# TYPE coder_users gauge
28+
coder_users 1
29+
# HELP coder_workspace_resources The workspace resources in a Coder deployment.
30+
# TYPE coder_workspace_resources gauge
31+
coder_workspace_resources{workspace_resource_type="google_compute_instance"} 2
32+
# HELP coder_workspaces The workspaces in a Coder deployment.
33+
# TYPE coder_workspaces gauge
34+
coder_workspaces 2
35+
`
36+
require.NoError(t, testutil.CollectAndCompare(collector, strings.NewReader(expected)))
37+
}
38+
39+
func populateDB(ctx context.Context, db database.Store) {
40+
user, _ := db.InsertUser(ctx, database.InsertUserParams{
41+
ID: uuid.New(),
42+
Username: "kyle",
43+
})
44+
org, _ := db.InsertOrganization(ctx, database.InsertOrganizationParams{
45+
ID: uuid.New(),
46+
Name: "potato",
47+
})
48+
template, _ := db.InsertTemplate(ctx, database.InsertTemplateParams{
49+
ID: uuid.New(),
50+
Name: "something",
51+
OrganizationID: org.ID,
52+
})
53+
workspace, _ := db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
54+
ID: uuid.New(),
55+
OwnerID: user.ID,
56+
OrganizationID: org.ID,
57+
TemplateID: template.ID,
58+
Name: "banana1",
59+
})
60+
job, _ := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
61+
ID: uuid.New(),
62+
OrganizationID: org.ID,
63+
})
64+
version, _ := db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
65+
ID: uuid.New(),
66+
TemplateID: uuid.NullUUID{
67+
UUID: template.ID,
68+
Valid: true,
69+
},
70+
CreatedAt: database.Now(),
71+
OrganizationID: org.ID,
72+
JobID: job.ID,
73+
})
74+
db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
75+
ID: uuid.New(),
76+
JobID: job.ID,
77+
WorkspaceID: workspace.ID,
78+
TemplateVersionID: version.ID,
79+
Transition: database.WorkspaceTransitionStart,
80+
})
81+
db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
82+
ID: uuid.New(),
83+
JobID: job.ID,
84+
Type: "google_compute_instance",
85+
Name: "banana2",
86+
})
87+
db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
88+
ID: uuid.New(),
89+
JobID: job.ID,
90+
Type: "google_compute_instance",
91+
Name: "banana3",
92+
})
93+
db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
94+
ID: uuid.New(),
95+
OwnerID: user.ID,
96+
OrganizationID: org.ID,
97+
TemplateID: template.ID,
98+
Name: "banana4",
99+
})
100+
}

0 commit comments

Comments
 (0)