Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add transformed metrics #1349

Merged
merged 4 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading