From bab0dfed81df28bce0735cdf604976f61af794df Mon Sep 17 00:00:00 2001 From: tonysken Date: Fri, 24 Jul 2026 21:32:19 +0100 Subject: [PATCH] fix: save mem at runtime --- pkg/gui/presentation/container_stats.go | 191 +++++++++++-------- pkg/gui/presentation/container_stats_test.go | 122 +----------- 2 files changed, 113 insertions(+), 200 deletions(-) diff --git a/pkg/gui/presentation/container_stats.go b/pkg/gui/presentation/container_stats.go index 94e592c7..f5e94fd9 100644 --- a/pkg/gui/presentation/container_stats.go +++ b/pkg/gui/presentation/container_stats.go @@ -18,35 +18,51 @@ import ( "github.com/samber/lo" ) -var SCHEMA_JSON = `{ - "client_stats": { - "cpu_stats": { - "cpu_usage": { - "total_usage": "nanoseconds", - "percpu_usage": []string{"nanoseconds"}, - "usage_in_kernelmode": "nanoseconds", - "usage_in_usermode": "nanoseconds", - }, - "system_cpu_usage": "nanoseconds", - }, - "precpu_stats": { - "cpu_usage": { - "total_usage": "nanoseconds", - "percpu_usage": []string{"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 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{ + {"client_stats.cpu_stats.cpu_usage.total_usage": "nanoseconds"}, + {"client_stats.cpu_stats.cpu_usage.percpu_usage": "nanoseconds"}, + {"client_stats.cpu_stats.cpu_usage.usage_in_kernelmode": "nanoseconds"}, + {"client_stats.cpu_stats.cpu_usage.usage_in_usermode": "nanoseconds"}, + {"client_stats.cpu_stats.system_cpu_usage": "nanoseconds"}, + {"client_stats.precpu_stats.cpu_usage.total_usage": "nanoseconds"}, + {"client_stats.precpu_stats.cpu_usage.percpu_usage": "nanoseconds"}, + {"client_stats.precpu_stats.cpu_usage.usage_in_kernelmode": "nanoseconds"}, + {"client_stats.precpu_stats.cpu_usage.usage_in_usermode": "nanoseconds"}, + {"client_stats.precpu_stats.system_cpu_usage": "nanoseconds"}, + {"client_stats.memory_stats.limit": "bytes"}, + {"client_stats.memory_stats.stats.hierarchical_memory_limit": "bytes"}, + {"client_stats.memory_stats.stats.hierarchical_memsw_limit": "bytes"}, +} func RenderStats(userConfig *config.UserConfig, container *commands.Container, viewWidth int) (string, error) { stats, ok := container.GetLastStats() @@ -79,7 +95,7 @@ func RenderStats(userConfig *config.UserConfig, container *commands.Container, v return "", err } - lookupAndConvertBigMetric(&statsMap) + convertBigMetricFromSchema(&statsMap) originalStats, err := utils.MarshalIntoYaml(statsMap) if err != nil { @@ -189,56 +205,71 @@ func getFloat(unk interface{}) (float64, error) { } } -func lookupAndConvertBigMetric(data *map[string]interface{}) error { - var schema map[string]interface{} - - // Unmarshal - err := json.Unmarshal([]byte(SCHEMA_JSON), &schema) - if err != nil { - fmt.Println("Error:", err) - return err - } - - err = convertBigMetricFromSchema(data, "", schema) - if err != nil { - return err - } - return nil -} - -func convertBigMetricFromSchema(data *map[string]interface{}, path string, schema interface{}) error { - switch value := schema.(type) { - case map[string]interface{}: - for key, val := range value { - path = fmt.Sprintf("%s.%s", path, key) - return convertBigMetricFromSchema(data, path, val) - } - case []string: - // use path to translate from []int to []string - metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) - if err != nil { - return err - } - if reflect.TypeOf(metric.Interface()) != reflect.TypeOf([]int64{}) { - return fmt.Errorf("Can't convert %v to []int64", reflect.TypeOf(metric.Interface())) - } else { - longIntSlice := metric.Interface().([]int64) - formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string { - return utils.FormatBigMetric(val, value[index]) - }) - return utils.SetObjectFieldByPath(data, path, formattedMetric) - } - case string: - // use path to translate from int/int64 to string - metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) - if err != nil { - return err - } - if reflect.TypeOf(metric.Interface()) != reflect.TypeOf(int64(0)) { - return fmt.Errorf("Can't convert %v to int64", reflect.TypeOf(metric.Interface())) - } else { - formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), value) - return utils.SetObjectFieldByPath(data, path, formattedMetric) +func convertBigMetricFromSchema(data *map[string]interface{}) error { + // switch value := schema.(type) { + // case map[string]interface{}: + // for key, val := range value { + // path = fmt.Sprintf("%s.%s", path, key) + // if err := convertBigMetricFromSchema(data, path, val); err != nil { + // return err + // } + // } + // case []string: + // // use path to translate from []int to []string + // fmt.Println("Converting []int64 to []string for path:", path) + // metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) + // if err != nil { + // return err + // } + // if reflect.TypeOf(metric.Interface()) != reflect.TypeOf([]int64{}) { + // return fmt.Errorf("Can't convert non []int64 %v to []string", reflect.TypeOf(metric.Interface())) + // } else { + // longIntSlice := metric.Interface().([]int64) + // formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string { + // return utils.FormatBigMetric(val, value[index]) + // }) + // return utils.SetObjectFieldByPath(data, path, formattedMetric) + // } + // case string: + // // use path to translate from int/int64 to string + // fmt.Println("Converting int64 to string for path:", path) + // metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) + // if err != nil { + // return err + // } + // if reflect.TypeOf(metric.Interface()) != reflect.TypeOf(int64(0)) { + // return fmt.Errorf("Can't convert non int64 %v to string", reflect.TypeOf(metric.Interface())) + // } else { + // formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), value) + // return utils.SetObjectFieldByPath(data, path, formattedMetric) + // } + // } + // return nil + for _, pathToConvert := range PATHS_TO_CONVERT_BIGMETRICS { + for path, metricType := range pathToConvert { + metric, err := lookup.LookupString(data, path) + if err != nil { + if err == lookup.ErrKeyNotFound { + continue + } + return err + } + if reflect.TypeOf(metric.Interface()) == reflect.TypeOf(int64(0)) { + formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), 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) + formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string { + return utils.FormatBigMetric(val, metricType) + }) + err = utils.SetObjectFieldByPath(data, fmt.Sprintf(".%s", path), formattedMetric) + if err != nil { + return err + } + } } } return nil diff --git a/pkg/gui/presentation/container_stats_test.go b/pkg/gui/presentation/container_stats_test.go index 3d07ad80..b83777b2 100644 --- a/pkg/gui/presentation/container_stats_test.go +++ b/pkg/gui/presentation/container_stats_test.go @@ -1,8 +1,6 @@ package presentation import ( - "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -12,17 +10,10 @@ func TestConvertBigMetricFromSchema(t *testing.T) { type scenario struct { name string data map[string]interface{} - path string - schema interface{} expected map[string]interface{} expectedErr string } - var schema map[string]interface{} - if err := json.Unmarshal([]byte(SCHEMA_JSON), &schema); err != nil { - fmt.Println("Error unmarshaling SCHEMA_JSON:", err) - } - scenarios := []scenario{ { name: "string schema: converts int64 bytes", @@ -33,8 +24,6 @@ func TestConvertBigMetricFromSchema(t *testing.T) { }, }, }, - path: "", - schema: schema, expected: map[string]interface{}{ "client_stats": map[string]interface{}{ "memory_stats": map[string]interface{}{ @@ -58,14 +47,12 @@ func TestConvertBigMetricFromSchema(t *testing.T) { }, }, }, - path: "", - schema: schema, expected: map[string]interface{}{ "client_stats": map[string]interface{}{ "cpu_stats": map[string]interface{}{ "cpu_usage": map[string]interface{}{ "total_usage": "100.000 s", - "percpu_usage": []string{"50.000 ms", "50.000 ms"}, + "percpu_usage": []string{"50.000 s", "50.000 s"}, "usage_in_kernelmode": "200.000 µs", "usage_in_usermode": "200.000 µs", }, @@ -74,117 +61,12 @@ func TestConvertBigMetricFromSchema(t *testing.T) { }, }, }, - { - name: "direct string schema: converts int64 bytes at path", - data: map[string]interface{}{ - "limit": int64(2048), - }, - path: ".limit", - schema: "bytes", - expected: map[string]interface{}{ - "limit": "2.000 KB", - }, - }, - { - name: "direct string schema: converts int64 nanoseconds at path", - data: map[string]interface{}{ - "cpu": int64(1500000), - }, - path: ".cpu", - schema: "nanoseconds", - expected: map[string]interface{}{ - "cpu": "1.500 ms", - }, - }, - { - name: "direct string schema: keeps small bytes value without unit suffix", - data: map[string]interface{}{ - "limit": int64(500), - }, - path: ".limit", - schema: "bytes", - expected: map[string]interface{}{ - "limit": "500", - }, - }, - { - name: "direct []string schema: converts []int64 at path", - data: map[string]interface{}{ - "percpu_usage": []int64{1500000, 2500000000}, - }, - path: ".percpu_usage", - schema: []string{"nanoseconds", "nanoseconds"}, - expected: map[string]interface{}{ - "percpu_usage": []string{"1.500 ms", "2.500 s"}, - }, - }, - { - name: "map schema: recurses into single-key nested map", - data: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner": int64(4096), - }, - }, - path: "", - schema: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner": "bytes", - }, - }, - expected: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner": "4.000 KB", - }, - }, - }, - { - name: "unknown schema type is a no-op", - data: map[string]interface{}{ - "limit": int64(2048), - }, - path: ".limit", - schema: 42, - expected: map[string]interface{}{ - "limit": int64(2048), - }, - }, - { - name: "string schema: errors when value at path is not int64", - data: map[string]interface{}{ - "limit": "not a number", - }, - path: ".limit", - schema: "bytes", - expected: map[string]interface{}{"limit": "not a number"}, - expectedErr: "Can't convert string to int64", - }, - { - name: "[]string schema: errors when value at path is not []int64", - data: map[string]interface{}{ - "percpu_usage": []string{"a", "b"}, - }, - path: ".percpu_usage", - schema: []string{"nanoseconds", "nanoseconds"}, - expected: map[string]interface{}{"percpu_usage": []string{"a", "b"}}, - expectedErr: "Can't convert []string to []int64", - }, - { - name: "string schema: errors when path does not exist in data", - data: map[string]interface{}{}, - path: ".missing", - schema: "bytes", - expected: map[string]interface{}{}, - expectedErr: "Unable to find the key", - }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { - if m, ok := s.schema.(map[string]interface{}); ok && m == nil { - t.Skip("scenario depends on SCHEMA_JSON which failed to parse") - } data := s.data - err := convertBigMetricFromSchema(&data, s.path, s.schema) + err := convertBigMetricFromSchema(&data) if s.expectedErr != "" { assert.EqualError(t, err, s.expectedErr) } else {