Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ Whether you're debugging a production issue, optimizing performance, or just cur
- **Session replay** to replay past observations
- **Watch by binary or PID** of the target Go program

> [!NOTE]
> `xgotop` only supports `arm64` architecture for now, we will rollout support for `amd64` soon!

## Usage

Running `xgotop` is relatively straightforward. Get a Go binary ready, and run each of the commands below in a separate terminal:
Expand Down Expand Up @@ -129,7 +126,7 @@ The sampling feature is one of the most powerful ways to reduce overhead when mo
You can sample the following event types:

- `newgoroutine`: Goroutine creation
- `goexit`: Goroutine exit
- `goexit`: Goroutine exit
- `makemap`: Map allocation
- `makeslice`: Slice allocation
- `newobject`: Object allocation
Expand Down
2 changes: 1 addition & 1 deletion gen.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package main

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -type go_runtime_event_t -target arm64 -output-dir cmd/xgotop ebpf xgotop.bpf.c
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -type go_runtime_event_t -target ${GOARCH} -output-dir cmd/xgotop ebpf xgotop.bpf.c
82 changes: 39 additions & 43 deletions xgotop.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

// func casgstatus(gp *g, oldval, newval uint32)
SEC("uprobe/runtime.casgstatus")
int BPF_KPROBE(uprobe_casgstatus,
const void *gp,
const u32 oldval,
const u32 newval) {
int BPF_KPROBE(uprobe_casgstatus, const void *gp, const u32 oldval, const u32 newval) {
u64 probe_start_ns = bpf_ktime_get_ns();
u64 _ret, gp_id, g_id, g_parent_id;
struct go_runtime_g g;
Expand All @@ -22,7 +19,7 @@ int BPF_KPROBE(uprobe_casgstatus,
bpf_printk("casgstatus: goid=%llu, oldval=%u, newval=%u", g.goid, oldval, newval);
#endif

_ret = get_go_g_struct_arm(ctx, &g);
_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("Failed to read g, ret=%d", _ret);
return 0;
Expand All @@ -35,10 +32,12 @@ int BPF_KPROBE(uprobe_casgstatus,
u64 *ts = bpf_map_lookup_elem(&goroutines_in_exit, &gp_id);
if (ts != NULL) {
// Still send the event for the CAS_G_STATUS
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_CAS_G_STATUS, g_id, g_parent_id, oldval, newval, gp_id, 0, 0, probe_start_ns);

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_CAS_G_STATUS, g_id, g_parent_id, oldval,
newval, gp_id, 0, 0, probe_start_ns);

// Notify the userspace program that the goroutine has exited
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_GOEXIT, g_id, g_parent_id, gp_id, *ts, 0, 0, 0, probe_start_ns);
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_GOEXIT, g_id, g_parent_id, gp_id, *ts, 0,
0, 0, probe_start_ns);
_ret = bpf_map_delete_elem(&goroutines_in_exit, &g_id);
if (_ret < 0) {
bpf_printk("Failed to delete goroutines_in_exit, ret=%d", _ret);
Expand All @@ -52,17 +51,19 @@ int BPF_KPROBE(uprobe_casgstatus,

u64 *callerg_id = bpf_map_lookup_elem(&goroutines_in_creation, &g_id);
if (callerg_id != NULL) {
// This function is called inside the newproc1 function, so we need to send an event for the caller.
// We cannot use a uretprobe on newproc1 so we're using this trick!
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_NEWGOROUTINE, g_id, g_parent_id, *callerg_id, gp_id, 0, 0, 0, probe_start_ns);
// This function is called inside the newproc1 function, so we need to send an event for the
// caller. We cannot use a uretprobe on newproc1 so we're using this trick!
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_NEWGOROUTINE, g_id, g_parent_id, *callerg_id,
gp_id, 0, 0, 0, probe_start_ns);
_ret = bpf_map_delete_elem(&goroutines_in_creation, &g_id);
if (_ret < 0) {
bpf_printk("Failed to delete goroutines_in_creation, ret=%d", _ret);
return 0;
}
}

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_CAS_G_STATUS, g_id, g_parent_id, oldval, newval, gp_id, 0, 0, probe_start_ns);
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_CAS_G_STATUS, g_id, g_parent_id, oldval, newval,
gp_id, 0, 0, probe_start_ns);
return 0;
}

