github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/bpfstats/bpfstats.go (about) 1 // Copyright 2019-2022 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 package bpfstats 16 17 import ( 18 "errors" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 "sync" 24 25 "github.com/cilium/ebpf" 26 "golang.org/x/sys/unix" 27 ) 28 29 type BPFStatsMethod int 30 31 const ( 32 // MethodNone means that no call to EnableBPFStats() has been made or was unsuccessful 33 MethodNone BPFStatsMethod = iota 34 35 // MethodBPFFunc uses stats collection via BPF(BPF_ENABLE_STATS) 36 MethodBPFFunc 37 38 // MethodSysctl uses stats collection via sysctl (/proc/sys/kernel/bpf_stats_enabled) 39 MethodSysctl 40 ) 41 42 var ( 43 mutex sync.Mutex 44 refCnt int 45 statsSock io.Closer 46 method = MethodNone 47 ) 48 49 // EnableBPFStats enables collection of bpf program stats. It tries to use BPF_ENABLE_STATS first 50 // (which requires Linux >= 5.8). If that fails, it will fall back to trying to 51 // enable via sysctl (/proc/sys/kernel/bpf_stats_enabled). This function will make 52 // sure that repeated calls will not enable stats collection more than once. Instead, 53 // it will keep track of the number of calls and only stop stat collection, when 54 // DisableBPFStats() has been called the same number of times. 55 func EnableBPFStats() error { 56 mutex.Lock() 57 defer mutex.Unlock() 58 59 if refCnt != 0 { 60 return nil 61 } 62 63 // Actually enable 64 s, err := ebpf.EnableStats(unix.BPF_STATS_RUN_TIME) 65 if err != nil { 66 // Use fallback method 67 err = os.WriteFile(filepath.Join(os.Getenv("HOST_ROOT"), "/proc/sys/kernel/bpf_stats_enabled"), []byte("1"), 0o644) 68 if err != nil { 69 return fmt.Errorf("enabling stat collection: %w", err) 70 } 71 method = MethodSysctl 72 } else { 73 statsSock = s 74 method = MethodBPFFunc 75 } 76 77 refCnt++ 78 79 return nil 80 } 81 82 // DisableBPFStats disables collection of bpf program stats if no consumer 83 func DisableBPFStats() error { 84 mutex.Lock() 85 defer mutex.Unlock() 86 87 refCnt-- 88 89 if refCnt < 0 { 90 refCnt = 0 91 return errors.New("bpf stat collection already disabled") 92 } 93 94 if refCnt != 0 { 95 return nil 96 } 97 98 // Actually disable 99 switch method { 100 case MethodBPFFunc: 101 err := statsSock.Close() 102 statsSock = nil 103 if err != nil { 104 return fmt.Errorf("disabling stat collection using BPF(): %w", err) 105 } 106 case MethodSysctl: 107 err := os.WriteFile(filepath.Join(os.Getenv("HOST_ROOT"), "/proc/sys/kernel/bpf_stats_enabled"), []byte("0"), 0o644) 108 if err != nil { 109 return fmt.Errorf("disabling stat collection using sysctl: %w", err) 110 } 111 } 112 113 return nil 114 } 115 116 // GetMethod returns the currently used method to enable stats collection. If 117 // EnableBPFStats() has not yet been called, it will return MethodNone. 118 func GetMethod() BPFStatsMethod { 119 mutex.Lock() 120 defer mutex.Unlock() 121 return method 122 }