[#1629] fix(operator): Support parsing NaN float value in metrics (#1630)
### What changes were proposed in this pull request?
When parsing json, handle special cases where the value might be NaN
### Why are the changes needed?
Fix: #1629
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
test in real cluster and add unit test
diff --git a/deploy/kubernetes/operator/pkg/webhook/util/util.go b/deploy/kubernetes/operator/pkg/webhook/util/util.go
index 0383716..0528310 100644
--- a/deploy/kubernetes/operator/pkg/webhook/util/util.go
+++ b/deploy/kubernetes/operator/pkg/webhook/util/util.go
@@ -22,6 +22,7 @@
"fmt"
"io"
"io/ioutil"
+ "math"
"net/http"
"time"
@@ -158,12 +159,40 @@
return false
}
+// JSONFloat is used to parse the float64 which may be NaN
+type JSONFloat float64
+
+// MarshalJSON return bytes representing JSONFloat
+func (j JSONFloat) MarshalJSON() ([]byte, error) {
+ v := float64(j)
+ if math.IsNaN(v) {
+ s := "\"NaN\""
+ return []byte(s), nil
+ }
+ return json.Marshal(v) // marshal result as standard float64
+}
+
+// UnmarshalJSON return the parsed JSONFloat
+func (j *JSONFloat) UnmarshalJSON(v []byte) error {
+ if s := string(v); s == "\"NaN\"" {
+ *j = JSONFloat(math.NaN())
+ return nil
+ }
+ // just a regular float value
+ var fv float64
+ if err := json.Unmarshal(v, &fv); err != nil {
+ return err
+ }
+ *j = JSONFloat(fv)
+ return nil
+}
+
// MetricItem records an item of metric information of shuffle servers.
type MetricItem struct {
- Name string `json:"name"`
- LabelNames []string `json:"labelNames"`
- LabelValues []string `json:"labelValues"`
- Value float32 `json:"value"`
+ Name string `json:"name"`
+ LabelNames []string `json:"labelNames"`
+ LabelValues []string `json:"labelValues"`
+ Value JSONFloat `json:"value"`
}
// MetricList records all items of metric information of shuffle servers.
diff --git a/deploy/kubernetes/operator/pkg/webhook/util/util_test.go b/deploy/kubernetes/operator/pkg/webhook/util/util_test.go
new file mode 100644
index 0000000..144257a
--- /dev/null
+++ b/deploy/kubernetes/operator/pkg/webhook/util/util_test.go
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import (
+ "encoding/json"
+ "math"
+ "testing"
+)
+
+// JSONFloatWrapper is struct which contain JSONFloat
+type JSONFloatWrapper struct {
+ Value1 JSONFloat `json:"value1"`
+ Value2 JSONFloat `json:"value2"`
+}
+
+func TestParseJsonFloat(t *testing.T) {
+ wrapper := JSONFloatWrapper{Value1: JSONFloat(math.NaN()), Value2: 9.99}
+ bytes, err := json.Marshal(wrapper)
+ if err != nil {
+ t.Fatal("Marshal JsonFloat failed, caused by ", err.Error())
+ }
+ parsed := &JSONFloatWrapper{}
+ if err := json.Unmarshal(bytes, parsed); err != nil {
+ t.Fatal("Unmarshal JsonFloat failed, caused by ", err.Error())
+ } else if !math.IsNaN(float64(parsed.Value1)) {
+ t.Fatal("Value1 should be Nan")
+ } else if math.Abs(float64(parsed.Value2)-9.99) > 1e-9 {
+ t.Fatal("Value1 should be 9.99")
+ }
+}
+
+func TestGetLastAppNum(t *testing.T) {
+ jsonString := `
+ {
+ "metrics": [{
+ "name": "app_num_with_node",
+ "labelNames": ["tags"],
+ "labelValues": ["ss_v5,GRPC"],
+ "value": 10.0,
+ "timestampMs": null
+ }, {
+ "name": "total_remove_resource_time",
+ "labelNames": ["quantile"],
+ "labelValues": ["0.5"],
+ "value": "NaN",
+ "timestampMs": null
+ }],
+ "timeStamp": 1712575271639
+ }
+ `
+ if num, err := getLastAppNum([]byte(jsonString)); err != nil {
+ t.Fatal("getLastAppNum failed, casued by ", err.Error())
+ } else if num != 10 {
+ t.Fatal("Get wrong app number: ", num)
+ }
+}