...

Source file src/github.com/cilium/ebpf/info_test.go

Documentation: github.com/cilium/ebpf

     1  package ebpf
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/cilium/ebpf/asm"
    10  	"github.com/cilium/ebpf/internal"
    11  	"github.com/cilium/ebpf/internal/sys"
    12  	"github.com/cilium/ebpf/internal/testutils"
    13  	"github.com/cilium/ebpf/internal/unix"
    14  	qt "github.com/frankban/quicktest"
    15  )
    16  
    17  func TestMapInfoFromProc(t *testing.T) {
    18  	hash, err := NewMap(&MapSpec{
    19  		Name:       "testing",
    20  		Type:       Hash,
    21  		KeySize:    4,
    22  		ValueSize:  5,
    23  		MaxEntries: 2,
    24  		Flags:      unix.BPF_F_NO_PREALLOC,
    25  	})
    26  	testutils.SkipIfNotSupported(t, err)
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	defer hash.Close()
    31  
    32  	info, err := newMapInfoFromProc(hash.fd)
    33  	testutils.SkipIfNotSupported(t, err)
    34  	if err != nil {
    35  		t.Fatal("Can't get map info:", err)
    36  	}
    37  
    38  	if info.Type != Hash {
    39  		t.Error("Expected Hash, got", info.Type)
    40  	}
    41  
    42  	if info.KeySize != 4 {
    43  		t.Error("Expected KeySize of 4, got", info.KeySize)
    44  	}
    45  
    46  	if info.ValueSize != 5 {
    47  		t.Error("Expected ValueSize of 5, got", info.ValueSize)
    48  	}
    49  
    50  	if info.MaxEntries != 2 {
    51  		t.Error("Expected MaxEntries of 2, got", info.MaxEntries)
    52  	}
    53  
    54  	if info.Flags != unix.BPF_F_NO_PREALLOC {
    55  		t.Errorf("Expected Flags to be %d, got %d", unix.BPF_F_NO_PREALLOC, info.Flags)
    56  	}
    57  
    58  	if info.Name != "" && info.Name != "testing" {
    59  		t.Error("Expected name to be testing, got", info.Name)
    60  	}
    61  
    62  	if _, ok := info.ID(); ok {
    63  		t.Error("Expected ID to not be available")
    64  	}
    65  
    66  	nested, err := NewMap(&MapSpec{
    67  		Type:       ArrayOfMaps,
    68  		KeySize:    4,
    69  		MaxEntries: 2,
    70  		InnerMap: &MapSpec{
    71  			Type:       Array,
    72  			KeySize:    4,
    73  			ValueSize:  4,
    74  			MaxEntries: 2,
    75  		},
    76  	})
    77  	testutils.SkipIfNotSupported(t, err)
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	defer nested.Close()
    82  
    83  	_, err = newMapInfoFromProc(nested.fd)
    84  	if err != nil {
    85  		t.Fatal("Can't get nested map info from /proc:", err)
    86  	}
    87  }
    88  
    89  func TestProgramInfo(t *testing.T) {
    90  	prog := mustSocketFilter(t)
    91  
    92  	for name, fn := range map[string]func(*sys.FD) (*ProgramInfo, error){
    93  		"generic": newProgramInfoFromFd,
    94  		"proc":    newProgramInfoFromProc,
    95  	} {
    96  		t.Run(name, func(t *testing.T) {
    97  			info, err := fn(prog.fd)
    98  			testutils.SkipIfNotSupported(t, err)
    99  			if err != nil {
   100  				t.Fatal("Can't get program info:", err)
   101  			}
   102  
   103  			if info.Type != SocketFilter {
   104  				t.Error("Expected Type to be SocketFilter, got", info.Type)
   105  			}
   106  
   107  			if info.Name != "" && info.Name != "test" {
   108  				t.Error("Expected Name to be test, got", info.Name)
   109  			}
   110  
   111  			if want := "d7edec644f05498d"; info.Tag != want {
   112  				t.Errorf("Expected Tag to be %s, got %s", want, info.Tag)
   113  			}
   114  
   115  			if id, ok := info.ID(); ok && id == 0 {
   116  				t.Error("Expected a valid ID:", id)
   117  			} else if name == "proc" && ok {
   118  				t.Error("Expected ID to not be available")
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func TestProgramInfoMapIDs(t *testing.T) {
   125  	testutils.SkipOnOldKernel(t, "4.10", "reading program info")
   126  
   127  	arr, err := NewMap(&MapSpec{
   128  		Type:       Array,
   129  		KeySize:    4,
   130  		ValueSize:  4,
   131  		MaxEntries: 1,
   132  	})
   133  	qt.Assert(t, err, qt.IsNil)
   134  	defer arr.Close()
   135  
   136  	prog, err := NewProgram(&ProgramSpec{
   137  		Type: SocketFilter,
   138  		Instructions: asm.Instructions{
   139  			asm.LoadMapPtr(asm.R0, arr.FD()),
   140  			asm.LoadImm(asm.R0, 2, asm.DWord),
   141  			asm.Return(),
   142  		},
   143  		License: "MIT",
   144  	})
   145  	qt.Assert(t, err, qt.IsNil)
   146  	defer prog.Close()
   147  
   148  	info, err := prog.Info()
   149  	qt.Assert(t, err, qt.IsNil)
   150  
   151  	ids, ok := info.MapIDs()
   152  	if testutils.MustKernelVersion().Less(internal.Version{4, 15, 0}) {
   153  		qt.Assert(t, ok, qt.IsFalse)
   154  		qt.Assert(t, ids, qt.HasLen, 0)
   155  	} else {
   156  		qt.Assert(t, ok, qt.IsTrue)
   157  		qt.Assert(t, ids, qt.HasLen, 1)
   158  
   159  		mapInfo, err := arr.Info()
   160  		qt.Assert(t, err, qt.IsNil)
   161  		mapID, ok := mapInfo.ID()
   162  		qt.Assert(t, ok, qt.IsTrue)
   163  		qt.Assert(t, ids[0], qt.Equals, mapID)
   164  	}
   165  }
   166  
   167  func TestScanFdInfoReader(t *testing.T) {
   168  	tests := []struct {
   169  		fields map[string]interface{}
   170  		valid  bool
   171  	}{
   172  		{nil, true},
   173  		{map[string]interface{}{"foo": new(string)}, true},
   174  		{map[string]interface{}{"zap": new(string)}, false},
   175  		{map[string]interface{}{"foo": new(int)}, false},
   176  	}
   177  
   178  	for _, test := range tests {
   179  		err := scanFdInfoReader(strings.NewReader("foo:\tbar\n"), test.fields)
   180  		if test.valid {
   181  			if err != nil {
   182  				t.Errorf("fields %v returns an error: %s", test.fields, err)
   183  			}
   184  		} else {
   185  			if err == nil {
   186  				t.Errorf("fields %v doesn't return an error", test.fields)
   187  			}
   188  		}
   189  	}
   190  }
   191  
   192  // TestStats loads a BPF program once and executes back-to-back test runs
   193  // of the program. See testStats for details.
   194  func TestStats(t *testing.T) {
   195  	testutils.SkipOnOldKernel(t, "5.8", "BPF_ENABLE_STATS")
   196  
   197  	prog := mustSocketFilter(t)
   198  
   199  	pi, err := prog.Info()
   200  	if err != nil {
   201  		t.Errorf("failed to get ProgramInfo: %v", err)
   202  	}
   203  
   204  	rc, ok := pi.RunCount()
   205  	if !ok {
   206  		t.Errorf("expected run count info to be available")
   207  	}
   208  	if rc != 0 {
   209  		t.Errorf("expected a run count of 0 but got %d", rc)
   210  	}
   211  
   212  	rt, ok := pi.Runtime()
   213  	if !ok {
   214  		t.Errorf("expected runtime info to be available")
   215  	}
   216  	if rt != 0 {
   217  		t.Errorf("expected a runtime of 0ns but got %v", rt)
   218  	}
   219  
   220  	if err := testStats(prog); err != nil {
   221  		t.Error(err)
   222  	}
   223  }
   224  
   225  // BenchmarkStats is a benchmark of TestStats. See testStats for details.
   226  func BenchmarkStats(b *testing.B) {
   227  	testutils.SkipOnOldKernel(b, "5.8", "BPF_ENABLE_STATS")
   228  
   229  	prog := mustSocketFilter(b)
   230  
   231  	for n := 0; n < b.N; n++ {
   232  		if err := testStats(prog); err != nil {
   233  			b.Fatal(fmt.Errorf("iter %d: %w", n, err))
   234  		}
   235  	}
   236  }
   237  
   238  // testStats implements the behaviour under test for TestStats
   239  // and BenchmarkStats. First, a test run is executed with runtime statistics
   240  // enabled, followed by another with runtime stats disabled. Counters are only
   241  // expected to increase on the runs where runtime stats are enabled.
   242  //
   243  // Due to runtime behaviour on Go 1.14 and higher, the syscall backing
   244  // (*Program).Test() could be invoked multiple times for each call to Test(),
   245  // resulting in RunCount incrementing by more than one. Expecting RunCount to
   246  // be of a specific value after a call to Test() is therefore not possible.
   247  // See https://golang.org/doc/go1.14#runtime for more details.
   248  func testStats(prog *Program) error {
   249  	in := make([]byte, 14)
   250  
   251  	stats, err := EnableStats(uint32(unix.BPF_STATS_RUN_TIME))
   252  	if err != nil {
   253  		return fmt.Errorf("failed to enable stats: %v", err)
   254  	}
   255  	defer stats.Close()
   256  
   257  	// Program execution with runtime statistics enabled.
   258  	// Should increase both runtime and run counter.
   259  	if _, _, err := prog.Test(in); err != nil {
   260  		return fmt.Errorf("failed to trigger program: %v", err)
   261  	}
   262  
   263  	pi, err := prog.Info()
   264  	if err != nil {
   265  		return fmt.Errorf("failed to get ProgramInfo: %v", err)
   266  	}
   267  
   268  	rc, ok := pi.RunCount()
   269  	if !ok {
   270  		return errors.New("expected run count info to be available")
   271  	}
   272  	if rc < 1 {
   273  		return fmt.Errorf("expected a run count of at least 1 but got %d", rc)
   274  	}
   275  	// Store the run count for the next invocation.
   276  	lc := rc
   277  
   278  	rt, ok := pi.Runtime()
   279  	if !ok {
   280  		return errors.New("expected runtime info to be available")
   281  	}
   282  	if rt == 0 {
   283  		return errors.New("expected a runtime other than 0ns")
   284  	}
   285  	// Store the runtime value for the next invocation.
   286  	lt := rt
   287  
   288  	if err := stats.Close(); err != nil {
   289  		return fmt.Errorf("failed to disable statistics: %v", err)
   290  	}
   291  
   292  	// Second program execution, with runtime statistics gathering disabled.
   293  	// Total runtime and run counters are not expected to increase.
   294  	if _, _, err := prog.Test(in); err != nil {
   295  		return fmt.Errorf("failed to trigger program: %v", err)
   296  	}
   297  
   298  	pi, err = prog.Info()
   299  	if err != nil {
   300  		return fmt.Errorf("failed to get ProgramInfo: %v", err)
   301  	}
   302  
   303  	rc, ok = pi.RunCount()
   304  	if !ok {
   305  		return errors.New("expected run count info to be available")
   306  	}
   307  	if rc != lc {
   308  		return fmt.Errorf("run count unexpectedly increased over previous value (current: %v, prev: %v)", rc, lc)
   309  	}
   310  
   311  	rt, ok = pi.Runtime()
   312  	if !ok {
   313  		return errors.New("expected runtime info to be available")
   314  	}
   315  	if rt != lt {
   316  		return fmt.Errorf("runtime unexpectedly increased over the previous value (current: %v, prev: %v)", rt, lt)
   317  	}
   318  
   319  	return nil
   320  }
   321  

View as plain text