14 "git.curoverse.com/arvados.git/lib/crunchstat"
17 const MaxLogLine = 1 << 14 // Child stderr lines >16KiB will be split
19 var signalOnDeadPPID int
20 var ppidCheckInterval = time.Second
23 reporter := crunchstat.Reporter{
24 Logger: log.New(os.Stderr, "crunchstat: ", 0),
27 flag.StringVar(&reporter.CgroupRoot, "cgroup-root", "", "Root of cgroup tree")
28 flag.StringVar(&reporter.CgroupParent, "cgroup-parent", "", "Name of container parent under cgroup")
29 flag.StringVar(&reporter.CIDFile, "cgroup-cid", "", "Path to container id file")
30 flag.IntVar(&signalOnDeadPPID, "signal-on-dead-ppid", 15, "Signal to send child if crunchstat's parent process disappears")
31 flag.DurationVar(&ppidCheckInterval, "ppid-check-interval", ppidCheckInterval, "Time between checks for parent process disappearance")
32 pollMsec := flag.Int64("poll", 1000, "Reporting interval, in milliseconds")
36 if reporter.CgroupRoot == "" {
37 reporter.Logger.Fatal("error: must provide -cgroup-root")
39 reporter.PollPeriod = time.Duration(*pollMsec) * time.Millisecond
42 err := runCommand(flag.Args(), reporter.Logger)
45 if err, ok := err.(*exec.ExitError); ok {
46 // The program has exited with an exit code != 0
48 // This works on both Unix and Windows. Although
49 // package syscall is generally platform dependent,
50 // WaitStatus is defined for both Unix and Windows and
51 // in both cases has an ExitStatus() method with the
53 if status, ok := err.Sys().(syscall.WaitStatus); ok {
54 os.Exit(status.ExitStatus())
56 reporter.Logger.Fatalln("ExitError without WaitStatus:", err)
58 } else if err != nil {
59 reporter.Logger.Fatalln("error in cmd.Wait:", err)
63 func runCommand(argv []string, logger *log.Logger) error {
64 cmd := exec.Command(argv[0], argv[1:]...)
66 logger.Println("Running", argv)
68 // Child process will use our stdin and stdout pipes
69 // (we close our copies below)
71 cmd.Stdout = os.Stdout
73 // Forward SIGINT and SIGTERM to child process
74 sigChan := make(chan os.Signal, 1)
75 go func(sig <-chan os.Signal) {
77 if cmd.Process != nil {
78 cmd.Process.Signal(catch)
80 logger.Println("notice: caught signal:", catch)
82 signal.Notify(sigChan, syscall.SIGTERM)
83 signal.Notify(sigChan, syscall.SIGINT)
85 // Kill our child proc if our parent process disappears
86 if signalOnDeadPPID != 0 {
87 go sendSignalOnDeadPPID(signalOnDeadPPID, os.Getppid(), cmd, logger)
90 // Funnel stderr through our channel
91 stderr_pipe, err := cmd.StderrPipe()
93 logger.Fatalln("error in StderrPipe:", err)
97 if err := cmd.Start(); err != nil {
98 logger.Fatalln("error in cmd.Start:", err)
101 // Close stdin/stdout in this (parent) process
105 copyPipeToChildLog(stderr_pipe, log.New(os.Stderr, "", 0))
110 func sendSignalOnDeadPPID(signum, ppidOrig int, cmd *exec.Cmd, logger *log.Logger) {
111 for _ = range time.NewTicker(ppidCheckInterval).C {
113 if ppid == ppidOrig {
116 if cmd.Process == nil {
117 // Child process isn't running yet
120 logger.Printf("notice: crunchstat ppid changed from %d to %d -- killing child pid %d with signal %d", ppidOrig, ppid, cmd.Process.Pid, signum)
121 err := cmd.Process.Signal(syscall.Signal(signum))
123 logger.Printf("error: sending signal: %d", err)
130 func copyPipeToChildLog(in io.ReadCloser, logger *log.Logger) {
131 reader := bufio.NewReaderSize(in, MaxLogLine)
134 line, isPrefix, err := reader.ReadLine()
137 } else if err != nil {
138 logger.Fatal("error reading child stderr:", err)
144 logger.Print(prefix, string(line), suffix)
145 // Set up prefix for following line