feat(metrics): add build info metric on server start (#1984)
Adds a Prometheus gauge metric set to the value of 1, with labels for go version, git revision, and Boundary version. Assigns the Prometheus DefaultRegisterer to the Server struct as a parameter to NewServer(), to avoid "duplicate metrics collector registration" panics from test cases.
This commit is contained in:
1 parent
8d80d10ff7
commit
e3f72bd0a4
5 files changed
+85
-5
No files matched your search
@@ -0,0 +1,50 @@
|
||||
// Package metric provides functions to initialize a prometheus metric
|
||||
// detailing build info
|
||||
package metric
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/version"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
labelGoVersion = "goversion"
|
||||
labelGitRevision = "revision"
|
||||
labelBoundaryVersion = "version"
|
||||
)
|
||||
|
||||
// buildInfoVec is a gauge metric whose value is always equal to 1 and whose
|
||||
// labels contain the current go version, git revision, and boundary version.
|
||||
var buildInfoVec = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: globals.MetricNamespace,
|
||||
Name: "build_info",
|
||||
Help: "Gauge with labels describing go version, git revision hash, and Boundary release version.",
|
||||
},
|
||||
[]string{labelGoVersion, labelGitRevision, labelBoundaryVersion},
|
||||
)
|
||||
|
||||
func retrieveBuildInfoLabels() map[string]string {
|
||||
verInfo := version.Get()
|
||||
|
||||
return map[string]string{
|
||||
labelGoVersion: runtime.Version(),
|
||||
labelGitRevision: verInfo.Revision,
|
||||
labelBoundaryVersion: verInfo.Version,
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeBuildInfo registers the boundary_build_info metric with its
|
||||
// correct labels and sets its value to 1.
|
||||
func InitializeBuildInfo(r prometheus.Registerer) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.MustRegister(buildInfoVec)
|
||||
l := prometheus.Labels(retrieveBuildInfoLabels())
|
||||
buildInfoVec.With(l).Set(float64(1))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct.
|
||||
@@ -40,6 +41,7 @@ type Options struct {
|
||||
withStatusCode int
|
||||
withHostPlugin func() (string, plugin.HostPluginServiceClient)
|
||||
withEventGating bool
|
||||
withPrometheusRegisterer prometheus.Registerer
|
||||
}
|
||||
|
||||
func getDefaultOptions() Options {
|
||||
@@ -188,3 +190,10 @@ func WithEventGating(with bool) Option {
|
||||
o.withEventGating = with
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrometheusRegisterer uses the provided prometheus registerer
|
||||
func WithPrometheusRegisterer(with prometheus.Registerer) Option {
|
||||
return func(o *Options) {
|
||||
o.withPrometheusRegisterer = with
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -124,4 +125,12 @@ func Test_GetOpts(t *testing.T) {
|
||||
opts := getOpts(WithEventGating(true))
|
||||
assert.True(opts.withEventGating)
|
||||
})
|
||||
t.Run("withPrometheusRegisterer", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
pmr := prometheus.NewRegistry()
|
||||
opts := getOpts(WithPrometheusRegisterer(pmr))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withPrometheusRegisterer = pmr
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base/internal/metric"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base/logging"
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
@@ -133,7 +134,15 @@ type Server struct {
|
||||
StatusGracePeriodDuration time.Duration
|
||||
}
|
||||
|
||||
func NewServer(cmd *Command) *Server {
|
||||
// The only option used here is WithPrometheusRegisterer; all others are ignored.
|
||||
func NewServer(cmd *Command, opt ...Option) *Server {
|
||||
// Create a new prometheus registry here to avoid "duplicate metrics collector
|
||||
// registration" panics in tests where new servers are called consecutively.
|
||||
// prometheus.DefaultRegisterer and prometheus.DefaultGatherer vars need to be
|
||||
// assigned for promhttp package to work correctly.
|
||||
opts := getOpts(opt...)
|
||||
metric.InitializeBuildInfo(opts.withPrometheusRegisterer)
|
||||
|
||||
return &Server{
|
||||
Command: cmd,
|
||||
InfoKeys: make([]string, 0, 20),
|
||||
@@ -142,7 +151,7 @@ func NewServer(cmd *Command) *Server {
|
||||
ReloadFuncsLock: new(sync.RWMutex),
|
||||
ReloadFuncs: make(map[string][]reloadutil.ReloadFunc),
|
||||
StderrLock: new(sync.Mutex),
|
||||
PrometheusRegisterer: prometheus.DefaultRegisterer,
|
||||
PrometheusRegisterer: opts.withPrometheusRegisterer,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/version"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Commands is the mapping of all the available commands.
|
||||
@@ -36,14 +37,16 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
"server": func() (cli.Command, error) {
|
||||
return &server.Command{
|
||||
Server: base.NewServer(base.NewCommand(serverCmdUi)),
|
||||
Server: base.NewServer(base.NewCommand(serverCmdUi),
|
||||
base.WithPrometheusRegisterer(prometheus.DefaultRegisterer)),
|
||||
SighupCh: base.MakeSighupCh(),
|
||||
SigUSR2Ch: MakeSigUSR2Ch(),
|
||||
}, nil
|
||||
},
|
||||
"dev": func() (cli.Command, error) {
|
||||
return &dev.Command{
|
||||
Server: base.NewServer(base.NewCommand(serverCmdUi)),
|
||||
Server: base.NewServer(base.NewCommand(serverCmdUi),
|
||||
base.WithPrometheusRegisterer(prometheus.DefaultRegisterer)),
|
||||
SighupCh: base.MakeSighupCh(),
|
||||
SigUSR2Ch: MakeSigUSR2Ch(),
|
||||
}, nil
|
||||
@@ -318,7 +321,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
},
|
||||
"database init": func() (cli.Command, error) {
|
||||
return &database.InitCommand{
|
||||
Server: base.NewServer(base.NewCommand(ui)),
|
||||
Server: base.NewServer(base.NewCommand(ui), base.WithPrometheusRegisterer(prometheus.DefaultRegisterer)),
|
||||
}, nil
|
||||
},
|
||||
"database migrate": func() (cli.Command, error) {
|
||||
|
||||
Reference in new issue
Block a user