diff --git a/CHANGELOG.md b/CHANGELOG.md index cc05cd7..760d012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] - 2023-10-11 + +### Added + +- New histogram metrics. Every existing summary Prometheus metric now has a corresponding + histogram metric, with the `_hist_` in the metrics name. + +### Removed + +- "io/ioutil" library which is deprecated since go-1.16. + ## [0.4.3] - 2023-02-28 ### Added diff --git a/README.md b/README.md index 0a2a6a5..71e9c57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Minigun -[![Minigun Version](https://img.shields.io/badge/Minigun-0.4.3-7f187f.svg)](https://github.com/wayfair-incubator/minigun/blob/main/CHANGELOG.md) +[![Minigun Version](https://img.shields.io/badge/Minigun-0.5.0-7f187f.svg)](https://github.com/wayfair-incubator/minigun/blob/main/CHANGELOG.md) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md) ## About The Project diff --git a/main.go b/main.go index 4b71568..c416cff 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" + "io" "math/rand" "net" "net/http" @@ -35,7 +35,7 @@ import ( ) // Constants and vars -const version = "0.4.3" +const version = "0.5.0" const workersCannelSize = 1024 const errorBadHTTPCode = "Bad HTTP status code" @@ -43,10 +43,8 @@ var applog *logger.Logger var workerStatuses []workerStatus var registry = prometheus.NewRegistry() -// NOTES: histograms have been replaced with summaries. I'm keeping it just in case I decide -// to use histograms again. // Let's use the same buckets for histograms as NGINX Ingress controller -// var secondsDurationBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10} +var secondsDurationBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10} // Objectives for summary distributions var summaryObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001} @@ -297,6 +295,7 @@ func sendDataHTTP(data []byte, config appConfig, client *http.Client) error { trace := &httptrace.ClientTrace{ DNSStart: func(dsi httptrace.DNSStartInfo) { dns = time.Now() }, DNSDone: func(ddi httptrace.DNSDoneInfo) { + config.metrics.histDNSDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(dns).Seconds()) config.metrics.summaryDNSDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(dns).Seconds()) applog.Infof("DNS Done: %v\n", time.Since(dns)) applog.Infof("DNS Result: %v\n", ddi.Addrs) @@ -304,24 +303,28 @@ func sendDataHTTP(data []byte, config appConfig, client *http.Client) error { TLSHandshakeStart: func() { tlsHandshake = time.Now() }, TLSHandshakeDone: func(cs tls.ConnectionState, err error) { + config.metrics.histTLSHandshakeDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(tlsHandshake).Seconds()) config.metrics.summaryTLSHandshakeDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(tlsHandshake).Seconds()) applog.Infof("TLS Handshake: %v\n", time.Since(tlsHandshake)) }, ConnectStart: func(network, addr string) { connect = time.Now() }, ConnectDone: func(network, addr string, err error) { + config.metrics.histConnectDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(connect).Seconds()) config.metrics.summaryConnectDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(connect).Seconds()) applog.Infof("Connect time: %v\n", time.Since(connect)) }, WroteHeaders: func() { headers = time.Now() }, WroteRequest: func(wri httptrace.WroteRequestInfo) { + config.metrics.histWroteRequestBodyDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(headers).Seconds()) config.metrics.summaryWroteRequestBodyDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(headers).Seconds()) wroteRequest = time.Now() applog.Infof("Wrote request body time: %v\n", time.Since(headers)) }, GotFirstResponseByte: func() { + config.metrics.histGotFirstByteDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(start).Seconds()) config.metrics.summaryGotFirstByteDuration.WithLabelValues(config.metrics.labelValues...).Observe(time.Since(start).Seconds()) applog.Infof("Time from start to first byte: %v\n", time.Since(start)) }, @@ -335,6 +338,7 @@ func sendDataHTTP(data []byte, config appConfig, client *http.Client) error { responseTime := time.Since(wroteRequest) if err == nil && resp != nil { localLabelValues := append(config.metrics.labelValues, fmt.Sprintf("%v", resp.StatusCode)) + config.metrics.histResponseDuration.WithLabelValues(localLabelValues...).Observe(responseTime.Seconds()) config.metrics.summaryResponseDuration.WithLabelValues(localLabelValues...).Observe(responseTime.Seconds()) } } @@ -342,6 +346,7 @@ func sendDataHTTP(data []byte, config appConfig, client *http.Client) error { totalTime := time.Since(start) config.metrics.requestsSendBytesSum.WithLabelValues(config.metrics.labelValues...).Add(float64(len(config.sendPayload))) + config.metrics.histRequestsDuration.WithLabelValues(config.metrics.labelValues...).Observe(totalTime.Seconds()) config.metrics.summaryRequestsDuration.WithLabelValues(config.metrics.labelValues...).Observe(totalTime.Seconds()) applog.Infof("Total time: %v\n", totalTime) @@ -357,7 +362,7 @@ func sendDataHTTP(data []byte, config appConfig, client *http.Client) error { if resp.StatusCode >= 200 && resp.StatusCode <= 299 { - bodyBytes, err := ioutil.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { applog.Errorf("Failed to read response body from %q, error: %s", config.sendEndpoint, err.Error()) return err @@ -676,7 +681,7 @@ func main() { workerStatuses = make([]workerStatus, config.workers) // Logger - applog = logger.Init("minigun", config.verbose, false, ioutil.Discard) + applog = logger.Init("minigun", config.verbose, false, io.Discard) // Some checks if config.sendEndpoint == "" { @@ -697,7 +702,7 @@ func main() { // Load file if requested if config.sendFile != "" { applog.Infof("Reading file %q", config.sendFile) - if data, err := ioutil.ReadFile(config.sendFile); err == nil { + if data, err := os.ReadFile(config.sendFile); err == nil { config.sendPayload = data } else { applog.Fatalf("Error reading file %q: %s", config.sendFile, err.Error()) diff --git a/metrics.go b/metrics.go index 4c3f497..c50cb47 100644 --- a/metrics.go +++ b/metrics.go @@ -33,6 +33,15 @@ type appMetrics struct { channelLength *prometheus.GaugeVec channelConfigLength *prometheus.GaugeVec + // Histograms + histRequestsDuration *prometheus.HistogramVec + histDNSDuration *prometheus.HistogramVec + histConnectDuration *prometheus.HistogramVec + histGotFirstByteDuration *prometheus.HistogramVec + histTLSHandshakeDuration *prometheus.HistogramVec + histWroteRequestBodyDuration *prometheus.HistogramVec + histResponseDuration *prometheus.HistogramVec + // Summaries summaryRequestsDuration *prometheus.SummaryVec summaryDNSDuration *prometheus.SummaryVec @@ -95,6 +104,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics am.labelNames, ) + am.histRequestsDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "requests", + Name: "hist_duration_seconds", + Help: "Histogram distribution of request durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryRequestsDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -107,6 +127,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics ) // DNS metrics + am.histDNSDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "httptrace", + Name: "hist_dns_duration_seconds", + Help: "Histogram distribution of DNS durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryDNSDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -119,6 +150,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics ) // Connection metrics + am.histConnectDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "httptrace", + Name: "hist_connect_duration_seconds", + Help: "Histogram distribution of connection durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryConnectDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -131,6 +173,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics ) // Response first byte metrics + am.histGotFirstByteDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "httptrace", + Name: "hist_time_to_first_byte_seconds", + Help: "Histogram distribution of time to first byte durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryGotFirstByteDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -143,6 +196,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics ) // TLS handshake metrics + am.histTLSHandshakeDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "httptrace", + Name: "hist_tls_handshake_duration_seconds", + Help: "Histogram distribution of TLS Handshake durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryTLSHandshakeDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -155,6 +219,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics ) // Request body sent metrics + am.histWroteRequestBodyDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "httptrace", + Name: "hist_write_request_body_duration_seconds", + Help: "Histogram distribution of WroteRequestBody durations, in seconds", + Buckets: secondsDurationBuckets, + }, + am.labelNames, + ) + am.summaryWroteRequestBodyDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun", @@ -187,6 +262,17 @@ func initMetrics(config appConfig, labelNames, labelValues []string) appMetrics am.labelNames, ) + am.histResponseDuration = promauto.With(registry).NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "minigun", + Subsystem: "response", + Name: "hist_duration_seconds", + Help: "Histogram distribution of response durations, in seconds", + Buckets: secondsDurationBuckets, + }, + append(am.labelNames, "status"), + ) + am.summaryResponseDuration = promauto.With(registry).NewSummaryVec( prometheus.SummaryOpts{ Namespace: "minigun",