| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build linux |
| |
| package driver |
| |
| import ( |
| "bufio" |
| "bytes" |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| "regexp" |
| "runtime" |
| "strconv" |
| "strings" |
| "syscall" |
| "unsafe" |
| |
| "golang.org/x/sys/unix" |
| ) |
| |
| const rssMultiplier = 1 << 10 |
| |
| // Runs the cmd under perf. Returns filename of the profile. Any errors are ignored. |
| func RunUnderProfiler(args ...string) (string, string) { |
| cmd := exec.Command("perf", append([]string{"record", "-o", "perf.data"}, args...)...) |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| log.Printf("Failed to execute 'perf record %v': %v\n%v", args, err, string(out)) |
| return "", "" |
| } |
| |
| perf1 := perfReport("--sort", "comm") |
| perf2 := perfReport() |
| return perf1, perf2 |
| } |
| |
| func perfReport(args ...string) string { |
| var stdout bytes.Buffer |
| var stderr bytes.Buffer |
| cmd := exec.Command("perf", append([]string{"report", "--stdio"}, args...)...) |
| cmd.Stdout = &stdout |
| cmd.Stderr = &stderr |
| if err := cmd.Run(); err != nil { |
| log.Printf("Failed to execute 'perf report': %v\n%v", err, stderr.String()) |
| return "" |
| } |
| |
| f, err := os.Create(tempFilename("perf.txt")) |
| if err != nil { |
| log.Printf("Failed to create profile file: %v", err) |
| return "" |
| } |
| defer f.Close() |
| |
| ff := bufio.NewWriter(f) |
| defer ff.Flush() |
| |
| // Strip lines starting with #, and limit output to 100 lines. |
| r := bufio.NewReader(&stdout) |
| for n := 0; n < 100; { |
| ln, err := r.ReadBytes('\n') |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| log.Printf("Failed to scan profile: %v", err) |
| return "" |
| } |
| if len(ln) == 0 || ln[0] == '#' { |
| continue |
| } |
| ff.Write(ln) |
| n++ |
| } |
| |
| return f.Name() |
| } |
| |
| // Size runs size command on the file. Returns filename with output. Any errors are ignored. |
| func Size(file string) string { |
| resf, err := os.Create(tempFilename("size.txt")) |
| if err != nil { |
| log.Printf("Failed to create output file: %v", err) |
| return "" |
| } |
| defer resf.Close() |
| |
| var stderr bytes.Buffer |
| cmd := exec.Command("size", "-A", file) |
| cmd.Stdout = resf |
| cmd.Stderr = &stderr |
| if err := cmd.Run(); err != nil { |
| log.Printf("Failed to execute 'size -m %v': %v\n%v", file, err, stderr.String()) |
| return "" |
| } |
| |
| return resf.Name() |
| } |
| |
| func getVMPeak() uint64 { |
| data, err := os.ReadFile("/proc/self/status") |
| if err != nil { |
| log.Printf("Failed to read /proc/self/status: %v", err) |
| return 0 |
| } |
| |
| re := regexp.MustCompile("VmPeak:[ \t]*([0-9]+) kB") |
| match := re.FindSubmatch(data) |
| if match == nil { |
| log.Printf("No VmPeak in /proc/self/status") |
| return 0 |
| } |
| v, err := strconv.ParseUint(string(match[1]), 10, 64) |
| if err != nil { |
| log.Printf("Failed to parse VmPeak in /proc/self/status: %v", string(match[1])) |
| return 0 |
| } |
| return v * 1024 |
| } |
| |
| func setProcessAffinity(v int) { |
| runtime.LockOSThread() |
| defer runtime.UnlockOSThread() |
| _, _, errno := syscall.Syscall(uintptr(unix.SYS_SCHED_SETAFFINITY), uintptr(syscall.Getpid()), uintptr(unsafe.Sizeof(v)), uintptr(unsafe.Pointer(&v))) |
| if errno != 0 { |
| log.Printf("failed to set affinity to %v: %v", v, errno.Error()) |
| return |
| } |
| // Re-exec the process w/o affinity flag. |
| var args []string |
| for i := 0; i < len(os.Args); i++ { |
| a := os.Args[i] |
| if strings.HasPrefix(a, "-affinity") { |
| if a == "-affinity" { |
| i++ // also skip the value |
| } |
| continue |
| } |
| args = append(args, a) |
| } |
| if err := syscall.Exec(os.Args[0], args, os.Environ()); err != nil { |
| log.Printf("failed to exec: %v", err) |
| } |
| } |