From c0cae4384f8c0440f25b6c4fe6980d64033eb865 Mon Sep 17 00:00:00 2001 From: flaviostutz Date: Fri, 9 Oct 2020 18:17:28 -0300 Subject: [PATCH] making changes related to PR #534 review --- Dockerfile | 1 - README.md | 10 ++--- attack.go | 13 ++----- go.mod | 1 - lib/prom/prom.go | 31 ++++++---------- lib/prom/prom_test.go | 86 ++++++++++++++++++++++++++++++++----------- 6 files changed, 83 insertions(+), 59 deletions(-) diff --git a/Dockerfile b/Dockerfile index 37fb7958..b0e6f695 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,6 @@ RUN go mod download # now build source code ADD / /vegeta -RUN go test -v ./... RUN make vegeta FROM alpine:3.12.0 diff --git a/README.md b/README.md index 13afe06c..415a25ee 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,8 @@ attack command: Attack name -output string Output file (default "stdout") - -prom-bind - Host to bind Prometheus service to. Defaults to 0.0.0.0 - -prometheus-enable - Enable Prometheus metrics endpoint so that requests can be monitored by Prometheus with default bind (0.0.0.0:8880). Defaults to false. -prometheus-url - Prometheus metrics bind. Defaults to 0.0.0.0:8880 + Prometheus metrics bind url for enabling Prometheus metrics exporter. Ex.: "http://0.0.0.0:8880" -proxy-header value Proxy CONNECT header -rate value @@ -815,7 +811,7 @@ 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 Vegeta process itself. -To enable Prometheus Exporter on command line, see flags with "prom-" prefix. +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". Prometheus HTTP endpoint will be available only during the lifespan of an "attack" and will be closed right after the attack is finished. @@ -839,7 +835,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=http://0.0.0.0:8880' prometheus: image: flaviostutz/prometheus:2.19.2.0 diff --git a/attack.go b/attack.go index 66139e1f..236a33be 100644 --- a/attack.go +++ b/attack.go @@ -28,7 +28,6 @@ func attackCmd() command { laddr: localAddr{&vegeta.DefaultLocalAddr}, rate: vegeta.Rate{Freq: 50, Per: time.Second}, maxBody: vegeta.DefaultMaxBody, - promEnable: false, promURL: "0.0.0.0:8880", } fs.StringVar(&opts.name, "name", "", "Attack name") @@ -59,7 +58,6 @@ func attackCmd() command { fs.Var(&opts.laddr, "laddr", "Local IP address") fs.BoolVar(&opts.keepalive, "keepalive", true, "Use persistent connections") fs.StringVar(&opts.unixSocket, "unix-socket", "", "Connect over a unix socket. This overrides the host address in target URLs") - fs.BoolVar(&opts.promEnable, "prometheus-enable", false, "Enable Prometheus metrics endpoint so that requests can be monitored by Prometheus using default parameters(bind to 0.0.0.0:8880). Defaults to false.") fs.StringVar(&opts.promURL, "prometheus-url", "", "Enable Prometheus metrics with specific bind parameters in format [bind ip]:[bind port]. Example: 0.0.0.0:8880") systemSpecificFlags(fs, opts) @@ -104,7 +102,6 @@ type attackOpts struct { keepalive bool resolvers csl unixSocket string - promEnable bool promURL string } @@ -180,12 +177,8 @@ func attack(opts *attackOpts) (err error) { } var promMetrics *prom.PrometheusMetrics - if opts.promEnable || opts.promURL != "" { - purl := opts.promURL - if purl == "" { - purl = "0.0.0.0:8880" - } - promMetrics, err = prom.NewPrometheusMetricsWithParams(purl) + if opts.promURL != "" { + promMetrics, err = prom.NewPrometheusMetricsWithParams(opts.promURL) if err != nil { return err } @@ -223,7 +216,7 @@ func attack(opts *attackOpts) (err error) { if !ok { return nil } - if opts.promEnable { + if opts.promURL != "" { promMetrics.Observe(r) } if err = enc.Encode(r); err != nil { diff --git a/go.mod b/go.mod index 8adf14db..77f5d292 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( github.com/miekg/dns v1.1.17 github.com/prometheus/client_golang v1.7.1 github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 - github.com/stretchr/testify v1.4.0 github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 pgregory.net/rapid v0.3.3 diff --git a/lib/prom/prom.go b/lib/prom/prom.go index c139a018..9c63125c 100644 --- a/lib/prom/prom.go +++ b/lib/prom/prom.go @@ -3,14 +3,16 @@ package prom import ( "context" "fmt" + "net" "net/http" - "regexp" + "net/url" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" + vegeta "github.com/tsenart/vegeta/v12/lib" ) @@ -26,32 +28,23 @@ type PrometheusMetrics struct { //NewPrometheusMetrics same as NewPrometheusMetricsWithParams with default params: func NewPrometheusMetrics() (*PrometheusMetrics, error) { - return NewPrometheusMetricsWithParams("0.0.0.0:8880") + return NewPrometheusMetricsWithParams("http://0.0.0.0:8880") } // NewPrometheusMetricsWithParams creates a new Prometheus Metrics to Observe attack results and expose metrics -// For example, after using NewPrometheusMetricsWithParams("0.0.0.0:8880"), +// For example, after using NewPrometheusMetricsWithParams("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) { //parse bind url elements - re := regexp.MustCompile("(.+):([0-9]+)") - rr := re.FindAllStringSubmatch(bindURL, 3) - bindHost := "" - bindPort := 0 - var err error - if len(rr) == 1 { - if len(rr[0]) == 3 { - bindHost = rr[0][1] - bindPort, err = strconv.Atoi(rr[0][2]) - if err != nil { - return nil, err - } - } + 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) } - if bindHost == "" { - return nil, fmt.Errorf("Invalid bindURL %s. Must be in format '0.0.0.0:8880'", bindURL) + bindHost, bindPort, err := net.SplitHostPort(p.Host) + if err != nil { + return nil, fmt.Errorf("Invalid bindURL %s. Must be in format 'http://0.0.0.0:8880'. err=%s", bindURL, err) } pm := &PrometheusMetrics{ @@ -101,7 +94,7 @@ func NewPrometheusMetricsWithParams(bindURL string) (*PrometheusMetrics, error) //setup prometheus metrics http server pm.srv = http.Server{ - Addr: fmt.Sprintf("%s:%d", bindHost, bindPort), + Addr: fmt.Sprintf("%s:%s", bindHost, bindPort), Handler: promhttp.HandlerFor(pm.registry, promhttp.HandlerOpts{}), } diff --git a/lib/prom/prom_test.go b/lib/prom/prom_test.go index 28937b73..25606df5 100644 --- a/lib/prom/prom_test.go +++ b/lib/prom/prom_test.go @@ -3,6 +3,7 @@ package prom import ( "io/ioutil" "net/http" + "strings" "testing" "time" @@ -12,26 +13,43 @@ import ( func TestPromServerBasic1(t *testing.T) { pm, err := NewPrometheusMetrics() - assert.Nil(t, err, "Error launching Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error launching Prometheus http server. err=%s", err) + } + err = pm.Close() - assert.Nil(t, err, "Error stopping Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error stopping Prometheus http server. err=%s", err) + } } func TestPromServerBasic2(t *testing.T) { pm, err := NewPrometheusMetrics() - assert.Nil(t, err, "Error launching Prometheus metrics. err=%s", err) + if err != nil { + t.Errorf("Error launching Prometheus metrics. err=%s", err) + } err = pm.Close() - assert.Nil(t, err, "Error stopping Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error stopping Prometheus http server. err=%s", err) + } pm, err = NewPrometheusMetrics() - assert.Nil(t, err, "Error launching Prometheus metrics. err=%s", err) + if err != nil { + t.Errorf("Error launching Prometheus metrics. err=%s", err) + } err = pm.Close() - assert.Nil(t, err, "Error stopping Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error stopping Prometheus http server. err=%s", err) + } pm, err = NewPrometheusMetrics() - assert.Nil(t, err, "Error launching Prometheus metrics. err=%s", err) + if err != nil { + t.Errorf("Error launching Prometheus metrics. err=%s", err) + } err = pm.Close() - assert.Nil(t, err, "Error stopping Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error stopping Prometheus http server. err=%s", err) + } } func TestPromServerObserve(t *testing.T) { @@ -54,32 +72,58 @@ func TestPromServerObserve(t *testing.T) { time.Sleep(3 * time.Second) resp, err := http.Get("http://localhost:8880") - assert.Nil(t, err, "Error calling prometheus metrics. err=%s", err) - assert.Equal(t, 200, resp.StatusCode, "Status code should be 200") + if err != nil { + t.Errorf("Error calling prometheus metrics. err=%s", err) + } + if resp.StatusCode != 200 { + t.Errorf("Status code should be 200") + } data, err := ioutil.ReadAll(resp.Body) - assert.Nil(t, err, "Error calling prometheus metrics. err=%s", err) + if err != nil { + t.Errorf("Error calling prometheus metrics. err=%s", err) + } str := string(data) - assert.NotEqual(t, 0, len(str), "Body not empty") - assert.Contains(t, str, "request_seconds", "Metrics should contain request_seconds") - assert.Contains(t, str, "request_bytes_in", "Metrics should contain request_bytes_in") - assert.Contains(t, str, "request_bytes_out", "Metrics should contain request_bytes_out") - assert.NotContains(t, str, "request_fail_count", "Metrics should contain request_fail_count") + if len(str) == 0 { + t.Errorf("Body not empty. body=%s", str) + } + if !strings.Contains(str, "request_seconds") { + t.Error("Metrics should contain request_seconds") + } + if !strings.Contains(str, "request_bytes_in") { + t.Error("Metrics should contain request_bytes_in") + } + if !strings.Contains(str, "request_bytes_out") { + t.Error("Metrics should contain request_bytes_out") + } + if strings.Contains(str, "request_fail_count") { + t.Error("Metrics should contain request_fail_count") + } r.Code = 500 r.Error = "REQUEST FAILED" pm.Observe(r) resp, err = http.Get("http://localhost:8880") - assert.Nil(t, err, "Error calling prometheus metrics. err=%s", err) - assert.Equal(t, 200, resp.StatusCode, "Status code should be 200") + if err != nil { + t.Errorf("Error calling prometheus metrics. err=%s", err) + } + if resp.StatusCode != 200 { + t.Errorf("Status code should be 200") + } data, err = ioutil.ReadAll(resp.Body) - assert.Nil(t, err, "Error calling prometheus metrics. err=%s", err) + if err != nil { + t.Errorf("Error calling prometheus metrics. err=%s", err) + } str = string(data) - assert.Contains(t, str, "request_fail_count", "Metrics should contain request_fail_count") + if !strings.Contains(str, "request_fail_count") { + t.Error("Metrics should contain request_fail_count") + } err = pm.Close() - assert.Nil(t, err, "Error stopping Prometheus http server. err=%s", err) + if err != nil { + t.Errorf("Error stopping Prometheus http server. err=%s", err) + } }