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 <[email protected]>
  • Loading branch information
rexagod committed Mar 26, 2024
1 parent bb69644 commit 56b4bb7
Show file tree
Hide file tree
Showing 3 changed files with 137 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: [lorem_resourceB] # or [available_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
118 changes: 93 additions & 25 deletions pkg/customresourcestate/registry_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,44 @@ type compiledGauge struct {
labelFromKey string
}

func underscoresToIndices(extractedValueFrom string, it interface{}) interface{} {
// `it` is the search space.
m, isResolvableMap := it.(map[string]interface{})
arr, isResolvableArr := it.([]interface{})
if !isResolvableMap && !isResolvableArr {
return nil
}
// `extractedValueFrom` is the search term.
// Split `extractedValueFrom` by underscores.
terms := strings.Split(extractedValueFrom, "_")
resolvedTerm := interface{}(terms[0])
for _, term := range terms[1:] {
if isResolvableMap {
t, ok := m[term]
if !ok {
return resolvedTerm
}
resolvedTerm = t
if _, isResolvableMap = t.(map[string]interface{}); isResolvableMap {
m = t.(map[string]interface{})
}
} else if isResolvableArr {
for _, el := range arr {
t, ok := el.(map[string]interface{})
if !ok {
continue
}
if v, ok := t[term]; ok {
resolvedTerm = v
m = t
break
}
}
}
}
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 @@ -249,9 +287,41 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error)
sValueFrom[0] == '[' && sValueFrom[len(sValueFrom)-1] == ']' &&
// "[...]" and not "[]".
len(sValueFrom) > 2 {
// remove the brackets
extractedValueFrom := sValueFrom[1 : len(sValueFrom)-1]
if key == extractedValueFrom {
gotFloat, err := toFloat64(it, c.NilIsZero)
// search space to resolve the dynamic valueFrom from the wildcard labels
dynamicValueFromScope := c.compiledCommon.labelFromPath
lastUnderscoreIndex := strings.LastIndex(extractedValueFrom, "_")
if lastUnderscoreIndex != -1 {
// For:
// labelsFromPath:
// "foo_*": ["spec", "fooObj"]
unresolvedKey := extractedValueFrom[:lastUnderscoreIndex] + "_*"
dynamicPaths, ok := dynamicValueFromScope[unresolvedKey]
if ok {
var resolvedKeyArr []string
for _, dynamicPath := range dynamicPaths {
resolvedKeyArr = append(resolvedKeyArr, dynamicPath.part)
}
// resolvedKey will map to unresolved key "foo_*"'s corresponding valid path string, i.e., "spec_fooObj_*".
resolvedKey := strings.Join(resolvedKeyArr, "_")
extractedValueFrom = resolvedKey + extractedValueFrom[lastUnderscoreIndex:]
}
}
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)
}
if _, isFloat := resolvedExtractedValueFrom.(float64); isFloat {
gotFloat = resolvedExtractedValueFrom.(float64)
}
} else {
gotFloat, err = toFloat64(it, c.NilIsZero)
}
if err != nil {
onError(fmt.Errorf("[%s]: %w", key, err))
continue
Expand Down Expand Up @@ -710,23 +780,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 +835,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 +855,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
41 changes: 40 additions & 1 deletion pkg/customresourcestate/registry_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func init() {
"spec": Obj{
"replicas": 1,
"version": "v0.0.0",
"template": Obj{
"foo": Obj{
"bar": "42",
},
},
"order": Array{
Obj{
"id": 1,
Expand Down Expand Up @@ -102,7 +107,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 +377,39 @@ func Test_values(t *testing.T) {
}, wantResult: []eachValue{
newEachValue(t, 1, "bar", "baz"),
}},
{name: "dynamic valueFrom independent of wildcard label", 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"),
}},
{name: "dynamic valueFrom dependent on wildcard label", each: &compiledGauge{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "metadata"),
labelFromPath: map[string]valuePath{
"lorem_*": mustCompilePath(t, "labels"),
},
},
ValueFrom: mustCompilePath(t, "lorem_numStr"),
}, wantResult: []eachValue{
newEachValue(t, 42, "lorem_numStr", "42", "lorem_foo", "bar"),
}},
{name: "dynamic valueFrom dependent on wildcard label with multiple underscores and path values", each: &compiledGauge{
compiledCommon: compiledCommon{
path: mustCompilePath(t, "spec"),
labelFromPath: map[string]valuePath{
"lorem_dolor_*": mustCompilePath(t, "template", "foo"),
},
},
ValueFrom: mustCompilePath(t, "lorem_dolor_bar"),
}, wantResult: []eachValue{
newEachValue(t, 42, "lorem_dolor_bar", "42"),
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit 56b4bb7

Please sign in to comment.