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

Skip to content

Commit ed7e43b

Browse files
authored
feat: expose parameter insights as Prometheus metrics (coder#10574)
1 parent e23873f commit ed7e43b

File tree

3 files changed

+118
-6
lines changed

3 files changed

+118
-6
lines changed

coderd/prometheusmetrics/insights/metricscollector.go

+90-3
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ import (
77

88
"github.com/google/uuid"
99
"github.com/prometheus/client_golang/prometheus"
10+
"golang.org/x/exp/slices"
1011
"golang.org/x/sync/errgroup"
1112
"golang.org/x/xerrors"
1213

1314
"cdr.dev/slog"
1415

1516
"github.com/coder/coder/v2/coderd/database"
17+
"github.com/coder/coder/v2/coderd/util/slice"
1618
"github.com/coder/coder/v2/codersdk"
1719
)
1820

1921
var (
2022
templatesActiveUsersDesc = prometheus.NewDesc("coderd_insights_templates_active_users", "The number of active users of the template.", []string{"template_name"}, nil)
2123
applicationsUsageSecondsDesc = prometheus.NewDesc("coderd_insights_applications_usage_seconds", "The application usage per template.", []string{"template_name", "application_name", "slug"}, nil)
24+
parametersDesc = prometheus.NewDesc("coderd_insights_parameters", "The parameter usage per template.", []string{"template_name", "parameter_name", "parameter_type", "parameter_value"}, nil)
2225
)
2326

2427
type MetricsCollector struct {
@@ -33,10 +36,20 @@ type MetricsCollector struct {
3336
type insightsData struct {
3437
templates []database.GetTemplateInsightsByTemplateRow
3538
apps []database.GetTemplateAppInsightsByTemplateRow
39+
params []parameterRow
3640

3741
templateNames map[uuid.UUID]string
3842
}
3943

44+
type parameterRow struct {
45+
templateID uuid.UUID
46+
name string
47+
aType string
48+
value string
49+
50+
count int64
51+
}
52+
4053
var _ prometheus.Collector = new(MetricsCollector)
4154

4255
func NewMetricsCollector(db database.Store, logger slog.Logger, timeWindow time.Duration, tickInterval time.Duration) (*MetricsCollector, error) {
@@ -75,10 +88,11 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
7588
// Phase 1: Fetch insights from database
7689
// FIXME errorGroup will be used to fetch insights for apps and parameters
7790
eg, egCtx := errgroup.WithContext(ctx)
78-
eg.SetLimit(2)
91+
eg.SetLimit(3)
7992

8093
var templateInsights []database.GetTemplateInsightsByTemplateRow
8194
var appInsights []database.GetTemplateAppInsightsByTemplateRow
95+
var paramInsights []parameterRow
8296

8397
eg.Go(func() error {
8498
var err error
@@ -102,13 +116,25 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
102116
}
103117
return err
104118
})
119+
eg.Go(func() error {
120+
var err error
121+
rows, err := mc.database.GetTemplateParameterInsights(egCtx, database.GetTemplateParameterInsightsParams{
122+
StartTime: startTime,
123+
EndTime: endTime,
124+
})
125+
if err != nil {
126+
mc.logger.Error(ctx, "unable to fetch parameter insights from database", slog.Error(err))
127+
}
128+
paramInsights = convertParameterInsights(rows)
129+
return err
130+
})
105131
err := eg.Wait()
106132
if err != nil {
107133
return
108134
}
109135

110136
// Phase 2: Collect template IDs, and fetch relevant details
111-
templateIDs := uniqueTemplateIDs(templateInsights, appInsights)
137+
templateIDs := uniqueTemplateIDs(templateInsights, appInsights, paramInsights)
112138

113139
templateNames := make(map[uuid.UUID]string, len(templateIDs))
114140
if len(templateIDs) > 0 {
@@ -126,6 +152,7 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
126152
mc.data.Store(&insightsData{
127153
templates: templateInsights,
128154
apps: appInsights,
155+
params: paramInsights,
129156

130157
templateNames: templateNames,
131158
})
@@ -153,6 +180,7 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
153180
func (*MetricsCollector) Describe(descCh chan<- *prometheus.Desc) {
154181
descCh <- templatesActiveUsersDesc
155182
descCh <- applicationsUsageSecondsDesc
183+
descCh <- parametersDesc
156184
}
157185

158186
func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
@@ -200,18 +228,26 @@ func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
200228
for _, templateRow := range data.templates {
201229
metricsCh <- prometheus.MustNewConstMetric(templatesActiveUsersDesc, prometheus.GaugeValue, float64(templateRow.ActiveUsers), data.templateNames[templateRow.TemplateID])
202230
}
231+
232+
// Parameters
233+
for _, parameterRow := range data.params {
234+
metricsCh <- prometheus.MustNewConstMetric(parametersDesc, prometheus.GaugeValue, float64(parameterRow.count), data.templateNames[parameterRow.templateID], parameterRow.name, parameterRow.aType, parameterRow.value)
235+
}
203236
}
204237

205238
// Helper functions below.
206239

207-
func uniqueTemplateIDs(templateInsights []database.GetTemplateInsightsByTemplateRow, appInsights []database.GetTemplateAppInsightsByTemplateRow) []uuid.UUID {
240+
func uniqueTemplateIDs(templateInsights []database.GetTemplateInsightsByTemplateRow, appInsights []database.GetTemplateAppInsightsByTemplateRow, paramInsights []parameterRow) []uuid.UUID {
208241
tids := map[uuid.UUID]bool{}
209242
for _, t := range templateInsights {
210243
tids[t.TemplateID] = true
211244
}
212245
for _, t := range appInsights {
213246
tids[t.TemplateID] = true
214247
}
248+
for _, t := range paramInsights {
249+
tids[t.templateID] = true
250+
}
215251

216252
uniqueUUIDs := make([]uuid.UUID, len(tids))
217253
var i int
@@ -229,3 +265,54 @@ func onlyTemplateNames(templates []database.Template) map[uuid.UUID]string {
229265
}
230266
return m
231267
}
268+
269+
func convertParameterInsights(rows []database.GetTemplateParameterInsightsRow) []parameterRow {
270+
type uniqueKey struct {
271+
templateID uuid.UUID
272+
parameterName string
273+
parameterType string
274+
parameterValue string
275+
}
276+
277+
m := map[uniqueKey]int64{}
278+
for _, r := range rows {
279+
for _, t := range r.TemplateIDs {
280+
key := uniqueKey{
281+
templateID: t,
282+
parameterName: r.Name,
283+
parameterType: r.Type,
284+
parameterValue: r.Value,
285+
}
286+
287+
if _, ok := m[key]; !ok {
288+
m[key] = 0
289+
}
290+
m[key] = m[key] + r.Count
291+
}
292+
}
293+
294+
converted := make([]parameterRow, len(m))
295+
var i int
296+
for k, c := range m {
297+
converted[i] = parameterRow{
298+
templateID: k.templateID,
299+
name: k.parameterName,
300+
aType: k.parameterType,
301+
value: k.parameterValue,
302+
count: c,
303+
}
304+
i++
305+
}
306+
307+
slices.SortFunc(converted, func(a, b parameterRow) int {
308+
if a.templateID != b.templateID {
309+
return slice.Ascending(a.templateID.String(), b.templateID.String())
310+
}
311+
if a.name != b.name {
312+
return slice.Ascending(a.name, b.name)
313+
}
314+
return slice.Ascending(a.value, b.value)
315+
})
316+
317+
return converted
318+
}

coderd/prometheusmetrics/insights/metricscollector_test.go

+25-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestCollectInsights(t *testing.T) {
5757
authToken := uuid.NewString()
5858
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
5959
Parse: echo.ParseComplete,
60-
ProvisionPlan: echo.PlanComplete,
60+
ProvisionPlan: provisionPlanWithParameters(),
6161
ProvisionApply: provisionApplyWithAgentAndApp(authToken),
6262
})
6363
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
@@ -66,7 +66,13 @@ func TestCollectInsights(t *testing.T) {
6666
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
6767

6868
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
69-
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
69+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
70+
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
71+
{Name: "first_parameter", Value: "Foobar"},
72+
{Name: "second_parameter", Value: "true"},
73+
{Name: "third_parameter", Value: "789"},
74+
}
75+
})
7076
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
7177

7278
// Start an agent so that we can generate stats.
@@ -142,7 +148,7 @@ func TestCollectInsights(t *testing.T) {
142148
// Then
143149
for _, metric := range metrics {
144150
switch metric.GetName() {
145-
case "coderd_insights_applications_usage_seconds", "coderd_insights_templates_active_users":
151+
case "coderd_insights_applications_usage_seconds", "coderd_insights_templates_active_users", "coderd_insights_parameters":
146152
for _, m := range metric.Metric {
147153
key := metric.GetName()
148154
if len(m.Label) > 0 {
@@ -167,6 +173,22 @@ func metricLabelAsString(m *io_prometheus_client.Metric) string {
167173
return strings.Join(labels, ",")
168174
}
169175

176+
func provisionPlanWithParameters() []*proto.Response {
177+
return []*proto.Response{
178+
{
179+
Type: &proto.Response_Plan{
180+
Plan: &proto.PlanComplete{
181+
Parameters: []*proto.RichParameter{
182+
{Name: "first_parameter", Type: "string", Mutable: true},
183+
{Name: "second_parameter", Type: "bool", Mutable: true},
184+
{Name: "third_parameter", Type: "number", Mutable: true},
185+
},
186+
},
187+
},
188+
},
189+
}
190+
}
191+
170192
func provisionApplyWithAgentAndApp(authToken string) []*proto.Response {
171193
return []*proto.Response{{
172194
Type: &proto.Response_Apply{

coderd/prometheusmetrics/insights/testdata/insights-metrics.json

+3
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
"coderd_insights_applications_usage_seconds[application_name=Web Terminal,slug=,template_name=golden-template]": 0,
55
"coderd_insights_applications_usage_seconds[application_name=SSH,slug=,template_name=golden-template]": 60,
66
"coderd_insights_applications_usage_seconds[application_name=Golden Slug,slug=golden-slug,template_name=golden-template]": 180,
7+
"coderd_insights_parameters[parameter_name=first_parameter,parameter_type=string,parameter_value=Foobar,template_name=golden-template]": 1,
8+
"coderd_insights_parameters[parameter_name=second_parameter,parameter_type=bool,parameter_value=true,template_name=golden-template]": 1,
9+
"coderd_insights_parameters[parameter_name=third_parameter,parameter_type=number,parameter_value=789,template_name=golden-template]": 1,
710
"coderd_insights_templates_active_users[template_name=golden-template]": 1
811
}

0 commit comments

Comments
 (0)