Fatal error on insufficient resource to use Goma in Soong UI.

As suggested in
https://android-review.googlesource.com/c/platform/build/soong/+/839293
I am moving some features in goma.mk to goma.go in Soong UI.

With this CL, let me implement ulimit check to Soong UI.

Test: export USE_GOMA=true
Test: launch aosp_arm-eng
Test: make
Test: the command succeeds if "ulimit -n" and "ulimit -u" are large enough.
Test: Otherwise, it shows error.  I confirmed both cases.
Change-Id: I5d7d5ed71f620302a0d635770d1a51a2baab51fd
Signed-off-by: Yoshisato Yanagisawa <yyanagisawa@google.com>
diff --git a/ui/build/goma.go b/ui/build/goma.go
index d0dc9cf..015a7c7 100644
--- a/ui/build/goma.go
+++ b/ui/build/goma.go
@@ -15,34 +15,65 @@
 package build
 
 import (
-	"errors"
+	"fmt"
+	"math"
 	"path/filepath"
+	"strconv"
+	"strings"
 
 	"android/soong/ui/metrics"
 )
 
 const gomaCtlScript = "goma_ctl.py"
+const gomaLeastNProcs = 2500
+const gomaLeastNFiles = 16000
 
-var gomaCtlNotFound = errors.New("goma_ctl.py not found")
+// ulimit returns ulimit result for |opt|.
+// if the resource is unlimited, it returns math.MaxInt32 so that a caller do
+// not need special handling of the returned value.
+//
+// Note that since go syscall package do not have RLIMIT_NPROC constant,
+// we use bash ulimit instead.
+func ulimitOrFatal(ctx Context, config Config, opt string) int {
+	commandText := fmt.Sprintf("ulimit %s", opt)
+	cmd := Command(ctx, config, commandText, "bash", "-c", commandText)
+	output := strings.TrimRight(string(cmd.CombinedOutputOrFatal()), "\n")
+	ctx.Verbose(output + "\n")
+	ctx.Verbose("done\n")
 
-func startGoma(ctx Context, config Config) error {
+	if output == "unlimited" {
+		return math.MaxInt32
+	}
+	num, err := strconv.Atoi(output)
+	if err != nil {
+		ctx.Fatalf("ulimit returned unexpected value: %s: %v\n", opt, err)
+	}
+	return num
+}
+
+func startGoma(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSetupTool, "goma_ctl")
 	defer ctx.EndTrace()
 
+	if u := ulimitOrFatal(ctx, config, "-u"); u < gomaLeastNProcs {
+		ctx.Fatalf("max user processes is insufficient: %d; want >= %d.\n", u, gomaLeastNProcs)
+	}
+	if n := ulimitOrFatal(ctx, config, "-n"); n < gomaLeastNFiles {
+		ctx.Fatalf("max open files is insufficient: %d; want >= %d.\n", n, gomaLeastNFiles)
+	}
+
 	var gomaCtl string
 	if gomaDir, ok := config.Environment().Get("GOMA_DIR"); ok {
 		gomaCtl = filepath.Join(gomaDir, gomaCtlScript)
 	} else if home, ok := config.Environment().Get("HOME"); ok {
 		gomaCtl = filepath.Join(home, "goma", gomaCtlScript)
 	} else {
-		return gomaCtlNotFound
+		ctx.Fatalln("goma_ctl.py not found")
 	}
 
 	cmd := Command(ctx, config, "goma_ctl.py ensure_start", gomaCtl, "ensure_start")
 
 	if err := cmd.Run(); err != nil {
-		ctx.Fatalf("goma_ctl.py ensure_start failed with: %v", err)
+		ctx.Fatalf("goma_ctl.py ensure_start failed with: %v\n", err)
 	}
-
-	return nil
 }