Skip to content

Commit

Permalink
Merge pull request #1349 from flanksource/transformed-metrics
Browse files Browse the repository at this point in the history
feat: add transformed metrics
  • Loading branch information
moshloop authored Oct 16, 2023
2 parents 4536a5e + f1d463b commit fe7f298
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 85 deletions.
16 changes: 8 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ updates:
schedule:
interval: daily

- package-ecosystem: gomod
directory: /fixtures/datasources
schedule:
interval: daily
# - package-ecosystem: gomod
# directory: /fixtures/datasources
# schedule:
# interval: daily

- package-ecosystem: gomod
directory: /hack/generate-schemas
schedule:
interval: daily
# - package-ecosystem: gomod
# directory: /hack/generate-schemas
# schedule:
# interval: daily

- package-ecosystem: gomod
directory: /sdk
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
CI: false
run: |
make resources
git checkout hack/generate-schemas/go.*
git checkout fixtures/datasources/go.*
git diff
changed_files=$(git status -s)
[[ -z "$changed_files" ]] || (printf "Change is detected in some files: \n$changed_files\n Did you run 'make resources' before sending the PR?" && exit 1)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static: generate manifests
# Generate OpenAPI schema
.PHONY: gen-schemas
gen-schemas:
cd hack/generate-schemas && go run ./main.go
cd hack/generate-schemas && go mod tidy && go run ./main.go