Expand All @@ -71,13 +72,12 @@ int BPF_KPROBE(uprobe_casgstatus,
// }
// _type is abi.Type
SEC("uprobe/runtime.newobject")
int BPF_KPROBE(uprobe_newobject,
const void *typ) {
int BPF_KPROBE(uprobe_newobject, const void *typ) {
u64 probe_start_ns = bpf_ktime_get_ns();
u64 _ret;
struct go_runtime_g g;

_ret = get_go_g_struct_arm(ctx, &g);
_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("Failed to read g, ret=%d", _ret);
return 0;
Expand All @@ -97,21 +97,19 @@ int BPF_KPROBE(uprobe_newobject,
bpf_printk("newobject: size=%llu, kind=%llu", go_type.size, go_type.kind);
#endif

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_NEW_OBJECT, g.goid, g.parentGoid, go_type.size, go_type.kind, 0, 0, 0, probe_start_ns);
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_NEW_OBJECT, g.goid, g.parentGoid, go_type.size,
go_type.kind, 0, 0, 0, probe_start_ns);
return 0;
}

// func makeslice(et *_type, len, cap int) unsafe.Pointer
// func makeslice(et *_type, len, cap int) unsafe.Pointer
SEC("uprobe/runtime.makeslice")
int BPF_KPROBE(uprobe_makeslice,
const void *typ,
const u64 len,
const u64 cap) {
int BPF_KPROBE(uprobe_makeslice, const void *typ, const u64 len, const u64 cap) {
u64 probe_start_ns = bpf_ktime_get_ns();
u64 _ret;

struct go_runtime_g g;
_ret = get_go_g_struct_arm(ctx, &g);
_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("Failed to read g, ret=%d", _ret);
return 0;
Expand All @@ -133,21 +131,19 @@ int BPF_KPROBE(uprobe_makeslice,
bpf_printk("makeslice: len=%llu, cap=%llu", len, cap);
#endif

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_MAKE_SLICE, g.goid, g.parentGoid, go_type.size, go_type.kind, len, cap, 0, probe_start_ns);
SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_MAKE_SLICE, g.goid, g.parentGoid, go_type.size,
go_type.kind, len, cap, 0, probe_start_ns);
return 0;
}

// func makemap(t *abi.MapType, hint int, m *maps.Map) *maps.Map
SEC("uprobe/runtime.makemap")
int BPF_KPROBE(uprobe_makemap,
const void *typ,
const u64 hint,
const void *m) {
int BPF_KPROBE(uprobe_makemap, const void *typ, const u64 hint, const void *m) {
u64 probe_start_ns = bpf_ktime_get_ns();
u64 _ret;

struct go_runtime_g g;
_ret = get_go_g_struct_arm(ctx, &g);
_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("Failed to read g, ret=%d", _ret);
return 0;
Expand All @@ -162,34 +158,34 @@ int BPF_KPROBE(uprobe_makemap,
bpf_printk("Failed to read go_map, ret=%d, go_map=%p", _ret, go_map);
return 0;
}

struct go_abi_type key_type, elem_type;
_ret = bpf_probe_read(&key_type, sizeof(key_type), (void*)go_map.key_ptr);
_ret = bpf_probe_read(&key_type, sizeof(key_type), (void *)go_map.key_ptr);
if (_ret < 0) {
bpf_printk("Failed to read key_type, ret=%d, key_type=%p", _ret, key_type);
return 0;
}
_ret = bpf_probe_read(&elem_type, sizeof(elem_type), (void*)go_map.elem_ptr);

_ret = bpf_probe_read(&elem_type, sizeof(elem_type), (void *)go_map.elem_ptr);
if (_ret < 0) {
bpf_printk("Failed to read elem_type, ret=%d, elem_type=%p", _ret, elem_type);
return 0;
}

#ifdef BPF_DEBUG
bpf_printk("makemap: key size=%llu, key kind=%llu", key_type.size, key_type.kind);
bpf_printk("makemap: elem size=%llu, elem kind=%llu, hint=%llu", elem_type.size, elem_type.kind, hint);
bpf_printk("makemap: elem size=%llu, elem kind=%llu, hint=%llu", elem_type.size, elem_type.kind,
hint);
#endif

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_MAKE_MAP, g.goid, g.parentGoid, key_type.size, key_type.kind, elem_type.size, elem_type.kind, hint, probe_start_ns);

SEND_EVENT_WITH_SAMPLING(GO_RUNTIME_EVENT_TYPE_MAKE_MAP, g.goid, g.parentGoid, key_type.size,
key_type.kind, elem_type.size, elem_type.kind, hint, probe_start_ns);
return 0;
}

// func newproc1(fn *funcval, callergp *g, callerpc uintptr, parked bool, waitreason waitReason) *g
SEC("uprobe/runtime.newproc1")
int BPF_KPROBE(uprobe_newproc1,
const void *__skip_fn,
const void *callergp) {
int BPF_KPROBE(uprobe_newproc1, const void *__skip_fn, const void *callergp) {
u64 _ret, goid, callergoid;
struct go_runtime_g g;

Expand All @@ -200,8 +196,8 @@ int BPF_KPROBE(uprobe_newproc1,
}

callergoid = g.goid;
_ret = get_go_g_struct_arm(ctx, &g);

_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("newproc1: failed to read g, ret=%d", _ret);
return 0;
Expand Down Expand Up @@ -229,7 +225,7 @@ int BPF_KPROBE(uprobe_goexit1) {
u64 _ret;

struct go_runtime_g g;
_ret = get_go_g_struct_arm(ctx, &g);
_ret = get_go_g_struct(ctx, &g);
if (_ret < 0) {
bpf_printk("goexit1: failed to read g, ret=%d", _ret);
return 0;
Expand All @@ -247,4 +243,4 @@ int BPF_KPROBE(uprobe_goexit1) {
}

return 0;
}
}
Loading