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

     1  // Copyright 2022-2024 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 columns
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  	"unsafe"
    23  
    24  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns/ellipsis"
    25  )
    26  
    27  const (
    28  	MaxCharsUint8  = 3  // 255
    29  	MaxCharsInt8   = 4  // -128
    30  	MaxCharsUint16 = 5  // 65535
    31  	MaxCharsInt16  = 6  // -32768
    32  	MaxCharsUint32 = 10 // 4294967295
    33  	MaxCharsInt32  = 11 // -2147483648
    34  	MaxCharsUint64 = 20 // 18446744073709551615
    35  	MaxCharsInt64  = 20 // −9223372036854775808
    36  	MaxCharsBool   = 5  // false
    37  	MaxCharsChar   = 1  // 1 character
    38  )
    39  
    40  type subField struct {
    41  	index       int     // number of the referenced field inside the struct
    42  	offset      uintptr // offset of the referenced field inside the struct
    43  	parentIsPtr bool    // true, if the referenced field is a member of a pointer type
    44  	isPtr       bool    // true, if the referenced field is a pointer type
    45  }
    46  
    47  type Attributes struct {
    48  	// Name of the column; case-insensitive for most use cases; includes inherited prefixes
    49  	Name string `yaml:"name"`
    50  	// Name of the columns without inherited prefixes
    51  	RawName string `yaml:"raw_name"`
    52  	// Width to reserve for this column
    53  	Width int `yaml:"width"`
    54  	// MinWidth will be the minimum width this column will be scaled to when using auto-scaling
    55  	MinWidth int `yaml:"min_width"`
    56  	// MaxWidth will be the maximum width this column will be scaled to when using auto-scaling
    57  	MaxWidth int `yaml:"max_width"`
    58  	// Alignment of this column (left or right)
    59  	Alignment Alignment `yaml:"alignment"`
    60  	// Visible defines whether a column is to be shown by default
    61  	Visible bool `yaml:"visible"`
    62  	// GroupType defines the aggregation method used when grouping this column
    63  	GroupType GroupType `yaml:"group_type"`
    64  	// EllipsisType defines how to abbreviate this column if the value needs more space than is available
    65  	EllipsisType ellipsis.EllipsisType `yaml:"ellipsis_type"`
    66  	// FixedWidth forces the Width even when using Auto-Scaling
    67  	FixedWidth bool `yaml:"fixed_width"`
    68  	// Precision defines how many decimals should be shown on float values, default: 2
    69  	Precision int `yaml:"precision"`
    70  	// Description can hold a short description of the field that can be used to aid the user
    71  	Description string `yaml:"description"`
    72  	// Order defines the default order in which columns are shown
    73  	Order int `yaml:"order"`
    74  	// Tags can be used to dynamically include or exclude columns
    75  	Tags []string `yaml:"tags"`
    76  	// Template defines the template that will be used. Non-typed templates will be applied first.
    77  	Template string `yaml:"template"`
    78  }
    79  
    80  type Column[T any] struct {
    81  	Attributes
    82  	Extractor func(*T) any // Extractor to be used; this can be defined to transform the output before retrieving the actual value
    83  
    84  	explicitName  bool                    // true, if the name has been set explicitly
    85  	offset        uintptr                 // offset to the field (relative to root non-ptr struct)
    86  	getStart      func(*T) unsafe.Pointer // getStarts, if present, should point to the start of the struct to be used
    87  	fieldIndex    int                     // used for the main struct
    88  	subFieldIndex []subField              // used for embedded structs
    89  	kind          reflect.Kind            // cached kind info from reflection
    90  	columnType    reflect.Type            // cached type info from reflection
    91  	rawColumnType reflect.Type            // cached type info from reflection
    92  	useTemplate   bool                    // if a template has been set, this will be true
    93  	template      string                  // defines the template that will be used. Non-typed templates will be applied first.
    94  }
    95  
    96  func (ci *Column[T]) GetAttributes() *Attributes {
    97  	return &ci.Attributes
    98  }
    99  
   100  func (ci *Column[T]) getWidthFromType() int {
   101  	return GetWidthFromType(ci.kind)
   102  }
   103  
   104  func (ci *Column[T]) getWidth(params []string) (int, error) {
   105  	if len(params) == 1 {
   106  		return 0, fmt.Errorf("missing %q value for field %q", params[0], ci.Name)
   107  	}
   108  	if params[1] == "type" {
   109  		// Special case, we get the maximum length this field can have by its type
   110  		w := ci.getWidthFromType()
   111  		if w > 0 {
   112  			return w, nil
   113  		}
   114  		return 0, fmt.Errorf("special value %q used for field %q is only available for integer and bool types", params[1], ci.Name)
   115  	}
   116  
   117  	res, err := strconv.Atoi(params[1])
   118  	if err != nil {
   119  		return 0, fmt.Errorf("invalid width %q for field %q: %w", params[1], ci.Name, err)
   120  	}
   121  
   122  	return res, nil
   123  }
   124  
   125  func (ci *Column[T]) fromTag(tag string) error {
   126  	tagInfo := strings.Split(tag, ",")
   127  	// Don't overwrite the name if it has been already set. This prevents an
   128  	// already computed name (for example, with a prefix) from being overwritten
   129  	// when applying a template.
   130  	if ci.Name == "" {
   131  		ci.Name = tagInfo[0]
   132  		ci.RawName = ci.Name
   133  	}
   134  	if len(ci.Name) > 0 {
   135  		ci.explicitName = true
   136  	}
   137  	return ci.parseTagInfo(tagInfo[1:])
   138  }
   139  
   140  func (ci *Column[T]) applyTemplate() error {
   141  	if ci.Template == "" {
   142  		return nil
   143  	}
   144  	name := ci.Name
   145  	tpl, ok := getTemplate(ci.Template)
   146  	if !ok {
   147  		return fmt.Errorf("applying template %q for %q on field %q: template not found", ci.Template, ci.rawColumnType.Name(), name)
   148  	}
   149  	err := ci.parseTagInfo(strings.Split(tpl, ","))
   150  	if err != nil {
   151  		return fmt.Errorf("applying template %q for %q on field %q: %w", ci.Template, ci.rawColumnType.Name(), name, err)
   152  	}
   153  	ci.Name = name
   154  	return nil
   155  }
   156  
   157  func (ci *Column[T]) parseTagInfo(tagInfo []string) error {
   158  	var err error
   159  	for _, subTag := range tagInfo {
   160  		params := strings.SplitN(subTag, ":", 2)
   161  		paramsLen := len(params)
   162  		switch params[0] {
   163  		case "align":
   164  			if paramsLen == 1 {
   165  				return fmt.Errorf("missing alignment value for field %q", ci.Name)
   166  			}
   167  			switch params[1] {
   168  			case "left":
   169  				ci.Alignment = AlignLeft
   170  			case "right":
   171  				ci.Alignment = AlignRight
   172  			default:
   173  				return fmt.Errorf("invalid alignment %q for field %q", params[1], ci.Name)
   174  			}
   175  		case "ellipsis":
   176  			if paramsLen == 1 {
   177  				ci.EllipsisType = ellipsis.End
   178  				continue
   179  			}
   180  			switch params[1] {
   181  			case "end", "":
   182  				ci.EllipsisType = ellipsis.End
   183  			case "middle":
   184  				ci.EllipsisType = ellipsis.Middle
   185  			case "none":
   186  				ci.EllipsisType = ellipsis.None
   187  			case "start":
   188  				ci.EllipsisType = ellipsis.Start
   189  			default:
   190  				return fmt.Errorf("invalid ellipsis value %q for field %q", params[1], ci.Name)
   191  			}
   192  		case "fixed":
   193  			if paramsLen != 1 {
   194  				return fmt.Errorf("parameter fixed on field %q must not have a value", ci.Name)
   195  			}
   196  			ci.FixedWidth = true
   197  		case "group":
   198  			if paramsLen == 1 {
   199  				return fmt.Errorf("missing group value for field %q", ci.Name)
   200  			}
   201  			switch params[1] {
   202  			case "sum":
   203  				if !ci.columnType.ConvertibleTo(reflect.TypeOf(int(0))) {
   204  					return fmt.Errorf("invalid use of sum on field %q of kind %q", ci.Name, ci.kind.String())
   205  				}
   206  				ci.GroupType = GroupTypeSum
   207  			default:
   208  				return fmt.Errorf("invalid group value %q for field %q", params[1], ci.Name)
   209  			}
   210  		case "hide":
   211  			if paramsLen != 1 {
   212  				return fmt.Errorf("parameter hide on field %q must not have a value", ci.Name)
   213  			}
   214  			ci.Visible = false
   215  		case "noembed":
   216  			if ci.Kind() != reflect.Struct && (ci.Kind() != reflect.Pointer || ci.Type().Elem().Kind() != reflect.Struct) {
   217  				return fmt.Errorf("parameter noembed on field %q is only valid for struct types", ci.Name)
   218  			}
   219  		case "order":
   220  			if paramsLen == 1 {
   221  				return fmt.Errorf("missing width value for field %q", ci.Name)
   222  			}
   223  			w, err := strconv.Atoi(params[1])
   224  			if err != nil {
   225  				return fmt.Errorf("invalid order value %q for field %q: %w", params[1], ci.Name, err)
   226  			}
   227  			ci.Order = w
   228  		case "precision":
   229  			if ci.kind != reflect.Float32 && ci.kind != reflect.Float64 {
   230  				return fmt.Errorf("field %q is not a float field and thereby cannot have precision defined", ci.Name)
   231  			}
   232  			if paramsLen == 1 {
   233  				return fmt.Errorf("missing precision value for field %q", ci.Name)
   234  			}
   235  			w, err := strconv.Atoi(params[1])
   236  			if err != nil {
   237  				return fmt.Errorf("invalid precision value %q for field %q: %w", params[1], ci.Name, err)
   238  			}
   239  			if w < -1 {
   240  				return fmt.Errorf("negative precision value %q for field %q", params[1], ci.Name)
   241  			}
   242  			ci.Precision = w
   243  		case "width":
   244  			ci.Width, err = ci.getWidth(params)
   245  			if err != nil {
   246  				return err
   247  			}
   248  		case "maxWidth":
   249  			ci.MaxWidth, err = ci.getWidth(params)
   250  			if err != nil {
   251  				return err
   252  			}
   253  		case "minWidth":
   254  			ci.MinWidth, err = ci.getWidth(params)
   255  			if err != nil {
   256  				return err
   257  			}
   258  		case "template":
   259  			ci.useTemplate = true
   260  			if paramsLen < 2 || params[1] == "" {
   261  				return fmt.Errorf("no template specified for field %q", ci.Name)
   262  			}
   263  			ci.Template = params[1]
   264  		case "stringer":
   265  			if ci.Extractor != nil {
   266  				break
   267  			}
   268  			stringer := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
   269  			if ci.Type().Implements(stringer) {
   270  				ci.Extractor = func(t *T) any {
   271  					return ci.getRawField(reflect.ValueOf(t)).Interface().(fmt.Stringer).String()
   272  				}
   273  				ci.kind = reflect.String
   274  				ci.columnType = stringType
   275  			} else {
   276  				return fmt.Errorf("column parameter %q set for field %q, but doesn't implement fmt.Stringer", params[0], ci.Name)
   277  			}
   278  		default:
   279  			return fmt.Errorf("invalid column parameter %q for field %q", params[0], ci.Name)
   280  		}
   281  	}
   282  	return nil
   283  }
   284  
   285  // Get returns the reflected value of an entry for the current column; if given nil, it will return the zero value of
   286  // the underlying type
   287  func (ci *Column[T]) Get(entry *T) reflect.Value {
   288  	if entry == nil {
   289  		return reflect.Zero(ci.Type())
   290  	}
   291  	v := reflect.ValueOf(entry)
   292  	if ci.Extractor != nil {
   293  		return reflect.ValueOf(ci.Extractor(v.Interface().(*T)))
   294  	}
   295  	return ci.getRawField(v)
   296  }
   297  
   298  func (ci *Column[T]) getOffset() uintptr {
   299  	return ci.offset
   300  }
   301  
   302  func (ci *Column[T]) getSubFields() []subField {
   303  	return ci.subFieldIndex
   304  }
   305  
   306  // GetRef returns the reflected value of an already reflected entry for the current column; expects v to be valid or
   307  // will panic
   308  func (ci *Column[T]) GetRef(v reflect.Value) reflect.Value {
   309  	if ci.Extractor != nil || ci.fieldIndex == manualIndex {
   310  		return reflect.ValueOf(ci.Extractor(v.Interface().(*T)))
   311  	}
   312  	return ci.getRawField(v)
   313  }
   314  
   315  // GetRaw returns the reflected value of an entry for the current column without evaluating the extractor func;
   316  // if given nil or run on a virtual or manually added column, it will return the zero value of the underlying type.
   317  // If using embedded structs via pointers and the embedded value is nil, it will also return the zero value of the
   318  // underlying type.
   319  func (ci *Column[T]) GetRaw(entry *T) reflect.Value {
   320  	if entry == nil || ci.fieldIndex == virtualIndex || ci.fieldIndex == manualIndex {
   321  		return reflect.Zero(ci.RawType())
   322  	}
   323  	v := reflect.ValueOf(entry)
   324  	return ci.getRawField(v)
   325  }
   326  
   327  func (ci *Column[T]) getRawField(v reflect.Value) reflect.Value {
   328  	if v.Kind() == reflect.Pointer {
   329  		v = v.Elem()
   330  	}
   331  	if len(ci.subFieldIndex) > 0 {
   332  		return ci.getFieldRec(v, ci.subFieldIndex)
   333  	}
   334  	return v.Field(ci.fieldIndex)
   335  }
   336  
   337  func (ci *Column[T]) getFieldRec(v reflect.Value, sub []subField) reflect.Value {
   338  	if sub[0].parentIsPtr {
   339  		if v.IsNil() {
   340  			// Return the (empty) default value for this type
   341  			return reflect.Zero(ci.Type())
   342  		}
   343  		v = v.Elem()
   344  	}
   345  	val := v.Field(sub[0].index)
   346  	if len(sub) == 1 {
   347  		return val
   348  	}
   349  	return ci.getFieldRec(val, sub[1:])
   350  }
   351  
   352  // Kind returns the underlying kind of the column (always reflect.String in case of virtual columns)
   353  func (ci *Column[T]) Kind() reflect.Kind {
   354  	return ci.kind
   355  }
   356  
   357  // Type returns the underlying type of the column
   358  // (reflect.String, if a custom extractor is used)
   359  func (ci *Column[T]) Type() reflect.Type {
   360  	return ci.columnType
   361  }
   362  
   363  // RawType returns the underlying type of the column
   364  func (ci *Column[T]) RawType() reflect.Type {
   365  	return ci.rawColumnType
   366  }
   367  
   368  func (ci *Column[T]) HasTag(tag string) bool {
   369  	for _, curTag := range ci.Tags {
   370  		if curTag == tag {
   371  			return true
   372  		}
   373  	}
   374  	return false
   375  }
   376  
   377  func (ci *Column[T]) HasNoTags() bool {
   378  	if len(ci.Tags) == 0 {
   379  		return true
   380  	}
   381  	return false
   382  }
   383  
   384  // IsEmbedded returns true, if the current column is a member of an embedded struct
   385  func (ci *Column[T]) IsEmbedded() bool {
   386  	if len(ci.subFieldIndex) == 0 {
   387  		return false
   388  	}
   389  	return true
   390  }
   391  
   392  // IsVirtual returns true, if the column has direct reference to a field
   393  func (ci *Column[T]) IsVirtual() bool {
   394  	return ci.fieldIndex == virtualIndex
   395  }
   396  
   397  // HasCustomExtractor returns true, if the column has a user defined extractor set
   398  func (ci *Column[T]) HasCustomExtractor() bool {
   399  	return ci.Extractor != nil
   400  }