# Generate manifests e.g. CRD, RBAC etc.
manifests: .bin/controller-gen
Expand Down
101 changes: 77 additions & 24 deletions checks/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,37 +73,90 @@ func transform(ctx *context.Context, in *pkg.CheckResult) ([]*pkg.CheckResult, e

var transformed []pkg.TransformedCheckResult
if err := json.Unmarshal([]byte(out), &transformed); err != nil {
return nil, err
var t pkg.TransformedCheckResult
if errSingle := json.Unmarshal([]byte(out), &t); errSingle != nil {
return nil, err
}
transformed = []pkg.TransformedCheckResult{t}
}

var results []*pkg.CheckResult

for _, t := range transformed {
t.Icon = def(t.Icon, in.Check.GetIcon())
t.Description = def(t.Description, in.Check.GetDescription())
t.Name = def(t.Name, in.Check.GetName())
t.Type = def(t.Type, in.Check.GetType())
t.Endpoint = def(t.Endpoint, in.Check.GetEndpoint())
t.TransformDeleteStrategy = def(t.TransformDeleteStrategy, in.Check.GetTransformDeleteStrategy())
r := t.ToCheckResult()
r.Canary = in.Canary
r.Canary.Namespace = def(t.Namespace, r.Canary.Namespace)
if r.Canary.Labels == nil {
r.Canary.Labels = make(map[string]string)
}

// We use this label to set the transformed column to true
// This label is used and then removed in pkg.FromV1 function
r.Canary.Labels["transformed"] = "true" //nolint:goconst
r.Transformed = true
results = append(results, &r)
if len(transformed) == 0 {
ctx.Tracef("transformation returned empty array")
return nil, nil
}

if ctx.IsTrace() {
ctx.Tracef("transformed %s into %v", in, results)
t := transformed[0]

if t.Name != "" && t.Name != in.Check.GetName() {
// new check result created with a new name
for _, t := range transformed {
t.Icon = def(t.Icon, in.Check.GetIcon())
t.Description = def(t.Description, in.Check.GetDescription())
t.Name = def(t.Name, in.Check.GetName())
t.Type = def(t.Type, in.Check.GetType())
t.Endpoint = def(t.Endpoint, in.Check.GetEndpoint())
t.TransformDeleteStrategy = def(t.TransformDeleteStrategy, in.Check.GetTransformDeleteStrategy())
r := t.ToCheckResult()
r.Canary = in.Canary
r.Canary.Namespace = def(t.Namespace, r.Canary.Namespace)
if r.Canary.Labels == nil {
r.Canary.Labels = make(map[string]string)
}

// We use this label to set the transformed column to true
// This label is used and then removed in pkg.FromV1 function
r.Canary.Labels["transformed"] = "true" //nolint:goconst
r.Transformed = true
results = append(results, &r)
}
if ctx.IsTrace() {
ctx.Tracef("transformed %s into %v", in, results)
}
return results, nil
} else if len(transformed) == 1 && t.Name == "" {
if ctx.IsTrace() {
ctx.Tracef("merging %v into %v", t, in)
}
in.Metrics = append(in.Metrics, t.Metrics...)
if t.Start != nil {
in.Start = *t.Start
}

if t.Pass != nil {
in.Pass = *t.Pass
}
if t.Invalid != nil {
in.Invalid = *t.Invalid
}
if t.Duration != nil {
in.Duration = *t.Duration
}
if t.Message != "" {
in.Message = t.Message
}
if t.Description != "" {
in.Description = t.Description
}
if t.Error != "" {
in.Error = t.Error
}
if t.Detail != nil {
in.Detail = t.Detail
}
if t.DisplayType != "" {
in.DisplayType = t.DisplayType
}
if len(t.Data) > 0 {
for k, v := range t.Data {
in.Data[k] = v
}
}
} else {
return nil, fmt.Errorf("transformation returned more than 1 entry without a name")
}

return results, nil
return []*pkg.CheckResult{in}, nil
}

func GetJunitReportFromResults(canaryName string, results []*pkg.CheckResult) JunitTestSuite {
Expand Down
101 changes: 65 additions & 36 deletions checks/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,32 @@ func getWithEnvironment(ctx *context.Context, r *pkg.CheckResult) *context.Conte
return ctx.New(r.Data)
}

func getLabels(ctx *context.Context, metric external.Metrics) (map[string]string, []string, error) {
func getLabels(ctx *context.Context, metric external.Metrics) (map[string]string, error) {
var labels = make(map[string]string)
var names = []string{}
for _, label := range metric.Labels {
val := label.Value
if label.ValueExpr != "" {
var err error
val, err = template(ctx, v1.Template{Expression: label.ValueExpr})
if err != nil {
return nil, nil, err
return nil, err
}
}
labels[label.Name] = val
names = append(names, label.Name)
}
sort.Strings(names)
return labels, names, nil

return labels, nil
}

func getLabelNames(labels map[string]string) []string {
var s []string

for k := range labels {
s = append(s, k)
}
sort.Strings(s)

return s
}

func getLabelString(labels map[string]string) string {
Expand All @@ -98,51 +107,71 @@ func exportCheckMetrics(ctx *context.Context, results pkg.Results) {
}

for _, r := range results {
for _, metric := range r.Metrics {
if err := exportMetric(ctx, metric); err != nil {
r.ErrorMessage(err)
}
}
for _, spec := range r.Check.GetMetricsSpec() {
if spec.Name == "" || spec.Value == "" {
continue
}

ctx = getWithEnvironment(ctx, r)

var err error
var labels map[string]string
var labelNames []string
if labels, labelNames, err = getLabels(ctx, spec); err != nil {
if metric, err := templateMetrics(ctx, spec); err != nil {
r.ErrorMessage(err)
} else if err := exportMetric(ctx, *metric); err != nil {
r.ErrorMessage(err)
continue
}
}
}
}

var collector prometheus.Collector
var e any
if collector, e = getOrAddPrometheusMetric(spec.Name, spec.Type, labelNames); e != nil {
r.ErrorMessage(fmt.Errorf("failed to create metric %s (%s) %s: %s", spec.Name, spec.Type, labelNames, e))
continue
}
func templateMetrics(ctx *context.Context, spec external.Metrics) (*pkg.Metric, error) {
var val float64
var err error
var labels map[string]string
if val, err = getMetricValue(ctx, spec); err != nil {
return nil, err
}

var val float64
if val, err = getMetricValue(ctx, spec); err != nil {
r.ErrorMessage(err)
continue
}
if labels, err = getLabels(ctx, spec); err != nil {
return nil, err
}

if ctx.IsDebug() {
ctx.Debugf("%s%v=%0.3f", spec.Name, getLabelString(labels), val)
}
return &pkg.Metric{
Name: spec.Name,
Type: pkg.MetricType(spec.Type),
Value: val,
Labels: labels,
}, nil
}

switch collector := collector.(type) {
case *prometheus.HistogramVec:
collector.With(labels).Observe(val)
case *prometheus.GaugeVec:
collector.With(labels).Set(val)
case *prometheus.CounterVec:
if val <= 0 {
continue
}
collector.With(labels).Add(val)
}
func exportMetric(ctx *context.Context, spec pkg.Metric) error {
var collector prometheus.Collector
labelNames := getLabelNames(spec.Labels)
var e any
if collector, e = getOrAddPrometheusMetric(spec.Name, string(spec.Type), labelNames); e != nil {
return fmt.Errorf("failed to create metric %s (%s) %s: %s", spec.Name, spec.Type, labelNames, e)
}

if ctx.IsDebug() {
ctx.Debugf("%s%v=%0.3f", spec.Name, getLabelString(spec.Labels), spec.Value)
}

switch collector := collector.(type) {
case *prometheus.HistogramVec:
collector.With(spec.Labels).Observe(spec.Value)
case *prometheus.GaugeVec:
collector.With(spec.Labels).Set(spec.Value)
case *prometheus.CounterVec:
if spec.Value <= 0 {
return nil
}
collector.With(spec.Labels).Add(spec.Value)
}
return nil
}

func getMetricValue(ctx *context.Context, spec external.Metrics) (float64, error) {
Expand Down
28 changes: 28 additions & 0 deletions fixtures/minimal/metrics-transformed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: exchange-rates
annotations:
trace: "true"
spec:
schedule: "every 30 @minutes"
http:
- name: exchange-rates
url: https://api.frankfurter.app/latest?from=USD&to=GBP,EUR,ILS
transform:
expr: |
{
'metrics': json.rates.keys().map(k, {
'name': "exchange_rate",
'type': "gauge",
'value': json.rates[k],
'labels': {
"from": json.base,
"to": k
}
})
}.toJSON()
metrics:
- name: exchange_rate_api
type: histogram
value: elapsed.getMilliseconds()
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/fergusstrange/embedded-postgres v1.24.0
github.com/flanksource/commons v1.12.0
github.com/flanksource/duty v1.0.191
github.com/flanksource/gomplate/v3 v3.20.16
github.com/flanksource/gomplate/v3 v3.20.18
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7
github.com/flanksource/kommons v0.31.4
github.com/friendsofgo/errors v0.9.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,8 @@ github.com/flanksource/commons v1.12.0/go.mod h1:zYEhi6E2+diQ+loVcROUHo/Bgv+Tn61
github.com/flanksource/duty v1.0.191 h1:acnvyTeQlfqmtyXxWprNFGK/vBTUlqkYwxEPLtXSPrk=
github.com/flanksource/duty v1.0.191/go.mod h1:ikyl/TcRy6Cc0R5b0wEHT7CecV7gyJvrDGq/4oIZHoc=
github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc=
github.com/flanksource/gomplate/v3 v3.20.16 h1:Bfn+nbD0iK0iGQcu6alV8Nr7O5+KpeDo8OD9WOu831Q=
github.com/flanksource/gomplate/v3 v3.20.16/go.mod h1:2GgHZ2vWmtDspJMBfUIryOuzJSwc8jU7Kw9fDLr0TMA=
github.com/flanksource/gomplate/v3 v3.20.18 h1:qYiznMxhq+Zau5iWnVzW1yDzA1deHOsmo6yldCN7JhQ=
github.com/flanksource/gomplate/v3 v3.20.18/go.mod h1:2GgHZ2vWmtDspJMBfUIryOuzJSwc8jU7Kw9fDLr0TMA=
github.com/flanksource/is-healthy v0.0.0-20230705092916-3b4cf510c5fc/go.mod h1:4pQhmF+TnVqJroQKY8wSnSp+T18oLson6YQ2M0qPHfQ=
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7 h1:s6jf6P1pRfdvksVFjIXFRfnimvEYUR0/Mmla1EIjiRM=
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7/go.mod h1:BH5gh9JyEAuuWVP6Q5y9h43VozS0RfKyjNpM9L4v4hw=
Expand Down
Loading

0 comments on commit fe7f298

Please sign in to comment.