This is an automated email from the ASF dual-hosted git repository.

roryqi pushed a commit to branch branch-0.9
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git

commit 92d984b0438a2d97199e550bed77d8c9d3acbefb
Author: zhengchenyu <[email protected]>
AuthorDate: Tue Apr 16 20:30:23 2024 +0800

    [#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
---
 .../kubernetes/operator/pkg/webhook/util/util.go   | 37 +++++++++--
 .../operator/pkg/webhook/util/util_test.go         | 72 ++++++++++++++++++++++
 2 files changed, 105 insertions(+), 4 deletions(-)

diff --git a/deploy/kubernetes/operator/pkg/webhook/util/util.go 
b/deploy/kubernetes/operator/pkg/webhook/util/util.go
index 038371678..0528310e6 100644
--- a/deploy/kubernetes/operator/pkg/webhook/util/util.go
+++ b/deploy/kubernetes/operator/pkg/webhook/util/util.go
@@ -22,6 +22,7 @@ import (
        "fmt"
        "io"
        "io/ioutil"
+       "math"
        "net/http"
        "time"
 
@@ -158,12 +159,40 @@ func NeedInspectPod(pod *corev1.Pod) bool {
        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 000000000..144257ade
--- /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)
+       }
+}

Reply via email to