Skip to content

Commit

Permalink
Allow labelFromKey field
Browse files Browse the repository at this point in the history
Allow `labelFromKey` field for the following types:
* Gauge: Done.
* Info: Done.
* StateSet: N/A (redundant use case, see doc changes for more info).

Signed-off-by: Pranshu Srivastava <rexagod@gmail.com>
  • Loading branch information
rexagod committed Nov 4, 2022
1 parent 086a6fd commit 6d2a555
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 34 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
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
default:
value, err := c.values(v)
if err != nil {
onError(err...)
break
}
// labelFromKey logic
if vv, ok := v.(map[string]interface{}); ok {
for key, val := range vv {
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,
},
Value: n,
})
}
}
}
if len(result) == 0 {
result = value
}
}

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)
}
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.
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

0 comments on commit 6d2a555

Please sign in to comment.