sweet/benchmark/internal/cgroups: test system-run functionality

Add a rudimentary test for starting a user scope with systemd-run. If it
doesn't work, then skip the wrapper.

For golang/go#54760.

Change-Id: I7b742b49ae453b8d485d7d0c008b9fb9148c37ac
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/426238
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
diff --git a/sweet/benchmarks/internal/cgroups/cgroups.go b/sweet/benchmarks/internal/cgroups/cgroups.go
index 878e344..0a55a3d 100644
--- a/sweet/benchmarks/internal/cgroups/cgroups.go
+++ b/sweet/benchmarks/internal/cgroups/cgroups.go
@@ -13,6 +13,54 @@
 	"path/filepath"
 	"strconv"
 	"strings"
+	"sync"
+	"time"
+)
+
+func findSystemdRun() (string, error) {
+	bin, err := exec.LookPath("systemd-run")
+	if errors.Is(err, exec.ErrNotFound) {
+		return "", fmt.Errorf("systemd-run binary not found")
+	} else if err != nil {
+		return "", fmt.Errorf("error looking for systemd-run: %w", err)
+	}
+
+	scope := fmt.Sprintf("systemd-run-test-%d.scope", time.Now().UnixNano())
+
+	cmd := exec.Command(bin, "--user", "--scope", "--unit", scope, "/bin/true")
+	sout, serr := cmd.CombinedOutput()
+	if serr == nil {
+		// It works!
+		return bin, nil
+	}
+
+	var context strings.Builder
+	fmt.Fprintf(&context, "\noutput: %s", string(sout))
+
+	// Failed. systemd-run probably just said to look at journalctl;
+	// collect that additional context.
+	cmd = exec.Command("journalctl", "--catalog", "--user", "--unit", scope)
+	jout, jerr := cmd.CombinedOutput()
+	if jerr != nil {
+		fmt.Fprintf(&context, "\njournalctl error: %v\noutout: %s", jerr, string(jout))
+	} else {
+		fmt.Fprintf(&context, "\njournalctl output: %s", string(jout))
+	}
+
+	// Attempt to cleanup unit.
+	cmd = exec.Command("systemctl", "--user", "reset-failed", scope)
+	scout, scerr := cmd.CombinedOutput()
+	if scerr != nil {
+		fmt.Fprintf(&context, "\nsystemctl cleanup error: %v\noutput: %s", scerr, string(scout))
+	}
+
+	return "", fmt.Errorf("system-run failed: %w%s", serr, context.String())
+}
+
+var (
+	systemdOnce     sync.Once
+	systemdRunPath  string
+	systemdRunError error
 )
 
 type Cmd struct {
@@ -25,13 +73,13 @@
 func WrapCommand(cmd *exec.Cmd, scope string) (*Cmd, error) {
 	wrapped := Cmd{Cmd: *cmd}
 
-	// TODO(mknyszek): Maybe make a more stringent check?
-	systemdRunPath, err := exec.LookPath("systemd-run")
-	if errors.Is(err, exec.ErrNotFound) {
-		fmt.Fprintln(os.Stderr, "# warning: systemd-run not available, skipping...")
+	systemdOnce.Do(func() {
+		systemdRunPath, systemdRunError = findSystemdRun()
+	})
+
+	if systemdRunError != nil {
+		fmt.Fprintf(os.Stderr, "# warning: systemd-run not available: %v\n# skipping cgroup wrapper...\n", systemdRunError)
 		return &wrapped, nil
-	} else if err != nil {
-		return nil, err
 	}
 
 	u, err := user.Current()
@@ -62,3 +110,4 @@
 		return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
 	}
 }
+