Files
weave-scope/experimental/ftrace/ftrace.go
2015-08-21 10:46:30 +00:00

207 lines
4.6 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
"strconv"
"strings"
)
const (
perms = 0777
)
var (
lineMatcher = regexp.MustCompile(`^\s*[a-z\-]+\-(\d+)\s+\[(\d{3})] (?:\.|1){4} ([\d\.]+): (.*)$`)
enterMatcher = regexp.MustCompile(`^([\w_]+)\((.*)\)$`)
argMatcher = regexp.MustCompile(`(\w+): (\w+)`)
exitMatcher = regexp.MustCompile(`^([\w_]+) -> (\w+)$`)
)
// Ftrace is a tracer using ftrace...
type Ftrace struct {
ftraceRoot string
root string
outstanding map[int]*syscall // map from pid (readlly tid) to outstanding syscall
}
type syscall struct {
pid int
cpu int
ts float64
name string
args map[string]string
returnCode int64
}
func findDebugFS() (string, error) {
contents, err := ioutil.ReadFile("/proc/mounts")
if err != nil {
return "", err
}
scanner := bufio.NewScanner(bytes.NewBuffer(contents))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 3 {
continue
}
if fields[2] == "debugfs" {
return fields[1], nil
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", fmt.Errorf("Not found")
}
// NewFtracer constucts a new Ftrace instance.
func NewFtracer() (*Ftrace, error) {
root, err := findDebugFS()
if err != nil {
return nil, err
}
scopeRoot := path.Join(root, "tracing", "instances", "scope")
if err := os.Mkdir(scopeRoot, perms); err != nil && os.IsExist(err) {
if err := os.Remove(scopeRoot); err != nil {
return nil, err
}
if err := os.Mkdir(scopeRoot, perms); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return &Ftrace{
ftraceRoot: root,
root: scopeRoot,
outstanding: map[int]*syscall{},
}, nil
}
func (f *Ftrace) destroy() error {
return os.Remove(f.root)
}
func (f *Ftrace) enableTracing() error {
// need to enable tracing at root to get trace_pipe to block in my instance. Weird
if err := ioutil.WriteFile(path.Join(f.ftraceRoot, "tracing", "tracing_on"), []byte("1"), perms); err != nil {
return err
}
return ioutil.WriteFile(path.Join(f.root, "tracing_on"), []byte("1"), perms)
}
func (f *Ftrace) disableTracing() error {
if err := ioutil.WriteFile(path.Join(f.root, "tracing_on"), []byte("0"), perms); err != nil {
return err
}
return ioutil.WriteFile(path.Join(f.ftraceRoot, "tracing", "tracing_on"), []byte("1"), perms)
}
func (f *Ftrace) enableEvent(class, event string) error {
return ioutil.WriteFile(path.Join(f.root, "events", class, event, "enable"), []byte("1"), perms)
}
func mustAtoi(a string) int {
i, err := strconv.Atoi(a)
if err != nil {
panic(err)
}
return i
}
func mustAtof(a string) float64 {
i, err := strconv.ParseFloat(a, 64)
if err != nil {
panic(err)
}
return i
}
func (f *Ftrace) events(out chan<- *syscall) {
file, err := os.Open(path.Join(f.root, "trace_pipe"))
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
matches := lineMatcher.FindStringSubmatch(scanner.Text())
if matches == nil {
continue
}
pid := mustAtoi(matches[1])
log := matches[4]
if enterMatches := enterMatcher.FindStringSubmatch(log); enterMatches != nil {
name := enterMatches[1]
args := map[string]string{}
for _, arg := range argMatcher.FindAllStringSubmatch(enterMatches[2], -1) {
args[arg[1]] = arg[2]
}
s := &syscall{
pid: pid,
cpu: mustAtoi(matches[2]),
ts: mustAtof(matches[3]),
name: strings.TrimPrefix(name, "sys_"),
args: args,
}
f.outstanding[pid] = s
} else if exitMatches := exitMatcher.FindStringSubmatch(log); exitMatches != nil {
s, ok := f.outstanding[pid]
if !ok {
continue
}
delete(f.outstanding, pid)
returnCode, err := strconv.ParseUint(exitMatches[2], 0, 64)
if err != nil {
panic(err)
}
s.returnCode = int64(returnCode)
out <- s
} else {
fmt.Printf("Unmatched: %s", log)
}
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func (f *Ftrace) start() error {
for _, e := range []struct{ class, event string }{
{"syscalls", "sys_enter_socket"},
{"syscalls", "sys_exit_socket"},
{"syscalls", "sys_enter_connect"},
{"syscalls", "sys_exit_connect"},
{"syscalls", "sys_enter_accept"},
{"syscalls", "sys_exit_accept"},
{"syscalls", "sys_enter_accept4"},
{"syscalls", "sys_exit_accept4"},
{"syscalls", "sys_enter_close"},
{"syscalls", "sys_exit_close"},
} {
if err := f.enableEvent(e.class, e.event); err != nil {
return err
}
}
return f.enableTracing()
}
func (f *Ftrace) stop() error {
defer f.destroy()
return f.disableTracing()
}