diff --git a/buildinfo/buildinfo.go b/buildinfo/buildinfo.go index 3a0a9ebb1a..7beba8b4d7 100644 --- a/buildinfo/buildinfo.go +++ b/buildinfo/buildinfo.go @@ -48,7 +48,7 @@ const ( // Use golang.org/x/mod/semver to compare versions. func Version() string { readVersion.Do(func() { - revision, valid := revision() + revision, valid := Revision() if valid { revision = "+" + revision[:7] } @@ -124,7 +124,7 @@ func IsBoringCrypto() bool { func ExternalURL() string { readExternalURL.Do(func() { repo := "https://github.com/coder/coder" - revision, valid := revision() + revision, valid := Revision() if !valid { externalURL = repo return @@ -147,8 +147,8 @@ func Time() (time.Time, bool) { return parsed, true } -// revision returns the Git hash of the build. -func revision() (string, bool) { +// Revision returns the full Git hash of the build. +func Revision() (string, bool) { return find("vcs.revision") } diff --git a/cli/server.go b/cli/server.go index 0604b06bc4..6bf924bcce 100644 --- a/cli/server.go +++ b/cli/server.go @@ -1013,6 +1013,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. if err = prometheusmetrics.Experiments(options.PrometheusRegistry, active); err != nil { return xerrors.Errorf("register experiments metric: %w", err) } + + revision, _ := buildinfo.Revision() + if err = prometheusmetrics.BuildInfo(options.PrometheusRegistry, buildinfo.Version(), revision); err != nil { + return xerrors.Errorf("register build info metric: %w", err) + } } // This is helpful for tests, but can be silently ignored. diff --git a/coderd/prometheusmetrics/prometheusmetrics.go b/coderd/prometheusmetrics/prometheusmetrics.go index 70b351cd3a..ea3801230e 100644 --- a/coderd/prometheusmetrics/prometheusmetrics.go +++ b/coderd/prometheusmetrics/prometheusmetrics.go @@ -710,6 +710,24 @@ func Experiments(registerer prometheus.Registerer, active codersdk.Experiments) return nil } +// BuildInfo registers a gauge which is always set to 1, with labels +// describing the running server version. This follows the common +// pattern used by Prometheus itself and many Go services. +func BuildInfo(registerer prometheus.Registerer, version, revision string) error { + gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Name: "build_info", + Help: "Describes the current build/version of the Coder server. Value is always 1.", + }, []string{"version", "revision"}) + if err := registerer.Register(gauge); err != nil { + return err + } + + gauge.WithLabelValues(version, revision).Set(1) + + return nil +} + // filterAcceptableAgentLabels handles a slightly messy situation whereby `prometheus-aggregate-agent-stats-by` can control on // which labels agent stats are aggregated, but for these specific metrics in this file there is no `template` label value, // and therefore we have to exclude it from the list of acceptable labels. diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 54903f2769..e6a55a8a1b 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -906,6 +906,33 @@ func TestExperimentsMetric(t *testing.T) { } } +func TestBuildInfo(t *testing.T) { + t.Parallel() + + reg := prometheus.NewRegistry() + version := "v2.15.0+abc1234" + revision := "abc1234def5678" + + require.NoError(t, prometheusmetrics.BuildInfo(reg, version, revision)) + + out, err := reg.Gather() + require.NoError(t, err) + require.Len(t, out, 1) + require.Equal(t, "coderd_build_info", out[0].GetName()) + + metrics := out[0].GetMetric() + require.Len(t, metrics, 1) + + // Labels are sorted alphabetically by Prometheus. + labels := metrics[0].GetLabel() + require.Len(t, labels, 2) + require.Equal(t, "revision", labels[0].GetName()) + require.Equal(t, revision, labels[0].GetValue()) + require.Equal(t, "version", labels[1].GetName()) + require.Equal(t, version, labels[1].GetValue()) + require.Equal(t, float64(1), metrics[0].GetGauge().GetValue()) +} + func prepareWorkspaceAndAgent(ctx context.Context, t *testing.T, client *codersdk.Client, user codersdk.CreateFirstUserResponse, workspaceNum int) agentproto.DRPCAgentClient { authToken := uuid.NewString() diff --git a/docs/admin/integrations/prometheus.md b/docs/admin/integrations/prometheus.md index 0c3154301e..97a9588278 100644 --- a/docs/admin/integrations/prometheus.md +++ b/docs/admin/integrations/prometheus.md @@ -197,6 +197,7 @@ deployment. They will always be available from the agent. | `coderd_api_workspace_latest_build` | gauge | The current number of workspace builds by status for all non-deleted workspaces. | `status` | | `coderd_authz_authorize_duration_seconds` | histogram | Duration of the 'Authorize' call in seconds. Only counts calls that succeed. | `allowed` | | `coderd_authz_prepare_authorize_duration_seconds` | histogram | Duration of the 'PrepareAuthorize' call in seconds. | | +| `coderd_build_info` | gauge | Describes the current build/version of the Coder server. Value is always 1. | `revision` `version` | | `coderd_db_query_counts_total` | counter | Total number of queries labelled by HTTP route, method, and query name. | `method` `query` `route` | | `coderd_db_query_latencies_seconds` | histogram | Latency distribution of queries in seconds. | `query` | | `coderd_db_tx_duration_seconds` | histogram | Duration of transactions in seconds. | `success` `tx_id` | diff --git a/scripts/metricsdocgen/generated_metrics b/scripts/metricsdocgen/generated_metrics index 99a9638532..b7ada39d28 100644 --- a/scripts/metricsdocgen/generated_metrics +++ b/scripts/metricsdocgen/generated_metrics @@ -223,6 +223,9 @@ 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_build_info Describes the current build/version of the Coder server. Value is always 1. +# TYPE coderd_build_info gauge +coderd_build_info{version="",revision=""} 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