Skip to content

Commit 054f2a4

Browse files
authored
Merge pull request #1989 from mrueg/crs-support-quantity
feat(CustomResourceState): Support quantities and percentages
2 parents 3a7e617 + d799f40 commit 054f2a4

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

docs/customresourcestate-metrics.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ Supported types are:
254254
* for string the following logic applies
255255
* `"true"` and `"yes"` are mapped to `1.0` and `"false"` and `"no"` are mapped to `0.0` (all case insensitive)
256256
* RFC3339 times are parsed to float timestamp
257+
* Quantities like "250m" or "512Gi" are parsed to float using https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go
258+
* Percentages ending with a "%" are parsed to float
257259
* finally the string is parsed to float using https://pkg.go.dev/strconv#ParseFloat which should support all common number formats. If that fails an error is yielded
258260

259261
##### Example for status conditions on Kubernetes Controllers

pkg/customresourcestate/registry_factory.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import (
2525
"strings"
2626
"time"
2727

28+
"k8s.io/apimachinery/pkg/api/resource"
2829
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/util/validation"
2931
"k8s.io/klog/v2"
3032

3133
"k8s.io/kube-state-metrics/v2/pkg/metric"
@@ -690,16 +692,28 @@ func toFloat64(value interface{}, nilIsZero bool) (float64, error) {
690692
}
691693
return 0, nil
692694
case string:
695+
// The string contains a boolean as a string
693696
normalized := strings.ToLower(value.(string))
694697
if normalized == "true" || normalized == "yes" {
695698
return 1, nil
696699
}
697700
if normalized == "false" || normalized == "no" {
698701
return 0, nil
699702
}
703+
// The string contains a RFC3339 timestamp
700704
if t, e := time.Parse(time.RFC3339, value.(string)); e == nil {
701705
return float64(t.Unix()), nil
702706
}
707+
// The string contains a quantity with a suffix like "25m" (milli) or "5Gi" (binarySI)
708+
if t, e := resource.ParseQuantity(value.(string)); e == nil {
709+
return t.AsApproximateFloat64(), nil
710+
}
711+
// The string contains a percentage with a suffix "%"
712+
if e := validation.IsValidPercent(value.(string)); len(e) == 0 {
713+
t, e := strconv.ParseFloat(strings.TrimRight(value.(string), "%"), 64)
714+
return t / 100, e
715+
}
716+
703717
return strconv.ParseFloat(value.(string), 64)
704718
case byte:
705719
v = float64(vv)

pkg/customresourcestate/registry_factory_test.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ func init() {
6464
"ready": 4,
6565
},
6666
},
67-
"uptime": 43.21,
67+
"uptime": 43.21,
68+
"quantity_milli": "250m",
69+
"quantity_binarySI": "5Gi",
70+
"percentage": "28%",
6871
"condition_values": Array{
6972
Obj{
7073
"name": "a",
@@ -95,6 +98,7 @@ func init() {
9598
"qux": "quxx",
9699
"bar": "baz",
97100
},
101+
"percentage": "39%",
98102
"creationTimestamp": "2022-06-28T00:00:00Z",
99103
},
100104
})
@@ -225,6 +229,38 @@ func Test_values(t *testing.T) {
225229
}, wantResult: []eachValue{
226230
newEachValue(t, 1656374400),
227231
}},
232+
{name: "quantity_milli", each: &compiledGauge{
233+
compiledCommon: compiledCommon{
234+
path: mustCompilePath(t, "status", "quantity_milli"),
235+
},
236+
}, wantResult: []eachValue{
237+
newEachValue(t, 0.25),
238+
}},
239+
{name: "quantity_binarySI", each: &compiledGauge{
240+
compiledCommon: compiledCommon{
241+
path: mustCompilePath(t, "status", "quantity_binarySI"),
242+
},
243+
}, wantResult: []eachValue{
244+
newEachValue(t, 5.36870912e+09),
245+
}},
246+
{name: "percentage", each: &compiledGauge{
247+
compiledCommon: compiledCommon{
248+
path: mustCompilePath(t, "status", "percentage"),
249+
},
250+
}, wantResult: []eachValue{
251+
newEachValue(t, 0.28),
252+
}},
253+
{name: "path-relative valueFrom percentage", each: &compiledGauge{
254+
compiledCommon: compiledCommon{
255+
path: mustCompilePath(t, "metadata"),
256+
labelFromPath: map[string]valuePath{
257+
"name": mustCompilePath(t, "name"),
258+
},
259+
},
260+
ValueFrom: mustCompilePath(t, "percentage"),
261+
}, wantResult: []eachValue{
262+
newEachValue(t, 0.39, "name", "foo"),
263+
}},
228264
{name: "boolean_string", each: &compiledGauge{
229265
compiledCommon: compiledCommon{
230266
path: mustCompilePath(t, "spec", "paused"),

0 commit comments

Comments
 (0)