Skip to content

Commit 7c54090

Browse files
authored
feat: add support for Graphite metrics provider (argoproj#1406)
Signed-off-by: Mike Ball <[email protected]>
1 parent d9ba36a commit 7c54090

19 files changed

+3401
-489
lines changed

docs/CONTRIBUTING.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
2828
```
2929

3030
Brew users can quickly install the lot:
31-
31+
3232
```bash
3333
brew install go kubectl kustomize golangci-lint protobuf swagger-codegen
3434
```
@@ -76,7 +76,7 @@ make test
7676
## Running E2E tests
7777

7878
The end-to-end tests need to run against a kubernetes cluster with the Argo Rollouts controller
79-
running. The rollout controller can be started with the command:
79+
running. The rollout controller can be started with the command:
8080

8181
```
8282
make start-e2e
@@ -106,8 +106,8 @@ make test-e2e E2E_TEST_OPTIONS="-testify.m ^TestRolloutRestart$"
106106

107107
3. The e2e tests are designed to run as quickly as possible, eliminating readiness and termination
108108
delays. However, it is often desired to artificially slow down the tests for debugging purposes,
109-
as well as to understand what the test is doing. To delay startup and termination of pods, set the
110-
`E2E_POD_DELAY` to an integer value in seconds. This environment variable is often coupled with
109+
as well as to understand what the test is doing. To delay startup and termination of pods, set the
110+
`E2E_POD_DELAY` to an integer value in seconds. This environment variable is often coupled with
111111
`E2E_TEST_OPTIONS` to debug and slow down a specific test.
112112

113113
```shell
@@ -176,14 +176,14 @@ kubectl -n argo-rollouts apply -f manifests/install.yaml
176176
```
177177

178178
## Upgrading Kubernetes Libraries
179-
Argo Rollouts has a dependency on the kubernetes/kubernetes repo for some of the functionality that has not been
180-
pushed into the other kubernetes repositories yet. In order to import the kubernetes/kubernetes repo, all of the
181-
associated repos have to pinned to the correct version specified by the kubernetes/kubernetes release. The
179+
Argo Rollouts has a dependency on the kubernetes/kubernetes repo for some of the functionality that has not been
180+
pushed into the other kubernetes repositories yet. In order to import the kubernetes/kubernetes repo, all of the
181+
associated repos have to pinned to the correct version specified by the kubernetes/kubernetes release. The
182182
`./hack/update-k8s-dependencies.sh` updates all the dependencies to the those correct versions.
183183

184184
## Documentation Changes
185185

186-
Modify contents in `docs/` directory.
186+
Modify contents in `docs/` directory.
187187

188188
Preview changes in your browser by visiting http://localhost:8000 after running:
189189

docs/analysis/graphite.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Graphite Metrics
2+
3+
A [Graphite](https://graphiteapp.org/) query can be used to obtain measurements for analysis.
4+
5+
```yaml
6+
apiVersion: argoproj.io/v1alpha1
7+
kind: AnalysisTemplate
8+
metadata:
9+
name: success-rate
10+
spec:
11+
args:
12+
- name: service-name
13+
metrics:
14+
- name: success-rate
15+
interval: 5m
16+
# Note that the Argo Rollouts Graphite metrics provider returns results as an array of float64s with 6 decimal places.
17+
successCondition: results[0] >= 90.000000
18+
failureLimit: 3
19+
provider:
20+
graphite:
21+
address: http://graphite.example.com:9090
22+
query: |
23+
target=summarize(
24+
asPercent(
25+
sumSeries(
26+
stats.timers.httpServerRequests.app.{{args.service-name}}.exception.*.method.*.outcome.{CLIENT_ERROR,INFORMATIONAL,REDIRECTION,SUCCESS}.status.*.uri.*.count
27+
),
28+
sumSeries(
29+
stats.timers.httpServerRequests.app.{{args.service-name}}.exception.*.method.*.outcome.*.status.*.uri.*.count
30+
)
31+
),
32+
'5min',
33+
'avg'
34+
)
35+
```

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ For these reasons, in large scale high-volume production environments, a rolling
2929
* Customizable metric queries and analysis of business KPIs
3030
* Ingress controller integration: NGINX, ALB
3131
* Service Mesh integration: Istio, Linkerd, SMI
32-
* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic
32+
* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic, Graphite
3333

3434
## Quick Start
3535

manifests/crds/analysis-run-crd.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ spec:
159159
required:
160160
- query
161161
type: object
162+
graphite:
163+
properties:
164+
address:
165+
type: string
166+
query:
167+
type: string
168+
type: object
162169
job:
163170
properties:
164171
metadata:

manifests/crds/analysis-template-crd.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ spec:
154154
required:
155155
- query
156156
type: object
157+
graphite:
158+
properties:
159+
address:
160+
type: string
161+
query:
162+
type: string
163+
type: object
157164
job:
158165
properties:
159166
metadata:

manifests/crds/cluster-analysis-template-crd.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ spec:
154154
required:
155155
- query
156156
type: object
157+
graphite:
158+
properties:
159+
address:
160+
type: string
161+
query:
162+
type: string
163+
type: object
157164
job:
158165
properties:
159166
metadata:

manifests/install.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ spec:
160160
required:
161161
- query
162162
type: object
163+
graphite:
164+
properties:
165+
address:
166+
type: string
167+
query:
168+
type: string
169+
type: object
163170
job:
164171
properties:
165172
metadata:
@@ -2711,6 +2718,13 @@ spec:
27112718
required:
27122719
- query
27132720
type: object
2721+
graphite:
2722+
properties:
2723+
address:
2724+
type: string
2725+
query:
2726+
type: string
2727+
type: object
27142728
job:
27152729
properties:
27162730
metadata:
@@ -5189,6 +5203,13 @@ spec:
51895203
required:
51905204
- query
51915205
type: object
5206+
graphite:
5207+
properties:
5208+
address:
5209+
type: string
5210+
query:
5211+
type: string
5212+
type: object
51925213
job:
51935214
properties:
51945215
metadata:

manifests/namespace-install.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ spec:
160160
required:
161161
- query
162162
type: object
163+
graphite:
164+
properties:
165+
address:
166+
type: string
167+
query:
168+
type: string
169+
type: object
163170
job:
164171
properties:
165172
metadata:
@@ -2711,6 +2718,13 @@ spec:
27112718
required:
27122719
- query
27132720
type: object
2721+
graphite:
2722+
properties:
2723+
address:
2724+
type: string
2725+
query:
2726+
type: string
2727+
type: object
27142728
job:
27152729
properties:
27162730
metadata:
@@ -5189,6 +5203,13 @@ spec:
51895203
required:
51905204
- query
51915205
type: object
5206+
graphite:
5207+
properties:
5208+
address:
5209+
type: string
5210+
query:
5211+
type: string
5212+
type: object
51925213
job:
51935214
properties:
51945215
metadata:

metricproviders/graphite/api.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package graphite
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"math"
8+
"net/http"
9+
"net/url"
10+
"path"
11+
"regexp"
12+
"strconv"
13+
"time"
14+
15+
log "github.com/sirupsen/logrus"
16+
)
17+
18+
// API represents a Graphite API client
19+
type API interface {
20+
Query(query string) ([]dataPoint, error)
21+
}
22+
23+
// APIClient is a Graphite API client
24+
type APIClient struct {
25+
url url.URL
26+
client *http.Client
27+
logCTX log.Entry
28+
}
29+
30+
// Query performs a Graphite API query with the query it's passed
31+
func (api APIClient) Query(quer string) ([]dataPoint, error) {
32+
query := api.trimQuery(quer)
33+
u, err := url.Parse(fmt.Sprintf("./render?%s", query))
34+
if err != nil {
35+
return []dataPoint{}, err
36+
}
37+
38+
q := u.Query()
39+
q.Set("format", "json")
40+
u.RawQuery = q.Encode()
41+
42+
u.Path = path.Join(api.url.Path, u.Path)
43+
u = api.url.ResolveReference(u)
44+
45+
req, err := http.NewRequest("GET", u.String(), nil)
46+
if err != nil {
47+
return []dataPoint{}, err
48+
}
49+
50+
r, err := api.client.Do(req)
51+
if err != nil {
52+
return []dataPoint{}, err
53+
}
54+
defer r.Body.Close()
55+
56+
b, err := ioutil.ReadAll(r.Body)
57+
if err != nil {
58+
return []dataPoint{}, err
59+
}
60+
61+
if 400 <= r.StatusCode {
62+
return []dataPoint{}, fmt.Errorf("error response: %s", string(b))
63+
}
64+
65+
var result graphiteResponse
66+
err = json.Unmarshal(b, &result)
67+
if err != nil {
68+
return []dataPoint{}, err
69+
}
70+
71+
return result[0].DataPoints, nil
72+
}
73+
74+
func (api APIClient) trimQuery(q string) string {
75+
space := regexp.MustCompile(`\s+`)
76+
return space.ReplaceAllString(q, " ")
77+
}
78+
79+
type dataPoint struct {
80+
Value *float64
81+
TimeStamp time.Time
82+
}
83+
84+
func (gdp *dataPoint) UnmarshalJSON(data []byte) error {
85+
var v []interface{}
86+
if err := json.Unmarshal(data, &v); err != nil {
87+
return err
88+
}
89+
90+
if len(v) != 2 {
91+
return fmt.Errorf("error unmarshaling data point: %v", v)
92+
}
93+
94+
switch v[0].(type) {
95+
case nil:
96+
// no value
97+
case float64:
98+
f, _ := v[0].(float64)
99+
gdp.Value = &f
100+
case string:
101+
f, err := strconv.ParseFloat(v[0].(string), 64)
102+
if err != nil {
103+
return err
104+
}
105+
gdp.Value = &f
106+
default:
107+
f, ok := v[0].(float64)
108+
if !ok {
109+
return fmt.Errorf("error unmarshaling value: %v", v[0])
110+
}
111+
gdp.Value = &f
112+
}
113+
114+
switch v[1].(type) {
115+
case nil:
116+
// no value
117+
case float64:
118+
ts := int64(math.Round(v[1].(float64)))
119+
gdp.TimeStamp = time.Unix(ts, 0)
120+
case string:
121+
ts, err := strconv.ParseInt(v[1].(string), 10, 64)
122+
if err != nil {
123+
return err
124+
}
125+
gdp.TimeStamp = time.Unix(ts, 0)
126+
default:
127+
ts, ok := v[1].(int64)
128+
if !ok {
129+
return fmt.Errorf("error unmarshaling timestamp: %v", v[0])
130+
}
131+
gdp.TimeStamp = time.Unix(ts, 0)
132+
}
133+
134+
return nil
135+
}
136+
137+
type graphiteTargetResp struct {
138+
Target string `json:"target"`
139+
DataPoints []dataPoint `json:"datapoints"`
140+
}
141+
142+
type graphiteResponse []graphiteTargetResp

0 commit comments

Comments
 (0)