Skip to content

feat: expose parameter insights as Prometheus metrics #10574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 9, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
mtojek committed Nov 8, 2023
commit f4343e6c21888e69af8aeddfdb9b678a112dbf18
82 changes: 79 additions & 3 deletions coderd/prometheusmetrics/insights/metricscollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (

"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"

"cdr.dev/slog"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
)

Expand All @@ -34,10 +36,19 @@ type MetricsCollector struct {
type insightsData struct {
templates []database.GetTemplateInsightsByTemplateRow
apps []database.GetTemplateAppInsightsByTemplateRow
params []parameterRow

templateNames map[uuid.UUID]string
}

type parameterRow struct {
templateID uuid.UUID
name string
value string

count int64
}

var _ prometheus.Collector = new(MetricsCollector)

func NewMetricsCollector(db database.Store, logger slog.Logger, timeWindow time.Duration, tickInterval time.Duration) (*MetricsCollector, error) {
Expand Down Expand Up @@ -80,6 +91,7 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {

var templateInsights []database.GetTemplateInsightsByTemplateRow
var appInsights []database.GetTemplateAppInsightsByTemplateRow
var paramInsights []parameterRow

eg.Go(func() error {
var err error
Expand All @@ -105,7 +117,14 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
})
eg.Go(func() error {
var err error

rows, err := mc.database.GetTemplateParameterInsights(egCtx, database.GetTemplateParameterInsightsParams{
StartTime: startTime,
EndTime: endTime,
})
if err != nil {
mc.logger.Error(ctx, "unable to fetch parameter insights from database", slog.Error(err))
}
paramInsights = convertParameterInsights(rows)
return err
})
err := eg.Wait()
Expand All @@ -114,7 +133,7 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
}

// Phase 2: Collect template IDs, and fetch relevant details
templateIDs := uniqueTemplateIDs(templateInsights, appInsights)
templateIDs := uniqueTemplateIDs(templateInsights, appInsights, paramInsights)

templateNames := make(map[uuid.UUID]string, len(templateIDs))
if len(templateIDs) > 0 {
Expand All @@ -132,6 +151,7 @@ func (mc *MetricsCollector) Run(ctx context.Context) (func(), error) {
mc.data.Store(&insightsData{
templates: templateInsights,
apps: appInsights,
params: paramInsights,

templateNames: templateNames,
})
Expand Down Expand Up @@ -207,18 +227,26 @@ func (mc *MetricsCollector) Collect(metricsCh chan<- prometheus.Metric) {
for _, templateRow := range data.templates {
metricsCh <- prometheus.MustNewConstMetric(templatesActiveUsersDesc, prometheus.GaugeValue, float64(templateRow.ActiveUsers), data.templateNames[templateRow.TemplateID])
}

// Parameters
for _, parameterRow := range data.params {
metricsCh <- prometheus.MustNewConstMetric(parametersDesc, prometheus.GaugeValue, float64(parameterRow.count), data.templateNames[parameterRow.templateID], parameterRow.name, parameterRow.value)
}
}

// Helper functions below.

func uniqueTemplateIDs(templateInsights []database.GetTemplateInsightsByTemplateRow, appInsights []database.GetTemplateAppInsightsByTemplateRow) []uuid.UUID {
func uniqueTemplateIDs(templateInsights []database.GetTemplateInsightsByTemplateRow, appInsights []database.GetTemplateAppInsightsByTemplateRow, paramInsights []parameterRow) []uuid.UUID {
tids := map[uuid.UUID]bool{}
for _, t := range templateInsights {
tids[t.TemplateID] = true
}
for _, t := range appInsights {
tids[t.TemplateID] = true
}
for _, t := range paramInsights {
tids[t.templateID] = true
}

uniqueUUIDs := make([]uuid.UUID, len(tids))
var i int
Expand All @@ -236,3 +264,51 @@ func onlyTemplateNames(templates []database.Template) map[uuid.UUID]string {
}
return m
}

func convertParameterInsights(rows []database.GetTemplateParameterInsightsRow) []parameterRow {
type uniqueKey struct {
templateID uuid.UUID
parameterName string
parameterValue string
}

m := map[uniqueKey]int64{}
for _, r := range rows {
for _, t := range r.TemplateIDs {
key := uniqueKey{
templateID: t,
parameterName: r.Name,
parameterValue: r.Value,
}

if _, ok := m[key]; !ok {
m[key] = 0
}
m[key] = m[key] + r.Count
}
}

converted := make([]parameterRow, len(m))
var i int
for k, c := range m {
converted[i] = parameterRow{
templateID: k.templateID,
name: k.parameterName,
value: k.parameterValue,
count: c,
}
i++
}

slices.SortFunc(converted, func(a, b parameterRow) int {
if a.templateID != b.templateID {
return slice.Ascending(a.templateID.String(), b.templateID.String())
}
if a.name != b.name {
return slice.Ascending(a.name, b.name)
}
return slice.Ascending(a.value, b.value)
})

return converted
}