github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/top/block-io/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/block-io/types" 33 "github.com/inspektor-gadget/inspektor-gadget/pkg/kallsyms" 34 eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 35 ) 36 37 //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -type info_t -type val_t -cc clang -cflags ${CFLAGS} biotop ./bpf/biotop.bpf.c -- 38 39 type Config struct { 40 MaxRows int 41 Interval time.Duration 42 Iterations int 43 SortBy []string 44 MountnsMap *ebpf.Map 45 } 46 47 type Tracer struct { 48 config *Config 49 objs biotopObjects 50 ioStartLink link.Link 51 startRequestLink link.Link 52 doneLink 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.ioStartLink = gadgets.CloseLink(t.ioStartLink) 96 t.startRequestLink = gadgets.CloseLink(t.startRequestLink) 97 t.doneLink = gadgets.CloseLink(t.doneLink) 98 99 t.objs.Close() 100 } 101 102 func (t *Tracer) install() error { 103 spec, err := loadBiotop() 104 if err != nil { 105 return fmt.Errorf("loading ebpf program: %w", err) 106 } 107 108 if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, nil, &t.objs); err != nil { 109 return fmt.Errorf("loading ebpf spec: %w", err) 110 } 111 112 kernelSymbols, err := kallsyms.NewKAllSyms() 113 if err != nil { 114 return fmt.Errorf("loading kernel symbols: %w", err) 115 } 116 117 // __blk_account_io_start and __blk_account_io_done were inlined in: 118 // be6bfe36db17 ("block: inline hot paths of blk_account_io_*()"). 119 // which was included in kernel 5.16. 120 // So let's be future proof and check if these symbols do not exist. 121 blkAccountIoStartFunction := "__blk_account_io_start" 122 if !kernelSymbols.SymbolExists(blkAccountIoStartFunction) { 123 blkAccountIoStartFunction = "blk_account_io_start" 124 } 125 126 blkAccountIoDoneFunction := "__blk_account_io_done" 127 if !kernelSymbols.SymbolExists(blkAccountIoDoneFunction) { 128 blkAccountIoDoneFunction = "blk_account_io_done" 129 } 130 131 t.ioStartLink, err = link.Kprobe(blkAccountIoStartFunction, t.objs.IgTopioStart, nil) 132 if err != nil { 133 return fmt.Errorf("attaching kprobe: %w", err) 134 } 135 136 t.startRequestLink, err = link.Kprobe("blk_mq_start_request", t.objs.IgTopioReq, nil) 137 if err != nil { 138 return fmt.Errorf("attaching kprobe: %w", err) 139 } 140 141 t.doneLink, err = link.Kprobe(blkAccountIoDoneFunction, t.objs.IgTopioDone, nil) 142 if err != nil { 143 return fmt.Errorf("attaching kprobe: %w", err) 144 } 145 146 return nil 147 } 148 149 func (t *Tracer) nextStats() ([]*types.Stats, error) { 150 stats := []*types.Stats{} 151 152 var prev *biotopInfoT = nil 153 key := biotopInfoT{} 154 counts := t.objs.Counts 155 156 defer func() { 157 // delete elements 158 err := counts.NextKey(nil, unsafe.Pointer(&key)) 159 if err != nil { 160 return 161 } 162 163 for { 164 if err := counts.Delete(key); err != nil { 165 return 166 } 167 168 prev = &key 169 if err := counts.NextKey(unsafe.Pointer(prev), unsafe.Pointer(&key)); err != nil { 170 return 171 } 172 } 173 }() 174 175 // gather elements 176 err := counts.NextKey(nil, unsafe.Pointer(&key)) 177 if err != nil { 178 if errors.Is(err, ebpf.ErrKeyNotExist) { 179 return stats, nil 180 } 181 return nil, fmt.Errorf("getting next key: %w", err) 182 } 183 184 for { 185 val := biotopValT{} 186 if err := counts.Lookup(key, unsafe.Pointer(&val)); err != nil { 187 return nil, err 188 } 189 190 stat := types.Stats{ 191 Write: key.Rwflag != 0, 192 Major: int(key.Major), 193 Minor: int(key.Minor), 194 WithMountNsID: eventtypes.WithMountNsID{MountNsID: key.Mntnsid}, 195 Pid: int32(key.Pid), 196 Comm: gadgets.FromCString(key.Name[:]), 197 Bytes: val.Bytes, 198 MicroSecs: val.Us, 199 Operations: val.Io, 200 } 201 202 if t.enricher != nil { 203 t.enricher.EnrichByMntNs(&stat.CommonData, stat.MountNsID) 204 } 205 206 stats = append(stats, &stat) 207 208 prev = &key 209 if err := counts.NextKey(unsafe.Pointer(prev), unsafe.Pointer(&key)); err != nil { 210 if errors.Is(err, ebpf.ErrKeyNotExist) { 211 break 212 } 213 return nil, fmt.Errorf("getting next key: %w", err) 214 } 215 } 216 217 top.SortStats(stats, t.config.SortBy, &t.colMap) 218 219 return stats, nil 220 } 221 222 func (t *Tracer) run(ctx context.Context) error { 223 // Don't use a context with a timeout but a counter to avoid having to deal 224 // with two timers: one for the timeout and another for the ticker. 225 count := t.config.Iterations 226 ticker := time.NewTicker(t.config.Interval) 227 defer ticker.Stop() 228 229 for { 230 select { 231 case <-t.done: 232 // TODO: Once we completely move to use Run instead of NewTracer, 233 // we can remove this as nobody will directly call Stop (cleanup). 234 return nil 235 case <-ctx.Done(): 236 return nil 237 case <-ticker.C: 238 stats, err := t.nextStats() 239 if err != nil { 240 return fmt.Errorf("getting next stats: %w", err) 241 } 242 243 n := len(stats) 244 if n > t.config.MaxRows { 245 n = t.config.MaxRows 246 } 247 t.eventCallback(&top.Event[types.Stats]{Stats: stats[:n]}) 248 249 // Count down only if user requested a finite number of iterations 250 // through a timeout. 251 if t.config.Iterations > 0 { 252 count-- 253 if count == 0 { 254 return nil 255 } 256 } 257 } 258 } 259 } 260 261 func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error { 262 if err := t.init(gadgetCtx); err != nil { 263 return fmt.Errorf("initializing tracer: %w", err) 264 } 265 266 defer t.close() 267 if err := t.install(); err != nil { 268 return fmt.Errorf("installing tracer: %w", err) 269 } 270 271 return t.run(gadgetCtx.Context()) 272 } 273 274 func (t *Tracer) SetEventHandlerArray(handler any) { 275 nh, ok := handler.(func(ev []*types.Stats)) 276 if !ok { 277 panic("event handler invalid") 278 } 279 280 // TODO: add errorHandler 281 t.eventCallback = func(ev *top.Event[types.Stats]) { 282 if ev.Error != "" { 283 return 284 } 285 nh(ev.Stats) 286 } 287 } 288 289 func (t *Tracer) SetMountNsMap(mntnsMap *ebpf.Map) { 290 t.config.MountnsMap = mntnsMap 291 } 292 293 func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) { 294 tracer := &Tracer{ 295 config: &Config{ 296 MaxRows: 20, 297 Interval: 1 * time.Second, 298 SortBy: nil, 299 }, 300 done: make(chan bool), 301 } 302 return tracer, nil 303 } 304 305 func (t *Tracer) init(gadgetCtx gadgets.GadgetContext) error { 306 params := gadgetCtx.GadgetParams() 307 t.config.MaxRows = params.Get(gadgets.ParamMaxRows).AsInt() 308 t.config.SortBy = params.Get(gadgets.ParamSortBy).AsStringSlice() 309 t.config.Interval = time.Second * time.Duration(params.Get(gadgets.ParamInterval).AsInt()) 310 311 var err error 312 if t.config.Iterations, err = top.ComputeIterations(t.config.Interval, gadgetCtx.Timeout()); err != nil { 313 return err 314 } 315 316 statCols, err := columns.NewColumns[types.Stats]() 317 if err != nil { 318 return err 319 } 320 t.colMap = statCols.GetColumnMap() 321 322 return nil 323 }