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.
pull/2009/head
Haotian 4 years ago committed by GitHub
parent 8d80d10ff7
commit e3f72bd0a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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) {

Loading…
Cancel
Save