From 7962a8752bba8b1421cbbb25e0d9a3de666d303f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Stutz?= Date: Sat, 22 Jul 2023 16:45:55 +0200 Subject: [PATCH] Implementing Prometheus Metrics Exporter as in #477 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flávio Stutz --- README.md | 3 +-- attack.go | 7 ++++--- attack_test.go | 4 ++-- docker-compose.yml | 2 +- go.mod | 2 +- go.sum | 1 - lib/prom/prom.go | 32 ++++++++++++++++---------------- lib/prom/prom_test.go | 17 ++++++++++------- 8 files changed, 35 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 617bad6c..585d7468 100644 --- a/README.md +++ b/README.md @@ -785,7 +785,7 @@ It'll read and sort them by timestamp before generating reports. vegeta report *.bin ``` -Another way to gather results in distributed tests is to use the built-in Prometheus Exporter and configure a Prometheus Server to get test results from all Vegeta instances. See `attack` option "prom-enable" for more details and a complete example in the section "Prometheus Exporter Support" +Another way to gather results in distributed tests is to use the built-in Prometheus Exporter and configure a Prometheus Server to get test results from all Vegeta instances. See `attack` option "prometheus-url" for more details and a complete example in the section "Prometheus Exporter Support" ## Usage: Real-time Analysis @@ -868,7 +868,6 @@ Just pass a new number as the argument to change it. Vegeta has a built-in Prometheus Exporter that may be enabled during "attacks" so that you can point any Prometheus instance to Vegeta instances and get some metrics about http requests performance and about the Vegeta process itself. To enable the Prometheus Exporter on the command line, use the "prometheus-url" flag. -To enable Prometheus Exporter on lib usage, add Option "PrometheusEnable" and/or "PrometheusSettings". A Prometheus HTTP endpoint will be available only during the lifespan of an "attack" and will be closed right after the attack is finished. diff --git a/attack.go b/attack.go index 3631bb8a..f5dd5e5f 100644 --- a/attack.go +++ b/attack.go @@ -215,7 +215,7 @@ func attack(opts *attackOpts) (err error) { sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM) - return processAttack(atk, res, enc, sig) + return processAttack(atk, res, enc, sig, promMetrics) } func processAttack( @@ -223,6 +223,7 @@ func processAttack( res <-chan *vegeta.Result, enc vegeta.Encoder, sig <-chan os.Signal, + promMetrics *prom.PrometheusMetrics, ) error { for { select { @@ -235,10 +236,10 @@ func processAttack( if !ok { return nil } - if opts.promURL != "" { + if promMetrics != nil { promMetrics.Observe(r) } - if err = enc.Encode(r); err != nil { + if err := enc.Encode(r); err != nil { return err } } diff --git a/attack_test.go b/attack_test.go index 967044c2..23cff272 100644 --- a/attack_test.go +++ b/attack_test.go @@ -86,7 +86,7 @@ func TestAttackSignalOnce(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - processAttack(atk, res, enc, sig) + processAttack(atk, res, enc, sig, nil) }() // Allow more than one request to have started before stopping. @@ -139,7 +139,7 @@ func TestAttackSignalTwice(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - processAttack(atk, res, enc, sig) + processAttack(atk, res, enc, sig, nil) }() // Exit as soon as possible. diff --git a/docker-compose.yml b/docker-compose.yml index b3f427e4..7d1800db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: image: tsenart/vegeta ports: - 8880:8880 - command: sh -c 'echo "GET https://www.yahoo.com" | vegeta attack -duration=30s -rate=5 -prometheus-enable=true' + command: sh -c 'echo "GET https://www.yahoo.com" | vegeta attack -duration=30s -rate=5 -prometheus-url=0.0.0.0:8880' prometheus: image: flaviostutz/prometheus diff --git a/go.mod b/go.mod index ccb6c6c3..f983596c 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/influxdata/tdigest v0.0.1 github.com/mailru/easyjson v0.7.7 github.com/miekg/dns v1.1.55 + github.com/prometheus/client_golang v1.16.0 github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3 @@ -26,7 +27,6 @@ require ( github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect diff --git a/go.sum b/go.sum index a1160c02..720e83b5 100644 --- a/go.sum +++ b/go.sum @@ -59,7 +59,6 @@ github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1 github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3 h1:pcQGQzTwCg//7FgVywqge1sW9Yf8VMsMdG58MI5kd8s= github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= diff --git a/lib/prom/prom.go b/lib/prom/prom.go index 9c63125c..012801b3 100644 --- a/lib/prom/prom.go +++ b/lib/prom/prom.go @@ -16,8 +16,8 @@ import ( vegeta "github.com/tsenart/vegeta/v12/lib" ) -//PrometheusMetrics vegeta metrics observer with exposition as Prometheus metrics endpoint -type PrometheusMetrics struct { +// Metrics vegeta metrics observer with exposition as Prometheus metrics endpoint +type Metrics struct { requestSecondsHistogram *prometheus.HistogramVec requestBytesInCounter *prometheus.CounterVec requestBytesOutCounter *prometheus.CounterVec @@ -26,18 +26,18 @@ type PrometheusMetrics struct { registry *prometheus.Registry } -//NewPrometheusMetrics same as NewPrometheusMetricsWithParams with default params: -func NewPrometheusMetrics() (*PrometheusMetrics, error) { - return NewPrometheusMetricsWithParams("http://0.0.0.0:8880") +// NewMetrics same as NewMetricsWithParams with default params: +func NewMetrics() (*Metrics, error) { + return NewMetricsWithParams("http://0.0.0.0:8880") } -// NewPrometheusMetricsWithParams creates a new Prometheus Metrics to Observe attack results and expose metrics -// For example, after using NewPrometheusMetricsWithParams("http://0.0.0.0:8880"), +// NewMetricsWithParams creates a new Prometheus Metrics to Observe attack results and expose metrics +// For example, after using NewMetricsWithParams("http://0.0.0.0:8880"), // during an "attack" you can call "curl http://127.0.0.0:8880" to see current metrics. // This endpoint can be configured in scrapper section of your Prometheus server. -func NewPrometheusMetricsWithParams(bindURL string) (*PrometheusMetrics, error) { +func NewMetricsWithParams(bindURL string) (*Metrics, error) { - //parse bind url elements + // parse bind url elements p, err := url.Parse(bindURL) if err != nil { return nil, fmt.Errorf("Invalid bindURL %s. Must be in format 'http://0.0.0.0:8880'. err=%s", bindURL, err) @@ -47,7 +47,7 @@ func NewPrometheusMetricsWithParams(bindURL string) (*PrometheusMetrics, error) return nil, fmt.Errorf("Invalid bindURL %s. Must be in format 'http://0.0.0.0:8880'. err=%s", bindURL, err) } - pm := &PrometheusMetrics{ + pm := &Metrics{ registry: prometheus.NewRegistry(), } @@ -92,7 +92,7 @@ func NewPrometheusMetricsWithParams(bindURL string) (*PrometheusMetrics, error) }) pm.registry.MustRegister(pm.requestFailCounter) - //setup prometheus metrics http server + // setup prometheus metrics http server pm.srv = http.Server{ Addr: fmt.Sprintf("%s:%s", bindHost, bindPort), Handler: promhttp.HandlerFor(pm.registry, promhttp.HandlerOpts{}), @@ -105,9 +105,9 @@ func NewPrometheusMetricsWithParams(bindURL string) (*PrometheusMetrics, error) return pm, nil } -//Close shutdown http server exposing Prometheus metrics and unregister -//all prometheus collectors -func (pm *PrometheusMetrics) Close() error { +// Close shutdown http server exposing Prometheus metrics and unregister +// all prometheus collectors +func (pm *Metrics) Close() error { prometheus.Unregister(pm.requestSecondsHistogram) prometheus.Unregister(pm.requestBytesInCounter) prometheus.Unregister(pm.requestBytesOutCounter) @@ -115,8 +115,8 @@ func (pm *PrometheusMetrics) Close() error { return pm.srv.Shutdown(context.Background()) } -//Observe register metrics about hit results -func (pm *PrometheusMetrics) Observe(res *vegeta.Result) { +// Observe register metrics about hit results +func (pm *Metrics) Observe(res *vegeta.Result) { code := strconv.FormatUint(uint64(res.Code), 10) pm.requestBytesInCounter.WithLabelValues(res.Method, res.URL, code).Add(float64(res.BytesIn)) pm.requestBytesOutCounter.WithLabelValues(res.Method, res.URL, code).Add(float64(res.BytesOut)) diff --git a/lib/prom/prom_test.go b/lib/prom/prom_test.go index 25606df5..1cc8ecdf 100644 --- a/lib/prom/prom_test.go +++ b/lib/prom/prom_test.go @@ -7,12 +7,11 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" vegeta "github.com/tsenart/vegeta/v12/lib" ) func TestPromServerBasic1(t *testing.T) { - pm, err := NewPrometheusMetrics() + pm, err := NewMetrics() if err != nil { t.Errorf("Error launching Prometheus http server. err=%s", err) } @@ -24,7 +23,7 @@ func TestPromServerBasic1(t *testing.T) { } func TestPromServerBasic2(t *testing.T) { - pm, err := NewPrometheusMetrics() + pm, err := NewMetrics() if err != nil { t.Errorf("Error launching Prometheus metrics. err=%s", err) } @@ -33,7 +32,7 @@ func TestPromServerBasic2(t *testing.T) { t.Errorf("Error stopping Prometheus http server. err=%s", err) } - pm, err = NewPrometheusMetrics() + pm, err = NewMetrics() if err != nil { t.Errorf("Error launching Prometheus metrics. err=%s", err) } @@ -42,7 +41,7 @@ func TestPromServerBasic2(t *testing.T) { t.Errorf("Error stopping Prometheus http server. err=%s", err) } - pm, err = NewPrometheusMetrics() + pm, err = NewMetrics() if err != nil { t.Errorf("Error launching Prometheus metrics. err=%s", err) } @@ -53,8 +52,12 @@ func TestPromServerBasic2(t *testing.T) { } func TestPromServerObserve(t *testing.T) { - pm, err := NewPrometheusMetrics() - assert.Nil(t, err, "Error launching Prometheus http server. err=%s", err) + pm, err := NewMetrics() + if err != nil { + if err != nil { + t.Errorf("Error launching Prometheus http server. err=%s", err) + } + } r := &vegeta.Result{ URL: "http://test.com/test1",