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  }