Skip to content

Commit

Permalink
add dynamic valueFrom
Browse files Browse the repository at this point in the history
allow specifying dynamic valueFrom based on wildcard labelFromPaths

Signed-off-by: Pranshu Srivastava <rexagod@gmail.com>
  • Loading branch information
rexagod committed Aug 29, 2023
1 parent 0f91e62 commit 96f8b37
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
5 changes: 4 additions & 1 deletion docs/customresourcestate-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,11 @@ path: [status, namespaces, "*", status]
labelsFromPath:
# this can be combined with the wildcard prefixes feature introduced in #2052
"available_*": [available]
"lorem_*": [pending]
# this can be combined with dynamic valueFrom expressions introduced in #2068
valueFrom: [pending_resourceB]
# outputs:
...{...,available_resourceA="10",available_resourceB="20",...} ...
...{...,available_resourceA="10",available_resourceB="20",lorem_resourceA="0",lorem_resourceB="6"...} 6
```

### Wildcard matching of version and kind fields
Expand Down
77 changes: 52 additions & 25 deletions pkg/customresourcestate/registry_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,26 @@ type compiledGauge struct {
labelFromKey string
}

func underscoresToIndices(extractedValueFrom string, it interface{}) interface{} {
// `it` is the search space.
_, isResolvable := it.(map[string]interface{})
if !isResolvable {
return nil
}
// `extractedValueFrom` is the search term.
// Split `extractedValueFrom` by underscores.
terms := strings.Split(extractedValueFrom, "_")
resolvedTerm := interface{}(terms[0])
for _, term := range terms[1:] {
t, ok := it.(map[string]interface{})[term]
if !ok {
return resolvedTerm
}
resolvedTerm = t
}
return resolvedTerm
}

func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) {
onError := func(err error) {
errs = append(errs, fmt.Errorf("%s: %v", c.Path(), err))
Expand All @@ -250,8 +270,17 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error)
// "[...]" and not "[]".
len(sValueFrom) > 2 {
extractedValueFrom := sValueFrom[1 : len(sValueFrom)-1]
if key == extractedValueFrom {
gotFloat, err := toFloat64(it, c.NilIsZero)
if strings.HasPrefix(extractedValueFrom, key) {
var gotFloat float64
var err error
if strings.Contains(extractedValueFrom, "_") {
resolvedExtractedValueFrom := underscoresToIndices(extractedValueFrom, it)
if _, didResolveFullPath := resolvedExtractedValueFrom.(string); didResolveFullPath {
gotFloat, err = toFloat64(resolvedExtractedValueFrom, c.NilIsZero)
}
} else {
gotFloat, err = toFloat64(it, c.NilIsZero)
}
if err != nil {
onError(fmt.Errorf("[%s]: %w", key, err))
continue
Expand Down Expand Up @@ -710,23 +739,24 @@ func famGen(f compiledFamily) generator.FamilyGenerator {
}
}

func findWildcard(path valuePath, i *int) bool {
for ; *i < len(path); *i++ {
if path[*i].part == "*" {
return true
}
}
return false
}

func resolveWildcard(path valuePath, object map[string]interface{}) []valuePath {
if path == nil {
return nil
}
fn := func(i *int) bool {
for ; *i < len(path); *i++ {
if path[*i].part == "*" {
return true
}
}
return false
}
checkpoint := object
var expandedPaths []valuePath
var list []interface{}
var l int
for i, j := 0, 0; fn(&i); /* i is at "*" now */ {
for i, j := 0, 0; findWildcard(path, &i); /* i is at "*" now */ {
for ; j < i; j++ {
maybeCheckpoint, ok := checkpoint[path[j].part]
if !ok {
Expand Down Expand Up @@ -764,23 +794,10 @@ func generate(u *unstructured.Unstructured, f compiledFamily, errLog klog.Verbos
klog.V(10).InfoS("Checked", "compiledFamilyName", f.Name, "unstructuredName", u.GetName())
var metrics []*metric.Metric
baseLabels := f.BaseLabels(u.Object)
fn := func() {
values, errorSet := scrapeValuesFor(f.Each, u.Object)
for _, err := range errorSet {
errLog.ErrorS(err, f.Name)
}

for _, v := range values {
v.DefaultLabels(baseLabels)
metrics = append(metrics, v.ToMetric())
}
klog.V(10).InfoS("Produced metrics for", "compiledFamilyName", f.Name, "metricsLength", len(metrics), "unstructuredName", u.GetName())
}
if f.Each.Path() != nil {
fPaths := resolveWildcard(f.Each.Path(), u.Object)
for _, fPath := range fPaths {
f.Each.SetPath(fPath)
fn()
}
}

Expand All @@ -797,9 +814,19 @@ func generate(u *unstructured.Unstructured, f compiledFamily, errLog klog.Verbos
if len(labelsFromPath) > 0 {
f.Each.SetLabelFromPath(labelsFromPath)
}
fn()
}

values, errorSet := scrapeValuesFor(f.Each, u.Object)
for _, err := range errorSet {
errLog.ErrorS(err, f.Name)
}

for _, v := range values {
v.DefaultLabels(baseLabels)
metrics = append(metrics, v.ToMetric())
}
klog.V(10).InfoS("Produced metrics for", "compiledFamilyName", f.Name, "metricsLength", len(metrics), "unstructuredName", u.GetName())

return &metric.Family{
Metrics: metrics,
}
Expand Down
14 changes: 13 additions & 1 deletion pkg/customresourcestate/registry_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ func init() {
"metadata": Obj{
"name": "foo",
"labels": Obj{
"foo": "bar",
"foo": "bar",
"numStr": "42",
},
"annotations": Obj{
"qux": "quxx",
Expand Down Expand Up @@ -371,6 +372,17 @@ func Test_values(t *testing.T) {
}, wantResult: []eachValue{
newEachValue(t, 1, "bar", "baz"),
}},
{name: "dynamic valueFrom", each: &compiledGauge{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "metadata"),
labelFromPath: map[string]valuePath{
"lorem_*": mustCompilePath(t, "labels"),
},
},
ValueFrom: mustCompilePath(t, "labels_numStr"),
}, wantResult: []eachValue{
newEachValue(t, 42, "lorem_numStr", "42", "lorem_foo", "bar"),
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit 96f8b37

Please sign in to comment.