about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2020-04-12 18:05:22 +0200
committerLeah Neukirchen <leah@vuxu.org>2020-04-12 18:08:40 +0200
commit3c83670c5f5a2395c7e08dfb0e45a001944ce406 (patch)
tree925a9ca7e95add977bb67332ddd915baa0cd867e
parent2cb547d0fa7af0f4570ed4f1286c27243ca31148 (diff)
downloadhtping-3c83670c5f5a2395c7e08dfb0e45a001944ce406.tar.gz
htping-3c83670c5f5a2395c7e08dfb0e45a001944ce406.tar.xz
htping-3c83670c5f5a2395c7e08dfb0e45a001944ce406.zip
add prometheus metrics
-rw-r--r--README10
-rw-r--r--go.mod2
-rw-r--r--htping.113
-rw-r--r--htping.go110
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)