Skip to content

Commit

Permalink
feat: add transformed metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
moshloop committed Oct 16, 2023
1 parent 4536a5e commit 4e2bfda
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 76 deletions.
103 changes: 79 additions & 24 deletions checks/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,37 +73,92 @@ 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

Check failure on line 117 in checks/common.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)
} 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

Check failure on line 161 in checks/common.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)
}

func GetJunitReportFromResults(canaryName string, results []*pkg.CheckResult) JunitTestSuite {
Expand Down
102 changes: 66 additions & 36 deletions checks/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,33 @@ 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)

Check failure on line 75 in checks/metrics.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)
}

return labels, nil
}

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

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

return s
}

func getLabelString(labels map[string]string) string {
Expand All @@ -98,51 +108,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
26 changes: 14 additions & 12 deletions pkg/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,19 +461,20 @@ func (generic GenericCheck) GetEndpoint() string {
}

type TransformedCheckResult struct {
Start time.Time `json:"start,omitempty"`
Pass bool `json:"pass,omitempty"`
Invalid bool `json:"invalid,omitempty"`
Start *time.Time `json:"start,omitempty"`
Pass *bool `json:"pass,omitempty"`
Invalid *bool `json:"invalid,omitempty"`
Detail interface{} `json:"detail,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
Duration int64 `json:"duration,omitempty"`
Duration *int64 `json:"duration,omitempty"`
Description string `json:"description,omitempty"`
DisplayType string `json:"displayType,omitempty"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
Name string `json:"name,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Namespace string `json:"namespace,omitempty"`
Metrics []Metric `json:"metrics,omitempty"`
Icon string `json:"icon,omitempty"`
Type string `json:"type,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Expand All @@ -486,16 +487,17 @@ func (t TransformedCheckResult) ToCheckResult() CheckResult {
labels = make(map[string]string)
}
return CheckResult{
Start: t.Start,
Pass: t.Pass,
Invalid: t.Invalid,
Start: utils.Deref(t.Start, time.Now()),
Pass: utils.Deref(t.Pass, false),
Invalid: utils.Deref(t.Invalid, false),
Detail: t.Detail,
Data: t.Data,
Duration: t.Duration,
Duration: utils.Deref(t.Duration, 0),
Description: t.Description,
DisplayType: t.DisplayType,
Message: t.Message,
Error: t.Error,
Metrics: t.Metrics,
Check: GenericCheck{
Description: v1.Description{
Description: t.Description,
Expand All @@ -517,10 +519,10 @@ func (t TransformedCheckResult) GetDescription() string {
type MetricType string

type Metric struct {
Name string
Type MetricType
Labels map[string]string
Value float64
Name string `json:"name,omitempty"`
Type MetricType `json:"type,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Value float64 `json:"value,omitempty"`
}

func (m Metric) String() string {
Expand Down
5 changes: 4 additions & 1 deletion pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ func Ptr[T any](t T) *T {
return &t
}

func Deref[T any](v *T) T {
func Deref[T any](v *T, zeroVal ...T) T {
if v == nil {
if len(zeroVal) > 0 {
return zeroVal[0]
}
var zero T
return zero
}
Expand Down

0 comments on commit 4e2bfda

Please sign in to comment.