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 }