Skip to content

Commit

Permalink
feat: use urfave/cli to parse the CLI flags
Browse files Browse the repository at this point in the history
  • Loading branch information
marcfrederick committed May 23, 2023
1 parent b655076 commit 96883e9
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 109 deletions.
16 changes: 11 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ module github.com/marcfrederick/imaginary-exporter

go 1.20

require github.com/prometheus/client_golang v1.15.1
require (
github.com/prometheus/client_golang v1.15.1
github.com/urfave/cli/v2 v2.25.3
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/sys v0.6.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.8.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)
25 changes: 16 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ 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/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
Expand All @@ -14,15 +15,21 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.10.0 h1:UkG7GPYkO4UZyLnyXjaWYcgOSONqwdBqFUT95ugmt6I=
github.com/prometheus/procfs v0.10.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
Expand Down
50 changes: 50 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cli

import (
"log"
"time"

"github.com/urfave/cli/v2"

"github.com/marcfrederick/imaginary-exporter/internal/cmd"
)

func Run(version string, args []string) {
if err := NewApp(version).Run(args); err != nil {
log.Fatalln(err)
}
}

func NewApp(version string) *cli.App {
return &cli.App{
Name: "imaginary-exporter",
Version: version,
Compiled: time.Now(),
Authors: []*cli.Author{
{Name: "Marc Trölitzsch", Email: "[email protected]"},
},
Usage: "Prometheus exporter for Imaginary metrics",
EnableBashCompletion: true,

Flags: cli.FlagsByName{
&cli.StringFlag{
Name: "addr",
Usage: "address to listen on",
Value: ":8080",
},
&cli.StringFlag{
Name: "url",
Usage: "base url of the imaginary instance",
Required: true,
},
},

Action: func(cliCtx *cli.Context) error {
exitCode, err := cmd.Run(cliCtx)
if err != nil {
return cli.Exit(err.Error(), exitCode)
}
return cli.Exit("", exitCode)
},
}
}
42 changes: 42 additions & 0 deletions internal/cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd

import (
"fmt"
"log"
"net/http"
"net/url"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/urfave/cli/v2"

"github.com/marcfrederick/imaginary-exporter/pkg/collector"
"github.com/marcfrederick/imaginary-exporter/pkg/imaginary"
)

func Run(cliCtx *cli.Context) (int, error) {
address := cliCtx.String("addr")
imaginaryURL := cliCtx.String("url")
if !isURL(imaginaryURL) {
return 1, fmt.Errorf("the given URL '%s' is invalid", imaginaryURL)
}

client := imaginary.NewClient(imaginaryURL)
c := collector.NewImaginaryCollector(client)
if err := prometheus.Register(c); err != nil {
return 1, fmt.Errorf("error registering imaginary collector: %w", err)
}

http.Handle("/metrics", promhttp.Handler())
log.Printf("listening on address %s", address)
if err := http.ListenAndServe(address, nil); err != nil {
return 1, fmt.Errorf("error listening on address %s: %w", address, err)
}

return 0, nil
}

func isURL(str string) bool {
u, err := url.Parse(str)
return err == nil && u.Scheme != "" && u.Host != ""
}
101 changes: 6 additions & 95 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,104 +1,15 @@
package main

import (
"flag"
"log"
"net/http"
"net/url"
"os"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"

"github.com/marcfrederick/imaginary-exporter/pkg/imaginary"
"github.com/marcfrederick/imaginary-exporter/internal/cli"
)

// ImaginaryCollector collects metrics from a given Imaginary instance.
type ImaginaryCollector struct {
client *imaginary.Client
uptimeMetric *prometheus.Desc
allocatedMemoryMetric *prometheus.Desc
totalAllocatedMemoryMetric *prometheus.Desc
goroutinesMetric *prometheus.Desc
completedGCCyclesMetric *prometheus.Desc
cpusMetric *prometheus.Desc
maxHeapUsageMetric *prometheus.Desc
heapInUseMetric *prometheus.Desc
objectsInUseMetric *prometheus.Desc
oSMemoryObtainedMetric *prometheus.Desc
}

var _ prometheus.Collector = (*ImaginaryCollector)(nil)

