diff options
-rw-r--r-- | README | 10 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | htping.1 | 13 | ||||
-rw-r--r-- | htping.go | 110 |
4 files changed, 123 insertions, 12 deletions
diff --git a/README b/README index e012ed6..1b710c9 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ NAME SYNOPSIS htping [-4] [-6] [-H field:value] [-X method] [-c count] [-i interval] - [-f] [-k] [--http1.1] [--keepalive] host + [-f] [-k] [--http1.1] [--keepalive] [-l addr] [-q] host DESCRIPTION The htping utility periodically sends HTTP requests to host, prints the @@ -40,6 +40,12 @@ DESCRIPTION --keepalive Enable keepalive resp. use persistent connections. + -l addr + Start a HTTP server at addr (e.g. ‘:9100’) to provide Prometheus + metrics at ‘/metrics’. + + -q Quiet mode, don't print request status lines. + EXIT STATUS The htping utility exits 0 on success, and >0 if an error occurs. @@ -69,4 +75,4 @@ LICENSE http://creativecommons.org/publicdomain/zero/1.0/ -Void Linux March 14, 2020 Void Linux +Void Linux April 12, 2020 Void Linux diff --git a/go.mod b/go.mod index d6b499c..87d861f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/leahneukirchen/htping go 1.14 + +require github.com/prometheus/client_golang v1.5.1 diff --git a/htping.1 b/htping.1 index a77da05..5ca4582 100644 --- a/htping.1 +++ b/htping.1 @@ -1,4 +1,4 @@ -.Dd March 14, 2020 +.Dd April 12, 2020 .Dt HTPING 1 .Os .Sh NAME @@ -16,6 +16,8 @@ .Op Fl k .Op Fl -http1.1 .Op Fl -keepalive +.Op Fl l Ar addr +.Op Fl q .Ar host .Sh DESCRIPTION The @@ -58,6 +60,15 @@ Turn TLS verification errors into warnings. Disable HTTP/2 requests. .It Fl -keepalive Enable keepalive resp.\& use persistent connections. +.It Fl l Ar addr +Start a HTTP server at +.Ar addr +(e.g.\& +.Sq :9100 ) +to provide Prometheus metrics at +.Sq /metrics . +.It Fl q +Quiet mode, don't print request status lines. .El .Sh EXIT STATUS .Ex -std diff --git a/htping.go b/htping.go index c9034f7..600ad50 100644 --- a/htping.go +++ b/htping.go @@ -1,4 +1,4 @@ -// htping - periodically send HTTP requests +// htping - periodically send HTTP requests and keep statistics // // To the extent possible under law, Leah Neukirchen <leah@vuxu.org> // has waived all copyright and related or neighboring rights to this work. @@ -15,6 +15,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "math" "net" "net/http" @@ -22,17 +23,71 @@ import ( "net/url" "os" "os/signal" + "strconv" "strings" "sync/atomic" "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" ) const VERSION = "0.1" +var ( + sizeGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "htpingd", + Name: "responses_size_bytes", + Help: "Size of HTTP response.", + }, + []string{ + "url", + "addr", + "code", + }, + ) + requestCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "htpingd", + Name: "requests_total", + Help: "Number of HTTP requests.", + }, + []string{ + "url", + }, + ) + responseCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "htpingd", + Name: "responses_total", + Help: "Number of HTTP responses.", + }, + []string{ + "url", + "addr", + "code", + }, + ) + durSummary = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Namespace: "htpingd", + Name: "duration_seconds", + Help: "Request duration in seconds.", + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, + }, + []string{ + "url", + "addr", + }, + ) +) + var ntotal int32 var flag4 bool var flag6 bool +var quiet bool var myHeaders headers var method string @@ -122,6 +177,7 @@ type result struct { func ping(url string, seq int, results chan result) { start := time.Now() + requestCounter.WithLabelValues(url).Inc() atomic.AddInt32(&ntotal, 1) req, err := http.NewRequest(method, url, nil) @@ -130,7 +186,7 @@ func ping(url string, seq int, results chan result) { return } - req.Header.Set("User-Agent", "htping/" + VERSION) + req.Header.Set("User-Agent", "htping/"+VERSION) for _, e := range myHeaders { req.Header.Set(e.key, e.value) @@ -161,13 +217,19 @@ func ping(url string, seq int, results chan result) { dur := float64(stop.Sub(start)) / float64(time.Second) - fmt.Printf("%d bytes from %v: %s %d seq=%d time=%.3f ms\n", - written, - myTransport.addr, - res.Proto, - res.StatusCode, - seq, - dur) + sizeGauge.WithLabelValues(url, string(myTransport.addr), strconv.Itoa(res.StatusCode)).Set(float64(written)) + responseCounter.WithLabelValues(url, string(myTransport.addr), strconv.Itoa(res.StatusCode)).Inc() + durSummary.WithLabelValues(url, string(myTransport.addr)).Observe(dur) + + if !quiet { + fmt.Printf("%d bytes from %v: %s %d seq=%d time=%.3f ms\n", + written, + myTransport.addr, + res.Proto, + res.StatusCode, + seq, + dur) + } results <- result{dur, res.StatusCode} } @@ -238,6 +300,7 @@ func (i *headers) Set(value string) error { func main() { flag.BoolVar(&flag4, "4", false, "resolve IPv4 only") flag.BoolVar(&flag6, "6", false, "resolve IPv6 only") + flag.BoolVar(&quiet, "q", false, "quiet") flag.Var(&myHeaders, "H", "set custom `header`s") flag.StringVar(&method, "X", "HEAD", "HTTP `method`") @@ -250,6 +313,8 @@ func main() { flag.BoolVar(&keepalive, "keepalive", false, "enable keepalive/use persistent connections") + listenAddr := flag.String("l", "", "listen on `addr`") + flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [FLAGS...] URL\n", os.Args[0]) flag.PrintDefaults() @@ -277,6 +342,33 @@ func main() { os.Exit(1) } + if *listenAddr != "" { + prometheus.MustRegister(requestCounter) + prometheus.MustRegister(responseCounter) + prometheus.MustRegister(durSummary) + + go func() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(`<html> + <head><title>htpingd</title></head> + <body> + <h1>htpingd</h1> + <p><a href="/metrics">Metrics</a></p> +</body> + </html> +`)) + }) + http.Handle("/metrics", promhttp.Handler()) + log.Println("Prometheus metrics listening on", *listenAddr) + err := http.ListenAndServe(*listenAddr, nil) + if err != http.ErrServerClosed { + log.Fatal(err) + os.Exit(1) + } + }() + } + fmt.Printf("%s %s\n", method, u) interrupt := make(chan os.Signal, 1) |