github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/columns/group/group.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 group 16 17 import ( 18 "fmt" 19 "reflect" 20 "strings" 21 22 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns" 23 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/sort" 24 ) 25 26 // GroupEntries will group the given entries using the column names given in groupBy and return a new 27 // array with the results; if groupBy contains an empty string, all given entries will be grouped 28 func GroupEntries[T any](cols columns.ColumnMap[T], entries []*T, groupBy []string) ([]*T, error) { 29 if entries == nil { 30 return nil, nil 31 } 32 33 newEntries := entries 34 35 for _, groupName := range groupBy { 36 groupName = strings.ToLower(groupName) 37 38 // Special case: empty group 39 // This means we will reduce the output to one record 40 if groupName == "" { 41 groupMap := make(map[string][]*T) 42 allValues := make([]*T, 0, len(entries)) 43 for _, entry := range entries { 44 if entry == nil { 45 // Skip nil entries 46 continue 47 } 48 allValues = append(allValues, entry) 49 } 50 groupMap[""] = allValues 51 52 outEntries := make([]*T, 0, len(allValues)) 53 54 flattenValues(cols, &outEntries, groupMap) 55 56 // We may exit now, since grouping more fields makes no sense after this 57 return outEntries, nil 58 } 59 60 // Get column to group 61 column, ok := cols.GetColumn(groupName) 62 if !ok { 63 return nil, fmt.Errorf("grouping by %q: column not found", groupName) 64 } 65 66 // Create a new map with key matching the group key 67 groupMap := make(map[string][]*T) 68 69 stringFunc := columns.GetFieldAsString[T](column) 70 71 // Iterate over entries and push them to their corresponding map key 72 for _, entry := range newEntries { 73 if entry == nil { 74 continue 75 } 76 77 // Transform group key according to request 78 key := stringFunc(entry) 79 80 if _, ok := groupMap[key]; !ok { 81 groupMap[key] = make([]*T, 0) 82 } 83 84 groupMap[key] = append(groupMap[key], entry) 85 } 86 87 outEntries := make([]*T, 0, len(groupMap)) 88 flattenValues(cols, &outEntries, groupMap) 89 90 // Sort by groupName to get a deterministic result 91 sort.SortEntries(cols, outEntries, []string{groupName}) 92 93 newEntries = outEntries 94 } 95 96 return newEntries, nil 97 } 98 99 func flattenValues[T any](cols columns.ColumnMap[T], outEntries *[]*T, groupMap map[string][]*T) { 100 for _, v := range groupMap { 101 if len(v) == 0 { 102 continue 103 } 104 // Use first entry as base 105 entry := new(T) 106 *entry = *v[0] // Copy base 107 for _, curEntry := range v[1:] { 108 for _, column := range cols.GetColumnMap() { 109 switch column.GroupType { 110 case columns.GroupTypeNone: 111 continue 112 case columns.GroupTypeSum: 113 switch column.Kind() { 114 case reflect.Int, 115 reflect.Int8, 116 reflect.Int16, 117 reflect.Int32, 118 reflect.Int64: 119 fs := columns.SetFieldAsNumberFunc[int64, T](column) 120 fg := columns.GetFieldAsNumberFunc[int64, T](column) 121 fs(entry, fg(curEntry)+fg(entry)) 122 case reflect.Uint, 123 reflect.Uint8, 124 reflect.Uint16, 125 reflect.Uint32, 126 reflect.Uint64: 127 fs := columns.SetFieldAsNumberFunc[uint64, T](column) 128 fg := columns.GetFieldAsNumberFunc[uint64, T](column) 129 fs(entry, fg(curEntry)+fg(entry)) 130 case reflect.Float32, 131 reflect.Float64: 132 fs := columns.SetFieldAsNumberFunc[float64, T](column) 133 fg := columns.GetFieldAsNumberFunc[float64, T](column) 134 fs(entry, fg(curEntry)+fg(entry)) 135 } 136 } 137 } 138 } 139 *outEntries = append(*outEntries, entry) 140 } 141 }