From b30e4afd57922eb81b9998160c0361d6b44eba26 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Thu, 8 Jan 2026 20:44:03 +0000 Subject: [PATCH 1/2] feat(scripts/metricsdocgen): add promauto.With() pattern to metrics scanner --- scripts/metricsdocgen/generated_metrics | 111 +++++++++++++++++++++++ scripts/metricsdocgen/scanner/scanner.go | 85 ++++++++++++++++- 2 files changed, 194 insertions(+), 2 deletions(-) diff --git a/scripts/metricsdocgen/generated_metrics b/scripts/metricsdocgen/generated_metrics index d3ef452499d14..2e95afde0062f 100644 --- a/scripts/metricsdocgen/generated_metrics +++ b/scripts/metricsdocgen/generated_metrics @@ -136,12 +136,33 @@ coderd_agentstats_tx_bytes 0 # HELP coderd_api_active_users_duration_hour The number of users that have been active within the last hour. # TYPE coderd_api_active_users_duration_hour gauge coderd_api_active_users_duration_hour 0 +# HELP coderd_api_concurrent_requests The number of concurrent API requests. +# TYPE coderd_api_concurrent_requests gauge +coderd_api_concurrent_requests{method="",path=""} 0 +# HELP coderd_api_concurrent_websockets The total number of concurrent API websockets. +# TYPE coderd_api_concurrent_websockets gauge +coderd_api_concurrent_websockets{path=""} 0 +# HELP coderd_api_request_latencies_seconds Latency distribution of requests in seconds. +# TYPE coderd_api_request_latencies_seconds histogram +coderd_api_request_latencies_seconds{method="",path=""} 0 +# HELP coderd_api_requests_processed_total The total number of processed API requests +# TYPE coderd_api_requests_processed_total counter +coderd_api_requests_processed_total{code="",method="",path=""} 0 # HELP coderd_api_total_user_count The total number of registered users, partitioned by status. # TYPE coderd_api_total_user_count gauge coderd_api_total_user_count{status=""} 0 +# HELP coderd_api_websocket_durations_seconds Websocket duration distribution of requests in seconds. +# TYPE coderd_api_websocket_durations_seconds histogram +coderd_api_websocket_durations_seconds{path=""} 0 # HELP coderd_api_workspace_latest_build The current number of workspace builds by status for all non-deleted workspaces. # TYPE coderd_api_workspace_latest_build gauge coderd_api_workspace_latest_build{status=""} 0 +# HELP coderd_authz_authorize_duration_seconds Duration of the 'Authorize' call in seconds. Only counts calls that succeed. +# TYPE coderd_authz_authorize_duration_seconds histogram +coderd_authz_authorize_duration_seconds{allowed=""} 0 +# HELP coderd_authz_prepare_authorize_duration_seconds Duration of the 'PrepareAuthorize' call in seconds. +# TYPE coderd_authz_prepare_authorize_duration_seconds histogram +coderd_authz_prepare_authorize_duration_seconds 0 # HELP coderd_db_query_counts_total Total number of queries labelled by HTTP route, method, and query name. # TYPE coderd_db_query_counts_total counter coderd_db_query_counts_total{route="",method="",query=""} 0 @@ -187,6 +208,69 @@ 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_lifecycle_autobuild_execution_duration_seconds Duration of each autobuild execution. +# TYPE coderd_lifecycle_autobuild_execution_duration_seconds histogram +coderd_lifecycle_autobuild_execution_duration_seconds 0 +# HELP coderd_notifications_dispatch_attempts_total +# TYPE coderd_notifications_dispatch_attempts_total counter +coderd_notifications_dispatch_attempts_total{method="",notification_template_id="",result=""} 0 +# HELP coderd_notifications_dispatcher_send_seconds The time taken to dispatch notifications. +# TYPE coderd_notifications_dispatcher_send_seconds histogram +coderd_notifications_dispatcher_send_seconds{method=""} 0 +# HELP coderd_notifications_inflight_dispatches The number of dispatch attempts which are currently in progress. +# TYPE coderd_notifications_inflight_dispatches gauge +coderd_notifications_inflight_dispatches{method="",notification_template_id=""} 0 +# HELP coderd_notifications_pending_updates The number of dispatch attempt results waiting to be flushed to the store. +# TYPE coderd_notifications_pending_updates gauge +coderd_notifications_pending_updates 0 +# HELP coderd_notifications_queued_seconds The time elapsed between a notification being enqueued in the store and retrieved for dispatching (measures the latency of the notifications system). This should generally be within CODER_NOTIFICATIONS_FETCH_INTERVAL seconds; higher values for a sustained period indicates delayed processing and CODER_NOTIFICATIONS_LEASE_COUNT can be increased to accommodate this. +# TYPE coderd_notifications_queued_seconds histogram +coderd_notifications_queued_seconds{method=""} 0 +# HELP coderd_notifications_retry_count The count of notification dispatch retry attempts. +# TYPE coderd_notifications_retry_count counter +coderd_notifications_retry_count{method="",notification_template_id=""} 0 +# HELP coderd_notifications_synced_updates_total The number of dispatch attempt results flushed to the store. +# TYPE coderd_notifications_synced_updates_total counter +coderd_notifications_synced_updates_total 0 +# HELP coderd_oauth2_external_requests_rate_limit The total number of allowed requests per interval. +# TYPE coderd_oauth2_external_requests_rate_limit gauge +coderd_oauth2_external_requests_rate_limit{name="",resource=""} 0 +# HELP coderd_oauth2_external_requests_rate_limit_next_reset_unix Unix timestamp for when the next interval starts +# TYPE coderd_oauth2_external_requests_rate_limit_next_reset_unix gauge +coderd_oauth2_external_requests_rate_limit_next_reset_unix{name="",resource=""} 0 +# HELP coderd_oauth2_external_requests_rate_limit_remaining The remaining number of allowed requests in this interval. +# TYPE coderd_oauth2_external_requests_rate_limit_remaining gauge +coderd_oauth2_external_requests_rate_limit_remaining{name="",resource=""} 0 +# HELP coderd_oauth2_external_requests_rate_limit_reset_in_seconds Seconds until the next interval +# TYPE coderd_oauth2_external_requests_rate_limit_reset_in_seconds gauge +coderd_oauth2_external_requests_rate_limit_reset_in_seconds{name="",resource=""} 0 +# HELP coderd_oauth2_external_requests_rate_limit_used The number of requests made in this interval. +# TYPE coderd_oauth2_external_requests_rate_limit_used gauge +coderd_oauth2_external_requests_rate_limit_used{name="",resource=""} 0 +# HELP coderd_oauth2_external_requests_total The total number of api calls made to external oauth2 providers. 'status_code' will be 0 if the request failed with no response. +# TYPE coderd_oauth2_external_requests_total counter +coderd_oauth2_external_requests_total{name="",source="",status_code=""} 0 +# HELP coderd_open_file_refs_current The count of file references currently open in the file cache. Multiple references can be held for the same file. +# TYPE coderd_open_file_refs_current gauge +coderd_open_file_refs_current 0 +# HELP coderd_open_file_refs_total The total number of file references ever opened in the file cache. The 'hit' label indicates if the file was loaded from the cache. +# TYPE coderd_open_file_refs_total counter +coderd_open_file_refs_total{hit=""} 0 +# HELP coderd_open_files_current The count of unique files currently open in the file cache. +# TYPE coderd_open_files_current gauge +coderd_open_files_current 0 +# HELP coderd_open_files_size_bytes_current The current amount of memory of all files currently open in the file cache. +# TYPE coderd_open_files_size_bytes_current gauge +coderd_open_files_size_bytes_current 0 +# HELP coderd_open_files_size_bytes_total The total amount of memory ever opened in the file cache. This number never decrements. +# TYPE coderd_open_files_size_bytes_total counter +coderd_open_files_size_bytes_total 0 +# HELP coderd_open_files_total The total count of unique files ever opened in the file cache. +# TYPE coderd_open_files_total counter +coderd_open_files_total 0 +# HELP coderd_prebuilds_reconciliation_duration_seconds Duration of each prebuilds reconciliation cycle. +# TYPE coderd_prebuilds_reconciliation_duration_seconds histogram +coderd_prebuilds_reconciliation_duration_seconds 0 # HELP coderd_prebuilt_workspace_claim_duration_seconds Time to claim a prebuilt workspace by organization, template, and preset. # TYPE coderd_prebuilt_workspace_claim_duration_seconds histogram coderd_prebuilt_workspace_claim_duration_seconds{organization_name="",template_name="",preset_name=""} 0 @@ -235,6 +319,18 @@ coderd_prometheusmetrics_metrics_aggregator_execution_update_seconds 0 # HELP coderd_prometheusmetrics_metrics_aggregator_store_size The number of metrics stored in the aggregator # TYPE coderd_prometheusmetrics_metrics_aggregator_store_size gauge coderd_prometheusmetrics_metrics_aggregator_store_size 0 +# HELP coderd_provisionerd_job_timings_seconds The provisioner job time duration in seconds. +# TYPE coderd_provisionerd_job_timings_seconds histogram +coderd_provisionerd_job_timings_seconds{provisioner="",status=""} 0 +# HELP coderd_provisionerd_jobs_current The number of currently running provisioner jobs. +# TYPE coderd_provisionerd_jobs_current gauge +coderd_provisionerd_jobs_current{provisioner=""} 0 +# HELP coderd_provisionerd_num_daemons The number of provisioner daemons. +# TYPE coderd_provisionerd_num_daemons gauge +coderd_provisionerd_num_daemons 0 +# HELP coderd_provisionerd_workspace_build_timings_seconds The time taken for a workspace to build. +# TYPE coderd_provisionerd_workspace_build_timings_seconds histogram +coderd_provisionerd_workspace_build_timings_seconds{template_name="",template_version="",workspace_transition="",status=""} 0 # HELP coderd_proxyhealth_health_check_duration_seconds Histogram for duration of proxy health collection in seconds. # TYPE coderd_proxyhealth_health_check_duration_seconds histogram coderd_proxyhealth_health_check_duration_seconds 0 @@ -244,6 +340,9 @@ coderd_proxyhealth_health_check_results{proxy_id=""} 0 # HELP coderd_template_workspace_build_duration_seconds Duration from workspace build creation to agent ready, by template. # TYPE coderd_template_workspace_build_duration_seconds histogram coderd_template_workspace_build_duration_seconds{template_name="",organization_name="",transition="",status="",is_prebuild=""} 0 +# HELP coderd_workspace_builds_total The number of workspaces started, updated, or deleted. +# TYPE coderd_workspace_builds_total counter +coderd_workspace_builds_total{workspace_owner="",workspace_name="",template_name="",template_version="",workspace_transition="",status=""} 0 # HELP coderd_workspace_creation_duration_seconds Time to create a workspace by organization, template, preset, and type (regular or prebuild). # TYPE coderd_workspace_creation_duration_seconds histogram coderd_workspace_creation_duration_seconds{organization_name="",template_name="",preset_name="",type=""} 0 @@ -253,3 +352,15 @@ coderd_workspace_creation_total{organization_name="",template_name="",preset_nam # HELP coderd_workspace_latest_build_status The current workspace statuses by template, transition, and owner for all non-deleted workspaces. # TYPE coderd_workspace_latest_build_status gauge coderd_workspace_latest_build_status{status="",template_name="",template_version="",workspace_owner="",workspace_transition=""} 0 +# HELP connect_sessions_total Total number of CONNECT sessions established. +# TYPE connect_sessions_total counter +connect_sessions_total{type=""} 0 +# HELP inflight_mitm_requests Number of MITM requests currently being processed. +# TYPE inflight_mitm_requests gauge +inflight_mitm_requests{provider=""} 0 +# HELP mitm_requests_total Total number of MITM requests handled by the proxy. +# TYPE mitm_requests_total counter +mitm_requests_total{provider=""} 0 +# HELP mitm_responses_total Total number of MITM responses by HTTP status code class. +# TYPE mitm_responses_total counter +mitm_responses_total{code="",provider=""} 0 diff --git a/scripts/metricsdocgen/scanner/scanner.go b/scripts/metricsdocgen/scanner/scanner.go index e2ce64cb0ff82..00a8f6a2772be 100644 --- a/scripts/metricsdocgen/scanner/scanner.go +++ b/scripts/metricsdocgen/scanner/scanner.go @@ -549,6 +549,85 @@ func extractOptsMetric(call *ast.CallExpr, decls declarations) (Metric, bool) { }, true } +// isPromautoCall checks if an expression is a promauto factory call. +// Matches: +// - promauto.With(reg): direct chained call +// - factory: variable that was assigned from promauto.With() +func isPromautoCall(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.CallExpr: + // Check for promauto.With(reg).New*() + sel, ok := e.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + ident, ok := sel.X.(*ast.Ident) + if !ok { + return false + } + return ident.Name == "promauto" && sel.Sel.Name == "With" + case *ast.Ident: + // Check for factory.New*() where factory is a variable. + // We assume any identifier used as receiver for New*Vec/New* methods + // that isn't "prometheus" is a promauto factory. + // This is a heuristic but works for the codebase patterns. + return e.Name != "prometheus" + } + return false +} + +// extractPromautoMetric extracts a metric from promauto.With().New*() or factory.New*() calls. +// Supported patterns: +// - promauto.With(reg).NewCounterVec(prometheus.CounterOpts{...}, labels) +// - factory.NewGaugeVec(prometheus.GaugeOpts{...}, labels) where factory := promauto.With(reg) +func extractPromautoMetric(call *ast.CallExpr, decls declarations) (Metric, bool) { + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return Metric{}, false + } + + funcName := sel.Sel.Name + metricType, isVec := parseMetricFuncName(funcName) + if metricType == "" { + return Metric{}, false + } + + // Check if this is a promauto call by examining the receiver. + if !isPromautoCall(sel.X) { + return Metric{}, false + } + + // Need at least one argument (the Opts struct). + if len(call.Args) < 1 { + return Metric{}, false + } + + // Extract metric info from the Opts struct. + opts, ok := extractOpts(call.Args[0], decls) + if !ok { + return Metric{}, false + } + + // Extract labels for Vec types. + var labels []string + if isVec && len(call.Args) >= 2 { + labels = extractLabels(call.Args[1], decls) + } + + // Build the full metric name. + name := buildMetricName(opts.Namespace, opts.Subsystem, opts.Name) + if name == "" { + return Metric{}, false + } + + return Metric{ + Name: name, + Type: metricType, + Help: opts.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. @@ -568,8 +647,10 @@ func extractMetricFromCall(call *ast.CallExpr, decls declarations) (Metric, bool return metric, true } - // TODO(ssncferreira): Implement upstack. - // Handle promauto.With(reg).New*() pattern + // Check for promauto.With(reg).New*() pattern. + if metric, ok := extractPromautoMetric(call, decls); ok { + return metric, true + } return Metric{}, false } From 3a022d49aed37b737b401933ec36d1e4556a212f Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Thu, 12 Feb 2026 18:51:32 +0000 Subject: [PATCH 2/2] chore: improve comments and add logging --- scripts/metricsdocgen/scanner/scanner.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/metricsdocgen/scanner/scanner.go b/scripts/metricsdocgen/scanner/scanner.go index 00a8f6a2772be..87de92760e6dd 100644 --- a/scripts/metricsdocgen/scanner/scanner.go +++ b/scripts/metricsdocgen/scanner/scanner.go @@ -565,12 +565,16 @@ func isPromautoCall(expr ast.Expr) bool { if !ok { return false } + // Match calls that are exactly "promauto.With(...)". This checks the local + // package identifier, not the resolved import path. If the promauto package + // is imported with an alias, this will not match. return ident.Name == "promauto" && sel.Sel.Name == "With" case *ast.Ident: - // Check for factory.New*() where factory is a variable. - // We assume any identifier used as receiver for New*Vec/New* methods - // that isn't "prometheus" is a promauto factory. - // This is a heuristic but works for the codebase patterns. + // Heuristic: assume any identifier that isn't "prometheus" used as a + // receiver for New*() methods is a promauto factory variable. + // This works for the codebase patterns (e.g., factory.NewGaugeVec(...)) + // but could false-positive on other receivers. Downstream extractOpts + // validation prevents incorrect metrics from being emitted. return e.Name != "prometheus" } return false @@ -605,6 +609,7 @@ func extractPromautoMetric(call *ast.CallExpr, decls declarations) (Metric, bool // Extract metric info from the Opts struct. opts, ok := extractOpts(call.Args[0], decls) if !ok { + log.Printf("extractPromautoMetric: skipping promauto.%s() call: could not extract opts", funcName) return Metric{}, false } @@ -617,6 +622,7 @@ func extractPromautoMetric(call *ast.CallExpr, decls declarations) (Metric, bool // Build the full metric name. name := buildMetricName(opts.Namespace, opts.Subsystem, opts.Name) if name == "" { + log.Printf("extractPromautoMetric: skipping promauto.%s() call: could not build metric name", funcName) return Metric{}, false }