Skip to content

Commit

Permalink
Update README and set affinity when locking a goroutine to an OS thread
Browse files Browse the repository at this point in the history
  • Loading branch information
hodgesds committed Aug 6, 2019
1 parent 1e0ea04 commit 4ca39c9
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 10 deletions.
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
[![GoDoc](https://godoc.org/github.com/hodgesds/perf-utils?status.svg)](https://godoc.org/github.com/hodgesds/perf-utils)

This package is a go library for interacting with the `perf` subsystem in
Linux. It allows you to do things like see how many CPU instructions a function
takes, profile a process for various hardware events, and other interesting
things. The library is by no means finalized and should be considered pre-alpha.
Linux. I had trouble finding a golang perf library so I decided to write this
by using the linux's perf as a reference. This library allows you to do things
like see how many CPU instructions a function takes (roughly), profile a
process for various hardware events, and other interesting things. Note that
because the go scheduler can schedule a goroutine across many OS threads it
becomes rather difficult to get an _exact_ profile of an invididual goroutine.
However, a few tricks can be used; first a call to
`[runtime.LockOSThread](https://golang.org/pkg/runtime/#LockOSThread)` to lock
the current goroutine to an OS thread. Second a call to
`[unix.SchedSetaffinity](https://godoc.org/golang.org/x/sys/unix#SchedSetaffinity)`,
with a CPU set mask set. Note that if the pid argument is set 0 the calling
thread is used (the thread that was just locked). Before using this library you
should probably read the
[`perf_event_open`](http://www.man7.org/linux/man-pages/man2/perf_event_open.2.html)
man page which this library uses heavily.

# Use Cases
If you are looking to interact with the perf subsystem directly with
Expand All @@ -15,18 +27,20 @@ runtime being extremely tricky to profile on the goroutine level, with the
exception of a long running worker goroutine locked to an OS thread. Eventually
this library could be used to implement many of the features of `perf` but in
pure Go. Currently this library is used in
[node_exporter](https://github.com/prometheus/node_exporter) as well as
[perf_exporter](https://github.com/hodgesds/perf_exporter), which is a
Prometheus exporter for perf related metrics.

## Caveats
* Some utility functions will call
[`runtime.LockOSThread`](https://golang.org/pkg/runtime/#LockOSThread) for
you, they will also unlock the thread after profiling. ***Note*** using these
utility functions will incur significant overhead.
utility functions will incur significant overhead (~4ms).
* Overflow handling is not implemented.

# Setup
Most likely you will need to tweak some system settings unless you are running as root. From `man perf_event_open`:
Most likely you will need to tweak some system settings unless you are running
as root. From `man perf_event_open`:

```
perf_event related configuration files
Expand Down Expand Up @@ -111,8 +125,7 @@ ok github.com/hodgesds/perf-utils 1.981s
# BPF Support
BPF is supported by using the `BPFProfiler` which is available via the
`ProfileTracepoint` function. To use BPF you need to create the BPF program and
then call `AttachBPF` with the file descriptor of the BPF program. This is not
well tested so use at your own peril.
then call `AttachBPF` with the file descriptor of the BPF program.

# Misc
Originally I set out to use `go generate` to build Go structs that were
Expand Down
19 changes: 16 additions & 3 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package perf

import (
"encoding/binary"
"math/rand"
"runtime"
"syscall"
"unsafe"
Expand All @@ -16,10 +17,22 @@ var (
EventAttrSize = uint32(unsafe.Sizeof(unix.PerfEventAttr{}))
)

// profileFn is a helper function to profile a function.
func profileFn(eventAttr *unix.PerfEventAttr, f func() error) (*ProfileValue, error) {
// LockThread locks an goroutine to an OS thread and then sets the affinity of
// the thread to a processor core.
func LockThread(core int) (func(), error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cpuSet := unix.CPUSet{}
cpuSet.Set(core)
return runtime.UnlockOSThread, unix.SchedSetaffinity(0, &cpuSet)
}

// profileFn is a helper function to profile a function, it will randomly choose a core to run on.
func profileFn(eventAttr *unix.PerfEventAttr, f func() error) (*ProfileValue, error) {
cb, err := LockThread(rand.Intn(runtime.NumCPU()))
if err != nil {
return nil, err
}
defer cb()
fd, err := unix.PerfEventOpen(
eventAttr,
unix.Gettid(),
Expand Down

0 comments on commit 4ca39c9

Please sign in to comment.