// newImaginaryCollector creates a new ImaginaryCollector and initializes it.
func newImaginaryCollector(client *imaginary.Client) *ImaginaryCollector {
return &ImaginaryCollector{
client: client,
uptimeMetric: prometheus.NewDesc("imaginary_uptime", "The current uptime.", nil, nil),
allocatedMemoryMetric: prometheus.NewDesc("imaginary_allocated_memory", "The currently allocated memory.", nil, nil),
totalAllocatedMemoryMetric: prometheus.NewDesc("imaginary_allocated_memory_total", "The total allocated memory.", nil, nil),
goroutinesMetric: prometheus.NewDesc("imaginary_goroutines", "The number of running goroutines.", nil, nil),
completedGCCyclesMetric: prometheus.NewDesc("imaginary_gc_cycles_total", "The number of garbage collection cycles.", nil, nil),
cpusMetric: prometheus.NewDesc("imaginary_cpus_total", "The number of CPUs available.", nil, nil),
maxHeapUsageMetric: prometheus.NewDesc("imaginary_heap_usage_max", "The maximum heap usage.", nil, nil),
heapInUseMetric: prometheus.NewDesc("imaginary_heap_usage", "The current heap usage.", nil, nil),
objectsInUseMetric: prometheus.NewDesc("imaginary_objects", "The number of currently used objects.", nil, nil),
oSMemoryObtainedMetric: prometheus.NewDesc("imaginary_os_memory", "The amount of OS memory obtained.", nil, nil),
}
}

func (c *ImaginaryCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.uptimeMetric
ch <- c.uptimeMetric
ch <- c.allocatedMemoryMetric
ch <- c.totalAllocatedMemoryMetric
ch <- c.goroutinesMetric
ch <- c.completedGCCyclesMetric
ch <- c.cpusMetric
ch <- c.maxHeapUsageMetric
ch <- c.heapInUseMetric
ch <- c.objectsInUseMetric
ch <- c.oSMemoryObtainedMetric
}

func (c *ImaginaryCollector) Collect(ch chan<- prometheus.Metric) {
res, err := c.client.GetHealthStats()
if err != nil {
log.Printf("error getting metrics: %s", err)
return
}
ch <- prometheus.MustNewConstMetric(c.uptimeMetric, prometheus.CounterValue, float64(res.Uptime))
ch <- prometheus.MustNewConstMetric(c.allocatedMemoryMetric, prometheus.GaugeValue, res.AllocatedMemory)
ch <- prometheus.MustNewConstMetric(c.totalAllocatedMemoryMetric, prometheus.GaugeValue, res.TotalAllocatedMemory)
ch <- prometheus.MustNewConstMetric(c.goroutinesMetric, prometheus.GaugeValue, float64(res.Goroutines))
ch <- prometheus.MustNewConstMetric(c.completedGCCyclesMetric, prometheus.GaugeValue, float64(res.CompletedGCCycles))
ch <- prometheus.MustNewConstMetric(c.cpusMetric, prometheus.GaugeValue, float64(res.CPUs))
ch <- prometheus.MustNewConstMetric(c.maxHeapUsageMetric, prometheus.GaugeValue, res.MaxHeapUsage)
ch <- prometheus.MustNewConstMetric(c.heapInUseMetric, prometheus.GaugeValue, res.HeapInUse)
ch <- prometheus.MustNewConstMetric(c.objectsInUseMetric, prometheus.GaugeValue, float64(res.ObjectsInUse))
ch <- prometheus.MustNewConstMetric(c.oSMemoryObtainedMetric, prometheus.GaugeValue, res.OSMemoryObtained)
}

// isURL checks whether the given string is a valid url
func isURL(str string) bool {
u, err := url.Parse(str)
return err == nil && u.Scheme != "" && u.Host != ""
}
// Injected by GoReleaser during the release process
// https://goreleaser.com/cookbooks/using-main.version/
var version = "devel"

func main() {
addr := flag.String("addr", ":8080", "address to listen on")
imaginaryURL := flag.String("url", "", "url of the imaginary instance")
flag.Parse()
if !isURL(*imaginaryURL) {
log.Fatalf("the given imaginary URL '%s' is invalid.", *imaginaryURL)
}

c := newImaginaryCollector(imaginary.NewClient(*imaginaryURL))
if err := prometheus.Register(c); err != nil {
log.Fatalf("failed to register imaginary collector: %+v", err)
}

http.Handle("/metrics", promhttp.Handler())
if err := http.ListenAndServe(*addr, nil); err != nil {
log.Fatal(err)
}
cli.Run(version, os.Args)
}
75 changes: 75 additions & 0 deletions pkg/collector/imaginary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package collector

