Tracing Go Function Arguments in Production

Cover Image for Tracing Go Function Arguments in Production
Animesh Pathak
Table of Contents

Kubernetes is an incredibly complex system that is used to manage and orchestrate containerized applications. It provides a way to run and manage your applications across multiple machines, abstracting away the underlying infrastructure complexities. With Kubernetes, you can easily deploy, scale, and update your applications, ensuring high availability, fault tolerance, and efficient resource utilization.

With so many moving parts, it can be difficult to pinpoint performance issues and troubleshoot problems when they arise. However, eBPF (extended Berkeley Packet Filter) has emerged as a powerful tool that can be used to trace system events in real-time and provide detailed insights into system behaviour. In this blog post, we will explore how eBPF can be used to trace Go function arguments in a Kubernetes environment.

What is eBPF?

eBPF (extended Berkeley Packet Filter) is a revolutionary technology that allows you to safely and efficiently extend the capabilities of the Linux kernel without modifying its source code. It provides a programmable and highly efficient way to analyze and manipulate network packets, system calls, and various kernel events. With eBPF, you can write small, sandboxed programs that run directly within the kernel, enabling powerful networking, monitoring, and security applications. It has gained significant popularity in the Kubernetes ecosystem, empowering developers to build advanced networking and observability solutions for containerized environments.

Why Trace Go Function Arguments?

In a Kubernetes environment, applications are often written in Go, a popular programming language for building containerized applications. When performance issues arise, it can be challenging to pinpoint the root cause. By tracing Go function arguments, we can gain valuable insights into the application's behaviour and identify areas where improvements can be made.

Tracing Go Function Arguments with eBPF

eBPF is a powerful tool that can be used to trace system events in real-time. It allows us to insert probes into the kernel or user-space code and capture data about the system's behavior.

First, make sure you have the bcc tool installed on your system. You can refer to the bcc documentation for installation instructions: https://github.com/iovisor/bcc

Let's get started with the code, in the below example we are writing a code to add the number passed in the arguments.

//main.go
package main

import (
    "fmt"
)

func add(a, b int) int {
    return a + b
}

func main() {
    result := add(2, 3)
    fmt.Println("Result:", result)
}

Now it's time to write our eBPF program:

//trace_args.c
#include <uapi/linux/ptrace.h>

BPF_PERF_OUTPUT(events);

int trace_func_args(struct pt_regs *ctx) {
    int arg1 = PT_REGS_PARM1(ctx);
    int arg2 = PT_REGS_PARM2(ctx);

    bpf_trace_printk("arg1: %d, arg2: %d\\n", arg1, arg2);

    return 0;
}

We are specifying the function we want to trace and capture the function arguments using the arg1 and arg2 variables. Save the above code to a file called trace_args.c.
Compile the eBPF program using the bcc tool:

clang -O2 -target bpf -c trace_args.c -o trace_args.o

Once we have written our eBPF program, let's modify the Go code to load and attach the eBPF program:

//main.go
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "unsafe"

    "golang.org/x/sys/unix"
)

const (
    perfEventTypeTracepoint = 10
    tracepointCategory      = "go"

    bpfProgramPath = "./trace_args.o"
)

func loadBPFProgram() int {
    fd, err := unix.BPF(unix.BPF_PROG_LOAD, bpfProgramPath)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to load BPF program: %v\n", err)
        return -1
    }
    return fd
}

func attachTracepoint(fd int, tracepoint string) int {
    attr := &unix.PerfEventAttr{
        Type:   perfEventTypeTracepoint,
        Config: uint64(fd),
    }
    attr.SetSampleFreq(1)
    attr.SetWakeupEvents(1)

    tpName := fmt.Sprintf("%s:%s", tracepointCategory, tracepoint)
    cTpName := unix.ByteSliceFromString(tpName)
    attr.SetTracepoint(unix.BytePtrFromString(tpName))

    _, _, errno := unix.Syscall6(
        unix.SYS_PERF_EVENT_OPEN,
        uintptr(unsafe.Pointer(attr)),
        unix.Getpid(),
        -1,
        -1,
        unix.PERF_FLAG_FD_CLOEXEC,
    )
    if errno != 0 {
        fmt.Fprintf(os.Stderr, "Failed to attach to tracepoint %s: %v\n", tracepoint, errno)
        return -1
    }
    return 0
}

func main() {
    fd := loadBPFProgram()
    if fd < 0 {
        os.Exit(1)
    }
    defer unix.Close(fd)

    if attachTracepoint(fd, "trace_func_args") < 0 {
        os.Exit(1)
    }

    signalChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
    <-signalChan

    fmt.Println("Exiting...")
}

With our eBPF program running, we can now send requests to our application and capture the function arguments in real-time. This allows us to gain valuable insights into our application's behaviour and identify areas where improvements can be made.

Now, let's compile and run the modified Go code:

go build -o main main.go
sudo ./main

When you run the code, it will trace the add function arguments using the eBPF program and print them. Press Ctrl+C to stop the program.

The output will be similar to:

arg1: 2, arg2: 3

Conclusion

eBPF is a powerful tool that can be used to trace system events in real-time and provide detailed insights into system behaviour. By tracing Go function arguments in a Kubernetes environment, we can gain valuable insights into our application's behaviour and identify areas where improvements can be made. We can improve the performance and reliability of our applications and ensure that they are running at their best, with the power of eBPF.

Author


More Stories

Cover Image for Canary Testing: A Comprehensive Guide for Developers

Canary Testing: A Comprehensive Guide for Developers

Animesh Pathak

Table of Contents What’s Canary Testing, Anyway? Imagine you’re a miner with a canary in a cage. If the air...

Cover Image for Mock vs Stub vs Fake: Understand the difference

Mock vs Stub vs Fake: Understand the difference

Arindam Majumder

Table of Contents Introduction Testing software is like putting it through a series of challenges to make sure it’s tough...