Repository: cilium/ebpf
Pull request #1512 (2024-07-11 22:56:27)

info: expose additional `bpf_prog_info` fields in `ProgramInfo`
Hello! 👋 

I'm implementing a Telegraf plugin that extracts metadata information from the `bpf_prog_info` object. There's some additional fields that I'd like to include in the metric, that aren't exposed in `ProgramInfo`.

The additional fields that are exposes are:
- `jited_prog_len`
- `xlated_prog_len`
- `load_time`
- `verified_insns`

lmb: Hi @tyrone-wu, thanks for your contribution! Please take a look at the comment I wrote in https://github.com/cilium/ebpf/pull/1515 which explains how to extend the Info API. Please also add tests for the API you're introducing.
tyrone-wu: Sorry for the delay, I've been caught in some other things. I can definitely get this done by the end of today. 😺

Patch:
diff --git a/info.go b/info.go
index 04c60c6..93cc2db 100644
--- a/info.go
+++ b/info.go
@@ -133,9 +133,12 @@ type ProgramInfo struct {
 	haveCreatedByUID bool
 	btf              btf.ID
 	stats            *programStats
+	loadTime         time.Duration
 
-	maps  []MapID
-	insns []byte
+	maps                 []MapID
+	insns                []byte
+	jitedSize            uint32
+	verifiedInstructions uint32
 
 	lineInfos    []byte
 	numLineInfos uint32
@@ -164,6 +167,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 			runCount:        info.RunCnt,
 			recursionMisses: info.RecursionMisses,
 		},
+		jitedSize:            info.JitedProgLen,
+		loadTime:             time.Duration(info.LoadTime),
+		verifiedInstructions: info.VerifiedInsns,
 	}
 
 	// Start with a clean struct for the second call, otherwise we may get EFAULT.
@@ -391,6 +397,29 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
 	return insns, nil
 }
 
+// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the
+// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled.
+//
+// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
+func (pi *ProgramInfo) JitedSize() (uint32, error) {
+	if pi.jitedSize == 0 {
+		return 0, fmt.Errorf("insufficient permissions, unsupported kernel, or JIT compiler disabled: %w", ErrNotSupported)
+	}
+	return pi.jitedSize, nil
+}
+
+// TranslatedSize returns the size of the program's translated instructions in bytes, after it has
+// been verified and rewritten by the kernel.
+//
+// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
+func (pi *ProgramInfo) TranslatedSize() (int, error) {
+	insns := len(pi.insns)
+	if insns == 0 {
+		return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
+	}
+	return insns, nil
+}
+
 // MapIDs returns the maps related to the program.
 //
 // Available from 4.15.
@@ -400,6 +429,25 @@ func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
 	return pi.maps, pi.maps != nil
 }
 
+// LoadTime returns when the program was loaded since boot time.
+//
+// Available from 4.15.
+//
+// The bool return value indicates whether this optional field is available.
+func (pi *ProgramInfo) LoadTime() (time.Duration, bool) {
+	// loadTime and NrMapIds were introduced in the same kernel version.
+	return pi.loadTime, pi.loadTime > 0
+}
+
+// VerifiedInstructions returns the number verified instructions in the program.
+//
+// Available from 5.16.
+//
+// The bool return value indicates whether this optional field is available.
+func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) {
+	return pi.verifiedInstructions, pi.verifiedInstructions > 0
+}
+
 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
 	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
 	if err != nil {


Test patch:
diff --git a/info_test.go b/info_test.go
index 13ec3bb..a0987c4 100644
--- a/info_test.go
+++ b/info_test.go
@@ -122,16 +122,55 @@ func TestProgramInfo(t *testing.T) {
 			}
 
 			if name == "proc" {
+				_, err := info.JitedSize()
+				qt.Assert(t, qt.IsNotNil(err))
+
+				_, err = info.TranslatedSize()
+				qt.Assert(t, qt.IsNotNil(err))
+
 				_, ok := info.CreatedByUID()
 				qt.Assert(t, qt.IsFalse(ok))
+
+				_, ok = info.LoadTime()
+				qt.Assert(t, qt.IsFalse(ok))
+
+				_, ok = info.VerifiedInstructions()
+				qt.Assert(t, qt.IsFalse(ok))
 			} else {
-				uid, ok := info.CreatedByUID()
-				if testutils.IsKernelLessThan(t, "4.15") {
+				if jitedSize, err := info.JitedSize(); testutils.IsKernelLessThan(t, "4.13") {
+					qt.Assert(t, qt.IsNotNil(err))
+				} else {
+					qt.Assert(t, qt.IsNil(err))
+					qt.Assert(t, qt.IsTrue(jitedSize > 0))
+				}
+
+				if xlatedSize, err := info.TranslatedSize(); testutils.IsKernelLessThan(t, "4.13") {
+					qt.Assert(t, qt.IsNotNil(err))
+				} else {
+					qt.Assert(t, qt.IsNil(err))
+					qt.Assert(t, qt.IsTrue(xlatedSize > 0))
+				}
+
+				if uid, ok := info.CreatedByUID(); testutils.IsKernelLessThan(t, "4.15") {
 					qt.Assert(t, qt.IsFalse(ok))
 				} else {
 					qt.Assert(t, qt.IsTrue(ok))
 					qt.Assert(t, qt.Equals(uid, uint32(os.Getuid())))
 				}
+
+				if loadTime, ok := info.LoadTime(); testutils.IsKernelLessThan(t, "4.15") {
+					qt.Assert(t, qt.IsFalse(ok))
+				} else {
+					qt.Assert(t, qt.IsTrue(ok))
+					qt.Assert(t, qt.IsTrue(loadTime > 0))
+				}
+
+				if verifiedInsns, ok := info.VerifiedInstructions(); testutils.IsKernelLessThan(t, "5.16") {
+					qt.Assert(t, qt.IsFalse(ok))
+				} else {
+					qt.Assert(t, qt.IsTrue(ok))
+					qt.Assert(t, qt.IsTrue(verifiedInsns > 0))
+				}
 			}
 		})
 	}