github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/trace/fsslower/tracer/tracer.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  //go:build !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"unsafe"
    24  
    25  	"github.com/cilium/ebpf"
    26  	"github.com/cilium/ebpf/link"
    27  	"github.com/cilium/ebpf/perf"
    28  
    29  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/fsslower/types"
    32  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    33  )
    34  
    35  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -no-global-types -target $TARGET -cc clang -cflags ${CFLAGS} -type event fsslower ./bpf/fsslower.bpf.c -- -I./bpf/
    36  type Config struct {
    37  	MountnsMap *ebpf.Map
    38  
    39  	Filesystem string
    40  	MinLatency uint
    41  }
    42  
    43  type Tracer struct {
    44  	config        *Config
    45  	enricher      gadgets.DataEnricherByMntNs
    46  	eventCallback func(*types.Event)
    47  
    48  	objs            fsslowerObjects
    49  	readEnterLink   link.Link
    50  	readExitLink    link.Link
    51  	writeEnterLink  link.Link
    52  	writeExitLink   link.Link
    53  	openEnterLink   link.Link
    54  	openExitLink    link.Link
    55  	syncEnterLink   link.Link
    56  	syncExitLink    link.Link
    57  	statfsEnterLink link.Link
    58  	statfsExitLink  link.Link
    59  	reader          *perf.Reader
    60  }
    61  
    62  type fsConf struct {
    63  	read   string
    64  	write  string
    65  	open   string
    66  	fsync  string
    67  	statfs string
    68  }
    69  
    70  var fsConfMap = map[string]fsConf{
    71  	"btrfs": {
    72  		read:   "btrfs_file_read_iter",
    73  		write:  "btrfs_file_write_iter",
    74  		open:   "btrfs_file_open",
    75  		fsync:  "btrfs_sync_file",
    76  		statfs: "btrfs_statfs",
    77  	},
    78  	"ext4": {
    79  		read:   "ext4_file_read_iter",
    80  		write:  "ext4_file_write_iter",
    81  		open:   "ext4_file_open",
    82  		fsync:  "ext4_sync_file",
    83  		statfs: "ext4_statfs",
    84  	},
    85  	"fuse": {
    86  		read:  "fuse_file_read_iter",
    87  		write: "fuse_file_write_iter",
    88  		open:  "fuse_open",
    89  		fsync: "fuse_fsync",
    90  	},
    91  	"nfs": {
    92  		read:   "nfs_file_read",
    93  		write:  "nfs_file_write",
    94  		open:   "nfs_file_open",
    95  		fsync:  "nfs_file_fsync",
    96  		statfs: "nfs_statfs",
    97  	},
    98  	"ntfs3": {
    99  		read:  "ntfs_file_read_iter",
   100  		write: "ntfs_file_write_iter",
   101  		open:  "ntfs_file_open",
   102  		fsync: "generic_file_fsync",
   103  	},
   104  	"xfs": {
   105  		read:   "xfs_file_read_iter",
   106  		write:  "xfs_file_write_iter",
   107  		open:   "xfs_file_open",
   108  		fsync:  "xfs_file_fsync",
   109  		statfs: "xfs_fs_statfs",
   110  	},
   111  }
   112  
   113  func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,
   114  	eventCallback func(*types.Event),
   115  ) (*Tracer, error) {
   116  	t := &Tracer{
   117  		config:        config,
   118  		enricher:      enricher,
   119  		eventCallback: eventCallback,
   120  	}
   121  
   122  	if err := t.install(); err != nil {
   123  		t.close()
   124  		return nil, err
   125  	}
   126  
   127  	go t.run()
   128  
   129  	return t, nil
   130  }
   131  
   132  // Stop stops the tracer
   133  // TODO: Remove after refactoring
   134  func (t *Tracer) Stop() {
   135  	t.close()
   136  }
   137  
   138  func (t *Tracer) close() {
   139  	// read
   140  	t.readEnterLink = gadgets.CloseLink(t.readEnterLink)
   141  	t.readExitLink = gadgets.CloseLink(t.readExitLink)
   142  
   143  	// write
   144  	t.writeEnterLink = gadgets.CloseLink(t.writeEnterLink)
   145  	t.writeExitLink = gadgets.CloseLink(t.writeExitLink)
   146  
   147  	// open
   148  	t.openEnterLink = gadgets.CloseLink(t.openEnterLink)
   149  	t.openExitLink = gadgets.CloseLink(t.openExitLink)
   150  
   151  	// sync
   152  	t.syncEnterLink = gadgets.CloseLink(t.syncEnterLink)
   153  	t.syncExitLink = gadgets.CloseLink(t.syncExitLink)
   154  
   155  	// statfs
   156  	t.statfsEnterLink = gadgets.CloseLink(t.statfsEnterLink)
   157  	t.statfsExitLink = gadgets.CloseLink(t.statfsExitLink)
   158  
   159  	if t.reader != nil {
   160  		t.reader.Close()
   161  	}
   162  
   163  	t.objs.Close()
   164  }
   165  
   166  func (t *Tracer) install() error {
   167  	var err error
   168  
   169  	spec, err := loadFsslower()
   170  	if err != nil {
   171  		return fmt.Errorf("loading ebpf program: %w", err)
   172  	}
   173  
   174  	consts := map[string]interface{}{
   175  		"min_lat_ns": uint64(t.config.MinLatency * 1000 * 1000),
   176  	}
   177  
   178  	if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {
   179  		return fmt.Errorf("loading ebpf spec: %w", err)
   180  	}
   181  
   182  	// choose a configuration based on the filesystem type passed
   183  	fsConf, ok := fsConfMap[t.config.Filesystem]
   184  	if !ok {
   185  		return fmt.Errorf("%q is not a supported filesystem", t.config.Filesystem)
   186  	}
   187  
   188  	// read
   189  	t.readEnterLink, err = link.Kprobe(fsConf.read, t.objs.IgFsslReadE, nil)
   190  	if err != nil {
   191  		return fmt.Errorf("attaching kprobe: %w", err)
   192  	}
   193  	t.readExitLink, err = link.Kretprobe(fsConf.read, t.objs.IgFsslReadX, nil)
   194  	if err != nil {
   195  		return fmt.Errorf("attaching kretprobe: %w", err)
   196  	}
   197  
   198  	// write
   199  	t.writeEnterLink, err = link.Kprobe(fsConf.write, t.objs.IgFsslWrE, nil)
   200  	if err != nil {
   201  		return fmt.Errorf("attaching kprobe: %w", err)
   202  	}
   203  	t.writeExitLink, err = link.Kretprobe(fsConf.write, t.objs.IgFsslWrX, nil)
   204  	if err != nil {
   205  		return fmt.Errorf("attaching kretprobe: %w", err)
   206  	}
   207  
   208  	// open
   209  	t.openEnterLink, err = link.Kprobe(fsConf.open, t.objs.IgFsslOpenE, nil)
   210  	if err != nil {
   211  		return fmt.Errorf("attaching kprobe: %w", err)
   212  	}
   213  	t.openExitLink, err = link.Kretprobe(fsConf.open, t.objs.IgFsslOpenX, nil)
   214  	if err != nil {
   215  		return fmt.Errorf("attaching kretprobe: %w", err)
   216  	}
   217  
   218  	// sync
   219  	t.syncEnterLink, err = link.Kprobe(fsConf.fsync, t.objs.IgFsslSyncE, nil)
   220  	if err != nil {
   221  		return fmt.Errorf("attaching kprobe: %w", err)
   222  	}
   223  	t.syncExitLink, err = link.Kretprobe(fsConf.fsync, t.objs.IgFsslSyncX, nil)
   224  	if err != nil {
   225  		return fmt.Errorf("attaching kretprobe: %w", err)
   226  	}
   227  
   228  	// statfs
   229  	t.statfsEnterLink, err = link.Kprobe(fsConf.statfs, t.objs.IgFsslStatfsE, nil)
   230  	if err != nil {
   231  		return fmt.Errorf("attaching kprobe: %w", err)
   232  	}
   233  	t.statfsExitLink, err = link.Kretprobe(fsConf.statfs, t.objs.IgFsslStatfsX, nil)
   234  	if err != nil {
   235  		return fmt.Errorf("attaching kretprobe: %w", err)
   236  	}
   237  
   238  	t.reader, err = perf.NewReader(t.objs.fsslowerMaps.Events, gadgets.PerfBufferPages*os.Getpagesize())
   239  	if err != nil {
   240  		return fmt.Errorf("creating perf ring buffer: %w", err)
   241  	}
   242  	return nil
   243  }
   244  
   245  var ops = []string{"R", "W", "O", "F", "S"}
   246  
   247  func (t *Tracer) run() {
   248  	for {
   249  		record, err := t.reader.Read()
   250  		if err != nil {
   251  			if errors.Is(err, perf.ErrClosed) {
   252  				// nothing to do, we're done
   253  				return
   254  			}
   255  
   256  			msg := fmt.Sprintf("Error reading perf ring buffer: %s", err)
   257  			t.eventCallback(types.Base(eventtypes.Err(msg)))
   258  			return
   259  		}
   260  
   261  		if record.LostSamples > 0 {
   262  			msg := fmt.Sprintf("lost %d samples", record.LostSamples)
   263  			t.eventCallback(types.Base(eventtypes.Warn(msg)))
   264  			continue
   265  		}
   266  
   267  		bpfEvent := (*fsslowerEvent)(unsafe.Pointer(&record.RawSample[0]))
   268  
   269  		event := types.Event{
   270  			Event: eventtypes.Event{
   271  				Type:      eventtypes.NORMAL,
   272  				Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),
   273  			},
   274  			WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MntnsId},
   275  			Comm:          gadgets.FromCString(bpfEvent.Task[:]),
   276  			Pid:           bpfEvent.Pid,
   277  			Op:            ops[int(bpfEvent.Op)],
   278  			Bytes:         bpfEvent.Size,
   279  			Offset:        bpfEvent.Offset,
   280  			Latency:       bpfEvent.DeltaUs,
   281  			File:          gadgets.FromCString(bpfEvent.File[:]),
   282  		}
   283  
   284  		if t.enricher != nil {
   285  			t.enricher.EnrichByMntNs(&event.CommonData, event.MountNsID)
   286  		}
   287  
   288  		t.eventCallback(&event)
   289  	}
   290  }
   291  
   292  // --- Registry changes
   293  
   294  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   295  	params := gadgetCtx.GadgetParams()
   296  	t.config.Filesystem = params.Get(ParamFilesystem).AsString()
   297  	t.config.MinLatency = params.Get(ParamMinLatency).AsUint()
   298  
   299  	defer t.close()
   300  	if err := t.install(); err != nil {
   301  		return fmt.Errorf("installing tracer: %w", err)
   302  	}
   303  
   304  	go t.run()
   305  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
   306  
   307  	return nil
   308  }
   309  
   310  func (t *Tracer) SetMountNsMap(mountnsMap *ebpf.Map) {
   311  	t.config.MountnsMap = mountnsMap
   312  }
   313  
   314  func (t *Tracer) SetEventHandler(handler any) {
   315  	nh, ok := handler.(func(ev *types.Event))
   316  	if !ok {
   317  		panic("event handler invalid")
   318  	}
   319  	t.eventCallback = nh
   320  }
   321  
   322  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   323  	tracer := &Tracer{
   324  		config: &Config{},
   325  	}
   326  	return tracer, nil
   327  }