Repository: cilium/ebpf
Pull request #1465 (2024-05-11 17:45:33)

Add RecursionMisses to access ProgramInfo recursion misses counter
Adding RecursionMisses to access ProgramInfo recursion misses counter.

It does not depend on whether collection of statistics is enabled, the value is always counted.

lmb: Pushed a fixup, PTAL. Looking at the upstream commit it's not clear to me where the idea that this is related to program statistics comes from? Seems easier to me to just treat these things as completely independent.

Just returning `pi.stats.recursionMisses, true` doesn't work due to `newProgramInfoFromProc`. Reworked to just use a separate field which works around this problem.
olsajiri: hm, it's per program stats collected under same struct in kernel together with run count and run time

```
struct bpf_prog_stats {
        u64_stats_t cnt;
        u64_stats_t nsecs;
        u64_stats_t misses;
        struct u64_stats_sync syncp;
} __aligned(2 * sizeof(u64));
```

it's incremented through `bpf_prog_inc_misses_counter` call any time there's recursion detected on link and we can't run the program

it makes sense to me to place it under `programStats`
lmb: I can see your point, moving it out of the struct doesn't really add value. I've changed it back, fixed the panic and added a comment.
lmb: CI failures are due to unrelated upstream changes.

Patch:
diff --git a/info.go b/info.go
index 79b11c9..f447476 100644
--- a/info.go
+++ b/info.go
@@ -83,6 +83,9 @@ type programStats struct {
 	runtime time.Duration
 	// Total number of times the program was called.
 	runCount uint64
+	// Total number of times the programm was NOT called.
+	// Added in commit 9ed9e9ba2337 ("bpf: Count the number of times recursion was prevented").
+	recursionMisses uint64
 }
 
 // ProgramInfo describes a program.
@@ -125,8 +128,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		Name: unix.ByteSliceToString(info.Name[:]),
 		btf:  btf.ID(info.BtfId),
 		stats: &programStats{
-			runtime:  time.Duration(info.RunTimeNs),
-			runCount: info.RunCnt,
+			runtime:         time.Duration(info.RunTimeNs),
+			runCount:        info.RunCnt,
+			recursionMisses: info.RecursionMisses,
 		},
 	}
 
@@ -259,6 +263,16 @@ func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
 	return time.Duration(0), false
 }
 
+// RecursionMisses returns the total number of times the program was NOT called.
+// This can happen when another bpf program is already running on the cpu, which
+// is likely to happen for example when you interrupt bpf program execution.
+func (pi *ProgramInfo) RecursionMisses() (uint64, bool) {
+	if pi.stats != nil {
+		return pi.stats.recursionMisses, true
+	}
+	return 0, false
+}
+
 // Instructions returns the 'xlated' instruction stream of the program
 // after it has been verified and rewritten by the kernel. These instructions
 // cannot be loaded back into the kernel as-is, this is mainly used for


Test patch:
diff --git a/info_test.go b/info_test.go
index 6802fdf..b7653ff 100644
--- a/info_test.go
+++ b/info_test.go
@@ -262,6 +262,14 @@ func TestStats(t *testing.T) {
 		t.Errorf("expected a runtime of 0ns but got %v", rt)
 	}
 
+	rm, ok := pi.RecursionMisses()
+	if !ok {
+		t.Errorf("expected recursion misses info to be available")
+	}
+	if rm != 0 {
+		t.Errorf("expected a recursion misses of 0 but got %v", rm)
+	}
+
 	if err := testStats(prog); err != nil {
 		t.Error(err)
 	}