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

Allow labelFromKey field for all applicable types #1880

Merged
merged 5 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions docs/customresourcestate-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ spec:
path: [status, sub]

# if path targets an object, the object key will be used as label value
# This is not supported for StateSet type as all values will be truthy, which is redundant.
labelFromKey: type
# label values can be resolved specific to this path
labelsFromPath:
Expand Down
2 changes: 2 additions & 0 deletions pkg/customresourcestate/config_metrics_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type MetricGauge struct {
// Ref: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#info
type MetricInfo struct {
MetricMeta `yaml:",inline" json:",inline"`
// LabelFromKey adds a label with the given name if Path is an object. The label value will be the object key.
LabelFromKey string `yaml:"labelFromKey" json:"labelFromKey"`
}

// MetricStateSet is a metric which represent a series of related boolean values, also called a bitset.
Expand Down
81 changes: 51 additions & 30 deletions pkg/customresourcestate/registry_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func newCompiledMetric(m Metric) (compiledMetric, error) {
compiledCommon: *cc,
ValueFrom: valueFromPath,
NilIsZero: m.Gauge.NilIsZero,
labelFromKey: m.Gauge.LabelFromKey,
}, nil
case MetricTypeInfo:
if m.Info == nil {
Expand All @@ -168,6 +169,7 @@ func newCompiledMetric(m Metric) (compiledMetric, error) {
}
return &compiledInfo{
compiledCommon: *cc,
labelFromKey: m.Info.LabelFromKey,
}, nil
case MetricTypeStateSet:
if m.StateSet == nil {
Expand Down Expand Up @@ -195,23 +197,8 @@ func newCompiledMetric(m Metric) (compiledMetric, error) {
type compiledGauge struct {
compiledCommon
ValueFrom valuePath
LabelFromKey string
NilIsZero bool
}

func newCompiledGauge(m *MetricGauge) (*compiledGauge, error) {
cc, err := compileCommon(m.MetricMeta)
if err != nil {
return nil, fmt.Errorf("compile common: %w", err)
}
valueFromPath, err := compilePath(m.ValueFrom)
if err != nil {
return nil, fmt.Errorf("compile path ValueFrom: %w", err)
}
return &compiledGauge{
compiledCommon: *cc,
ValueFrom: valueFromPath,
}, nil
labelFromKey string
}

func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) {
Expand All @@ -227,8 +214,8 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error)
onError(fmt.Errorf("[%s]: %w", key, err))
continue
}
if key != "" && c.LabelFromKey != "" {
ev.Labels[c.LabelFromKey] = key
if key != "" && c.labelFromKey != "" {
ev.Labels[c.labelFromKey] = key
}
addPathLabels(it, c.LabelFromPath(), ev.Labels)
result = append(result, *ev)
Expand Down Expand Up @@ -257,22 +244,54 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error)

type compiledInfo struct {
compiledCommon
labelFromKey string
}

func (c *compiledInfo) Values(v interface{}) (result []eachValue, errs []error) {
if vs, isArray := v.([]interface{}); isArray {
for _, obj := range vs {
onError := func(err ...error) {
errs = append(errs, fmt.Errorf("%s: %v", c.Path(), err))
}

switch iter := v.(type) {
case []interface{}:
for _, obj := range iter {
ev, err := c.values(obj)
if len(err) > 0 {
errs = append(errs, err...)
onError(err...)
continue
}
result = append(result, ev...)
}
return
case map[string]interface{}:
value, err := c.values(v)
if err != nil {
onError(err...)
break
}
// labelFromKey logic
for key, val := range iter {
if key != "" && c.labelFromKey != "" {
n, err := toFloat64(val, false)
if err != nil {
onError(err)
continue
}
result = append(result, eachValue{
Labels: map[string]string{
c.labelFromKey: key,
},
mrueg marked this conversation as resolved.
Show resolved Hide resolved
Value: n,
mrueg marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
if len(result) == 0 {
result = value
}
mrueg marked this conversation as resolved.
Show resolved Hide resolved
default:
result, errs = c.values(v)
}

return c.values(v)
return
}

func (c *compiledInfo) values(v interface{}) (result []eachValue, err []error) {
Expand Down Expand Up @@ -355,7 +374,7 @@ func less(a, b map[string]string) bool {

func (c compiledGauge) value(it interface{}) (*eachValue, error) {
labels := make(map[string]string)
value, err := getNum(c.ValueFrom.Get(it), c.NilIsZero)
value, err := toFloat64(c.ValueFrom.Get(it), c.NilIsZero)
if err != nil {
return nil, fmt.Errorf("%s: %w", c.ValueFrom, err)
}
Expand Down Expand Up @@ -478,7 +497,7 @@ func compilePath(path []string) (out valuePath, _ error) {
return nil, fmt.Errorf("invalid list lookup: %s", part)
}
key, val := eq[0], eq[1]
num, notNum := getNum(val, false)
num, notNum := toFloat64(val, false)
boolVal, notBool := strconv.ParseBool(val)
out = append(out, pathOp{
part: part,
Expand All @@ -496,7 +515,7 @@ func compilePath(path []string) (out valuePath, _ error) {
}

if notNum == nil {
if i, err := getNum(candidate, false); err == nil && num == i {
if i, err := toFloat64(candidate, false); err == nil && num == i {
return m
}
}
Expand All @@ -522,13 +541,14 @@ func compilePath(path []string) (out valuePath, _ error) {
} else if s, ok := m.([]interface{}); ok {
i, err := strconv.Atoi(part)
if err != nil {
return nil
return fmt.Errorf("invalid list index: %s", part)
mrueg marked this conversation as resolved.
Show resolved Hide resolved
}
if i < 0 {
// negative index
i += len(s)
}
if !(0 <= i && i < len(s)) {
return nil
return fmt.Errorf("list index out of range: %s", part)
}
return s[i]
}
Expand All @@ -544,6 +564,7 @@ func famGen(f compiledFamily) generator.FamilyGenerator {
errLog := klog.V(f.ErrorLogV)
return generator.FamilyGenerator{
Name: f.Name,
// TODO(@rexagod): This should be dynamic.
mrueg marked this conversation as resolved.
Show resolved Hide resolved
Type: metric.Gauge,
Help: f.Help,
GenerateFunc: func(obj interface{}) *metric.Family {
Expand Down Expand Up @@ -585,8 +606,8 @@ func scrapeValuesFor(e compiledEach, obj map[string]interface{}) ([]eachValue, [
return result, errs
}

// getNum converts the value to a float64 which is the value type for any metric.
func getNum(value interface{}, nilIsZero bool) (float64, error) {
// toFloat64 converts the value to a float64 which is the value type for any metric.
func toFloat64(value interface{}, nilIsZero bool) (float64, error) {
var v float64
// same as bool==false but for bool pointers
if value == nil {
Expand Down
17 changes: 13 additions & 4 deletions pkg/customresourcestate/registry_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func Test_values(t *testing.T) {
compiledCommon: compiledCommon{
path: mustCompilePath(t, "status", "active"),
},
LabelFromKey: "type",
labelFromKey: "type",
}, wantResult: []eachValue{
newEachValue(t, 1, "type", "type-a"),
newEachValue(t, 3, "type", "type-b"),
Expand All @@ -167,7 +167,7 @@ func Test_values(t *testing.T) {
"active": mustCompilePath(t, "active"),
},
},
LabelFromKey: "type",
labelFromKey: "type",
ValueFrom: mustCompilePath(t, "ready"),
}, wantResult: []eachValue{
newEachValue(t, 2, "type", "type-a", "active", "1"),
Expand Down Expand Up @@ -201,7 +201,7 @@ func Test_values(t *testing.T) {
newEachValue(t, 0),
}},
{name: "info", each: &compiledInfo{
compiledCommon{
compiledCommon: compiledCommon{
labelFromPath: map[string]valuePath{
"version": mustCompilePath(t, "spec", "version"),
},
Expand All @@ -210,10 +210,19 @@ func Test_values(t *testing.T) {
newEachValue(t, 1, "version", "v0.0.0"),
}},
{name: "info nil path", each: &compiledInfo{
compiledCommon{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "does", "not", "exist"),
},
}, wantResult: nil},
{name: "info label from key", each: &compiledInfo{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "status", "active"),
},
labelFromKey: "type",
}, wantResult: []eachValue{
newEachValue(t, 1, "type", "type-a"),
newEachValue(t, 3, "type", "type-b"),
}},
{name: "stateset", each: &compiledStateSet{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "status", "phase"),
Expand Down