mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
feat: add FormatBigMetric, SetObjectFieldByPath from utils to support metric render
This commit is contained in:
parent
e758c35e3b
commit
96734cb3ef
2 changed files with 235 additions and 0 deletions
|
|
@ -410,3 +410,71 @@ func marshalIntoFormat(data interface{}, format string) ([]byte, error) {
|
||||||
return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
|
return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatBigMetric takes a big metric and formats it into a human readable string with the appropriate unit.
|
||||||
|
// For example, if you give it 1000000 with a base unit of bytes, it will return "1.000 MB".
|
||||||
|
// If you give it 1000000000 with a base unit of nanoseconds, it will return "1.000 s"
|
||||||
|
func FormatBigMetric(number int64, baseUnitName string) string {
|
||||||
|
memoryUnits := []string{"KB", "MB", "GB", "TB"}
|
||||||
|
cpuUnits := []string{"µs", "ms", "s", "m", "h"}
|
||||||
|
const unit = 1000
|
||||||
|
if number < unit {
|
||||||
|
return fmt.Sprintf("%d", number)
|
||||||
|
}
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := number / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
switch baseUnitName {
|
||||||
|
case "bytes":
|
||||||
|
return fmt.Sprintf("%.3f %s", float64(number)/float64(div), memoryUnits[exp-1])
|
||||||
|
case "nanoseconds":
|
||||||
|
return fmt.Sprintf("%.3f %s", float64(number)/float64(div), cpuUnits[exp-1])
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d", number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetObjectFieldByPath takes an object, a path to a field in that object, and a value,
|
||||||
|
// and sets the field at that path to the value.
|
||||||
|
// For example, if you have an object {"a": {"b": {"c": 1}}}, a path of "a.b.c", and a value of 2,
|
||||||
|
// it will set the object to {"a": {"b": {"c": 2}}}
|
||||||
|
func SetObjectFieldByPath(object *map[string]interface{}, path string, value interface{}) error {
|
||||||
|
re := regexp.MustCompile(`^\.`)
|
||||||
|
if !re.MatchString(path) && path != "" {
|
||||||
|
return fmt.Errorf("Invalid path format %s, path should start with a dot", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetObject := map[string]interface{}{}
|
||||||
|
currentObject := *object
|
||||||
|
targetObject = copyMapByPath(currentObject, targetObject, path, value)
|
||||||
|
*object = targetObject
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyMapByPath is a helper function for SetObjectFieldByPath
|
||||||
|
// that recursively copies an object while setting the value at the right path
|
||||||
|
func copyMapByPath(currentObject map[string]interface{}, targetObject map[string]interface{}, path string, value interface{}) map[string]interface{} {
|
||||||
|
if path == "" {
|
||||||
|
return currentObject
|
||||||
|
}
|
||||||
|
keyPathSlice := strings.Split(path, ".")[1:]
|
||||||
|
currentKeys := make([]string, 0, len(currentObject))
|
||||||
|
for k := range currentObject {
|
||||||
|
currentKeys = append(currentKeys, k)
|
||||||
|
}
|
||||||
|
for _, key := range currentKeys {
|
||||||
|
if key != keyPathSlice[0] {
|
||||||
|
targetObject[key] = currentObject[key]
|
||||||
|
} else {
|
||||||
|
if len(keyPathSlice) == 1 {
|
||||||
|
targetObject[key] = value
|
||||||
|
} else {
|
||||||
|
nextObject := currentObject[key].(map[string]interface{})
|
||||||
|
targetObject[key] = copyMapByPath(nextObject, map[string]interface{}{}, "."+strings.Join(keyPathSlice[1:], "."), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targetObject
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -319,3 +319,170 @@ quux:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetObjectFieldByPath(t *testing.T) {
|
||||||
|
type scenario struct {
|
||||||
|
object *map[string]interface{}
|
||||||
|
path string
|
||||||
|
value interface{}
|
||||||
|
expected map[string]interface{}
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
scenarios := []scenario{
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": "replacement",
|
||||||
|
},
|
||||||
|
path: ".foo",
|
||||||
|
value: "bar",
|
||||||
|
expected: map[string]interface{}{"foo": "bar"},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: ".foo.bar",
|
||||||
|
value: "bar_1",
|
||||||
|
expected: map[string]interface{}{"foo": map[string]interface{}{"bar": "bar_1"}},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"foo_1": "foo_1_1",
|
||||||
|
"foo_2": map[string]interface{}{
|
||||||
|
"foo_2_1": "foo_2_1_1",
|
||||||
|
},
|
||||||
|
"foo_3": map[string]interface{}{
|
||||||
|
"foo_3_1": "foo_3_1_1",
|
||||||
|
"foo_3_2": "foo_3_2_1",
|
||||||
|
"foo_3_3": map[string]interface{}{
|
||||||
|
"foo_3_3_1": "foo_3_3_1_1",
|
||||||
|
"foo_3_3_2": "foo_3_3_2_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"bar_1": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: ".foo.bar.bar_1",
|
||||||
|
value: "bar_1_1",
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"foo_1": "foo_1_1",
|
||||||
|
"foo_2": map[string]interface{}{
|
||||||
|
"foo_2_1": "foo_2_1_1",
|
||||||
|
},
|
||||||
|
"foo_3": map[string]interface{}{
|
||||||
|
"foo_3_1": "foo_3_1_1",
|
||||||
|
"foo_3_2": "foo_3_2_1",
|
||||||
|
"foo_3_3": map[string]interface{}{
|
||||||
|
"foo_3_3_1": "foo_3_3_1_1",
|
||||||
|
"foo_3_3_2": "foo_3_3_2_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"bar_1": "bar_1_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"foo_1": "foo_1_1",
|
||||||
|
"foo_2": map[string]interface{}{
|
||||||
|
"foo_2_1": "foo_2_1_1",
|
||||||
|
},
|
||||||
|
"foo_3": map[string]interface{}{
|
||||||
|
"foo_3_1": "foo_3_1_1",
|
||||||
|
"foo_3_2": "foo_3_2_1",
|
||||||
|
"foo_3_3": map[string]interface{}{
|
||||||
|
"foo_3_3_1": "foo_3_3_1_1",
|
||||||
|
"foo_3_3_2": "foo_3_3_2_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"bar_1": map[string]interface{}{
|
||||||
|
"bar_1_1": map[string]interface{}{
|
||||||
|
"bar_1_1_1": map[string]interface{}{
|
||||||
|
"bar_1_1_1_1": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: ".foo.bar.bar_1.bar_1_1.bar_1_1_1.bar_1_1_1_1",
|
||||||
|
value: "bar_1_1_1_1_1",
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"foo_1": "foo_1_1",
|
||||||
|
"foo_2": map[string]interface{}{
|
||||||
|
"foo_2_1": "foo_2_1_1",
|
||||||
|
},
|
||||||
|
"foo_3": map[string]interface{}{
|
||||||
|
"foo_3_1": "foo_3_1_1",
|
||||||
|
"foo_3_2": "foo_3_2_1",
|
||||||
|
"foo_3_3": map[string]interface{}{
|
||||||
|
"foo_3_3_1": "foo_3_3_1_1",
|
||||||
|
"foo_3_3_2": "foo_3_3_2_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"bar_1": map[string]interface{}{
|
||||||
|
"bar_1_1": map[string]interface{}{
|
||||||
|
"bar_1_1_1": map[string]interface{}{
|
||||||
|
"bar_1_1_1_1": "bar_1_1_1_1_1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: "",
|
||||||
|
value: "bar_1",
|
||||||
|
expected: map[string]interface{}{"foo": map[string]interface{}{"bar": "replacement"}},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: &map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: "foo.bar",
|
||||||
|
value: "bar_1",
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "replacement",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: errors.New("Invalid path format foo.bar, path should start with a dot"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
err := SetObjectFieldByPath(s.object, s.path, s.value)
|
||||||
|
assert.EqualValues(t, s.expected, *s.object)
|
||||||
|
if s.expectedErr != nil {
|
||||||
|
assert.EqualError(t, err, s.expectedErr.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue