Skip to content

Commit 6a42783

Browse files
committed
Implement stats as a Prometheus collector
1 parent f7617dd commit 6a42783

File tree

4 files changed

+250
-295
lines changed

4 files changed

+250
-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

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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(t, ctx, db)
23+
24+
type labels struct {
25+
name string
26+
value string
27+
}
28+
29+
collector := monitoring.NewCollector(ctx, db)
30+
expected := `
31+
# HELP coder_users The users in a Coder deployment.
32+
# TYPE coder_users gauge
33+
coder_users 1
34+
# HELP coder_workspace_resources The workspace resources in a Coder deployment.
35+
# TYPE coder_workspace_resources gauge
36+
coder_workspace_resources{workspace_resource_type="google_compute_instance"} 2
37+
# HELP coder_workspaces The workspaces in a Coder deployment.
38+
# TYPE coder_workspaces gauge
39+
coder_workspaces 2
40+
`
41+
require.NoError(t, testutil.CollectAndCompare(collector, strings.NewReader(expected)))
42+
}
43+
44+
func populateDB(_ *testing.T, ctx context.Context, db database.Store) {
45+
user, _ := db.InsertUser(ctx, database.InsertUserParams{
46+
ID: uuid.New(),
47+
Username: "kyle",
48+
})
49+
org, _ := db.InsertOrganization(ctx, database.InsertOrganizationParams{
50+
ID: uuid.New(),
51+
Name: "potato",
52+
})
53+
template, _ := db.InsertTemplate(ctx, database.InsertTemplateParams{
54+
ID: uuid.New(),
55+
Name: "something",
56+
OrganizationID: org.ID,
57+
})
58+
workspace, _ := db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
59+
ID: uuid.New(),
60+
OwnerID: user.ID,
61+
OrganizationID: org.ID,
62+
TemplateID: template.ID,
63+
Name: "banana1",
64+
})
65+
job, _ := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
66+
ID: uuid.New(),
67+
OrganizationID: org.ID,
68+
})
69+
version, _ := db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
70+
ID: uuid.New(),
71+
TemplateID: uuid.NullUUID{
72+
UUID: template.ID,
73+
Valid: true,
74+
},
75+
CreatedAt: database.Now(),
76+
OrganizationID: org.ID,
77+
JobID: job.ID,
78+
})
79+
db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
80+
ID: uuid.New(),
81+
JobID: job.ID,
82+
WorkspaceID: workspace.ID,
83+
TemplateVersionID: version.ID,
84+
Transition: database.WorkspaceTransitionStart,
85+
})
86+
db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
87+
ID: uuid.New(),
88+
JobID: job.ID,
89+
Type: "google_compute_instance",
90+
Name: "banana2",
91+
})
92+
db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
93+
ID: uuid.New(),
94+
JobID: job.ID,
95+
Type: "google_compute_instance",
96+
Name: "banana3",
97+
})
98+
db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
99+
ID: uuid.New(),
100+
OwnerID: user.ID,
101+
OrganizationID: org.ID,
102+
TemplateID: template.ID,
103+
Name: "banana4",
104+
})
105+
}

0 commit comments

Comments
 (0)