151 lines
3.5 KiB
Go
151 lines
3.5 KiB
Go
// 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
|
|
// +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)
|
|
}
|
|
}
|