diff --git a/tools/probe-ctrl/.gitignore b/tools/probe-ctrl/.gitignore new file mode 100644 index 000000000..4162e8151 --- /dev/null +++ b/tools/probe-ctrl/.gitignore @@ -0,0 +1 @@ +probe-ctrl diff --git a/tools/probe-ctrl/Makefile b/tools/probe-ctrl/Makefile new file mode 100644 index 000000000..2b091961b --- /dev/null +++ b/tools/probe-ctrl/Makefile @@ -0,0 +1,17 @@ +.PHONY: build clean lint test vuln-check + +build: + go build ./cmd/probe-ctrl + +clean: + go clean -cache -i + rm probe-ctrl + +lint: + go tool staticcheck -checks=all ./... + +test: + go test ./... + +vuln-check: + go tool govulncheck ./... diff --git a/tools/probe-ctrl/README.md b/tools/probe-ctrl/README.md new file mode 100644 index 000000000..1549d208e --- /dev/null +++ b/tools/probe-ctrl/README.md @@ -0,0 +1,4 @@ +probe-ctrl +========== + +The OTel eBPF profiler can be controlled by `probe-ctrl` when launched with the `-load-probe` argument. This control is achieved by attaching the OTel eBPF profiler's generic eBPF program to designated targets. This capability allows for the dynamic activation and deactivation of event-based profiling during the runtime of the OTel eBPF profiler. diff --git a/tools/probe-ctrl/cmd/probe-ctrl/main.go b/tools/probe-ctrl/cmd/probe-ctrl/main.go new file mode 100644 index 000000000..35decb358 --- /dev/null +++ b/tools/probe-ctrl/cmd/probe-ctrl/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "flag" + "fmt" + "os" + "time" + + "github.com/cilium/ebpf/link" +) + +var ( + argExec string + argSymbol string + argClear bool +) + +var ( + pinPath = "/sys/fs/bpf/probe-ctrl/" +) + +func init() { + flag.StringVar(&argExec, "exec", "", "Executable to which the probe should be attached.") + flag.StringVar(&argSymbol, "symb", "", "Symbol in the executable to which the probe will be attached.") + flag.BoolVar(&argClear, "clear", false, "Remove probe from all links.") +} + +func main() { + os.Exit(run()) +} + +func run() int { + flag.Parse() + + if argClear { + // bpf_link_detach does not exist for uprobes. So just remove + // the pinned path to deactivate uprobes as a work around. + if err := os.RemoveAll(pinPath); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return -1 + } + return 0 + } + + if argExec == "" || argSymbol == "" { + fmt.Fprintf(os.Stderr, "Both -exec and -symb need to be set\n") + return -1 + } + + exec, err := link.OpenExecutable(argExec) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return -1 + } + + probe, err := getProbe() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return -1 + } + + probeLink, err := exec.Uprobe(argSymbol, probe, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed attaching probe to '%s' in '%s': %v\n", argSymbol, argExec, err) + return -1 + } + + if err := os.MkdirAll(pinPath, 0700); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return -1 + } + + if err := probeLink.Pin(fmt.Sprintf("%s/%d", pinPath, time.Now().Unix())); err != nil { + fmt.Fprintf(os.Stderr, "Failed to pin link: %v\n", err) + return -1 + } + + return 0 +} diff --git a/tools/probe-ctrl/cmd/probe-ctrl/probe.go b/tools/probe-ctrl/cmd/probe-ctrl/probe.go new file mode 100644 index 000000000..7edc866f1 --- /dev/null +++ b/tools/probe-ctrl/cmd/probe-ctrl/probe.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/cilium/ebpf" +) + +func getProbe() (*ebpf.Program, error) { + var lastID ebpf.ProgramID + + for { + nextID, err := ebpf.ProgramGetNextID(lastID) + if err != nil { + return nil, err + } + lastID = nextID + + prog, err := ebpf.NewProgramFromID(nextID) + if err != nil { + return nil, err + } + defer prog.Close() + + info, err := prog.Info() + if err != nil { + return nil, err + } + + if info.Name == "uprobe__generic" { + return prog.Clone() + } + } +} diff --git a/tools/probe-ctrl/go.mod b/tools/probe-ctrl/go.mod new file mode 100644 index 000000000..ab485b947 --- /dev/null +++ b/tools/probe-ctrl/go.mod @@ -0,0 +1,22 @@ +module github.com/open-telemetry/opentelemetry-ebpf-profiler/tools/strobelight-ctrl + +go 1.24.4 + +tool ( + golang.org/x/vuln/cmd/govulncheck + honnef.co/go/tools/cmd/staticcheck +) + +require github.com/cilium/ebpf v0.19.0 + +require ( + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect + golang.org/x/tools v0.30.0 // indirect + golang.org/x/vuln v1.1.4 // indirect + honnef.co/go/tools v0.6.1 // indirect +) diff --git a/tools/probe-ctrl/go.sum b/tools/probe-ctrl/go.sum new file mode 100644 index 000000000..909d50331 --- /dev/null +++ b/tools/probe-ctrl/go.sum @@ -0,0 +1,44 @@ +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao= +github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= +github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= +github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= +golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= +golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=