mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
fix: handle float64 chosen by json and invalid metric
This commit is contained in:
parent
1ab3b8746d
commit
a2659b15aa
2 changed files with 166 additions and 51 deletions
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -19,36 +18,6 @@ import (
|
||||||
"github.com/samber/lo"
|
"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{
|
var PATHS_TO_CONVERT_BIGMETRICS = []map[string]string{
|
||||||
{"ClientStats.cpu_stats.cpu_usage.total_usage": "nanoseconds"},
|
{"ClientStats.cpu_stats.cpu_usage.total_usage": "nanoseconds"},
|
||||||
{"ClientStats.cpu_stats.cpu_usage.percpu_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{}
|
var statsMap map[string]interface{}
|
||||||
err = json.Unmarshal(statsJsonBytes, &statsMap)
|
err = json.Unmarshal(statsJsonBytes, &statsMap)
|
||||||
b, _ := json.MarshalIndent(statsMap, "", " ")
|
|
||||||
_ = os.WriteFile("in.json", b, 0644)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// err = convertBigMetricFromSchema(&statsMap)
|
err = convertBigMetricFromSchema(&statsMap)
|
||||||
// b, _ = json.MarshalIndent(statsMap, "", " ")
|
if err != nil {
|
||||||
// _ = os.WriteFile("out.json", b, 0644)
|
return "", err
|
||||||
// if err != nil {
|
}
|
||||||
// _ = os.WriteFile("out", []byte(err.Error()), 0644)
|
|
||||||
// return "", err
|
|
||||||
// }
|
|
||||||
|
|
||||||
originalStats, err := utils.MarshalIntoYaml(statsMap)
|
originalStats, err := utils.MarshalIntoYaml(statsMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -264,14 +228,19 @@ func convertBigMetricFromSchema(data *map[string]interface{}) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if reflect.TypeOf(metric.Interface()) == reflect.TypeOf(int64(0)) {
|
if !metric.IsValid() {
|
||||||
formattedMetric := utils.FormatBigMetric(metric.Interface().(int64), metricType)
|
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)
|
err = utils.SetObjectFieldByPath(data, fmt.Sprintf(".%s", path), formattedMetric)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if reflect.TypeOf(metric.Interface()) == reflect.TypeOf([]int64{}) {
|
} else if reflect.TypeOf(metric.Interface()) == reflect.TypeOf([]float64{}) {
|
||||||
longIntSlice := metric.Interface().([]int64)
|
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 {
|
formattedMetric := lo.Map(longIntSlice, func(val int64, index int) string {
|
||||||
return utils.FormatBigMetric(val, metricType)
|
return utils.FormatBigMetric(val, metricType)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package presentation
|
package presentation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -14,13 +16,44 @@ func TestConvertBigMetricFromSchema(t *testing.T) {
|
||||||
expectedErr string
|
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{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
name: "string schema: converts int64 bytes",
|
name: "string schema: converts float64 bytes",
|
||||||
data: map[string]interface{}{
|
data: map[string]interface{}{
|
||||||
"ClientStats": map[string]interface{}{
|
"ClientStats": map[string]interface{}{
|
||||||
"memory_stats": 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{}{
|
"ClientStats": 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": int64(100000000000),
|
"total_usage": float64(100000000000),
|
||||||
"percpu_usage": []int64{50000000000, 50000000000},
|
"percpu_usage": []float64{50000000000, 50000000000},
|
||||||
"usage_in_kernelmode": int64(200000),
|
"usage_in_kernelmode": float64(200000),
|
||||||
"usage_in_usermode": int64(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 {
|
for _, s := range scenarios {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue