Skip to content
Open
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
7 changes: 7 additions & 0 deletions node/cmd/node/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"morph-l2/node/sequencer/mock"
"morph-l2/node/sync"
"morph-l2/node/types"
"morph-l2/node/version"
)

func main() {
Expand Down Expand Up @@ -81,6 +82,12 @@ func L2NodeMain(ctx *cli.Context) error {
return err
}

// Publish build/version metadata as morphnode_info{...} 1 (mirrors morph-reth's
// reth_info{} gauge). Registers on the default Prometheus registry, so it's
// exposed regardless of which branch below brings up /metrics (own listener in
// layer1-verify mode, or Tendermint's instrumentation in every other mode).
version.PrometheusMetrics("morphnode", Version, GitCommit, BuildTime).Register()

// Wire the centralized-sequencer upgrade time into the consensus upgrade package before any
// consensus / blocksync routine runs. A user-provided --sequencerUpgradeTime wins. If it is
// absent, --mainnet / --hoodi selects the corresponding network default. With no network and no
Expand Down
2 changes: 1 addition & 1 deletion node/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/morph-l2/go-ethereum v1.10.14-0.20260908092055-4012f174b967
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.5.0
github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
Expand Down Expand Up @@ -98,7 +99,6 @@ require (
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect
Expand Down
54 changes: 54 additions & 0 deletions node/version/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Package version exposes morphnode's build/version metadata as a Prometheus
// info-style gauge, mirroring morph-reth's reth_info{} gauge: a static gauge
// permanently set to 1, whose Prometheus labels (not its value) carry the
// actual information. Scraping tools use this to build a "which build/version
// is running where" view (e.g. `max by (version) (morphnode_info{})`).
package version

import (
"runtime"

"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/discard"
"github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)

// Info is a static Prometheus gauge carrying morphnode's build metadata as
// labels. Its value is always 1; only the labels are meaningful.
type Info struct {
gauge metrics.Gauge
}

// PrometheusMetrics registers the morphnode_info gauge on the default
// Prometheus registry under the given namespace (no subsystem: the resulting
// metric name is "<namespace>_info", e.g. "morphnode_info"). version,
// gitCommit and buildTime should be the values injected via -ldflags at build
// time (see cmd/node/version.go); goVersion/os/arch are read from the runtime.
func PrometheusMetrics(namespace, version, gitCommit, buildTime string) *Info {
labels := []string{"version", "git_commit", "build_time", "go_version", "os", "arch"}
g := prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
Namespace: namespace,
Name: "info",
Help: "Static info metric (always 1) carrying morphnode build/version metadata as labels.",
}, labels).With(
"version", version,
"git_commit", gitCommit,
"build_time", buildTime,
"go_version", runtime.Version(),
"os", runtime.GOOS,
"arch", runtime.GOARCH,
)
return &Info{gauge: g}
}

// NopMetrics returns an Info that discards its observation (no registration).
// Mirrors the NopMetrics helpers used by the other metrics packages (e.g.
// hakeeper.NopMetrics) for tests that don't need a live Prometheus registry.
func NopMetrics() *Info {
return &Info{gauge: discard.NewGauge()}
}

// Register sets the gauge to 1, publishing the labels attached at
// construction time. Call once at startup.
func (i *Info) Register() { i.gauge.Set(1) }
53 changes: 53 additions & 0 deletions node/version/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package version

import (
"testing"

stdprometheus "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
)

// TestPrometheusMetrics_RegistersInfoGaugeSetToOne verifies that Register
// publishes a single gauge, always valued 1, carrying the build metadata
// passed to PrometheusMetrics as labels.
func TestPrometheusMetrics_RegistersInfoGaugeSetToOne(t *testing.T) {
// Scoped per-test namespace: dodges "duplicate metrics collector
// registration" panics against the shared default registry, matching the
// pattern used in derivation/base_client_test.go.
namespace := "morphnode_test_" + t.Name()

info := PrometheusMetrics(namespace, "v1.2.3", "abc1234", "2026-09-22T00:00:00Z")
info.Register()

mfs, err := stdprometheus.DefaultGatherer.Gather()
require.NoError(t, err)

var found *dto.Metric
for _, mf := range mfs {
if mf.GetName() == namespace+"_info" {
require.Len(t, mf.GetMetric(), 1)
found = mf.GetMetric()[0]
break
}
}
require.NotNil(t, found, "expected %s_info gauge to be registered", namespace)
require.Equal(t, float64(1), found.GetGauge().GetValue())

labels := map[string]string{}
for _, l := range found.GetLabel() {
labels[l.GetName()] = l.GetValue()
}
require.Equal(t, "v1.2.3", labels["version"])
require.Equal(t, "abc1234", labels["git_commit"])
require.Equal(t, "2026-09-22T00:00:00Z", labels["build_time"])
require.NotEmpty(t, labels["go_version"])
require.NotEmpty(t, labels["os"])
require.NotEmpty(t, labels["arch"])
}

// TestNopMetrics_DoesNotPanic verifies NopMetrics().Register() is a safe
// no-op, for callers (e.g. tests) that don't want a live Prometheus registry.
func TestNopMetrics_DoesNotPanic(t *testing.T) {
require.NotPanics(t, func() { NopMetrics().Register() })
}
Loading