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  }