github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/columns/sort/sort.go (about)

     1  // Copyright 2022-2023 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sort
    16  
    17  import (
    18  	"reflect"
    19  	"sort"
    20  
    21  	"golang.org/x/exp/constraints"
    22  
    23  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
    24  )
    25  
    26  type columnSorter[T any] struct {
    27  	column *columns.Column[T]
    28  	order  columns.Order
    29  }
    30  
    31  type ColumnSorterCollection[T any] struct {
    32  	sorters []*columnSorter[T]
    33  }
    34  
    35  func (csc *ColumnSorterCollection[T]) Sort(entries []*T) {
    36  	if len(entries) == 0 {
    37  		return
    38  	}
    39  
    40  	for _, s := range csc.sorters {
    41  		var sortFunc func(i, j int) bool
    42  		order := s.order
    43  
    44  		kind := s.column.Kind()
    45  		if s.column.HasCustomExtractor() {
    46  			kind = s.column.GetRaw(entries[0]).Kind()
    47  		}
    48  
    49  		switch kind {
    50  		case reflect.Int:
    51  			sortFunc = getLessFunc[int, T](entries, s.column, order)
    52  		case reflect.Int8:
    53  			sortFunc = getLessFunc[int8, T](entries, s.column, order)
    54  		case reflect.Int16:
    55  			sortFunc = getLessFunc[int16, T](entries, s.column, order)
    56  		case reflect.Int32:
    57  			sortFunc = getLessFunc[int32, T](entries, s.column, order)
    58  		case reflect.Int64:
    59  			sortFunc = getLessFunc[int64, T](entries, s.column, order)
    60  		case reflect.Uint:
    61  			sortFunc = getLessFunc[uint, T](entries, s.column, order)
    62  		case reflect.Uint8:
    63  			sortFunc = getLessFunc[uint8, T](entries, s.column, order)
    64  		case reflect.Uint16:
    65  			sortFunc = getLessFunc[uint16, T](entries, s.column, order)
    66  		case reflect.Uint32:
    67  			sortFunc = getLessFunc[uint32, T](entries, s.column, order)
    68  		case reflect.Uint64:
    69  			sortFunc = getLessFunc[uint64, T](entries, s.column, order)
    70  		case reflect.Float32:
    71  			sortFunc = getLessFunc[float32, T](entries, s.column, order)
    72  		case reflect.Float64:
    73  			sortFunc = getLessFunc[float64, T](entries, s.column, order)
    74  		case reflect.String:
    75  			sortFunc = getLessFunc[string, T](entries, s.column, order)
    76  		default:
    77  			continue
    78  		}
    79  
    80  		sort.SliceStable(entries, sortFunc)
    81  	}
    82  }
    83  
    84  // Prepare prepares a sorter collection that can be re-used for multiple calls to Sort() for efficiency. Filter rules
    85  // will be applied from right to left (first rule has the highest priority).
    86  func Prepare[T any](cols columns.ColumnMap[T], sortBy []string) *ColumnSorterCollection[T] {
    87  	valid, _ := FilterSortableColumns(cols, sortBy)
    88  
    89  	sorters := make([]*columnSorter[T], 0, len(sortBy))
    90  	for i := len(valid) - 1; i >= 0; i-- {
    91  		sortField := valid[i]
    92  		// Handle ordering
    93  		order := columns.OrderAsc
    94  		if sortField[0] == '-' {
    95  			sortField = sortField[1:]
    96  			order = columns.OrderDesc
    97  		}
    98  
    99  		column, _ := cols.GetColumn(sortField)
   100  
   101  		sorters = append(sorters, &columnSorter[T]{
   102  			column: column,
   103  			order:  order,
   104  		})
   105  	}
   106  
   107  	return &ColumnSorterCollection[T]{
   108  		sorters: sorters,
   109  	}
   110  }
   111  
   112  // SortEntries sorts entries by applying the sortBy rules from right to left (first rule has the highest
   113  // priority). The rules are strings containing the column names, optionally prefixed with "-" to switch to descending
   114  // sort order.
   115  func SortEntries[T any](cols columns.ColumnMap[T], entries []*T, sortBy []string) {
   116  	if entries == nil {
   117  		return
   118  	}
   119  
   120  	coll := Prepare(cols, sortBy)
   121  	coll.Sort(entries)
   122  }
   123  
   124  func getLessFunc[OT constraints.Ordered, T any](array []*T, column columns.ColumnInternals, order columns.Order) func(i, j int) bool {
   125  	fieldFunc := columns.GetFieldFuncExt[OT, T](column, true)
   126  	return func(i, j int) bool {
   127  		if array[i] == nil {
   128  			return false
   129  		}
   130  		if array[j] == nil {
   131  			return true
   132  		}
   133  		return !(fieldFunc(array[i]) < fieldFunc(array[j])) != order
   134  	}
   135  }
   136  
   137  // CanSortBy returns true, if all requested sortBy arguments can be used for sorting
   138  // This is not the case for a virtual column, which has no underlying value type
   139  func CanSortBy[T any](cols columns.ColumnMap[T], sortBy []string) bool {
   140  	valid, _ := FilterSortableColumns(cols, sortBy)
   141  
   142  	return len(valid) == len(sortBy)
   143  }
   144  
   145  // FilterSortableColumns returns two lists, one containing the valid column names
   146  // and another containing the invalid column names.
   147  func FilterSortableColumns[T any](cols columns.ColumnMap[T], sortBy []string) ([]string, []string) {
   148  	valid := make([]string, 0, len(sortBy))
   149  	invalid := make([]string, 0)
   150  
   151  	for _, sortField := range sortBy {
   152  		if len(sortField) == 0 {
   153  			invalid = append(invalid, sortField)
   154  			continue
   155  		}
   156  
   157  		rawSortField := sortField
   158  		if rawSortField[0] == '-' {
   159  			rawSortField = rawSortField[1:]
   160  		}
   161  
   162  		column, ok := cols.GetColumn(rawSortField)
   163  		if !ok {
   164  			invalid = append(invalid, sortField)
   165  			continue
   166  		}
   167  
   168  		// Skip virtual columns, they have no underlying value to sort by
   169  		if column.IsVirtual() {
   170  			invalid = append(invalid, sortField)
   171  			continue
   172  		}
   173  
   174  		valid = append(valid, sortField)
   175  	}
   176  
   177  	return valid, invalid
   178  }