fix: save mem at runtime

This commit is contained in:
tonysken 2026-07-24 21:32:19 +01:00
parent 612d34eb00
commit bab0dfed81
No known key found for this signature in database
GPG key ID: 67D2B9EB6131A6CE
2 changed files with 113 additions and 200 deletions

View file

@ -18,35 +18,51 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
var SCHEMA_JSON = `{ // var SCHEMA_JSON = `{
"client_stats": { // "client_stats": {
"cpu_stats": { // "cpu_stats": {
"cpu_usage": { // "cpu_usage": {
"total_usage": "nanoseconds", // "total_usage": "nanoseconds",
"percpu_usage": []string{"nanoseconds"}, // "percpu_usage": ["nanoseconds"],
"usage_in_kernelmode": "nanoseconds", // "usage_in_kernelmode": "nanoseconds",
"usage_in_usermode": "nanoseconds", // "usage_in_usermode": "nanoseconds"
}, // },
"system_cpu_usage": "nanoseconds", // "system_cpu_usage": "nanoseconds"
}, // },
"precpu_stats": { // "precpu_stats": {
"cpu_usage": { // "cpu_usage": {
"total_usage": "nanoseconds", // "total_usage": "nanoseconds",
"percpu_usage": []string{"nanoseconds"}, // "percpu_usage": ["nanoseconds"],
"usage_in_kernelmode": "nanoseconds", // "usage_in_kernelmode": "nanoseconds",
"usage_in_usermode": "nanoseconds", // "usage_in_usermode": "nanoseconds"
}, // },
"system_cpu_usage": "nanoseconds", // "system_cpu_usage": "nanoseconds"
}, // },
"memory_stats": { // "memory_stats": {
"stats": { // "stats": {
"hierarchical_memory_limit": "bytes", // "hierarchical_memory_limit": "bytes",
"hierarchical_memsw_limit": "bytes", // "hierarchical_memsw_limit": "bytes"
}, // },
"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) { func RenderStats(userConfig *config.UserConfig, container *commands.Container, viewWidth int) (string, error) {
stats, ok := container.GetLastStats() stats, ok := container.GetLastStats()
@ -79,7 +95,7 @@ func RenderStats(userConfig *config.UserConfig, container *commands.Container, v
return "", err return "", err
} }
lookupAndConvertBigMetric(&statsMap) convertBigMetricFromSchema(&statsMap)
originalStats, err := utils.MarshalIntoYaml(statsMap) originalStats, err := utils.MarshalIntoYaml(statsMap)
if err != nil { if err != nil {
@ -189,56 +205,71 @@ func getFloat(unk interface{}) (float64, error) {
} }
} }
func lookupAndConvertBigMetric(data *map[string]interface{}) error { func convertBigMetricFromSchema(data *map[string]interface{}) error {
var schema map[string]interface{} // switch value := schema.(type) {
// case map[string]interface{}:
// Unmarshal // for key, val := range value {
err := json.Unmarshal([]byte(SCHEMA_JSON), &schema) // path = fmt.Sprintf("%s.%s", path, key)
if err != nil { // if err := convertBigMetricFromSchema(data, path, val); err != nil {
fmt.Println("Error:", err) // return err
return err // }
} // }
// case []string:
err = convertBigMetricFromSchema(data, "", schema) // // use path to translate from []int to []string
if err != nil { // fmt.Println("Converting []int64 to []string for path:", path)
return err // metric, err := lookup.LookupString(data, strings.TrimPrefix(path, "."))
} // if err != nil {
return nil // return err
} // }
// if reflect.TypeOf(metric.Interface()) != reflect.TypeOf([]int64{}) {
func convertBigMetricFromSchema(data *map[string]interface{}, path string, schema interface{}) error { // return fmt.Errorf("Can't convert non []int64 %v to []string", reflect.TypeOf(metric.Interface()))
switch value := schema.(type) { // } else {
case map[string]interface{}: // longIntSlice := metric.Interface().([]int64)
for key, val := range value { // formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string {
path = fmt.Sprintf("%s.%s", path, key) // return utils.FormatBigMetric(val, value[index])
return convertBigMetricFromSchema(data, path, val) // })
} // return utils.SetObjectFieldByPath(data, path, formattedMetric)
case []string: // }
// use path to translate from []int to []string // case string:
metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) // // use path to translate from int/int64 to string
if err != nil { // fmt.Println("Converting int64 to string for path:", path)
return err // metric, err := lookup.LookupString(data, strings.TrimPrefix(path, "."))
} // if err != nil {
if reflect.TypeOf(metric.Interface()) != reflect.TypeOf([]int64{}) { // return err
return fmt.Errorf("Can't convert %v to []int64", reflect.TypeOf(metric.Interface())) // }
} else { // if reflect.TypeOf(metric.Interface()) != reflect.TypeOf(int64(0)) {
longIntSlice := metric.Interface().([]int64) // return fmt.Errorf("Can't convert non int64 %v to string", reflect.TypeOf(metric.Interface()))
formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string { // } else {
return utils.FormatBigMetric(val, value[index]) // formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), value)
}) // return utils.SetObjectFieldByPath(data, path, formattedMetric)
return utils.SetObjectFieldByPath(data, path, formattedMetric) // }
} // }
case string: // return nil
// use path to translate from int/int64 to string for _, pathToConvert := range PATHS_TO_CONVERT_BIGMETRICS {
metric, err := lookup.LookupString(data, strings.TrimPrefix(path, ".")) for path, metricType := range pathToConvert {
if err != nil { metric, err := lookup.LookupString(data, path)
return err if err != nil {
} if err == lookup.ErrKeyNotFound {
if reflect.TypeOf(metric.Interface()) != reflect.TypeOf(int64(0)) { continue
return fmt.Errorf("Can't convert %v to int64", reflect.TypeOf(metric.Interface())) }
} else { return err
formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), value) }
return utils.SetObjectFieldByPath(data, path, formattedMetric) 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 return nil

View file

@ -1,8 +1,6 @@
package presentation package presentation
import ( import (
"encoding/json"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -12,17 +10,10 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
type scenario struct { type scenario struct {
name string name string
data map[string]interface{} data map[string]interface{}
path string
schema interface{}
expected map[string]interface{} expected map[string]interface{}
expectedErr string 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{ scenarios := []scenario{
{ {
name: "string schema: converts int64 bytes", name: "string schema: converts int64 bytes",
@ -33,8 +24,6 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
}, },
}, },
}, },
path: "",
schema: schema,
expected: map[string]interface{}{ expected: map[string]interface{}{
"client_stats": map[string]interface{}{ "client_stats": map[string]interface{}{
"memory_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{}{ expected: map[string]interface{}{
"client_stats": map[string]interface{}{ "client_stats": map[string]interface{}{
"cpu_stats": map[string]interface{}{ "cpu_stats": map[string]interface{}{
"cpu_usage": map[string]interface{}{ "cpu_usage": map[string]interface{}{
"total_usage": "100.000 s", "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_kernelmode": "200.000 µs",
"usage_in_usermode": "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 { for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) { 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 data := s.data
err := convertBigMetricFromSchema(&data, s.path, s.schema) err := convertBigMetricFromSchema(&data)
if s.expectedErr != "" { if s.expectedErr != "" {
assert.EqualError(t, err, s.expectedErr) assert.EqualError(t, err, s.expectedErr)
} else { } else {