github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/top/file/tracer/tracer.go (about)

     1  // Copyright 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  //go:build !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  	"unsafe"
    25  
    26  	"github.com/cilium/ebpf"
    27  	"github.com/cilium/ebpf/link"
    28  
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/top"
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/top/file/types"
    33  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    34  )
    35  
    36  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -type file_stat -type file_id -cc clang -cflags ${CFLAGS} filetop ./bpf/filetop.bpf.c --
    37  
    38  type Config struct {
    39  	MountnsMap *ebpf.Map
    40  	TargetPid  int
    41  	AllFiles   bool
    42  	MaxRows    int
    43  	Interval   time.Duration
    44  	Iterations int
    45  	SortBy     []string
    46  }
    47  
    48  type Tracer struct {
    49  	config        *Config
    50  	objs          filetopObjects
    51  	readLink      link.Link
    52  	writeLink     link.Link
    53  	enricher      gadgets.DataEnricherByMntNs
    54  	eventCallback func(*top.Event[types.Stats])
    55  	done          chan bool
    56  	colMap        columns.ColumnMap[types.Stats]
    57  }
    58  
    59  func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,
    60  	eventCallback func(*top.Event[types.Stats]),
    61  ) (*Tracer, error) {
    62  	t := &Tracer{
    63  		config:        config,
    64  		enricher:      enricher,
    65  		eventCallback: eventCallback,
    66  		done:          make(chan bool),
    67  	}
    68  
    69  	if err := t.install(); err != nil {
    70  		t.close()
    71  		return nil, err
    72  	}
    73  
    74  	statCols, err := columns.NewColumns[types.Stats]()
    75  	if err != nil {
    76  		t.close()
    77  		return nil, err
    78  	}
    79  	t.colMap = statCols.GetColumnMap()
    80  
    81  	go t.run(context.TODO())
    82  
    83  	return t, nil
    84  }
    85  
    86  // Stop stops the tracer
    87  // TODO: Remove after refactoring
    88  func (t *Tracer) Stop() {
    89  	t.close()
    90  }
    91  
    92  func (t *Tracer) close() {
    93  	close(t.done)
    94  
    95  	t.readLink = gadgets.CloseLink(t.readLink)
    96  	t.writeLink = gadgets.CloseLink(t.writeLink)
    97  
    98  	t.objs.Close()
    99  }
   100  
   101  func (t *Tracer) install() error {
   102  	spec, err := loadFiletop()
   103  	if err != nil {
   104  		return fmt.Errorf("loading ebpf program: %w", err)
   105  	}
   106  
   107  	consts := map[string]interface{}{
   108  		"target_pid":        uint32(t.config.TargetPid),
   109  		"regular_file_only": !t.config.AllFiles,
   110  	}
   111  
   112  	if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {
   113  		return fmt.Errorf("loading ebpf spec: %w", err)
   114  	}
   115  
   116  	kpread, err := link.Kprobe("vfs_read", t.objs.IgTopfileRdE, nil)
   117  	if err != nil {
   118  		return fmt.Errorf("attaching kprobe: %w", err)
   119  	}
   120  	t.readLink = kpread
   121  
   122  	kpwrite, err := link.Kprobe("vfs_write", t.objs.IgTopfileWrE, nil)
   123  	if err != nil {
   124  		return fmt.Errorf("attaching kprobe: %w", err)
   125  	}
   126  	t.writeLink = kpwrite
   127  
   128  	return nil
   129  }
   130  
   131  func (t *Tracer) nextStats() ([]*types.Stats, error) {
   132  	stats := []*types.Stats{}
   133  
   134  	var prev *filetopFileId = nil
   135  	key := filetopFileId{}
   136  	entries := t.objs.Entries
   137  
   138  	defer func() {
   139  		// delete elements
   140  		err := entries.NextKey(nil, unsafe.Pointer(&key))
   141  		if err != nil {
   142  			return
   143  		}
   144  
   145  		for {
   146  			if err := entries.Delete(key); err != nil {
   147  				return
   148  			}
   149  
   150  			prev = &key
   151  			if err := entries.NextKey(unsafe.Pointer(prev), unsafe.Pointer(&key)); err != nil {
   152  				return
   153  			}
   154  		}
   155  	}()
   156  
   157  	// gather elements
   158  	err := entries.NextKey(nil, unsafe.Pointer(&key))
   159  	if err != nil {
   160  		if errors.Is(err, ebpf.ErrKeyNotExist) {
   161  			return stats, nil
   162  		}
   163  		return nil, fmt.Errorf("getting next key: %w", err)
   164  	}
   165  
   166  	for {
   167  		fileStat := filetopFileStat{}
   168  		if err := entries.Lookup(key, unsafe.Pointer(&fileStat)); err != nil {
   169  			return nil, err
   170  		}
   171  
   172  		stat := types.Stats{
   173  			Reads:         fileStat.Reads,
   174  			Writes:        fileStat.Writes,
   175  			ReadBytes:     fileStat.ReadBytes,
   176  			WriteBytes:    fileStat.WriteBytes,
   177  			Pid:           fileStat.Pid,
   178  			Tid:           fileStat.Tid,
   179  			Filename:      gadgets.FromCString(fileStat.Filename[:]),
   180  			Comm:          gadgets.FromCString(fileStat.Comm[:]),
   181  			FileType:      byte(fileStat.Type),
   182  			WithMountNsID: eventtypes.WithMountNsID{MountNsID: fileStat.MntnsId},
   183  		}
   184  
   185  		if t.enricher != nil {
   186  			t.enricher.EnrichByMntNs(&stat.CommonData, stat.MountNsID)
   187  		}
   188  
   189  		stats = append(stats, &stat)
   190  
   191  		prev = &key
   192  		if err := entries.NextKey(unsafe.Pointer(prev), unsafe.Pointer(&key)); err != nil {
   193  			if errors.Is(err, ebpf.ErrKeyNotExist) {
   194  				break
   195  			}
   196  			return nil, fmt.Errorf("getting next key: %w", err)
   197  		}
   198  	}
   199  
   200  	top.SortStats(stats, t.config.SortBy, &t.colMap)
   201  
   202  	return stats, nil
   203  }
   204  
   205  func (t *Tracer) run(ctx context.Context) error {
   206  	// Don't use a context with a timeout but a counter to avoid having to deal
   207  	// with two timers: one for the timeout and another for the ticker.
   208  	count := t.config.Iterations
   209  	ticker := time.NewTicker(t.config.Interval)
   210  	defer ticker.Stop()
   211  
   212  	for {
   213  		select {
   214  		case <-t.done:
   215  			// TODO: Once we completely move to use Run instead of NewTracer,
   216  			// we can remove this as nobody will directly call Stop (cleanup).
   217  			return nil
   218  		case <-ctx.Done():
   219  			return nil
   220  		case <-ticker.C:
   221  			stats, err := t.nextStats()
   222  			if err != nil {
   223  				return fmt.Errorf("getting next stats: %w", err)
   224  			}
   225  
   226  			n := len(stats)
   227  			if n > t.config.MaxRows {
   228  				n = t.config.MaxRows
   229  			}
   230  			t.eventCallback(&top.Event[types.Stats]{Stats: stats[:n]})
   231  
   232  			// Count down only if user requested a finite number of iterations
   233  			// through a timeout.
   234  			if t.config.Iterations > 0 {
   235  				count--
   236  				if count == 0 {
   237  					return nil
   238  				}
   239  			}
   240  		}
   241  	}
   242  }
   243  
   244  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   245  	if err := t.init(gadgetCtx); err != nil {
   246  		return fmt.Errorf("initializing tracer: %w", err)
   247  	}
   248  
   249  	defer t.close()
   250  	if err := t.install(); err != nil {
   251  		return fmt.Errorf("installing tracer: %w", err)
   252  	}
   253  
   254  	return t.run(gadgetCtx.Context())
   255  }
   256  
   257  func (t *Tracer) SetEventHandlerArray(handler any) {
   258  	nh, ok := handler.(func(ev []*types.Stats))
   259  	if !ok {
   260  		panic("event handler invalid")
   261  	}
   262  
   263  	// TODO: add errorHandler
   264  	t.eventCallback = func(ev *top.Event[types.Stats]) {
   265  		if ev.Error != "" {
   266  			return
   267  		}
   268  		nh(ev.Stats)
   269  	}
   270  }
   271  
   272  func (t *Tracer) SetMountNsMap(mntnsMap *ebpf.Map) {
   273  	t.config.MountnsMap = mntnsMap
   274  }
   275  
   276  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   277  	tracer := &Tracer{
   278  		config: &Config{},
   279  		done:   make(chan bool),
   280  	}
   281  	return tracer, nil
   282  }
   283  
   284  func (t *Tracer) init(gadgetCtx gadgets.GadgetContext) error {
   285  	params := gadgetCtx.GadgetParams()
   286  	t.config.MaxRows = params.Get(gadgets.ParamMaxRows).AsInt()
   287  	t.config.SortBy = params.Get(gadgets.ParamSortBy).AsStringSlice()
   288  	t.config.Interval = time.Second * time.Duration(params.Get(gadgets.ParamInterval).AsInt())
   289  	t.config.AllFiles = params.Get(types.AllFilesParam).AsBool()
   290  
   291  	var err error
   292  	if t.config.Iterations, err = top.ComputeIterations(t.config.Interval, gadgetCtx.Timeout()); err != nil {
   293  		return err
   294  	}
   295  
   296  	statCols, err := columns.NewColumns[types.Stats]()
   297  	if err != nil {
   298  		return err
   299  	}
   300  	t.colMap = statCols.GetColumnMap()
   301  
   302  	return nil
   303  }