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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
72 changes: 72 additions & 0 deletions scripts/metricsdocgen/generated_metrics
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# HELP coder_pubsub_current_events The current number of pubsub event channels listened for
# TYPE coder_pubsub_current_events gauge
coder_pubsub_current_events 0
# HELP coder_pubsub_current_subscribers The current number of active pubsub subscribers
# TYPE coder_pubsub_current_subscribers gauge
coder_pubsub_current_subscribers 0
# HELP coder_pubsub_latency_measure_errs_total The number of pubsub latency measurement failures
# TYPE coder_pubsub_latency_measure_errs_total counter
coder_pubsub_latency_measure_errs_total 0
# HELP coder_pubsub_latency_measures_total The number of pubsub latency measurements
# TYPE coder_pubsub_latency_measures_total counter
coder_pubsub_latency_measures_total 0
# HELP coder_pubsub_receive_latency_seconds The time taken to receive a message from a pubsub event channel
# TYPE coder_pubsub_receive_latency_seconds gauge
coder_pubsub_receive_latency_seconds 0
# HELP coder_pubsub_send_latency_seconds The time taken to send a message into a pubsub event channel
# TYPE coder_pubsub_send_latency_seconds gauge
coder_pubsub_send_latency_seconds 0
# HELP coderd_insights_applications_usage_seconds The application usage per template.
# TYPE coderd_insights_applications_usage_seconds gauge
coderd_insights_applications_usage_seconds{template_name="",application_name="",slug=""} 0
# HELP coderd_insights_parameters The parameter usage per template.
# TYPE coderd_insights_parameters gauge
coderd_insights_parameters{template_name="",parameter_name="",parameter_type="",parameter_value=""} 0
# HELP coderd_insights_templates_active_users The number of active users of the template.
# TYPE coderd_insights_templates_active_users gauge
coderd_insights_templates_active_users{template_name=""} 0
# HELP coderd_license_active_users The number of active users.
# TYPE coderd_license_active_users gauge
coderd_license_active_users 0
# HELP coderd_license_errors The number of active license errors.
# TYPE coderd_license_errors gauge
coderd_license_errors 0
# HELP coderd_license_limit_users The user seats limit based on the active Coder license.
# TYPE coderd_license_limit_users gauge
coderd_license_limit_users 0
# HELP coderd_license_user_limit_enabled Returns 1 if the current license enforces the user limit.
# TYPE coderd_license_user_limit_enabled gauge
coderd_license_user_limit_enabled 0
# HELP coderd_license_warnings The number of active license warnings.
# TYPE coderd_license_warnings gauge
coderd_license_warnings 0
# HELP coderd_prebuilt_workspaces_claimed_total Total number of prebuilt workspaces which were claimed by users. Claiming refers to creating a workspace with a preset selected for which eligible prebuilt workspaces are available and one is reassigned to a user.
# TYPE coderd_prebuilt_workspaces_claimed_total counter
coderd_prebuilt_workspaces_claimed_total{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_created_total Total number of prebuilt workspaces that have been created to meet the desired instance count of each template preset.
# TYPE coderd_prebuilt_workspaces_created_total counter
coderd_prebuilt_workspaces_created_total{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_desired Target number of prebuilt workspaces that should be available for each template preset.
# TYPE coderd_prebuilt_workspaces_desired gauge
coderd_prebuilt_workspaces_desired{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_eligible Current number of prebuilt workspaces that are eligible to be claimed by users. These are workspaces that have completed their build process with their agent reporting 'ready' status.
# TYPE coderd_prebuilt_workspaces_eligible gauge
coderd_prebuilt_workspaces_eligible{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_failed_total Total number of prebuilt workspaces that failed to build.
# TYPE coderd_prebuilt_workspaces_failed_total counter
coderd_prebuilt_workspaces_failed_total{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_metrics_last_updated The unix timestamp when the metrics related to prebuilt workspaces were last updated; these metrics are cached.
# TYPE coderd_prebuilt_workspaces_metrics_last_updated gauge
coderd_prebuilt_workspaces_metrics_last_updated 0
# HELP coderd_prebuilt_workspaces_preset_hard_limited Indicates whether a given preset has reached the hard failure limit (1 = hard-limited). Metric is omitted otherwise.
# TYPE coderd_prebuilt_workspaces_preset_hard_limited gauge
coderd_prebuilt_workspaces_preset_hard_limited{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_reconciliation_paused Indicates whether prebuilds reconciliation is currently paused (1 = paused, 0 = not paused).
# TYPE coderd_prebuilt_workspaces_reconciliation_paused gauge
coderd_prebuilt_workspaces_reconciliation_paused 0
# HELP coderd_prebuilt_workspaces_resource_replacements_total Total number of prebuilt workspaces whose resource(s) got replaced upon being claimed. In Terraform, drift on immutable attributes results in resource replacement. This represents a worst-case scenario for prebuilt workspaces because the pre-provisioned resource would have been recreated when claiming, thus obviating the point of pre-provisioning. See https://coder.com/docs/admin/templates/extending-templates/prebuilt-workspaces#preventing-resource-replacement
# TYPE coderd_prebuilt_workspaces_resource_replacements_total counter
coderd_prebuilt_workspaces_resource_replacements_total{template_name="",preset_name="",organization_name=""} 0
# HELP coderd_prebuilt_workspaces_running Current number of prebuilt workspaces that are in a running state. These workspaces have started successfully but may not yet be claimable by users (see coderd_prebuilt_workspaces_eligible).
# TYPE coderd_prebuilt_workspaces_running gauge
coderd_prebuilt_workspaces_running{template_name="",preset_name="",organization_name=""} 0
185 changes: 182 additions & 3 deletions scripts/metricsdocgen/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ type Metric struct {
Labels []string // Label names for this metric
}

// declarations holds const/var values collected from a file for resolving references.
type declarations struct {
strings map[string]string // string constants/variables
stringSlices map[string][]string // []string variables
}

func main() {
metrics, err := scanAllDirs()
if err != nil {
Expand Down Expand Up @@ -136,6 +142,9 @@ func scanFile(path string) ([]Metric, error) {
return nil, xerrors.Errorf("parsing file: %w", err)
}

// First pass: collect const and var declarations for resolving references.
decls := collectDecls(file)

var metrics []Metric

// Walk the AST looking for metric registration calls.
Expand All @@ -145,7 +154,7 @@ func scanFile(path string) ([]Metric, error) {
return true
}

metric, ok := extractMetricFromCall(call)
metric, ok := extractMetricFromCall(call, decls)
if ok {
metrics = append(metrics, metric)
}
Expand All @@ -156,6 +165,172 @@ func scanFile(path string) ([]Metric, error) {
return metrics, nil
}

// resolveStringExpr attempts to resolve an expression to a string value.
// Examples:
// - "my_metric": "my_metric" (string literal)
// - metricName: resolved value of metricName constant (identifier)
func resolveStringExpr(expr ast.Expr, decls declarations) string {
switch e := expr.(type) {
case *ast.BasicLit:
return strings.Trim(e.Value, `"`)
case *ast.Ident:
return decls.strings[e.Name]
case *ast.BinaryExpr:
return resolveBinaryExpr(e, decls)
}
return ""
}

// resolveBinaryExpr resolves a binary expression (string concatenation) to a string.
// It recursively resolves the left and right operands.
// Example:
// - "coderd_" + "api_" + "requests": "coderd_api_requests"
// - namespace + "_" + metricName: resolved concatenation
func resolveBinaryExpr(expr *ast.BinaryExpr, decls declarations) string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh... I trust you that it works the right way, although some tests would be helpful.

@ssncferreira ssncferreira Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but unfortunately didn't have time for tests. Tthis is currently working as seen by the generated file: scripts/metricsdocgen/generated_metrics. But yes, tests would be great for future metric changes on the codebase.

left := resolveStringExpr(expr.X, decls)
right := resolveStringExpr(expr.Y, decls)
if left != "" && right != "" {
return left + right
}
return ""
}

// extractStringSlice extracts a []string from a composite literal.
// Example:
// - []string{"a", "b", myConst}: ["a", "b", <resolved value of myConst>]
func extractStringSlice(lit *ast.CompositeLit, decls declarations) []string {
var labels []string
for _, elt := range lit.Elts {
if label := resolveStringExpr(elt, decls); label != "" {
labels = append(labels, label)
}
}
return labels
}

// collectDecls collects const and var declarations from a file.
// This is used to resolve constant and variable references in metric definitions.
func collectDecls(file *ast.File) declarations {
decls := declarations{
strings: make(map[string]string),
stringSlices: make(map[string][]string),
}

for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}

for _, spec := range genDecl.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}

for i, name := range valueSpec.Names {
if i >= len(valueSpec.Values) {
continue
}

switch v := valueSpec.Values[i].(type) {
case *ast.BasicLit:
// String literal: const name = "value"
decls.strings[name.Name] = strings.Trim(v.Value, `"`)
case *ast.BinaryExpr:
// Concatenation: const name = prefix + "suffix"
if resolved := resolveBinaryExpr(v, decls); resolved != "" {
decls.strings[name.Name] = resolved
}
case *ast.CompositeLit:
// Slice literal: var labels = []string{"a", "b"}
if resolved := extractStringSlice(v, decls); resolved != nil {
decls.stringSlices[name.Name] = resolved
}
}
}
}
}

return decls
}

// extractLabels extracts label names from an expression passed as an argument
// to a metric constructor. Handles both inline []string literals and
// variable references from decls.
// Examples:
// - []string{"label1", "label2"}: ["label1", "label2"] (inline literal)
// - myLabels: resolved value of myLabels variable (variable reference)
func extractLabels(expr ast.Expr, decls declarations) []string {
switch e := expr.(type) {
case *ast.CompositeLit:
// []string{"label1", "label2"}
return extractStringSlice(e, decls)
case *ast.Ident:
// Variable reference like 'labels'.
if labels, ok := decls.stringSlices[e.Name]; ok {
return labels
}
return nil
}
return nil
}

// extractNewDescMetric extracts a metric from a prometheus.NewDesc() call.
// Pattern: prometheus.NewDesc(name, help, variableLabels, constLabels)
// Currently, coder only uses MustNewConstMetric with NewDesc.
// TODO(ssncferreira): Add support for other MustNewConst* functions if needed.
func extractNewDescMetric(call *ast.CallExpr, decls declarations) (Metric, bool) {
// Check if this is a prometheus.NewDesc call.
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return Metric{}, false
}

// Match calls that are exactly "prometheus.NewDesc()". This checks the local
// package identifier, not the resolved import path. If the prometheus package
// is imported with an alias, this will not match.
ident, ok := sel.X.(*ast.Ident)
if !ok || ident.Name != "prometheus" || sel.Sel.Name != "NewDesc" {
return Metric{}, false
}

// NewDesc requires at least 4 arguments: name, help, variableLabels, constLabels
if len(call.Args) < 4 {
return Metric{}, false
}

// Extract name (first argument).
name := resolveStringExpr(call.Args[0], decls)
if name == "" {
log.Printf("extractNewDescMetric: skipping prometheus.NewDesc() call: could not resolve metric name")
return Metric{}, false
}

// Extract help (second argument).
help := resolveStringExpr(call.Args[1], decls)

// Extract labels (third argument).
labels := extractLabels(call.Args[2], decls)

// Infer metric type from name suffix.
// TODO(ssncferreira): The actual type is determined by the MustNewConst* function
// that uses this descriptor (e.g., MustNewConstMetric with prometheus.CounterValue or
// prometheus.GaugeValue). Currently, coder only uses MustNewConstMetric, so we
// infer the type from naming conventions.
metricType := MetricTypeGauge
if strings.HasSuffix(name, "_total") || strings.HasSuffix(name, "_count") {
metricType = MetricTypeCounter
}

return Metric{
Name: name,
Type: metricType,
Help: help,
Labels: labels,
}, true
}

// extractMetricFromCall attempts to extract a Metric from a function call expression.
// It returns the metric and true if successful, or an empty metric and false if
// the call is not a metric registration.
Expand All @@ -164,9 +339,13 @@ func scanFile(path string) ([]Metric, error) {
// - prometheus.NewDesc() calls
// - prometheus.New*() and prometheus.New*Vec() with *Opts{}
// - promauto.With(reg).New*() and factory.New*() patterns
func extractMetricFromCall(_ *ast.CallExpr) (Metric, bool) {
func extractMetricFromCall(call *ast.CallExpr, decls declarations) (Metric, bool) {
// Check for prometheus.NewDesc() pattern.
if metric, ok := extractNewDescMetric(call, decls); ok {
return metric, true
}

// TODO(ssncferreira): Implement upstack.
// Handle prometheus.NewDesc()
// Handle prometheus.New*Vec() and prometheus.New*() with *Opts{}
// Handle promauto.With(reg).New*() pattern

Expand Down
Loading