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  }