mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-27 13:57:09 +00:00
207 lines
4.6 KiB
Go
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()
|
|
}
|