import (
"log"

"github.com/prometheus/client_golang/prometheus"

"github.com/marcfrederick/imaginary-exporter/pkg/imaginary"
)

// ImaginaryCollector collects metrics from a given Imaginary instance.
type ImaginaryCollector struct {
client *imaginary.Client
uptimeMetric *prometheus.Desc
allocatedMemoryMetric *prometheus.Desc
totalAllocatedMemoryMetric *prometheus.Desc
goroutinesMetric *prometheus.Desc
completedGCCyclesMetric *prometheus.Desc
cpusMetric *prometheus.Desc
maxHeapUsageMetric *prometheus.Desc
heapInUseMetric *prometheus.Desc
objectsInUseMetric *prometheus.Desc
oSMemoryObtainedMetric *prometheus.Desc
}

var _ prometheus.Collector = (*ImaginaryCollector)(nil)

// NewImaginaryCollector creates a new ImaginaryCollector and initializes it.
func NewImaginaryCollector(client *imaginary.Client) *ImaginaryCollector {
return &ImaginaryCollector{
client: client,
uptimeMetric: prometheus.NewDesc("imaginary_uptime", "The current uptime.", nil, nil),
allocatedMemoryMetric: prometheus.NewDesc("imaginary_allocated_memory", "The currently allocated memory.", nil, nil),
totalAllocatedMemoryMetric: prometheus.NewDesc("imaginary_allocated_memory_total", "The total allocated memory.", nil, nil),
goroutinesMetric: prometheus.NewDesc("imaginary_goroutines", "The number of running goroutines.", nil, nil),
completedGCCyclesMetric: prometheus.NewDesc("imaginary_gc_cycles_total", "The number of garbage collection cycles.", nil, nil),
cpusMetric: prometheus.NewDesc("imaginary_cpus_total", "The number of CPUs available.", nil, nil),
maxHeapUsageMetric: prometheus.NewDesc("imaginary_heap_usage_max", "The maximum heap usage.", nil, nil),
heapInUseMetric: prometheus.NewDesc("imaginary_heap_usage", "The current heap usage.", nil, nil),
objectsInUseMetric: prometheus.NewDesc("imaginary_objects", "The number of currently used objects.", nil, nil),
oSMemoryObtainedMetric: prometheus.NewDesc("imaginary_os_memory", "The amount of OS memory obtained.", nil, nil),
}
}

func (c *ImaginaryCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.uptimeMetric
ch <- c.uptimeMetric
ch <- c.allocatedMemoryMetric
ch <- c.totalAllocatedMemoryMetric
ch <- c.goroutinesMetric
ch <- c.completedGCCyclesMetric
ch <- c.cpusMetric
ch <- c.maxHeapUsageMetric
ch <- c.heapInUseMetric
ch <- c.objectsInUseMetric
ch <- c.oSMemoryObtainedMetric
}

func (c *ImaginaryCollector) Collect(ch chan<- prometheus.Metric) {
res, err := c.client.GetHealthStats()
if err != nil {
log.Printf("error getting metrics: %s", err)
return
}
ch <- prometheus.MustNewConstMetric(c.uptimeMetric, prometheus.CounterValue, float64(res.Uptime))
ch <- prometheus.MustNewConstMetric(c.allocatedMemoryMetric, prometheus.GaugeValue, res.AllocatedMemory)
ch <- prometheus.MustNewConstMetric(c.totalAllocatedMemoryMetric, prometheus.GaugeValue, res.TotalAllocatedMemory)
ch <- prometheus.MustNewConstMetric(c.goroutinesMetric, prometheus.GaugeValue, float64(res.Goroutines))
ch <- prometheus.MustNewConstMetric(c.completedGCCyclesMetric, prometheus.GaugeValue, float64(res.CompletedGCCycles))
ch <- prometheus.MustNewConstMetric(c.cpusMetric, prometheus.GaugeValue, float64(res.CPUs))
ch <- prometheus.MustNewConstMetric(c.maxHeapUsageMetric, prometheus.GaugeValue, res.MaxHeapUsage)
ch <- prometheus.MustNewConstMetric(c.heapInUseMetric, prometheus.GaugeValue, res.HeapInUse)
ch <- prometheus.MustNewConstMetric(c.objectsInUseMetric, prometheus.GaugeValue, float64(res.ObjectsInUse))
ch <- prometheus.MustNewConstMetric(c.oSMemoryObtainedMetric, prometheus.GaugeValue, res.OSMemoryObtained)
}

0 comments on commit 96883e9

Please sign in to comment.