diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aa40a7b..f84059d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,3 +99,35 @@ jobs: working-directory: tools run: go build -v ./... + test-prometheus: + name: Test (Prometheus) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'prometheus/go.mod' + + - name: Test + working-directory: prometheus + run: go test -v ./... + + lint-prometheus: + name: Lint (Prometheus) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'prometheus/go.mod' + cache: false + + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.56.0 + working-directory: prometheus diff --git a/prometheus/README.md b/prometheus/README.md new file mode 100644 index 0000000..2ee3b90 --- /dev/null +++ b/prometheus/README.md @@ -0,0 +1,35 @@ +# buildinfo metrics + +Helper library for creating a [Prometheus](github.com/prometheus/client_golang) +metrics collector using the embedded build information as data source. + +```golang +package main + +import ( + "log" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + bicol "github.com/UiP9AV6Y/buildinfo/prometheus/collector" + + "example.com/version" +) + +func main() { + // Create a non-global registry. + reg := prometheus.NewRegistry() + + // Create a collector and register it with the custom registry. + if err := reg.Register(bicol.New("example", version.BuildInfo())); err != nil { + log.Fatal(err) + } + + // Expose metrics and custom registry via an HTTP server + // using the HandleFor function. "/metrics" is the usual endpoint for that. + http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` diff --git a/prometheus/collector/collector.go b/prometheus/collector/collector.go new file mode 100644 index 0000000..5cb5d6e --- /dev/null +++ b/prometheus/collector/collector.go @@ -0,0 +1,32 @@ +package collector + +import ( + "github.com/prometheus/client_golang/prometheus" + + "github.com/UiP9AV6Y/buildinfo" +) + +// New returns a collector that exports metrics +// using the provided data as information source. +func New(buildInfo *buildinfo.BuildInfo, program string) prometheus.Collector { + help := "A metric with a constant '1' value labeled by version, revision, branch, goversion from which " + + program + " was built, and the goos and goarch for the build." + labels := prometheus.Labels{ + "version": buildInfo.Version, + "revision": buildInfo.Revision, + "branch": buildInfo.Branch, + "goversion": buildinfo.GoVersion, + "goos": buildinfo.GoOS, + "goarch": buildinfo.GoArch, + } + + return prometheus.NewGaugeFunc( + prometheus.GaugeOpts{ + Namespace: program, + Name: "build_info", + Help: help, + ConstLabels: labels, + }, + func() float64 { return 1 }, + ) +} diff --git a/prometheus/collector/collector_test.go b/prometheus/collector/collector_test.go new file mode 100644 index 0000000..2a98681 --- /dev/null +++ b/prometheus/collector/collector_test.go @@ -0,0 +1,38 @@ +package collector + +import ( + "fmt" + "runtime" + "strings" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus/testutil" + + "gotest.tools/v3/assert" + + "github.com/UiP9AV6Y/buildinfo" +) + +func TestNew(t *testing.T) { + golabels := fmt.Sprintf("goarch=%q,goos=%q,goversion=%q", + runtime.GOARCH, runtime.GOOS, runtime.Version()) + vi := &buildinfo.VersionInfo{ + Version: "1.2.3", + Revision: "HEAD", + Branch: "trunk", + } + ei := &buildinfo.EnvironmentInfo{ + User: "root", + Host: "localhost", + Date: time.Unix(0, 0), + } + bi := buildinfo.NewBuildInfo(vi, ei) + got := New(bi, "test") + want := strings.NewReader(`# HELP test_build_info A metric with a constant '1' value labeled by version, revision, branch, goversion from which test was built, and the goos and goarch for the build. +# TYPE test_build_info gauge +test_build_info{branch="trunk",` + golabels + `,revision="HEAD",version="1.2.3"} 1 +`) + + assert.NilError(t, testutil.CollectAndCompare(got, want)) +} diff --git a/prometheus/go.mod b/prometheus/go.mod new file mode 100644 index 0000000..557f7eb --- /dev/null +++ b/prometheus/go.mod @@ -0,0 +1,21 @@ +module github.com/UiP9AV6Y/buildinfo/prometheus + +go 1.20 + +require ( + github.com/UiP9AV6Y/buildinfo v0.0.0-20240313195626-97a50b40242a + github.com/prometheus/client_golang v1.19.0 + gotest.tools/v3 v3.5.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.16.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/prometheus/go.sum b/prometheus/go.sum new file mode 100644 index 0000000..4c8d63b --- /dev/null +++ b/prometheus/go.sum @@ -0,0 +1,24 @@ +github.com/UiP9AV6Y/buildinfo v0.0.0-20240313195626-97a50b40242a h1:a2IDIfV2Smoe+mwj553zsTglo67fsMPN05tjBRpT4J0= +github.com/UiP9AV6Y/buildinfo v0.0.0-20240313195626-97a50b40242a/go.mod h1:JQKMnAuoFntCA2Lrxfl46yZRuhPPUNsq4Z9Dh03uqPA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=