-
Notifications
You must be signed in to change notification settings - Fork 873
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for Graphite metrics provider (#1406)
Signed-off-by: Mike Ball <mikedball@gmail.com>
- Loading branch information
Showing
19 changed files
with
3,401 additions
and
489 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Graphite Metrics | ||
|
||
A [Graphite](https://graphiteapp.org/) query can be used to obtain measurements for analysis. | ||
|
||
```yaml | ||
apiVersion: argoproj.io/v1alpha1 | ||
kind: AnalysisTemplate | ||
metadata: | ||
name: success-rate | ||
spec: | ||
args: | ||
- name: service-name | ||
metrics: | ||
- name: success-rate | ||
interval: 5m | ||
# Note that the Argo Rollouts Graphite metrics provider returns results as an array of float64s with 6 decimal places. | ||
successCondition: results[0] >= 90.000000 | ||
failureLimit: 3 | ||
provider: | ||
graphite: | ||
address: http://graphite.example.com:9090 | ||
query: | | ||
target=summarize( | ||
asPercent( | ||
sumSeries( | ||
stats.timers.httpServerRequests.app.{{args.service-name}}.exception.*.method.*.outcome.{CLIENT_ERROR,INFORMATIONAL,REDIRECTION,SUCCESS}.status.*.uri.*.count | ||
), | ||
sumSeries( | ||
stats.timers.httpServerRequests.app.{{args.service-name}}.exception.*.method.*.outcome.*.status.*.uri.*.count | ||
) | ||
), | ||
'5min', | ||
'avg' | ||
) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package graphite | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"math" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
"regexp" | ||
"strconv" | ||
"time" | ||
|
||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
// API represents a Graphite API client | ||
type API interface { | ||
Query(query string) ([]dataPoint, error) | ||
} | ||
|
||
// APIClient is a Graphite API client | ||
type APIClient struct { | ||
url url.URL | ||
client *http.Client | ||
logCTX log.Entry | ||
} | ||
|
||
// Query performs a Graphite API query with the query it's passed | ||
func (api APIClient) Query(quer string) ([]dataPoint, error) { | ||
query := api.trimQuery(quer) | ||
u, err := url.Parse(fmt.Sprintf("./render?%s", query)) | ||
if err != nil { | ||
return []dataPoint{}, err | ||
} | ||
|
||
q := u.Query() | ||
q.Set("format", "json") | ||
u.RawQuery = q.Encode() | ||
|
||
u.Path = path.Join(api.url.Path, u.Path) | ||
u = api.url.ResolveReference(u) | ||
|
||
req, err := http.NewRequest("GET", u.String(), nil) | ||
if err != nil { | ||
return []dataPoint{}, err | ||
} | ||
|
||
r, err := api.client.Do(req) | ||
if err != nil { | ||
return []dataPoint{}, err | ||
} | ||
defer r.Body.Close() | ||
|
||
b, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
return []dataPoint{}, err | ||
} | ||
|
||
if 400 <= r.StatusCode { | ||
return []dataPoint{}, fmt.Errorf("error response: %s", string(b)) | ||
} | ||
|
||
var result graphiteResponse | ||
err = json.Unmarshal(b, &result) | ||
if err != nil { | ||
return []dataPoint{}, err | ||
} | ||
|
||
return result[0].DataPoints, nil | ||
} | ||
|
||
func (api APIClient) trimQuery(q string) string { | ||
space := regexp.MustCompile(`\s+`) | ||
return space.ReplaceAllString(q, " ") | ||
} | ||
|
||
type dataPoint struct { | ||
Value *float64 | ||
TimeStamp time.Time | ||
} | ||
|
||
func (gdp *dataPoint) UnmarshalJSON(data []byte) error { | ||
var v []interface{} | ||
if err := json.Unmarshal(data, &v); err != nil { | ||
return err | ||
} | ||
|
||
if len(v) != 2 { | ||
return fmt.Errorf("error unmarshaling data point: %v", v) | ||
} | ||
|
||
switch v[0].(type) { | ||
case nil: | ||
// no value | ||
case float64: | ||
f, _ := v[0].(float64) | ||
gdp.Value = &f | ||
case string: | ||
f, err := strconv.ParseFloat(v[0].(string), 64) | ||
if err != nil { | ||
return err | ||
} | ||
gdp.Value = &f | ||
default: | ||
f, ok := v[0].(float64) | ||
if !ok { | ||
return fmt.Errorf("error unmarshaling value: %v", v[0]) | ||
} | ||
gdp.Value = &f | ||
} | ||
|
||
switch v[1].(type) { | ||
case nil: | ||
// no value | ||
case float64: | ||
ts := int64(math.Round(v[1].(float64))) | ||
gdp.TimeStamp = time.Unix(ts, 0) | ||
case string: | ||
ts, err := strconv.ParseInt(v[1].(string), 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
gdp.TimeStamp = time.Unix(ts, 0) | ||
default: | ||
ts, ok := v[1].(int64) | ||
if !ok { | ||
return fmt.Errorf("error unmarshaling timestamp: %v", v[0]) | ||
} | ||
gdp.TimeStamp = time.Unix(ts, 0) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type graphiteTargetResp struct { | ||
Target string `json:"target"` | ||
DataPoints []dataPoint `json:"datapoints"` | ||
} | ||
|
||
type graphiteResponse []graphiteTargetResp |
Oops, something went wrong.