consider units when sorting capacity (#2252)
parent
e906fa606f
commit
b2be433637
|
|
@ -17,6 +17,7 @@ type HeaderColumn struct {
|
|||
Wide bool
|
||||
MX bool
|
||||
Time bool
|
||||
Capacity bool
|
||||
}
|
||||
|
||||
// Clone copies a header.
|
||||
|
|
@ -163,6 +164,15 @@ func (h Header) IsTimeCol(col int) bool {
|
|||
return h[col].Time
|
||||
}
|
||||
|
||||
// IsCapacityCol checks if given column index represents a capacity.
|
||||
func (h Header) IsCapacityCol(col int) bool {
|
||||
if col < 0 || col >= len(h) {
|
||||
return false
|
||||
}
|
||||
|
||||
return h[col].Capacity
|
||||
}
|
||||
|
||||
// ValidColIndex returns the valid col index or -1 if none.
|
||||
func (h Header) ValidColIndex() int {
|
||||
return h.IndexOf("VALID", true)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
|
@ -57,6 +58,11 @@ func durationToSeconds(duration string) int64 {
|
|||
return n
|
||||
}
|
||||
|
||||
func capacityToNumber(capacity string) int64 {
|
||||
quantity := resource.MustParse(capacity)
|
||||
return quantity.Value()
|
||||
}
|
||||
|
||||
// AsThousands prints a number with thousand separator.
|
||||
func AsThousands(n int64) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (p PersistentVolume) ColorerFunc() ColorerFunc {
|
|||
func (PersistentVolume) Header(string) Header {
|
||||
return Header{
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "CAPACITY"},
|
||||
HeaderColumn{Name: "CAPACITY", Capacity: true},
|
||||
HeaderColumn{Name: "ACCESS MODES"},
|
||||
HeaderColumn{Name: "RECLAIM POLICY"},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func (PersistentVolumeClaim) Header(ns string) Header {
|
|||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
HeaderColumn{Name: "VOLUME"},
|
||||
HeaderColumn{Name: "CAPACITY"},
|
||||
HeaderColumn{Name: "CAPACITY", Capacity: true},
|
||||
HeaderColumn{Name: "ACCESS MODES"},
|
||||
HeaderColumn{Name: "STORAGECLASS"},
|
||||
HeaderColumn{Name: "LABELS", Wide: true},
|
||||
|
|
|
|||
|
|
@ -145,12 +145,13 @@ func (rr Rows) Find(id string) (int, bool) {
|
|||
}
|
||||
|
||||
// Sort rows based on column index and order.
|
||||
func (rr Rows) Sort(col int, asc, isNum, isDur bool) {
|
||||
func (rr Rows) Sort(col int, asc, isNum, isDur, isCapacity bool) {
|
||||
t := RowSorter{
|
||||
Rows: rr,
|
||||
Index: col,
|
||||
IsNumber: isNum,
|
||||
IsDuration: isDur,
|
||||
IsCapacity: isCapacity,
|
||||
Asc: asc,
|
||||
}
|
||||
sort.Sort(t)
|
||||
|
|
@ -160,10 +161,12 @@ func (rr Rows) Sort(col int, asc, isNum, isDur bool) {
|
|||
|
||||
// RowSorter sorts rows.
|
||||
type RowSorter struct {
|
||||
Rows Rows
|
||||
Index int
|
||||
IsNumber, IsDuration bool
|
||||
Asc bool
|
||||
Rows Rows
|
||||
Index int
|
||||
IsNumber bool
|
||||
IsDuration bool
|
||||
IsCapacity bool
|
||||
Asc bool
|
||||
}
|
||||
|
||||
func (s RowSorter) Len() int {
|
||||
|
|
@ -177,7 +180,7 @@ func (s RowSorter) Swap(i, j int) {
|
|||
func (s RowSorter) Less(i, j int) bool {
|
||||
v1, v2 := s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index]
|
||||
id1, id2 := s.Rows[i].ID, s.Rows[j].ID
|
||||
less := Less(s.IsNumber, s.IsDuration, id1, id2, v1, v2)
|
||||
less := Less(s.IsNumber, s.IsDuration, s.IsCapacity, id1, id2, v1, v2)
|
||||
if s.Asc {
|
||||
return less
|
||||
}
|
||||
|
|
@ -188,7 +191,7 @@ func (s RowSorter) Less(i, j int) bool {
|
|||
// Helpers...
|
||||
|
||||
// Less return true if c1 < c2.
|
||||
func Less(isNumber, isDuration bool, id1, id2, v1, v2 string) bool {
|
||||
func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool {
|
||||
var less bool
|
||||
switch {
|
||||
case isNumber:
|
||||
|
|
@ -197,6 +200,9 @@ func Less(isNumber, isDuration bool, id1, id2, v1, v2 string) bool {
|
|||
case isDuration:
|
||||
d1, d2 := durationToSeconds(v1), durationToSeconds(v2)
|
||||
less = d1 <= d2
|
||||
case isCapacity:
|
||||
c1, c2 := capacityToNumber(v1), capacityToNumber(v2)
|
||||
less = c1 <= c2
|
||||
default:
|
||||
less = sortorder.NaturalLess(v1, v2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ func (r RowEvents) FindIndex(id string) (int, bool) {
|
|||
}
|
||||
|
||||
// Sort rows based on column index and order.
|
||||
func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
|
||||
func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, asc bool) {
|
||||
if sortCol == -1 {
|
||||
return
|
||||
}
|
||||
|
|
@ -205,6 +205,7 @@ func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
|
|||
Asc: asc,
|
||||
IsNumber: numCol,
|
||||
IsDuration: isDuration,
|
||||
IsCapacity: isCapacity,
|
||||
}
|
||||
sort.Sort(t)
|
||||
}
|
||||
|
|
@ -218,6 +219,7 @@ type RowEventSorter struct {
|
|||
NS string
|
||||
IsNumber bool
|
||||
IsDuration bool
|
||||
IsCapacity bool
|
||||
Asc bool
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +234,7 @@ func (r RowEventSorter) Swap(i, j int) {
|
|||
func (r RowEventSorter) Less(i, j int) bool {
|
||||
f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields
|
||||
id1, id2 := r.Events[i].Row.ID, r.Events[j].Row.ID
|
||||
less := Less(r.IsNumber, r.IsDuration, id1, id2, f1[r.Index], f2[r.Index])
|
||||
less := Less(r.IsNumber, r.IsDuration, r.IsCapacity, id1, id2, f1[r.Index], f2[r.Index])
|
||||
if r.Asc {
|
||||
return less
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,6 +413,7 @@ func TestRowEventsSort(t *testing.T) {
|
|||
re render.RowEvents
|
||||
col int
|
||||
duration, num, asc bool
|
||||
capacity bool
|
||||
e render.RowEvents
|
||||
}{
|
||||
"age_time": {
|
||||
|
|
@ -464,12 +465,33 @@ func TestRowEventsSort(t *testing.T) {
|
|||
{Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3"}}},
|
||||
},
|
||||
},
|
||||
"capacity": {
|
||||
re: render.RowEvents{
|
||||
{Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3", "1Gi"}}},
|
||||
{Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3", "1.1G"}}},
|
||||
{Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3", "0.5Ti"}}},
|
||||
{Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3", "12e6"}}},
|
||||
{Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3", "1234"}}},
|
||||
{Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3", "0.1Ei"}}},
|
||||
},
|
||||
col: 3,
|
||||
asc: true,
|
||||
capacity: true,
|
||||
e: render.RowEvents{
|
||||
{Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3", "1234"}}},
|
||||
{Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3", "12e6"}}},
|
||||
{Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3", "1Gi"}}},
|
||||
{Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3", "1.1G"}}},
|
||||
{Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3", "0.5Ti"}}},
|
||||
{Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3", "0.1Ei"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.re.Sort("", u.col, u.duration, u.num, u.asc)
|
||||
u.re.Sort("", u.col, u.duration, u.num, u.capacity, u.asc)
|
||||
assert.Equal(t, u.e, u.re)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ func TestRowsSortText(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc, u.num, false)
|
||||
u.rows.Sort(u.col, u.asc, u.num, false, false)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ func TestRowsSortDuration(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc, false, true)
|
||||
u.rows.Sort(u.col, u.asc, false, true, false)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
@ -411,7 +411,49 @@ func TestRowsSortMetrics(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc, true, false)
|
||||
u.rows.Sort(u.col, u.asc, true, false, false)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsSortCapacity(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
rows render.Rows
|
||||
col int
|
||||
asc bool
|
||||
e render.Rows
|
||||
}{
|
||||
"capacityAsc": {
|
||||
rows: render.Rows{
|
||||
{Fields: []string{"10Gi", "duh"}},
|
||||
{Fields: []string{"10G", "blee"}},
|
||||
},
|
||||
col: 0,
|
||||
asc: true,
|
||||
e: render.Rows{
|
||||
{Fields: []string{"10G", "blee"}},
|
||||
{Fields: []string{"10Gi", "duh"}},
|
||||
},
|
||||
},
|
||||
"capacityDesc": {
|
||||
rows: render.Rows{
|
||||
{Fields: []string{"10000m", "1000Mi"}},
|
||||
{Fields: []string{"1m", "50Mi"}},
|
||||
},
|
||||
col: 1,
|
||||
asc: false,
|
||||
e: render.Rows{
|
||||
{Fields: []string{"10000m", "1000Mi"}},
|
||||
{Fields: []string{"1m", "50Mi"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc, false, false, true)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
@ -419,14 +461,17 @@ func TestRowsSortMetrics(t *testing.T) {
|
|||
|
||||
func TestLess(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
isNumber, isDuration bool
|
||||
id1, id2 string
|
||||
v1, v2 string
|
||||
e bool
|
||||
isNumber bool
|
||||
isDuration bool
|
||||
isCapacity bool
|
||||
id1, id2 string
|
||||
v1, v2 string
|
||||
e bool
|
||||
}{
|
||||
"years": {
|
||||
isNumber: false,
|
||||
isDuration: true,
|
||||
isCapacity: false,
|
||||
id1: "id1",
|
||||
id2: "id2",
|
||||
v1: "2y263d",
|
||||
|
|
@ -435,17 +480,38 @@ func TestLess(t *testing.T) {
|
|||
"hours": {
|
||||
isNumber: false,
|
||||
isDuration: true,
|
||||
isCapacity: false,
|
||||
id1: "id1",
|
||||
id2: "id2",
|
||||
v1: "2y263d",
|
||||
v2: "19h",
|
||||
},
|
||||
"capacity1": {
|
||||
isNumber: false,
|
||||
isDuration: false,
|
||||
isCapacity: true,
|
||||
id1: "id1",
|
||||
id2: "id2",
|
||||
v1: "1Gi",
|
||||
v2: "1G",
|
||||
e: false,
|
||||
},
|
||||
"capacity2": {
|
||||
isNumber: false,
|
||||
isDuration: false,
|
||||
isCapacity: true,
|
||||
id1: "id1",
|
||||
id2: "id2",
|
||||
v1: "1Gi",
|
||||
v2: "1Ti",
|
||||
e: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, render.Less(u.isNumber, u.isDuration, u.id1, u.id2, u.v1, u.v2))
|
||||
assert.Equal(t, u.e, render.Less(u.isNumber, u.isDuration, u.isCapacity, u.id1, u.id2, u.v1, u.v2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ func (t *Table) doUpdate(data *render.TableData) {
|
|||
colIndex,
|
||||
custData.Header.IsTimeCol(colIndex),
|
||||
custData.Header.IsMetricsCol(colIndex),
|
||||
custData.Header.IsCapacityCol(colIndex),
|
||||
t.sortCol.asc,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue