github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/parser/parser.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 /* 16 Package parser wraps a couple of helper libraries with the intention of hiding 17 type information and simplifying data handling outside the gadgets. It can be used to 18 wire the events of gadgets directly to the column formatter and use generic operations 19 like filtering and sorting on them. 20 */ 21 package parser 22 23 import ( 24 "context" 25 "encoding/json" 26 "fmt" 27 "reflect" 28 "sync" 29 "time" 30 31 "go.opentelemetry.io/otel/attribute" 32 33 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns" 34 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/filter" 35 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/formatter/textcolumns" 36 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/sort" 37 "github.com/inspektor-gadget/inspektor-gadget/pkg/logger" 38 "github.com/inspektor-gadget/inspektor-gadget/pkg/snapshotcombiner" 39 ) 40 41 type LogCallback func(severity logger.Level, fmt string, params ...any) 42 43 type GaugeVal struct { 44 Attrs []attribute.KeyValue 45 Int64Val int64 46 Float64Val float64 47 } 48 49 // Parser is the (untyped) interface used for parser 50 type Parser interface { 51 // GetTextColumnsFormatter returns the default formatter for this columns instance 52 GetTextColumnsFormatter(options ...textcolumns.Option) TextColumnsFormatter 53 54 // GetColumnAttributes returns a map of column names to their respective attributes 55 GetColumnAttributes() []columns.Attributes 56 57 // GetDefaultColumns returns a list of columns that are visible by default; optionally, hiddenTags will 58 // hide columns that contain any of the given tags 59 GetDefaultColumns(hiddenTags ...string) []string 60 61 // GetColumns returns the underlying columns definition (mainly used for serialization) 62 GetColumns() any 63 64 // VerifyColumnNames takes a list of column names and returns two lists, one containing the 65 // valid column names and another containing the invalid column names. Prefixes like "-" for 66 // descending sorting will be ignored. 67 VerifyColumnNames(columnNames []string) (valid []string, invalid []string) 68 69 // SetColumnFilters sets additional column filters that will be used whenever one of the other methods of this 70 // interface are called. This is for example used to filter columns with information on kubernetes in a non-k8s 71 // environment like ig 72 SetColumnFilters(...columns.ColumnFilter) 73 74 // SetSorting sets what sorting should be applied when calling SortEntries() // TODO 75 SetSorting([]string) error 76 77 // SetFilters sets which filter to apply before emitting events downstream 78 SetFilters([]string) error 79 80 // EventHandlerFunc returns a function that accepts an instance of type *T and pushes it downstream after applying 81 // enrichers and filters 82 EventHandlerFunc(enrichers ...func(any) error) any 83 EventHandlerFuncArray(enrichers ...func(any) error) any 84 85 // JSONHandlerFunc returns a function that accepts a JSON encoded event, unmarshal it into *T and pushes it 86 // downstream after applying enrichers and filters 87 JSONHandlerFunc(enrichers ...func(any) error) func([]byte) 88 JSONHandlerFuncArray(key string, enrichers ...func(any) error) func([]byte) 89 90 // SetEventCallback sets the downstream callback 91 SetEventCallback(eventCallback any) 92 93 // SetLogCallback sets the function to use to send log messages 94 SetLogCallback(logCallback LogCallback) 95 96 // EnableSnapshots initializes the snapshot combiner, which is able to aggregate snapshots from several sources 97 // and can return (optionally cached) results on demand; used for top gadgets 98 EnableSnapshots(ctx context.Context, t time.Duration, ttl int) 99 100 // EnableCombiner initializes the event combiner, which aggregates events from all sources; used for snapshot gadgets. 101 // Events are released by calling Flush(). 102 EnableCombiner() 103 104 // Flush sends the events downstream that were collected after EnableCombiner() was called. 105 Flush() 106 107 // Things related to Prometheus. TODO: move to a separate interface / file? 108 109 // AttrsGetter returns a function that accepts an instance of type *T and returns a list of 110 // attributes for cols. 111 AttrsGetter(cols []string) (func(any) []attribute.KeyValue, error) 112 113 // AggregateEntries receives an array of *T and aggregates them according to cols. 114 AggregateEntries(cols []string, entries any, field string, isInt bool) (map[string]*GaugeVal, error) 115 116 // GetColKind returns the reflect.Kind of the column with the given name 117 GetColKind(colName string) (reflect.Kind, error) 118 119 // ColIntGetter returns a function that accepts an instance of type *T and returns the value 120 // of the column as an int64. 121 ColIntGetter(colName string) (func(any) int64, error) 122 123 // ColFloatGetter returns a function that accepts an instance of type *T and returns the 124 // value of the column as an float64. 125 ColFloatGetter(colName string) (func(any) float64, error) 126 } 127 128 type parser[T any] struct { 129 columns *columns.Columns[T] 130 sortBy []string 131 sortSpec *sort.ColumnSorterCollection[T] 132 filters []string 133 filterSpecs *filter.FilterSpecs[T] // TODO: filter collection(!) 134 eventCallback func(*T) 135 eventCallbackArray func([]*T) 136 logCallback LogCallback 137 snapshotCombiner *snapshotcombiner.SnapshotCombiner[T] 138 columnFilters []columns.ColumnFilter 139 140 // event combiner related fields 141 eventCombinerEnabled bool 142 combinedEvents []*T 143 mu sync.Mutex 144 } 145 146 func NewParser[T any](columns *columns.Columns[T]) Parser { 147 p := &parser[T]{ 148 columns: columns, 149 } 150 return p 151 } 152 153 func (p *parser[T]) EnableSnapshots(ctx context.Context, interval time.Duration, ttl int) { 154 if p.eventCallbackArray == nil { 155 panic("EnableSnapshots needs EventCallbackArray set") 156 } 157 p.snapshotCombiner = snapshotcombiner.NewSnapshotCombiner[T](ttl) 158 go func() { 159 ticker := time.NewTicker(interval) 160 for { 161 select { 162 case <-ticker.C: 163 p.flushSnapshotCombiner() 164 case <-ctx.Done(): 165 return 166 } 167 } 168 }() 169 } 170 171 func (p *parser[T]) flushSnapshotCombiner() { 172 if p.snapshotCombiner == nil { 173 panic("snapshotCombiner is not initialized") 174 } 175 out, _ := p.snapshotCombiner.GetSnapshots() 176 if p.sortSpec != nil { 177 p.sortSpec.Sort(out) 178 } 179 p.eventCallbackArray(out) 180 } 181 182 func (p *parser[T]) EnableCombiner() { 183 if p.eventCallbackArray == nil { 184 panic("eventCallbackArray has to be set before using EnableCombiner()") 185 } 186 187 p.eventCombinerEnabled = true 188 p.combinedEvents = []*T{} 189 } 190 191 func (p *parser[T]) Flush() { 192 if p.snapshotCombiner != nil { 193 p.flushSnapshotCombiner() 194 return 195 } 196 if p.sortSpec != nil { 197 p.sortSpec.Sort(p.combinedEvents) 198 } 199 p.eventCallbackArray(p.combinedEvents) 200 } 201 202 func (p *parser[T]) SetColumnFilters(filters ...columns.ColumnFilter) { 203 p.columnFilters = filters 204 } 205 206 func (p *parser[T]) SetLogCallback(logCallback LogCallback) { 207 p.logCallback = logCallback 208 } 209 210 func (p *parser[T]) SetEventCallback(eventCallback any) { 211 switch cb := eventCallback.(type) { 212 case func(*T): 213 // Typed, can be used as eventCallback directly 214 p.eventCallback = cb 215 case func([]*T): 216 // Typed array, can be used as eventCallback directly 217 p.eventCallbackArray = cb 218 case func(any): 219 // Generic callback function (e.g. to print JSON) 220 p.eventCallback = func(ev *T) { 221 cb(ev) 222 } 223 p.eventCallbackArray = func(ev []*T) { 224 cb(ev) 225 } 226 default: 227 panic("cannot use event callback for parser") 228 } 229 } 230 231 func (p *parser[T]) eventHandler(cb func(*T), enrichers ...func(any) error) func(*T) { 232 if cb == nil { 233 panic("cb can't be nil in eventHandler from parser") 234 } 235 return func(ev *T) { 236 for _, enricher := range enrichers { 237 enricher(ev) 238 } 239 if p.filterSpecs != nil && !p.filterSpecs.MatchAll(ev) { 240 return 241 } 242 cb(ev) 243 } 244 } 245 246 func (p *parser[T]) eventHandlerArray(cb func([]*T), enrichers ...func(any) error) func([]*T) { 247 if cb == nil { 248 panic("cb can't be nil in eventHandlerArray from parser") 249 } 250 return func(events []*T) { 251 for _, enricher := range enrichers { 252 for _, ev := range events { 253 enricher(ev) 254 } 255 } 256 if p.filterSpecs != nil { 257 filteredEvents := make([]*T, 0, len(events)) 258 for _, event := range events { 259 if !p.filterSpecs.MatchAll(event) { 260 continue 261 } 262 filteredEvents = append(filteredEvents, event) 263 } 264 events = filteredEvents 265 } 266 if p.sortSpec != nil { 267 p.sortSpec.Sort(events) 268 } 269 cb(events) 270 } 271 } 272 273 func (p *parser[T]) writeLogMessage(severity logger.Level, fmt string, params ...any) { 274 if p.logCallback == nil { 275 return 276 } 277 p.logCallback(severity, fmt, params...) 278 } 279 280 func (p *parser[T]) combineEventsArrayCallback(events []*T) { 281 p.mu.Lock() 282 defer p.mu.Unlock() 283 284 p.combinedEvents = append(p.combinedEvents, events...) 285 } 286 287 func (p *parser[T]) combineEventsCallback(event *T) { 288 p.mu.Lock() 289 defer p.mu.Unlock() 290 291 p.combinedEvents = append(p.combinedEvents, event) 292 } 293 294 func (p *parser[T]) JSONHandlerFunc(enrichers ...func(any) error) func([]byte) { 295 cb := p.eventCallback 296 if p.eventCombinerEnabled { 297 cb = p.combineEventsCallback 298 } 299 300 handler := p.eventHandler(cb, enrichers...) 301 return func(event []byte) { 302 ev := new(T) 303 err := json.Unmarshal(event, ev) 304 if err != nil { 305 p.writeLogMessage(logger.WarnLevel, "unmarshalling: %s", err) 306 return 307 } 308 handler(ev) 309 } 310 } 311 312 func (p *parser[T]) JSONHandlerFuncArray(key string, enrichers ...func(any) error) func([]byte) { 313 cb := p.eventCallbackArray 314 if p.eventCombinerEnabled { 315 cb = p.combineEventsArrayCallback 316 } else if p.snapshotCombiner != nil { 317 cb = func(events []*T) { 318 p.snapshotCombiner.AddSnapshot(key, events) 319 } 320 } 321 322 handler := p.eventHandlerArray(cb, enrichers...) 323 324 return func(event []byte) { 325 var ev []*T 326 err := json.Unmarshal(event, &ev) 327 if err != nil { 328 p.writeLogMessage(logger.WarnLevel, "unmarshalling: %s", err) 329 return 330 } 331 handler(ev) 332 } 333 } 334 335 func (p *parser[T]) EventHandlerFunc(enrichers ...func(any) error) any { 336 return p.eventHandler(p.eventCallback, enrichers...) 337 } 338 339 func (p *parser[T]) EventHandlerFuncArray(enrichers ...func(any) error) any { 340 return p.eventHandlerArray(p.eventCallbackArray, enrichers...) 341 } 342 343 func (p *parser[T]) GetTextColumnsFormatter(options ...textcolumns.Option) TextColumnsFormatter { 344 return &outputHelper[T]{ 345 parser: p, 346 TextColumnsFormatter: textcolumns.NewFormatter(p.columns.GetColumnMap(p.columnFilters...), options...), 347 } 348 } 349 350 func (p *parser[T]) GetColumnAttributes() []columns.Attributes { 351 out := make([]columns.Attributes, 0) 352 for _, column := range p.columns.GetOrderedColumns(p.columnFilters...) { 353 out = append(out, column.Attributes) 354 } 355 return out 356 } 357 358 func (p *parser[T]) GetColumns() any { 359 return p.columns.GetColumnMap(p.columnFilters...) 360 } 361 362 func (p *parser[T]) GetDefaultColumns(hiddenTags ...string) []string { 363 cols := make([]string, 0) 364 columnLoop: 365 for _, column := range p.columns.GetOrderedColumns(p.columnFilters...) { 366 if !column.Visible { 367 continue 368 } 369 for _, tag := range hiddenTags { 370 if column.HasTag(tag) { 371 continue columnLoop 372 } 373 } 374 cols = append(cols, column.Name) 375 } 376 return cols 377 } 378 379 func (p *parser[T]) VerifyColumnNames(columnNames []string) (valid []string, invalid []string) { 380 return p.columns.VerifyColumnNames(columnNames) 381 } 382 383 func (p *parser[T]) SetSorting(sortBy []string) error { 384 _, invalid := p.columns.VerifyColumnNames(sortBy) 385 if len(invalid) > 0 { 386 return fmt.Errorf("invalid columns to sort by: %v", invalid) 387 } 388 p.sortSpec = sort.Prepare(p.columns.ColumnMap, sortBy) 389 p.sortBy = sortBy 390 return nil 391 } 392 393 func (p *parser[T]) SetFilters(filters []string) error { 394 if len(filters) == 0 { 395 return nil 396 } 397 398 filterSpecs, err := filter.GetFiltersFromStrings(p.columns.ColumnMap, filters) 399 if err != nil { 400 return err 401 } 402 403 p.filters = filters 404 p.filterSpecs = filterSpecs 405 return nil 406 } 407 408 // Prometheus related stuff 409 410 func (p *parser[T]) AttrsGetter(colNames []string) (func(any) []attribute.KeyValue, error) { 411 columnMap := p.columns.GetColumnMap() 412 413 keys := []attribute.Key{} 414 getters := []func(*T) attribute.Value{} 415 416 for _, colName := range colNames { 417 col, ok := columnMap.GetColumn(colName) 418 if !ok { 419 return nil, fmt.Errorf("unknown column: %s", colName) 420 } 421 422 var getter func(*T) attribute.Value 423 424 switch col.Kind() { 425 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 426 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 427 f := columns.GetFieldAsNumberFunc[int64, T](col) 428 getter = func(a *T) attribute.Value { 429 return attribute.Int64Value(f(a)) 430 } 431 case reflect.Float32, reflect.Float64: 432 f := columns.GetFieldAsNumberFunc[float64, T](col) 433 getter = func(a *T) attribute.Value { 434 return attribute.Float64Value(f(a)) 435 } 436 case reflect.String: 437 getter = func(a *T) attribute.Value { 438 ff := columns.GetFieldFunc[string, T](col) 439 return attribute.StringValue(ff(a)) 440 } 441 case reflect.Bool: 442 getter = func(a *T) attribute.Value { 443 ff := columns.GetFieldFunc[bool, T](col) 444 return attribute.BoolValue(ff(a)) 445 } 446 default: 447 return nil, fmt.Errorf("unsupported column type: %s", col.Kind()) 448 } 449 450 keys = append(keys, attribute.Key(col.Name)) 451 getters = append(getters, getter) 452 } 453 454 return func(ev any) []attribute.KeyValue { 455 attrs := []attribute.KeyValue{} 456 457 for i, key := range keys { 458 attr := attribute.KeyValue{ 459 Key: key, 460 Value: getters[i](ev.(*T)), 461 } 462 attrs = append(attrs, attr) 463 } 464 465 return attrs 466 }, nil 467 } 468 469 func attrsToString(kvs []attribute.KeyValue) string { 470 ret := "" 471 for _, kv := range kvs { 472 ret += fmt.Sprintf("%s=%s,", kv.Key, kv.Value.Emit()) 473 } 474 475 return ret 476 } 477 478 func (p *parser[T]) AggregateEntries(cols []string, entries any, field string, isInt bool) (map[string]*GaugeVal, error) { 479 gauges := make(map[string]*GaugeVal) 480 attrsGetter, err := p.AttrsGetter(cols) 481 if err != nil { 482 return nil, err 483 } 484 485 intFieldGetter := func(any) int64 { 486 return 1 487 } 488 489 floatFieldGetter := func(any) float64 { 490 return 1.0 491 } 492 493 if field != "" { 494 if isInt { 495 intFieldGetter, err = p.ColIntGetter(field) 496 if err != nil { 497 return nil, err 498 } 499 } else { 500 floatFieldGetter, err = p.ColFloatGetter(field) 501 if err != nil { 502 return nil, err 503 } 504 } 505 } 506 507 for _, entry := range entries.([]*T) { 508 attrs := attrsGetter(entry) 509 key := attrsToString(attrs) 510 gauge, ok := gauges[key] 511 if !ok { 512 gauge = &GaugeVal{ 513 Attrs: attrs, 514 } 515 gauges[key] = gauge 516 } 517 if isInt { 518 gauge.Int64Val += intFieldGetter(entry) 519 } else { 520 gauge.Float64Val += floatFieldGetter(entry) 521 } 522 } 523 524 return gauges, nil 525 } 526 527 func (p *parser[T]) GetColKind(colName string) (reflect.Kind, error) { 528 columnMap := p.columns.GetColumnMap() 529 530 col, ok := columnMap.GetColumn(colName) 531 if !ok { 532 return reflect.Invalid, fmt.Errorf("colunm %s not found", colName) 533 } 534 535 if col.HasCustomExtractor() { 536 return col.RawType().Kind(), nil 537 } 538 539 return col.Kind(), nil 540 } 541 542 func (p *parser[T]) ColIntGetter(colName string) (func(any) int64, error) { 543 columnMap := p.columns.GetColumnMap() 544 545 col, ok := columnMap.GetColumn(colName) 546 if !ok { 547 return nil, fmt.Errorf("column %s not found", colName) 548 } 549 550 // TODO: Handle all int types? 551 if col.HasCustomExtractor() && col.RawType().Kind() == reflect.Int64 { 552 f := columns.GetFieldFuncExt[int64, T](col, true) 553 return func(a any) int64 { 554 return f(a.(*T)) 555 }, nil 556 } 557 558 f := columns.GetFieldAsNumberFunc[int64, T](col) 559 560 return func(a any) int64 { 561 return f(a.(*T)) 562 }, nil 563 } 564 565 func (p *parser[T]) ColFloatGetter(colName string) (func(any) float64, error) { 566 columnMap := p.columns.GetColumnMap() 567 568 col, ok := columnMap.GetColumn(colName) 569 if !ok { 570 return nil, fmt.Errorf("colunm %s not found", colName) 571 } 572 573 // TODO: Handle all float types? 574 if col.HasCustomExtractor() && col.RawType().Kind() == reflect.Float64 { 575 f := columns.GetFieldFuncExt[float64, T](col, true) 576 return func(a any) float64 { 577 return f(a.(*T)) 578 }, nil 579 } 580 581 f := columns.GetFieldAsNumberFunc[float64, T](col) 582 583 return func(a any) float64 { 584 return f(a.(*T)) 585 }, nil 586 }