summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 07:23:45 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 07:23:45 +0300
commitdf1225efe494cc81513cf98e93891376e45f9615 (patch)
tree8fe131ba9ae5737022f26fcd60e662c1660329c6
parent11a8642b7035ff558fb84d7761e93525c84e4908 (diff)
task 07: add KindMem and separate address-space byte accounting
-rw-r--r--cmd/ioworkload/scenario_mmap.go28
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/mmap_test.go24
-rw-r--r--internal/c/generated_tracepoints.c20
-rw-r--r--internal/c/generated_tracepoints_result.txt4
-rw-r--r--internal/c/types.h14
-rw-r--r--internal/event/interface_assertions.go3
-rw-r--r--internal/event/pair.go3
-rw-r--r--internal/eventloop_bytes_test.go58
-rw-r--r--internal/eventloop_exit.go43
-rw-r--r--internal/eventloop_memory_test.go46
-rw-r--r--internal/eventloop_runtime.go12
-rw-r--r--internal/generate/bpfhandler.go13
-rw-r--r--internal/generate/classify.go5
-rw-r--r--internal/generate/classify_test.go18
-rw-r--r--internal/generate/codegen_test.go16
-rw-r--r--internal/generate/kindregistry.go1
-rw-r--r--internal/generate/testdata.go61
-rw-r--r--internal/statsengine/engine.go71
-rw-r--r--internal/statsengine/engine_reset_test.go4
-rw-r--r--internal/statsengine/engine_test.go57
-rw-r--r--internal/statsengine/snapshot.go18
-rw-r--r--internal/types/fastdecode.go21
-rw-r--r--internal/types/fastdecode_test.go23
-rw-r--r--internal/types/generated_types.go73
25 files changed, 571 insertions, 66 deletions
diff --git a/cmd/ioworkload/scenario_mmap.go b/cmd/ioworkload/scenario_mmap.go
index e7b9f02..0d953d2 100644
--- a/cmd/ioworkload/scenario_mmap.go
+++ b/cmd/ioworkload/scenario_mmap.go
@@ -7,6 +7,8 @@ import (
"unsafe"
)
+const mremapMayMove = 1
+
// mmapBasic creates a file-backed shared mapping.
// mmap(2) allows closing the fd after mapping without invalidating the mapping.
func mmapBasic() error {
@@ -108,3 +110,29 @@ func mmapMsyncInvalidFlags() error {
}
return nil
}
+
+// mmapMremapMunmap remaps an anonymous mapping and unmaps the resized region.
+// It is used to validate enter_mremap/enter_munmap tracing and memory-byte
+// accounting separation from I/O bytes.
+func mmapMremapMunmap() error {
+ const pageSize = 4096
+
+ mapped, err := syscall.Mmap(-1, 0, pageSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_PRIVATE|syscall.MAP_ANON)
+ if err != nil {
+ return fmt.Errorf("mmap: %w", err)
+ }
+
+ oldAddr := uintptr(unsafe.Pointer(&mapped[0]))
+ newSize := uintptr(pageSize * 2)
+ newAddr, _, errno := syscall.Syscall6(syscall.SYS_MREMAP, oldAddr, uintptr(len(mapped)), newSize, mremapMayMove, 0, 0)
+ if errno != 0 {
+ _ = syscall.Munmap(mapped)
+ return fmt.Errorf("mremap: %w", errno)
+ }
+
+ _, _, errno = syscall.Syscall(syscall.SYS_MUNMAP, newAddr, newSize, 0)
+ if errno != 0 {
+ return fmt.Errorf("munmap: %w", errno)
+ }
+ return nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 6a6e40b..8f4eef2 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -99,6 +99,7 @@ var scenarios = map[string]func() error{
"mmap-basic": mmapBasic,
"mmap-msync-sync": mmapMsyncSync,
"mmap-msync-invalid-flags": mmapMsyncInvalidFlags,
+ "mmap-mremap-munmap": mmapMremapMunmap,
"copy-file-range-basic": copyFileRangeBasic,
"copy-file-range-bad-dst-fd": copyFileRangeBadDstFd,
"truncate-basic": truncateBasic,
diff --git a/integrationtests/mmap_test.go b/integrationtests/mmap_test.go
index 89cce66..3ef84c5 100644
--- a/integrationtests/mmap_test.go
+++ b/integrationtests/mmap_test.go
@@ -44,3 +44,27 @@ func TestMmapMsyncInvalidFlags(t *testing.T) {
},
})
}
+
+func TestMmapMremapMunmap(t *testing.T) {
+ result, _ := runScenarioResult(t, "mmap-mremap-munmap", []ExpectedEvent{
+ {
+ Tracepoint: "enter_mremap",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ {
+ Tracepoint: "enter_munmap",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+
+ assertEventBytesEqual(t, result, ExpectedEvent{
+ Tracepoint: "enter_mremap",
+ Comm: "ioworkload",
+ }, 0)
+ assertEventBytesEqual(t, result, ExpectedEvent{
+ Tracepoint: "enter_munmap",
+ Comm: "ioworkload",
+ }, 0)
+}
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c
index 393954c..ad11b06 100644
--- a/internal/c/generated_tracepoints.c
+++ b/internal/c/generated_tracepoints.c
@@ -10736,22 +10736,26 @@ int handle_sys_exit_msync(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_mremap is a struct null_event
+/// sys_enter_mremap is a struct mem_event
SEC("tracepoint/syscalls/sys_enter_mremap")
int handle_sys_enter_mremap(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct mem_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct mem_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_MEM_EVENT;
ev->trace_id = SYS_ENTER_MREMAP;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->addr = (__u64)ctx->args[0];
+ ev->length = (__u64)ctx->args[1];
+ ev->length2 = (__u64)ctx->args[2];
+ ev->flags = (__u64)ctx->args[3];
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -11000,22 +11004,26 @@ int handle_sys_exit_brk(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_munmap is a struct null_event
+/// sys_enter_munmap is a struct mem_event
SEC("tracepoint/syscalls/sys_enter_munmap")
int handle_sys_enter_munmap(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct mem_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct mem_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_MEM_EVENT;
ev->trace_id = SYS_ENTER_MUNMAP;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->addr = (__u64)ctx->args[0];
+ ev->length = (__u64)ctx->args[1];
+ ev->length2 = 0;
+ ev->flags = 0;
bpf_ringbuf_submit(ev, 0);
return 0;
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index 4ba2dc8..77339f5 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -181,7 +181,7 @@ sys_enter_mq_open is a struct null_event
sys_enter_mq_timedreceive is a struct null_event
sys_enter_mq_timedsend is a struct null_event
sys_enter_mq_unlink is a struct null_event
-sys_enter_mremap is a struct null_event
+sys_enter_mremap is a struct mem_event
sys_enter_mseal is a struct null_event
sys_enter_msgctl is a struct null_event
sys_enter_msgget is a struct null_event
@@ -190,7 +190,7 @@ sys_enter_msgsnd is a struct null_event
sys_enter_msync is a struct null_event
sys_enter_munlock is a struct null_event
sys_enter_munlockall is a struct null_event
-sys_enter_munmap is a struct null_event
+sys_enter_munmap is a struct mem_event
sys_enter_name_to_handle_at is a struct path_event
sys_enter_nanosleep is a struct null_event
sys_enter_newfstat is a struct fd_event
diff --git a/internal/c/types.h b/internal/c/types.h
index 128d4e9..a496c1a 100644
--- a/internal/c/types.h
+++ b/internal/c/types.h
@@ -35,6 +35,8 @@
#define EXIT_EPOLL_CTL_EVENT 30
#define ENTER_POLL_EVENT 31
#define EXIT_POLL_EVENT 32
+#define ENTER_MEM_EVENT 33
+#define EXIT_MEM_EVENT 34
#define UNCLASSIFIED 0
#define READ_CLASSIFIED 1
@@ -207,3 +209,15 @@ struct poll_event {
__s32 nfds;
__s64 timeout_ns;
};
+
+struct mem_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __u64 addr;
+ __u64 length;
+ __u64 length2;
+ __u64 flags;
+};
diff --git a/internal/event/interface_assertions.go b/internal/event/interface_assertions.go
index 8a1c40d..97a198f 100644
--- a/internal/event/interface_assertions.go
+++ b/internal/event/interface_assertions.go
@@ -62,4 +62,7 @@ var (
// *types.PollEvent carries poll/select argument metadata (nfds and timeout).
_ Event = (*types.PollEvent)(nil)
+
+ // *types.MemEvent carries memory-operation metadata (addr/length/flags).
+ _ Event = (*types.MemEvent)(nil)
)
diff --git a/internal/event/pair.go b/internal/event/pair.go
index 1626e88..115323b 100644
--- a/internal/event/pair.go
+++ b/internal/event/pair.go
@@ -26,6 +26,9 @@ type Pair struct {
Duration uint64
DurationToPrev uint64
Bytes uint64 // Number of bytes transferred (read/write/transfer syscalls only)
+ // AddressSpaceBytes tracks memory-region extent for memory syscalls
+ // (e.g. munmap/mremap) and is intentionally separate from I/O bytes.
+ AddressSpaceBytes uint64
}
func NewPair(enterEv Event) *Pair {
diff --git a/internal/eventloop_bytes_test.go b/internal/eventloop_bytes_test.go
index a7c25ef..636f80f 100644
--- a/internal/eventloop_bytes_test.go
+++ b/internal/eventloop_bytes_test.go
@@ -89,3 +89,61 @@ func TestApplyRetBytesForNullEnterRetExitPair(t *testing.T) {
t.Fatalf("pair.Bytes = %d, want 4096", pair.Bytes)
}
}
+
+func TestAddressSpaceBytesFromMem(t *testing.T) {
+ tests := []struct {
+ name string
+ ev *types.MemEvent
+ want uint64
+ }{
+ {
+ name: "munmap",
+ ev: &types.MemEvent{TraceId: types.SYS_ENTER_MUNMAP, Length: 4096},
+ want: 4096,
+ },
+ {
+ name: "mremap uses larger extent",
+ ev: &types.MemEvent{TraceId: types.SYS_ENTER_MREMAP, Length: 4096, Length2: 8192},
+ want: 8192,
+ },
+ {
+ name: "non-memory",
+ ev: &types.MemEvent{TraceId: types.SYS_ENTER_READ, Length: 123},
+ want: 0,
+ },
+ {
+ name: "nil",
+ ev: nil,
+ want: 0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := addressSpaceBytesFromMem(tt.ev); got != tt.want {
+ t.Fatalf("addressSpaceBytesFromMem() = %d, want %d", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestApplyAddressSpaceBytes(t *testing.T) {
+ pair := &event.Pair{
+ EnterEv: &types.MemEvent{
+ TraceId: types.SYS_ENTER_MUNMAP,
+ Length: 16384,
+ },
+ ExitEv: &types.RetEvent{
+ TraceId: types.SYS_EXIT_MUNMAP,
+ Ret: 0,
+ },
+ }
+
+ applyAddressSpaceBytes(pair)
+ if pair.AddressSpaceBytes != 16384 {
+ t.Fatalf("pair.AddressSpaceBytes = %d, want 16384", pair.AddressSpaceBytes)
+ }
+ if pair.Bytes != 0 {
+ t.Fatalf("pair.Bytes = %d, want 0 (IO bytes must stay separate)", pair.Bytes)
+ }
+}
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index 239724b..7e26cb0 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -40,6 +40,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
return e.handleEpollCtlExit(ep, ev)
case *types.PollEvent:
return e.handlePollExit(ep, ev)
+ case *types.MemEvent:
+ return e.handleMemExit(ep, ev)
case *types.NullEvent:
return e.handleNullExit(ep, ev)
case *types.FcntlEvent:
@@ -406,6 +408,15 @@ func (e *eventLoop) handlePollExit(ep *event.Pair, pollEv *types.PollEvent) bool
return true
}
+func (e *eventLoop) handleMemExit(ep *event.Pair, memEv *types.MemEvent) bool {
+ ep.Comm = e.comm(memEv.GetTid())
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
func pipeDescriptorName(flags, fd0, fd1 int32) string {
return fmt.Sprintf("pipe:%d:%d:%d", flags, fd0, fd1)
}
@@ -518,6 +529,21 @@ func applyRetBytes(ep *event.Pair) {
ep.Bytes = bytesFromRet(retEv)
}
+func applyAddressSpaceBytes(ep *event.Pair) {
+ if ep == nil {
+ return
+ }
+ memEv, ok := ep.EnterEv.(*types.MemEvent)
+ if !ok {
+ return
+ }
+ retEv, ok := ep.ExitEv.(*types.RetEvent)
+ if !ok || retEv.Ret < 0 {
+ return
+ }
+ ep.AddressSpaceBytes = addressSpaceBytesFromMem(memEv)
+}
+
// dropMalformedRawEvent records a warning when a raw BPF event cannot be
// decoded, keeping the error visible without crashing the event loop.
func (e *eventLoop) dropMalformedRawEvent(evType types.EventType, raw []byte) {
@@ -537,3 +563,20 @@ func bytesFromRet(retEv *types.RetEvent) uint64 {
return 0
}
}
+
+func addressSpaceBytesFromMem(memEv *types.MemEvent) uint64 {
+ if memEv == nil {
+ return 0
+ }
+ switch memEv.GetTraceId() {
+ case types.SYS_ENTER_MUNMAP:
+ return memEv.Length
+ case types.SYS_ENTER_MREMAP:
+ if memEv.Length > memEv.Length2 {
+ return memEv.Length
+ }
+ return memEv.Length2
+ default:
+ return 0
+ }
+}
diff --git a/internal/eventloop_memory_test.go b/internal/eventloop_memory_test.go
new file mode 100644
index 0000000..4ac9580
--- /dev/null
+++ b/internal/eventloop_memory_test.go
@@ -0,0 +1,46 @@
+package internal
+
+import (
+ "testing"
+
+ "ior/internal/event"
+ "ior/internal/globalfilter"
+ "ior/internal/types"
+)
+
+func TestHandleMemExitAppliesPairFilter(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{
+ filter: globalfilter.Filter{
+ Syscall: &globalfilter.StringFilter{Pattern: "openat"},
+ },
+ })
+
+ enter := &types.MemEvent{
+ EventType: types.ENTER_MEM_EVENT,
+ TraceId: types.SYS_ENTER_MUNMAP,
+ Time: 100,
+ Pid: 91,
+ Tid: 92,
+ Length: 4096,
+ }
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_MUNMAP,
+ Time: 200,
+ Pid: 91,
+ Tid: 92,
+ Ret: 0,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleMemExit(ep, enter); ok {
+ t.Fatal("handleMemExit should reject pair due to filter mismatch")
+ }
+}
+
+func TestInitRawHandlersRegistersMemoryEvents(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ if _, ok := el.rawHandlers[types.ENTER_MEM_EVENT]; !ok {
+ t.Fatal("ENTER_MEM_EVENT handler is not registered")
+ }
+}
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index 3c1217b..35714a1 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -149,6 +149,7 @@ func (e *eventLoop) initRawHandlers() {
e.registerSocketHandlers()
e.registerIPCHandlers()
e.registerPollingHandlers()
+ e.registerMemoryHandlers()
}
// registerOpenHandlers wires enter/exit handlers for open-family events.
@@ -351,6 +352,16 @@ func (e *eventLoop) registerPollingHandlers() {
}
}
+func (e *eventLoop) registerMemoryHandlers() {
+ e.rawHandlers[types.ENTER_MEM_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ memEv, ok := decodeRawEvent(e, types.ENTER_MEM_EVENT, raw, types.NewMemEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(memEv)
+ }
+}
+
func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) {
decoded := decode(raw)
if decoded == nil {
@@ -404,6 +415,7 @@ func (e *eventLoop) tracepointExited(exitEv event.Event, ch chan<- *event.Pair)
return
}
applyRetBytes(ep)
+ applyAddressSpaceBytes(ep)
tid := ep.EnterEv.GetTid()
ep.CalculateDurations(e.pairs.prevTime(tid))
e.pairs.setPrevTime(tid, ep.ExitEv.GetTime())
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index 20859ee..e3d0d67 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -87,6 +87,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
return generateExtraEpollCtl()
case KindPoll:
return generateExtraPoll(f.Name)
+ case KindMem:
+ return generateExtraMem(f.Name)
case KindOpen:
return generateExtraOpen(f)
case KindPathname:
@@ -222,6 +224,17 @@ func generateExtraPoll(name string) string {
}
}
+func generateExtraMem(name string) string {
+ switch name {
+ case "sys_enter_munmap":
+ return " ev->addr = (__u64)ctx->args[0];\n ev->length = (__u64)ctx->args[1];\n ev->length2 = 0;\n ev->flags = 0;\n"
+ case "sys_enter_mremap":
+ return " ev->addr = (__u64)ctx->args[0];\n ev->length = (__u64)ctx->args[1];\n ev->length2 = (__u64)ctx->args[2];\n ev->flags = (__u64)ctx->args[3];\n"
+ default:
+ return " ev->addr = 0;\n ev->length = 0;\n ev->length2 = 0;\n ev->flags = 0;\n"
+ }
+}
+
// eventStructName returns the C struct name for a TracepointKind. The mapping
// is driven by kindRegistry so adding a new kind only requires a registry entry.
func eventStructName(kind TracepointKind) string {
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index 7c391fb..a2da47d 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -22,6 +22,7 @@ const (
KindEventfd
KindEpollCtl
KindPoll
+ KindMem
)
type RetClassification string
@@ -148,6 +149,10 @@ func classifyNameOnly(name string) (ClassificationResult, bool) {
return ClassificationResult{Kind: KindPoll}, true
case "sys_enter_pselect6":
return ClassificationResult{Kind: KindPoll}, true
+ case "sys_enter_munmap":
+ return ClassificationResult{Kind: KindMem}, true
+ case "sys_enter_mremap":
+ return ClassificationResult{Kind: KindMem}, true
}
if strings.HasPrefix(name, "sys_enter_io_") {
return ClassificationResult{Kind: KindNull}, true
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 367d7c8..2bc0e88 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -425,6 +425,20 @@ func TestClassifyPselect6(t *testing.T) {
}
}
+func TestClassifyMunmap(t *testing.T) {
+ r := classifyFromData(t, FormatMunmap)
+ if r.Kind != KindMem {
+ t.Errorf("munmap: got kind %d, want KindMem", r.Kind)
+ }
+}
+
+func TestClassifyMremap(t *testing.T) {
+ r := classifyFromData(t, FormatMremap)
+ if r.Kind != KindMem {
+ t.Errorf("mremap: got kind %d, want KindMem", r.Kind)
+ }
+}
+
func TestClassifyKillRequiresGenerationFallback(t *testing.T) {
r := classifyFromData(t, FormatKill)
if r.Kind != KindNone {
@@ -477,6 +491,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"ppoll", FormatPpoll, FormatExitPpoll, KindPoll},
{"select", FormatSelect, FormatExitSelect, KindPoll},
{"pselect6", FormatPselect6, FormatExitPselect6, KindPoll},
+ {"munmap", FormatMunmap, FormatExitMunmap, KindMem},
+ {"mremap", FormatMremap, FormatExitMremap, KindMem},
{"kill", FormatKill, FormatExitKill, KindNull},
}
@@ -514,6 +530,8 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
{"ppoll", FormatPpoll, FormatExitPpoll, FamilyPolling},
{"select", FormatSelect, FormatExitSelect, FamilyPolling},
{"pselect6", FormatPselect6, FormatExitPselect6, FamilyPolling},
+ {"munmap", FormatMunmap, FormatExitMunmap, FamilyMemory},
+ {"mremap", FormatMremap, FormatExitMremap, FamilyMemory},
{"kill", FormatKill, FormatExitKill, FamilySignals},
}
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index a3baed7..3ec8d72 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -168,6 +168,18 @@ func TestGenerateMmapHandlerUsesFdArgumentIndex(t *testing.T) {
requireContains(t, output, "ev->fd = (__s32)ctx->args[4];")
}
+func TestGenerateMemHandler(t *testing.T) {
+ output := generateFromPair(t, FormatMremap, FormatExitMremap)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_mremap")`)
+ requireContains(t, output, "struct mem_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_MEM_EVENT;")
+ requireContains(t, output, "ev->addr = (__u64)ctx->args[0];")
+ requireContains(t, output, "ev->length = (__u64)ctx->args[1];")
+ requireContains(t, output, "ev->length2 = (__u64)ctx->args[2];")
+ requireContains(t, output, "ev->flags = (__u64)ctx->args[3];")
+}
+
func TestGenerateDup3Handler(t *testing.T) {
output := generateFromPair(t, FormatDup3, FormatExitDup3)
@@ -412,6 +424,7 @@ func TestGenerateAllEventTypes(t *testing.T) {
{KindEventfd, "ENTER_EVENTFD_EVENT", "EXIT_EVENTFD_EVENT"},
{KindEpollCtl, "ENTER_EPOLL_CTL_EVENT", "EXIT_EPOLL_CTL_EVENT"},
{KindPoll, "ENTER_POLL_EVENT", "EXIT_POLL_EVENT"},
+ {KindMem, "ENTER_MEM_EVENT", "EXIT_MEM_EVENT"},
}
for _, tt := range tests {
@@ -445,6 +458,7 @@ func TestEventStructNames(t *testing.T) {
{KindEventfd, "eventfd_event"},
{KindEpollCtl, "epoll_ctl_event"},
{KindPoll, "poll_event"},
+ {KindMem, "mem_event"},
}
for _, tt := range tests {
@@ -463,7 +477,7 @@ func TestEnterReject(t *testing.T) {
t.Error("KindNone should be enter-rejected")
}
- accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindPoll}
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindPoll, KindMem}
for _, k := range accepted {
if isEnterRejected(k) {
t.Errorf("kind %d should NOT be enter-rejected", k)
diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go
index e8efe99..756ed17 100644
--- a/internal/generate/kindregistry.go
+++ b/internal/generate/kindregistry.go
@@ -32,6 +32,7 @@ var kindRegistry = map[TracepointKind]kindMeta{
KindEventfd: {structName: "eventfd_event", enterAccepted: true},
KindEpollCtl: {structName: "epoll_ctl_event", enterAccepted: true},
KindPoll: {structName: "poll_event", enterAccepted: true},
+ KindMem: {structName: "mem_event", enterAccepted: true},
// KindNone is intentionally absent: it represents "unclassified" and is
// never enter-accepted. lookupKind returns the zero kindMeta (enterAccepted=false)
// for any unregistered kind, so KindNone is implicitly rejected.
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index e3e3036..6b08f98 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -201,6 +201,67 @@ format:
print fmt: "0x%lx", REC->ret
`
+const FormatMunmap = `name: sys_enter_munmap
+ID: 696
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:unsigned long addr; offset:16; size:8; signed:0;
+ field:size_t len; offset:24; size:8; signed:0;
+
+print fmt: "addr: 0x%08lx, len: 0x%08lx", ((unsigned long)(REC->addr)), ((unsigned long)(REC->len))
+`
+
+const FormatExitMunmap = `name: sys_exit_munmap
+ID: 695
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
+const FormatMremap = `name: sys_enter_mremap
+ID: 710
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:unsigned long addr; offset:16; size:8; signed:0;
+ field:unsigned long old_len; offset:24; size:8; signed:0;
+ field:unsigned long new_len; offset:32; size:8; signed:0;
+ field:unsigned long flags; offset:40; size:8; signed:0;
+ field:unsigned long new_addr; offset:48; size:8; signed:0;
+
+print fmt: "addr: 0x%08lx, old_len: 0x%08lx, new_len: 0x%08lx, flags: 0x%08lx, new_addr: 0x%08lx", ((unsigned long)(REC->addr)), ((unsigned long)(REC->old_len)), ((unsigned long)(REC->new_len)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->new_addr))
+`
+
+const FormatExitMremap = `name: sys_exit_mremap
+ID: 709
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
const FormatExitMsync = `name: sys_exit_msync
ID: 1028
format:
diff --git a/internal/statsengine/engine.go b/internal/statsengine/engine.go
index 7d85e96..85017fd 100644
--- a/internal/statsengine/engine.go
+++ b/internal/statsengine/engine.go
@@ -47,13 +47,14 @@ type Engine struct {
startedAt time.Time
topN int
- totalSyscalls uint64
- totalErrors uint64
- totalBytes uint64
- totalReadBytes uint64
- totalWriteBytes uint64
- totalLatency uint64
- totalGap uint64
+ totalSyscalls uint64
+ totalErrors uint64
+ totalBytes uint64
+ totalAddressSpaceBytes uint64
+ totalReadBytes uint64
+ totalWriteBytes uint64
+ totalLatency uint64
+ totalGap uint64
syscalls *syscallAccumulator
families *familyAccumulator
@@ -70,13 +71,14 @@ type snapshotInputs struct {
now time.Time
startedAt time.Time
- totalSyscalls uint64
- totalErrors uint64
- totalBytes uint64
- totalReadBytes uint64
- totalWriteBytes uint64
- totalLatency uint64
- totalGap uint64
+ totalSyscalls uint64
+ totalErrors uint64
+ totalBytes uint64
+ totalAddressSpaceBytes uint64
+ totalReadBytes uint64
+ totalWriteBytes uint64
+ totalLatency uint64
+ totalGap uint64
latencySeries []float64
gapSeries []float64
@@ -130,6 +132,7 @@ func (e *Engine) Reset() {
e.totalSyscalls = 0
e.totalErrors = 0
e.totalBytes = 0
+ e.totalAddressSpaceBytes = 0
e.totalReadBytes = 0
e.totalWriteBytes = 0
e.totalLatency = 0
@@ -157,6 +160,7 @@ func (e *Engine) Ingest(pair *event.Pair) {
now := e.now()
e.totalSyscalls++
e.totalBytes += pair.Bytes
+ e.totalAddressSpaceBytes += pair.AddressSpaceBytes
e.totalLatency += pair.Duration
e.totalGap += pair.DurationToPrev
@@ -209,24 +213,25 @@ func (e *Engine) captureSnapshotInputs() snapshotInputs {
defer e.mu.Unlock()
return snapshotInputs{
- now: e.now(),
- startedAt: e.startedAt,
- totalSyscalls: e.totalSyscalls,
- totalErrors: e.totalErrors,
- totalBytes: e.totalBytes,
- totalReadBytes: e.totalReadBytes,
- totalWriteBytes: e.totalWriteBytes,
- totalLatency: e.totalLatency,
- totalGap: e.totalGap,
- latencySeries: e.latencySeries.Values(),
- gapSeries: e.gapSeries.Values(),
- throughputSeries: e.throughputSeries.Values(),
- syscalls: e.syscalls.snapshotInputs(),
- families: e.families.snapshotInputs(),
- files: e.files.snapshotInputs(),
- processes: e.processes.snapshotInputs(),
- latencyHist: e.latencyHist.snapshotInputs(),
- gapHist: e.gapHist.snapshotInputs(),
+ now: e.now(),
+ startedAt: e.startedAt,
+ totalSyscalls: e.totalSyscalls,
+ totalErrors: e.totalErrors,
+ totalBytes: e.totalBytes,
+ totalAddressSpaceBytes: e.totalAddressSpaceBytes,
+ totalReadBytes: e.totalReadBytes,
+ totalWriteBytes: e.totalWriteBytes,
+ totalLatency: e.totalLatency,
+ totalGap: e.totalGap,
+ latencySeries: e.latencySeries.Values(),
+ gapSeries: e.gapSeries.Values(),
+ throughputSeries: e.throughputSeries.Values(),
+ syscalls: e.syscalls.snapshotInputs(),
+ families: e.families.snapshotInputs(),
+ files: e.files.snapshotInputs(),
+ processes: e.processes.snapshotInputs(),
+ latencyHist: e.latencyHist.snapshotInputs(),
+ gapHist: e.gapHist.snapshotInputs(),
}
}
@@ -287,8 +292,10 @@ func populateSnapshotFields(snap *Snapshot, in snapshotInputs, elapsed time.Dura
snap.TotalSyscalls = in.totalSyscalls
snap.TotalErrors = in.totalErrors
snap.TotalBytes = in.totalBytes
+ snap.TotalAddressSpaceBytes = in.totalAddressSpaceBytes
snap.SyscallRatePerSec = safeRate(in.totalSyscalls, rateDiv)
snap.ErrorRatePerSec = safeRate(in.totalErrors, rateDiv)
+ snap.AddressSpaceBytesPerSec = safeRate(in.totalAddressSpaceBytes, rateDiv)
snap.ReadBytesPerSec = safeRate(in.totalReadBytes, rateDiv)
snap.WriteBytesPerSec = safeRate(in.totalWriteBytes, rateDiv)
snap.LatencyMeanNs = safeMean(in.totalLatency, in.totalSyscalls)
diff --git a/internal/statsengine/engine_reset_test.go b/internal/statsengine/engine_reset_test.go
index c09c059..5e8388a 100644
--- a/internal/statsengine/engine_reset_test.go
+++ b/internal/statsengine/engine_reset_test.go
@@ -9,7 +9,7 @@ import (
func TestEngineResetClearsAccumulatedStats(t *testing.T) {
e := NewEngine(8)
- e.Ingest(newEnginePair(types.SYS_ENTER_READ, 7, types.READ_CLASSIFIED, "test", 1, "/tmp/a", 7, 1000, 50))
+ e.Ingest(newEnginePair(types.SYS_ENTER_READ, 7, types.READ_CLASSIFIED, "test", 1, "/tmp/a", 7, 512, 1000, 50))
before, err := e.Snapshot()
if err != nil {
t.Fatalf("unexpected snapshot error: %v", err)
@@ -23,7 +23,7 @@ func TestEngineResetClearsAccumulatedStats(t *testing.T) {
if err != nil {
t.Fatalf("unexpected snapshot error after reset: %v", err)
}
- if after.TotalSyscalls != 0 || after.TotalBytes != 0 || after.TotalErrors != 0 {
+ if after.TotalSyscalls != 0 || after.TotalBytes != 0 || after.TotalAddressSpaceBytes != 0 || after.TotalErrors != 0 {
t.Fatalf("expected totals cleared after reset, got %+v", after)
}
if after.Elapsed > 2*time.Second {
diff --git a/internal/statsengine/engine_test.go b/internal/statsengine/engine_test.go
index 9543405..0500d20 100644
--- a/internal/statsengine/engine_test.go
+++ b/internal/statsengine/engine_test.go
@@ -26,11 +26,11 @@ func TestEngineIngestAndSnapshotIntegration(t *testing.T) {
clock := &fakeClock{now: time.Unix(1000, 0)}
engine := newEngineWithClock(2, clock.Now)
- engine.Ingest(newEnginePair(types.SYS_ENTER_READ, 100, types.READ_CLASSIFIED, "proc-a", 1, "/tmp/a", 100, 10, 3))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_READ, 100, types.READ_CLASSIFIED, "proc-a", 1, "/tmp/a", 100, 0, 10, 3))
clock.Advance(500 * time.Millisecond)
- engine.Ingest(newEnginePair(types.SYS_ENTER_WRITE, -1, types.WRITE_CLASSIFIED, "proc-a", 1, "/tmp/a", 50, 20, 5))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_WRITE, -1, types.WRITE_CLASSIFIED, "proc-a", 1, "/tmp/a", 50, 0, 20, 5))
clock.Advance(500 * time.Millisecond)
- engine.Ingest(newEnginePair(types.SYS_ENTER_COPY_FILE_RANGE, 80, types.TRANSFER_CLASSIFIED, "proc-b", 2, "/tmp/b", 20, 40, 8))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_COPY_FILE_RANGE, 80, types.TRANSFER_CLASSIFIED, "proc-b", 2, "/tmp/b", 20, 0, 40, 8))
clock.Advance(1 * time.Second)
snap, err := engine.Snapshot()
@@ -44,6 +44,9 @@ func TestEngineIngestAndSnapshotIntegration(t *testing.T) {
if snap.TotalSyscalls != 3 || snap.TotalErrors != 1 || snap.TotalBytes != 170 {
t.Fatalf("unexpected totals: syscalls=%d errors=%d bytes=%d", snap.TotalSyscalls, snap.TotalErrors, snap.TotalBytes)
}
+ if snap.TotalAddressSpaceBytes != 0 {
+ t.Fatalf("unexpected address-space total: %d", snap.TotalAddressSpaceBytes)
+ }
if snap.LatencyMeanNs != (10+20+40)/3.0 {
t.Fatalf("unexpected latency mean: %v", snap.LatencyMeanNs)
}
@@ -89,10 +92,10 @@ func TestEngineAggregatesSyscallFamilies(t *testing.T) {
clock := &fakeClock{now: time.Unix(3000, 0)}
engine := newEngineWithClock(10, clock.Now)
- engine.Ingest(newEnginePair(types.SYS_ENTER_EPOLL_WAIT, 0, types.UNCLASSIFIED, "poller", 1, "", 0, 100, 1))
- engine.Ingest(newEnginePair(types.SYS_ENTER_POLL, -1, types.UNCLASSIFIED, "poller", 1, "", 0, 300, 2))
- engine.Ingest(newEnginePair(types.SYS_ENTER_GETPID, 0, types.UNCLASSIFIED, "proc", 2, "", 0, 50, 3))
- engine.Ingest(newEnginePair(types.SYS_ENTER_READ, 4, types.READ_CLASSIFIED, "reader", 3, "/tmp/a", 4, 25, 4))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_EPOLL_WAIT, 0, types.UNCLASSIFIED, "poller", 1, "", 0, 0, 100, 1))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_POLL, -1, types.UNCLASSIFIED, "poller", 1, "", 0, 0, 300, 2))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_GETPID, 0, types.UNCLASSIFIED, "proc", 2, "", 0, 0, 50, 3))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_READ, 4, types.READ_CLASSIFIED, "reader", 3, "/tmp/a", 4, 0, 25, 4))
clock.Advance(time.Second)
snap, err := engine.Snapshot()
@@ -148,6 +151,29 @@ func TestEngineSnapshotWithNoEvents(t *testing.T) {
}
}
+func TestEngineTracksAddressSpaceBytesSeparately(t *testing.T) {
+ clock := &fakeClock{now: time.Unix(4000, 0)}
+ engine := newEngineWithClock(10, clock.Now)
+
+ engine.Ingest(newEnginePair(types.SYS_ENTER_MUNMAP, 0, types.UNCLASSIFIED, "proc", 1, "", 0, 4096, 10, 1))
+ engine.Ingest(newEnginePair(types.SYS_ENTER_MREMAP, 0, types.UNCLASSIFIED, "proc", 1, "", 0, 8192, 20, 2))
+ clock.Advance(2 * time.Second)
+
+ snap, err := engine.Snapshot()
+ if err != nil {
+ t.Fatalf("Snapshot() error = %v", err)
+ }
+ if snap.TotalBytes != 0 {
+ t.Fatalf("TotalBytes = %d, want 0 for non-IO memory operations", snap.TotalBytes)
+ }
+ if snap.TotalAddressSpaceBytes != 12288 {
+ t.Fatalf("TotalAddressSpaceBytes = %d, want 12288", snap.TotalAddressSpaceBytes)
+ }
+ if math.Abs(snap.AddressSpaceBytesPerSec-6144.0) > 1e-9 {
+ t.Fatalf("AddressSpaceBytesPerSec = %v, want 6144", snap.AddressSpaceBytesPerSec)
+ }
+}
+
func TestEngineTrendDetection(t *testing.T) {
if got := detectTrend(make([]float64, trendWindowSlots*2)); got.Direction != TrendStable {
t.Fatalf("expected stable for flat data, got %+v", got)
@@ -175,14 +201,15 @@ func TestEngineTrendDetection(t *testing.T) {
}
}
-func newEnginePair(traceID types.TraceId, ret int64, retType uint32, comm string, pid uint32, path string, bytes uint64, duration uint64, gap uint64) *event.Pair {
+func newEnginePair(traceID types.TraceId, ret int64, retType uint32, comm string, pid uint32, path string, bytes uint64, addressSpaceBytes uint64, duration uint64, gap uint64) *event.Pair {
return &event.Pair{
- EnterEv: &types.RetEvent{TraceId: traceID, Pid: pid},
- ExitEv: &types.RetEvent{TraceId: traceID, Pid: pid, Ret: ret, RetType: retType},
- Comm: comm,
- Duration: duration,
- DurationToPrev: gap,
- Bytes: bytes,
- File: file.NewFd(3, path, -1),
+ EnterEv: &types.RetEvent{TraceId: traceID, Pid: pid},
+ ExitEv: &types.RetEvent{TraceId: traceID, Pid: pid, Ret: ret, RetType: retType},
+ Comm: comm,
+ Duration: duration,
+ DurationToPrev: gap,
+ Bytes: bytes,
+ AddressSpaceBytes: addressSpaceBytes,
+ File: file.NewFd(3, path, -1),
}
}
diff --git a/internal/statsengine/snapshot.go b/internal/statsengine/snapshot.go
index 859cd2e..bec92fb 100644
--- a/internal/statsengine/snapshot.go
+++ b/internal/statsengine/snapshot.go
@@ -30,14 +30,16 @@ type Snapshot struct {
GeneratedAt time.Time
Elapsed time.Duration
- TotalSyscalls uint64
- TotalErrors uint64
- TotalBytes uint64
-
- SyscallRatePerSec float64
- ErrorRatePerSec float64
- ReadBytesPerSec float64
- WriteBytesPerSec float64
+ TotalSyscalls uint64
+ TotalErrors uint64
+ TotalBytes uint64
+ TotalAddressSpaceBytes uint64
+
+ SyscallRatePerSec float64
+ ErrorRatePerSec float64
+ AddressSpaceBytesPerSec float64
+ ReadBytesPerSec float64
+ WriteBytesPerSec float64
LatencyMeanNs float64
GapMeanNs float64
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go
index 9c0ac9c..1f89ee4 100644
--- a/internal/types/fastdecode.go
+++ b/internal/types/fastdecode.go
@@ -24,6 +24,7 @@ const (
epollCtlEventSize = 40
pollEventSize = 40
pollEventSizeV1 = 36
+ memEventSize = 56
)
func NewOpenEventFast(raw []byte) *OpenEvent {
@@ -339,3 +340,23 @@ func NewPollEventFast(raw []byte) *PollEvent {
p.TimeoutNs = int64(binary.LittleEndian.Uint64(raw[timeoutOffset : timeoutOffset+8]))
return p
}
+
+func NewMemEventFast(raw []byte) *MemEvent {
+ if len(raw) < memEventSize {
+ return nil
+ }
+ if len(raw) != memEventSize {
+ return NewMemEvent(raw)
+ }
+ m := poolOfMemEvents.Get().(*MemEvent)
+ m.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ m.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ m.Time = binary.LittleEndian.Uint64(raw[8:16])
+ m.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ m.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ m.Addr = binary.LittleEndian.Uint64(raw[24:32])
+ m.Length = binary.LittleEndian.Uint64(raw[32:40])
+ m.Length2 = binary.LittleEndian.Uint64(raw[40:48])
+ m.Flags = binary.LittleEndian.Uint64(raw[48:56])
+ return m
+}
diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go
index 272e6d4..8bb4b34 100644
--- a/internal/types/fastdecode_test.go
+++ b/internal/types/fastdecode_test.go
@@ -218,6 +218,29 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) {
t.Fatalf("poll decode mismatch")
}
})
+
+ t.Run("MemEvent", func(t *testing.T) {
+ ev := &MemEvent{
+ EventType: ENTER_MEM_EVENT,
+ TraceId: SYS_ENTER_MREMAP,
+ Time: 1,
+ Pid: 2,
+ Tid: 3,
+ Addr: 0x1000,
+ Length: 4096,
+ Length2: 8192,
+ Flags: 1,
+ }
+ raw, _ := ev.Bytes()
+
+ slow := NewMemEvent(raw)
+ fast := NewMemEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("mem decode mismatch")
+ }
+ })
}
func TestNewSocketpairEventFastKernelLayout(t *testing.T) {
diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go
index 6f95c7a..63cb28c 100644
--- a/internal/types/generated_types.go
+++ b/internal/types/generated_types.go
@@ -100,6 +100,8 @@ const ENTER_EPOLL_CTL_EVENT = 29
const EXIT_EPOLL_CTL_EVENT = 30
const ENTER_POLL_EVENT = 31
const EXIT_POLL_EVENT = 32
+const ENTER_MEM_EVENT = 33
+const EXIT_MEM_EVENT = 34
const UNCLASSIFIED = 0
const READ_CLASSIFIED = 1
const WRITE_CLASSIFIED = 2
@@ -1948,3 +1950,74 @@ func (p *PollEvent) Bytes() ([]byte, error) {
func (p *PollEvent) Recycle() {
poolOfPollEvents.Put(p)
}
+
+type MemEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Addr uint64
+ Length uint64
+ Length2 uint64
+ Flags uint64
+}
+
+func (m MemEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Addr:%v Length:%v Length2:%v Flags:%v", m.EventType, m.TraceId, m.Time, m.Pid, m.Tid, m.Addr, m.Length, m.Length2, m.Flags)
+}
+
+func (m MemEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*MemEvent)
+ if !ok {
+ return false
+ }
+ return m.EventType == otherConcrete.EventType && m.TraceId == otherConcrete.TraceId && m.Time == otherConcrete.Time && m.Pid == otherConcrete.Pid && m.Tid == otherConcrete.Tid && m.Addr == otherConcrete.Addr && m.Length == otherConcrete.Length && m.Length2 == otherConcrete.Length2 && m.Flags == otherConcrete.Flags
+}
+
+func (m *MemEvent) GetEventType() EventType {
+ return m.EventType
+}
+
+func (m *MemEvent) GetTraceId() TraceId {
+ return m.TraceId
+}
+
+func (m *MemEvent) GetPid() uint32 {
+ return m.Pid
+}
+
+func (m *MemEvent) GetTid() uint32 {
+ return m.Tid
+}
+
+func (m *MemEvent) GetTime() uint64 {
+ return m.Time
+}
+
+var poolOfMemEvents = sync.Pool{
+ New: func() any { return &MemEvent{} },
+}
+
+func NewMemEvent(raw []byte) *MemEvent {
+ m := poolOfMemEvents.Get().(*MemEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, m); err != nil {
+ *m = MemEvent{}
+ poolOfMemEvents.Put(m)
+ return nil
+ }
+ return m
+}
+
+func (m *MemEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, m)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (m *MemEvent) Recycle() {
+ poolOfMemEvents.Put(m)
+}