19563: Log memory usage of arv-mount, crunch-run, and keepstore.
[arvados.git] / services / crunchstat / crunchstat.go
index 6bf2e3399e61b4ec172e4ddb3dd76cee37c201d4..d28bee0f5e19591275eab2ae43d2a640d316de6d 100644 (file)
@@ -16,6 +16,7 @@ import (
        "syscall"
        "time"
 
+       "git.arvados.org/arvados.git/lib/cmd"
        "git.arvados.org/arvados.git/lib/crunchstat"
 )
 
@@ -27,38 +28,47 @@ var (
        version               = "dev"
 )
 
+type logger interface {
+       Printf(string, ...interface{})
+}
+
 func main() {
        reporter := crunchstat.Reporter{
                Logger: log.New(os.Stderr, "crunchstat: ", 0),
        }
 
-       flag.StringVar(&reporter.CgroupRoot, "cgroup-root", "", "Root of cgroup tree")
-       flag.StringVar(&reporter.CgroupParent, "cgroup-parent", "", "Name of container parent under cgroup")
-       flag.StringVar(&reporter.CIDFile, "cgroup-cid", "", "Path to container id file")
-       flag.IntVar(&signalOnDeadPPID, "signal-on-dead-ppid", signalOnDeadPPID, "Signal to send child if crunchstat's parent process disappears (0 to disable)")
-       flag.DurationVar(&ppidCheckInterval, "ppid-check-interval", ppidCheckInterval, "Time between checks for parent process disappearance")
-       pollMsec := flag.Int64("poll", 1000, "Reporting interval, in milliseconds")
-       getVersion := flag.Bool("version", false, "Print version information and exit.")
-
-       flag.Parse()
-
-       // Print version information if requested
-       if *getVersion {
+       flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+       flags.StringVar(&reporter.CgroupRoot, "cgroup-root", "", "Root of cgroup tree")
+       flags.StringVar(&reporter.CgroupParent, "cgroup-parent", "", "Name of container parent under cgroup")
+       flags.StringVar(&reporter.CIDFile, "cgroup-cid", "", "Path to container id file")
+       flags.IntVar(&signalOnDeadPPID, "signal-on-dead-ppid", signalOnDeadPPID, "Signal to send child if crunchstat's parent process disappears (0 to disable)")
+       flags.DurationVar(&ppidCheckInterval, "ppid-check-interval", ppidCheckInterval, "Time between checks for parent process disappearance")
+       pollMsec := flags.Int64("poll", 1000, "Reporting interval, in milliseconds")
+       getVersion := flags.Bool("version", false, "Print version information and exit.")
+
+       if ok, code := cmd.ParseFlags(flags, os.Args[0], os.Args[1:], "program [args ...]", os.Stderr); !ok {
+               os.Exit(code)
+       } else if *getVersion {
                fmt.Printf("crunchstat %s\n", version)
                return
+       } else if flags.NArg() == 0 {
+               fmt.Fprintf(os.Stderr, "missing required argument: program (try -help)\n")
+               os.Exit(2)
        }
 
        reporter.Logger.Printf("crunchstat %s started", version)
 
        if reporter.CgroupRoot == "" {
-               reporter.Logger.Fatal("error: must provide -cgroup-root")
+               reporter.Logger.Printf("error: must provide -cgroup-root")
+               os.Exit(2)
        } else if signalOnDeadPPID < 0 {
-               reporter.Logger.Fatalf("-signal-on-dead-ppid=%d is invalid (use a positive signal number, or 0 to disable)", signalOnDeadPPID)
+               reporter.Logger.Printf("-signal-on-dead-ppid=%d is invalid (use a positive signal number, or 0 to disable)", signalOnDeadPPID)
+               os.Exit(2)
        }
        reporter.PollPeriod = time.Duration(*pollMsec) * time.Millisecond
 
        reporter.Start()
-       err := runCommand(flag.Args(), reporter.Logger)
+       err := runCommand(flags.Args(), reporter.Logger)
        reporter.Stop()
 
        if err, ok := err.(*exec.ExitError); ok {
@@ -72,17 +82,19 @@ func main() {
                if status, ok := err.Sys().(syscall.WaitStatus); ok {
                        os.Exit(status.ExitStatus())
                } else {
-                       reporter.Logger.Fatalln("ExitError without WaitStatus:", err)
+                       reporter.Logger.Printf("ExitError without WaitStatus: %v", err)
+                       os.Exit(1)
                }
        } else if err != nil {
-               reporter.Logger.Fatalln("error in cmd.Wait:", err)
+               reporter.Logger.Printf("error running command: %v", err)
+               os.Exit(1)
        }
 }
 
-func runCommand(argv []string, logger *log.Logger) error {
+func runCommand(argv []string, logger logger) error {
        cmd := exec.Command(argv[0], argv[1:]...)
 
-       logger.Println("Running", argv)
+       logger.Printf("Running %v", argv)
 
        // Child process will use our stdin and stdout pipes
        // (we close our copies below)
@@ -96,7 +108,7 @@ func runCommand(argv []string, logger *log.Logger) error {
                if cmd.Process != nil {
                        cmd.Process.Signal(catch)
                }
-               logger.Println("notice: caught signal:", catch)
+               logger.Printf("notice: caught signal: %v", catch)
        }(sigChan)
        signal.Notify(sigChan, syscall.SIGTERM)
        signal.Notify(sigChan, syscall.SIGINT)
@@ -109,24 +121,30 @@ func runCommand(argv []string, logger *log.Logger) error {
        // Funnel stderr through our channel
        stderrPipe, err := cmd.StderrPipe()
        if err != nil {
-               logger.Fatalln("error in StderrPipe:", err)
+               logger.Printf("error in StderrPipe: %v", err)
+               return err
        }
 
        // Run subprocess
        if err := cmd.Start(); err != nil {
-               logger.Fatalln("error in cmd.Start:", err)
+               logger.Printf("error in cmd.Start: %v", err)
+               return err
        }
 
        // Close stdin/stdout in this (parent) process
        os.Stdin.Close()
        os.Stdout.Close()
 
-       copyPipeToChildLog(stderrPipe, log.New(os.Stderr, "", 0))
+       err = copyPipeToChildLog(stderrPipe, log.New(os.Stderr, "", 0))
+       if err != nil {
+               cmd.Process.Kill()
+               return err
+       }
 
        return cmd.Wait()
 }
 
-func sendSignalOnDeadPPID(intvl time.Duration, signum, ppidOrig int, cmd *exec.Cmd, logger *log.Logger) {
+func sendSignalOnDeadPPID(intvl time.Duration, signum, ppidOrig int, cmd *exec.Cmd, logger logger) {
        ticker := time.NewTicker(intvl)
        for range ticker.C {
                ppid := os.Getppid()
@@ -148,7 +166,7 @@ func sendSignalOnDeadPPID(intvl time.Duration, signum, ppidOrig int, cmd *exec.C
        }
 }
 
-func copyPipeToChildLog(in io.ReadCloser, logger *log.Logger) {
+func copyPipeToChildLog(in io.ReadCloser, logger logger) error {
        reader := bufio.NewReaderSize(in, MaxLogLine)
        var prefix string
        for {
@@ -156,13 +174,13 @@ func copyPipeToChildLog(in io.ReadCloser, logger *log.Logger) {
                if err == io.EOF {
                        break
                } else if err != nil {
-                       logger.Fatal("error reading child stderr:", err)
+                       return fmt.Errorf("error reading child stderr: %w", err)
                }
                var suffix string
                if isPrefix {
                        suffix = "[...]"
                }
-               logger.Print(prefix, string(line), suffix)
+               logger.Printf("%s%s%s", prefix, string(line), suffix)
                // Set up prefix for following line
                if isPrefix {
                        prefix = "[...]"
@@ -170,5 +188,5 @@ func copyPipeToChildLog(in io.ReadCloser, logger *log.Logger) {
                        prefix = ""
                }
        }
-       in.Close()
+       return in.Close()
 }