fix: handle float64 chosen by json and invalid metric

This commit is contained in:
tonysken 2026-07-24 23:20:46 +01:00
parent 1ab3b8746d
commit a2659b15aa
No known key found for this signature in database
GPG key ID: 67D2B9EB6131A6CE
2 changed files with 166 additions and 51 deletions

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"math"
"os"
"reflect"
"strconv"
"strings"
@ -19,36 +18,6 @@ import (
"github.com/samber/lo"
)
// var SCHEMA_JSON = `{
// "client_stats": {
// "cpu_stats": {
// "cpu_usage": {
// "total_usage": "nanoseconds",
// "percpu_usage": ["nanoseconds"],
// "usage_in_kernelmode": "nanoseconds",
// "usage_in_usermode": "nanoseconds"
// },
// "system_cpu_usage": "nanoseconds"
// },
// "precpu_stats": {
// "cpu_usage": {
// "total_usage": "nanoseconds",
// "percpu_usage": ["nanoseconds"],
// "usage_in_kernelmode": "nanoseconds",
// "usage_in_usermode": "nanoseconds"
// },
// "system_cpu_usage": "nanoseconds"
// },
// "memory_stats": {
// "stats": {
// "hierarchical_memory_limit": "bytes",
// "hierarchical_memsw_limit": "bytes"
// },
// "limit": "bytes"
// }
// }
// }`
var PATHS_TO_CONVERT_BIGMETRICS = []map[string]string{
{"ClientStats.cpu_stats.cpu_usage.total_usage": "nanoseconds"},
{"ClientStats.cpu_stats.cpu_usage.percpu_usage": "nanoseconds"},
@ -92,19 +61,14 @@ func RenderStats(userConfig *config.UserConfig, container *commands.Container, v
var statsMap map[string]interface{}
err = json.Unmarshal(statsJsonBytes, &statsMap)
b, _ := json.MarshalIndent(statsMap, "", " ")
_ = os.WriteFile("in.json", b, 0644)
if err != nil {
return "", err
}
// err = convertBigMetricFromSchema(&statsMap)
// b, _ = json.MarshalIndent(statsMap, "", " ")
// _ = os.WriteFile("out.json", b, 0644)
// if err != nil {
// _ = os.WriteFile("out", []byte(err.Error()), 0644)
// return "", err
// }
err = convertBigMetricFromSchema(&statsMap)
if err != nil {
return "", err
}
originalStats, err := utils.MarshalIntoYaml(statsMap)
if err != nil {
@ -264,14 +228,19 @@ func convertBigMetricFromSchema(data *map[string]interface{}) error {
}
return err
}
if reflect.TypeOf(metric.Interface()) == reflect.TypeOf(int64(0)) {
formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), metricType)
if !metric.IsValid() {
continue
}
if reflect.TypeOf(metric.Interface()) == reflect.TypeOf(float64(0)) {
formattedMetric := utils.FormatBigMetric(int64(metric.Interface().(float64)), metricType)
err = utils.SetObjectFieldByPath(data, fmt.Sprintf(".%s", path), formattedMetric)
if err != nil {
return err
}
} else if reflect.TypeOf(metric.Interface()) == reflect.TypeOf([]int64{}) {
longIntSlice := metric.Interface().([]int64)
} else if reflect.TypeOf(metric.Interface()) == reflect.TypeOf([]float64{}) {
longIntSlice := lo.Map(metric.Interface().([]float64), func(val float64, index int) int64 {
return int64(val)
})
formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string {
return utils.FormatBigMetric(val, metricType)
})

View file

@ -1,6 +1,8 @@
package presentation
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -14,13 +16,44 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
expectedErr string
}
inJSONBytes, err := os.ReadFile("testdata/stats.json")
if err != nil {
t.Fatalf("failed to read testdata/stats.json: %v", err)
}
loadInJSON := func() map[string]interface{} {
var m map[string]interface{}
if err := json.Unmarshal(inJSONBytes, &m); err != nil {
t.Fatalf("failed to unmarshal testdata/stats.json: %v", err)
}
return m
}
setNested := func(m map[string]interface{}, keys []string, val interface{}) {
for _, k := range keys[:len(keys)-1] {
m = m[k].(map[string]interface{})
}
m[keys[len(keys)-1]] = val
}
inJSONExpected := loadInJSON()
setNested(inJSONExpected, []string{"ClientStats", "memory_stats", "limit"}, "1.911GB")
setNested(inJSONExpected, []string{"ClientStats", "memory_stats", "stats", "hierarchical_memory_limit"}, "0")
setNested(inJSONExpected, []string{"ClientStats", "memory_stats", "stats", "hierarchical_memsw_limit"}, "0")
setNested(inJSONExpected, []string{"ClientStats", "cpu_stats", "cpu_usage", "total_usage"}, "30.283ms")
setNested(inJSONExpected, []string{"ClientStats", "cpu_stats", "cpu_usage", "usage_in_kernelmode"}, "15.664ms")
setNested(inJSONExpected, []string{"ClientStats", "cpu_stats", "cpu_usage", "usage_in_usermode"}, "14.619ms")
setNested(inJSONExpected, []string{"ClientStats", "cpu_stats", "system_cpu_usage"}, "25.994h")
setNested(inJSONExpected, []string{"ClientStats", "precpu_stats", "cpu_usage", "total_usage"}, "30.283ms")
setNested(inJSONExpected, []string{"ClientStats", "precpu_stats", "cpu_usage", "usage_in_kernelmode"}, "15.664ms")
setNested(inJSONExpected, []string{"ClientStats", "precpu_stats", "cpu_usage", "usage_in_usermode"}, "14.619ms")
setNested(inJSONExpected, []string{"ClientStats", "precpu_stats", "system_cpu_usage"}, "25.994h")
scenarios := []scenario{
{
name: "string schema: converts int64 bytes",
name: "string schema: converts float64 bytes",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": int64(2048),
"limit": float64(2048),
},
},
},
@ -38,12 +71,12 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
"ClientStats": map[string]interface{}{
"cpu_stats": map[string]interface{}{
"cpu_usage": map[string]interface{}{
"total_usage": int64(100000000000),
"percpu_usage": []int64{50000000000, 50000000000},
"usage_in_kernelmode": int64(200000),
"usage_in_usermode": int64(200000),
"total_usage": float64(100000000000),
"percpu_usage": []float64{50000000000, 50000000000},
"usage_in_kernelmode": float64(200000),
"usage_in_usermode": float64(200000),
},
"system_cpu_usage": int64(100000000000),
"system_cpu_usage": float64(100000000000),
},
},
},
@ -61,6 +94,119 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
},
},
},
{
name: "empty data is a no-op",
data: map[string]interface{}{},
expected: map[string]interface{}{},
},
{
name: "bytes below unit threshold stays as raw number",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": float64(500),
},
},
},
expected: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": "500",
},
},
},
},
{
name: "bytes at MB scale",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": float64(1500000),
},
},
},
expected: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": "1.431MB",
},
},
},
},
{
name: "converts deeply nested hierarchical memory limits",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"stats": map[string]interface{}{
"hierarchical_memory_limit": float64(1073741824),
"hierarchical_memsw_limit": float64(2147483648),
},
},
},
},
expected: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"stats": map[string]interface{}{
"hierarchical_memory_limit": "1.000GB",
"hierarchical_memsw_limit": "2.000GB",
},
},
},
},
},
{
name: "converts precpu_stats branch",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"precpu_stats": map[string]interface{}{
"cpu_usage": map[string]interface{}{
"total_usage": float64(1500000),
"percpu_usage": []float64{1000, 2000000000},
},
"system_cpu_usage": float64(1500000000000),
},
},
},
expected: map[string]interface{}{
"ClientStats": map[string]interface{}{
"precpu_stats": map[string]interface{}{
"cpu_usage": map[string]interface{}{
"total_usage": "1.500ms",
"percpu_usage": []string{"1.000µs", "2.000s"},
},
"system_cpu_usage": "1.500m",
},
},
},
},
{
name: "preserves fields not listed in PATHS_TO_CONVERT_BIGMETRICS",
data: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": float64(2048),
"usage": float64(1024),
},
"name": "my-container",
},
},
expected: map[string]interface{}{
"ClientStats": map[string]interface{}{
"memory_stats": map[string]interface{}{
"limit": "2.000KB",
"usage": float64(1024),
},
"name": "my-container",
},
},
},
{
name: "converts realistic docker stats loaded from testdata/stats.json",
data: loadInJSON(),
expected: inJSONExpected,
},
}
for _, s := range scenarios {