Skip to content

Commit

Permalink
Merge pull request #1880 from rexagod/1871-1868
Browse files Browse the repository at this point in the history
Allow labelFromKey field for all applicable types
  • Loading branch information
k8s-ci-robot committed Nov 14, 2022
2 parents 9edd02b + 3072502 commit b2c90e8
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 35 deletions.
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
88 changes: 57 additions & 31 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,12 @@ 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 _, ok := ev.Labels[c.labelFromKey]; ok {
onError(fmt.Errorf("labelFromKey (%s) generated labels conflict with labelsFromPath, consider renaming it", c.labelFromKey))
continue
}
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 +248,53 @@ 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
}
for _, ev := range value {
if _, ok := ev.Labels[c.labelFromKey]; ok {
onError(fmt.Errorf("labelFromKey (%s) generated labels conflict with labelsFromPath, consider renaming it", c.labelFromKey))
continue
}
}
// labelFromKey logic
for key := range iter {
if key != "" && c.labelFromKey != "" {
result = append(result, eachValue{
Labels: map[string]string{
c.labelFromKey: key,
},
Value: 1,
})
}
}
result = append(result, value...)
default:
result, errs = c.values(v)
}

return c.values(v)
return
}

func (c *compiledInfo) values(v interface{}) (result []eachValue, err []error) {
Expand All @@ -281,7 +303,9 @@ func (c *compiledInfo) values(v interface{}) (result []eachValue, err []error) {
}
value := eachValue{Value: 1, Labels: map[string]string{}}
addPathLabels(v, c.labelFromPath, value.Labels)
result = append(result, value)
if len(value.Labels) != 0 {
result = append(result, value)
}
return
}

Expand Down Expand Up @@ -355,7 +379,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 +502,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 +520,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 +546,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)
}
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 +569,7 @@ func famGen(f compiledFamily) generator.FamilyGenerator {
errLog := klog.V(f.ErrorLogV)
return generator.FamilyGenerator{
Name: f.Name,
// TODO(@rexagod): This should be dynamic.
Type: metric.Gauge,
Help: f.Help,
GenerateFunc: func(obj interface{}) *metric.Family {
Expand Down Expand Up @@ -585,8 +611,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, 1, "type", "type-b"),
}},
{name: "stateset", each: &compiledStateSet{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "status", "phase"),
Expand Down

0 comments on commit b2c90e8

Please sign in to comment.