From 418c9622f51e31c3926d7bf5ebcc1556aec7501a Mon Sep 17 00:00:00 2001 From: Sebastian Rabenhorst Date: Wed, 24 Apr 2024 17:35:59 +0200 Subject: [PATCH 1/5] Added pingdom_min_request_limit internal metric --- cmd/pingdom-exporter/main.go | 15 ++++++++++- pkg/pingdom/check.go | 40 +++++++++++++++++++++++++---- pkg/pingdom/check_test.go | 49 +++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/cmd/pingdom-exporter/main.go b/cmd/pingdom-exporter/main.go index d296c81..48bb78a 100644 --- a/cmd/pingdom-exporter/main.go +++ b/cmd/pingdom-exporter/main.go @@ -33,6 +33,12 @@ var ( nil, nil, ) + pingdomMinRequestLimit = prometheus.NewDesc( + "pingdom_min_request_limit", + "Minimal request limit from both Req-Limit-Short and Req-Limit-Long", + nil, nil, + ) + pingdomOutageCheckPeriodDesc = prometheus.NewDesc( "pingdom_slo_period_seconds", "Outage check period, in seconds", @@ -96,6 +102,7 @@ type pingdomCollector struct { func (pc pingdomCollector) Describe(ch chan<- *prometheus.Desc) { ch <- pingdomUpDesc + ch <- pingdomMinRequestLimit ch <- pingdomOutageCheckPeriodDesc ch <- pingdomCheckStatusDesc ch <- pingdomCheckResponseTimeDesc @@ -110,11 +117,17 @@ func (pc pingdomCollector) Collect(ch chan<- prometheus.Metric) { outageCheckPeriodDuration := time.Hour * time.Duration(24*outageCheckPeriod) outageCheckPeriodSecs := float64(outageCheckPeriodDuration / time.Second) - checks, err := pc.client.Checks.List(map[string]string{ + checks, minReqLimit, err := pc.client.Checks.List(map[string]string{ "include_tags": "true", "tags": pc.client.Tags, }) + ch <- prometheus.MustNewConstMetric( + pingdomMinRequestLimit, + prometheus.GaugeValue, + minReqLimit, + ) + if err != nil { fmt.Fprintf(os.Stderr, "Error getting checks: %v", err) ch <- prometheus.MustNewConstMetric( diff --git a/pkg/pingdom/check.go b/pkg/pingdom/check.go index 0e54911..ac5f4c2 100644 --- a/pkg/pingdom/check.go +++ b/pkg/pingdom/check.go @@ -3,6 +3,18 @@ package pingdom import ( "encoding/json" "io/ioutil" + "math" + "net/http" + "regexp" + "strconv" +) + +var ( + reqLimitHeaderKeys = []string{ + "req-limit-short", + "req-limit-long", + } + reqLimitRe = regexp.MustCompile(`Remaining: (\d+) Time until reset: (\d+)`) ) // CheckService provides an interface to Pingdom checks. @@ -13,24 +25,26 @@ type CheckService struct { // List returns a list of checks from Pingdom. // This returns type CheckResponse rather than Check since the // Pingdom API does not return a complete representation of a check. -func (cs *CheckService) List(params ...map[string]string) ([]CheckResponse, error) { +func (cs *CheckService) List(params ...map[string]string) ([]CheckResponse, float64, error) { param := map[string]string{} if len(params) == 1 { param = params[0] } req, err := cs.client.NewRequest("GET", "/checks", param) if err != nil { - return nil, err + return nil, 0, err } resp, err := cs.client.client.Do(req) if err != nil { - return nil, err + return nil, 0, err } defer resp.Body.Close() + minRequestLimit := minRequestLimitFromHeader(resp.Header) + if err := validateResponse(resp); err != nil { - return nil, err + return nil, minRequestLimit, err } bodyBytes, _ := ioutil.ReadAll(resp.Body) @@ -38,5 +52,21 @@ func (cs *CheckService) List(params ...map[string]string) ([]CheckResponse, erro m := &listChecksJSONResponse{} err = json.Unmarshal([]byte(bodyString), &m) - return m.Checks, err + return m.Checks, minRequestLimit, err +} + +func minRequestLimitFromHeader(header http.Header) float64 { + minRequestLimit := math.MaxFloat64 + + for _, key := range reqLimitHeaderKeys { + matches := reqLimitRe.FindStringSubmatch(header.Get(key)) + if len(matches) > 0 { + limit, err := strconv.ParseFloat(matches[1], 64) + if err == nil && limit < minRequestLimit { + minRequestLimit = limit + } + } + } + + return minRequestLimit } diff --git a/pkg/pingdom/check_test.go b/pkg/pingdom/check_test.go index b34d458..c2c8f57 100644 --- a/pkg/pingdom/check_test.go +++ b/pkg/pingdom/check_test.go @@ -2,6 +2,7 @@ package pingdom import ( "fmt" + "math" "net/http" "testing" @@ -14,6 +15,7 @@ func TestCheckServiceList(t *testing.T) { mux.HandleFunc("/checks", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") + w.Header().Set("req-limit-long", "Remaining: 12 Time until reset: 34") fmt.Fprint(w, `{ "checks": [ { @@ -141,7 +143,52 @@ func TestCheckServiceList(t *testing.T) { }, } - checks, err := client.Checks.List() + checks, minRequestLimit, err := client.Checks.List() assert.NoError(t, err) assert.Equal(t, want, checks) + assert.EqualValues(t, 12, minRequestLimit) +} + +func TestMinRequestLimitFromResp(t *testing.T) { + tc := []struct { + header http.Header + expected float64 + }{ + { + header: http.Header{}, + expected: math.MaxFloat64, + }, + { + header: http.Header{ + "Req-Limit-Short": []string{"Remaining: 12 Time until reset: 34"}, + }, + expected: 12, + }, + { + header: http.Header{ + "Req-Limit-Long": []string{"Remaining: 56 Time until reset: 78"}, + }, + expected: 56, + }, + { + header: http.Header{ + "Req-Limit-Long": []string{"Remaining: 0 Time until reset: 78"}, + "Req-Limit-Short": []string{"Remaining: 12 Time until reset: 34"}, + }, + expected: 0, + }, + { + header: http.Header{ + "Req-Limit-Long": []string{"invalid"}, + }, + expected: math.MaxFloat64, + }, + } + + for _, tt := range tc { + t.Run(fmt.Sprintf("%v", tt.header), func(t *testing.T) { + actual := minRequestLimitFromHeader(tt.header) + assert.Equal(t, tt.expected, actual) + }) + } } From aea569939d8aa2e64c02b8a59baa88b3b695d848 Mon Sep 17 00:00:00 2001 From: Sebastian Rabenhorst Date: Wed, 24 Apr 2024 17:56:39 +0200 Subject: [PATCH 2/5] Naming --- cmd/pingdom-exporter/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/pingdom-exporter/main.go b/cmd/pingdom-exporter/main.go index 48bb78a..ce4ad0f 100644 --- a/cmd/pingdom-exporter/main.go +++ b/cmd/pingdom-exporter/main.go @@ -33,9 +33,9 @@ var ( nil, nil, ) - pingdomMinRequestLimit = prometheus.NewDesc( - "pingdom_min_request_limit", - "Minimal request limit from both Req-Limit-Short and Req-Limit-Long", + pingdomMinRequestLimitDesc = prometheus.NewDesc( + "pingdom_rate_limit_min_remaining_requests", + "Tracks the minimum remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API.", nil, nil, ) @@ -102,7 +102,7 @@ type pingdomCollector struct { func (pc pingdomCollector) Describe(ch chan<- *prometheus.Desc) { ch <- pingdomUpDesc - ch <- pingdomMinRequestLimit + ch <- pingdomMinRequestLimitDesc ch <- pingdomOutageCheckPeriodDesc ch <- pingdomCheckStatusDesc ch <- pingdomCheckResponseTimeDesc @@ -123,7 +123,7 @@ func (pc pingdomCollector) Collect(ch chan<- prometheus.Metric) { }) ch <- prometheus.MustNewConstMetric( - pingdomMinRequestLimit, + pingdomMinRequestLimitDesc, prometheus.GaugeValue, minReqLimit, ) From 6d02682e68ee1cde873be102ea969b8ddc7e0a54 Mon Sep 17 00:00:00 2001 From: Sebastian Rabenhorst Date: Fri, 26 Apr 2024 19:14:26 +0200 Subject: [PATCH 3/5] Added description in readme --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e50a217..782f06f 100644 --- a/README.md +++ b/README.md @@ -61,17 +61,18 @@ on how to build your own image and push it to your private registry. ## Exported Metrics -| Metric Name | Description | -| --------------------------------------------------- | ------------------------------------------------------------------------------- | -| `pingdom_up` | Was the last query on Pingdom API successful | -| `pingdom_uptime_status` | The current status of the check (1: up, 0: down) | -| `pingdom_uptime_response_time_seconds` | The response time of last test, in seconds | -| `pingdom_slo_period_seconds` | Outage check period, in seconds (see `-outage-check-period` flag) | -| `pingdom_outages_total` | Number of outages within the outage check period | -| `pingdom_down_seconds` | Total down time within the outage check period, in seconds | -| `pingdom_up_seconds` | Total up time within the outage check period, in seconds | -| `pingdom_uptime_slo_error_budget_total_seconds` | Maximum number of allowed downtime, in seconds, according to the uptime SLO | -| `pingdom_uptime_slo_error_budget_available_seconds` | Number of seconds of downtime we can still have without breaking the uptime SLO | +| Metric Name | Description | +| --------------------------------------------------- |------------------------------------------------------------------------------------------------------------------| +| `pingdom_up` | Was the last query on Pingdom API successful | +| `pingdom_uptime_response_time_seconds` | The minimum remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API. | +| `pingdom_uptime_status` | The current status of the check (1: up, 0: down) | +| `pingdom_uptime_response_time_seconds` | The response time of last test, in seconds | +| `pingdom_slo_period_seconds` | Outage check period, in seconds (see `-outage-check-period` flag) | +| `pingdom_outages_total` | Number of outages within the outage check period | +| `pingdom_down_seconds` | Total down time within the outage check period, in seconds | +| `pingdom_up_seconds` | Total up time within the outage check period, in seconds | +| `pingdom_uptime_slo_error_budget_total_seconds` | Maximum number of allowed downtime, in seconds, according to the uptime SLO | +| `pingdom_uptime_slo_error_budget_available_seconds` | Number of seconds of downtime we can still have without breaking the uptime SLO | ## Development From f828a556a3ce8ad4cf542d26a6d30c9cfc64ed55 Mon Sep 17 00:00:00 2001 From: Sebastian Rabenhorst Date: Mon, 13 May 2024 19:08:50 +0200 Subject: [PATCH 4/5] Renamed remaining requests metric --- cmd/pingdom-exporter/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/pingdom-exporter/main.go b/cmd/pingdom-exporter/main.go index ce4ad0f..6370a17 100644 --- a/cmd/pingdom-exporter/main.go +++ b/cmd/pingdom-exporter/main.go @@ -33,9 +33,9 @@ var ( nil, nil, ) - pingdomMinRequestLimitDesc = prometheus.NewDesc( - "pingdom_rate_limit_min_remaining_requests", - "Tracks the minimum remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API.", + pingdomRateLimitRemainingRequestsDesc = prometheus.NewDesc( + "pingdom_rate_limit_remaining_requests", + "Tracks the remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API.", nil, nil, ) @@ -102,7 +102,7 @@ type pingdomCollector struct { func (pc pingdomCollector) Describe(ch chan<- *prometheus.Desc) { ch <- pingdomUpDesc - ch <- pingdomMinRequestLimitDesc + ch <- pingdomRateLimitRemainingRequestsDesc ch <- pingdomOutageCheckPeriodDesc ch <- pingdomCheckStatusDesc ch <- pingdomCheckResponseTimeDesc @@ -123,7 +123,7 @@ func (pc pingdomCollector) Collect(ch chan<- prometheus.Metric) { }) ch <- prometheus.MustNewConstMetric( - pingdomMinRequestLimitDesc, + pingdomRateLimitRemainingRequestsDesc, prometheus.GaugeValue, minReqLimit, ) From 9745680d0d1582203cb244cf9c73985e19964a3d Mon Sep 17 00:00:00 2001 From: Sebastian Rabenhorst Date: Mon, 13 May 2024 19:10:55 +0200 Subject: [PATCH 5/5] Fixed description of pingdom_rate_limit_remaining_requests in readme --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 782f06f..d74f4e6 100644 --- a/README.md +++ b/README.md @@ -61,18 +61,18 @@ on how to build your own image and push it to your private registry. ## Exported Metrics -| Metric Name | Description | -| --------------------------------------------------- |------------------------------------------------------------------------------------------------------------------| -| `pingdom_up` | Was the last query on Pingdom API successful | -| `pingdom_uptime_response_time_seconds` | The minimum remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API. | -| `pingdom_uptime_status` | The current status of the check (1: up, 0: down) | -| `pingdom_uptime_response_time_seconds` | The response time of last test, in seconds | -| `pingdom_slo_period_seconds` | Outage check period, in seconds (see `-outage-check-period` flag) | -| `pingdom_outages_total` | Number of outages within the outage check period | -| `pingdom_down_seconds` | Total down time within the outage check period, in seconds | -| `pingdom_up_seconds` | Total up time within the outage check period, in seconds | -| `pingdom_uptime_slo_error_budget_total_seconds` | Maximum number of allowed downtime, in seconds, according to the uptime SLO | -| `pingdom_uptime_slo_error_budget_available_seconds` | Number of seconds of downtime we can still have without breaking the uptime SLO | +| Metric Name | Description | +| --------------------------------------------------- |----------------------------------------------------------------------------------------------------------| +| `pingdom_up` | Was the last query on Pingdom API successful | +| `pingdom_rate_limit_remaining_requests` | The remaining requests allowed before hitting the short-term or long-term rate limit in the Pingdom API. | +| `pingdom_uptime_status` | The current status of the check (1: up, 0: down) | +| `pingdom_uptime_response_time_seconds` | The response time of last test, in seconds | +| `pingdom_slo_period_seconds` | Outage check period, in seconds (see `-outage-check-period` flag) | +| `pingdom_outages_total` | Number of outages within the outage check period | +| `pingdom_down_seconds` | Total down time within the outage check period, in seconds | +| `pingdom_up_seconds` | Total up time within the outage check period, in seconds | +| `pingdom_uptime_slo_error_budget_total_seconds` | Maximum number of allowed downtime, in seconds, according to the uptime SLO | +| `pingdom_uptime_slo_error_budget_available_seconds` | Number of seconds of downtime we can still have without breaking the uptime SLO | ## Development