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 }