feat: add coder_build_info metric (#24365)

_Disclaimer: produced by Claude Opus 4.6_

Adds a `coder_build_info` metric which allows operators to see which
versions of Coder are currently running.

---------

Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Danny Kopping
2026-04-15 14:48:38 +02:00
committed by GitHub
parent d0c9571f62
commit 48b90f8cc8
6 changed files with 58 additions and 4 deletions
+4 -4
View File
@@ -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")
}
+5
View File
@@ -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.
@@ -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.
@@ -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()
+1
View File
@@ -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` |
+3
View File
@@ -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