aboutsummaryrefslogtreecommitdiffstats
path: root/client/go/internal/admin/script-utils/startcbinary
diff options
context:
space:
mode:
Diffstat (limited to 'client/go/internal/admin/script-utils/startcbinary')
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/cmd.go48
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/common_env.go83
-rwxr-xr-xclient/go/internal/admin/script-utils/startcbinary/mockbin/bad-numactl25
-rwxr-xr-xclient/go/internal/admin/script-utils/startcbinary/mockbin/good-numactl25
-rwxr-xr-xclient/go/internal/admin/script-utils/startcbinary/mockbin/has-valgrind6
-rwxr-xr-xclient/go/internal/admin/script-utils/startcbinary/mockbin/no-numactl6
-rwxr-xr-xclient/go/internal/admin/script-utils/startcbinary/mockbin/no-valgrind6
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/numactl.go72
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/numactl_test.go89
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/progspec.go144
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/progspec_test.go38
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/startcbinary.go47
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/tuning.go13
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/valgrind.go82
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/valgrind_test.go92
-rw-r--r--client/go/internal/admin/script-utils/startcbinary/vespamalloc.go57
16 files changed, 833 insertions, 0 deletions
diff --git a/client/go/internal/admin/script-utils/startcbinary/cmd.go b/client/go/internal/admin/script-utils/startcbinary/cmd.go
new file mode 100644
index 00000000000..9580a9240bb
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/cmd.go
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "os"
+ "strings"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
+)
+
+func Run(args []string) int {
+ trace.AdjustVerbosity(0)
+ if len(args) < 1 {
+ trace.Warning("missing program argument")
+ return 1
+ }
+ spec := NewProgSpec(args)
+ err := vespa.LoadDefaultEnv()
+ if err != nil {
+ util.JustExitWith(err)
+ }
+ hostname, err := vespa.FindOurHostname()
+ if err != nil {
+ trace.Warning("could not detect hostname:", err, "; using fallback:", hostname)
+ }
+ return startCbinary(spec)
+}
+
+func IsCandidate(program string) bool {
+ binary := program + "-bin"
+ if strings.Contains(binary, "/") {
+ return util.IsRegularFile(binary)
+ } else {
+ path := strings.Split(os.Getenv(envvars.PATH), ":")
+ for _, dir := range path {
+ fn := dir + "/" + binary
+ if util.IsRegularFile(fn) {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/common_env.go b/client/go/internal/admin/script-utils/startcbinary/common_env.go
new file mode 100644
index 00000000000..6bc730b5119
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/common_env.go
@@ -0,0 +1,83 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "os"
+ "strings"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
+)
+
+func (spec *ProgSpec) considerFallback(varName, varValue string) {
+ if spec.getenv(varName) == "" && varValue != "" {
+ spec.setenv(varName, varValue)
+ }
+}
+
+func (spec *ProgSpec) considerEnvFallback(targetVar, fallbackVar string) {
+ spec.considerFallback(targetVar, spec.getenv(fallbackVar))
+}
+
+func (spec *ProgSpec) configureCommonEnv() {
+ os.Unsetenv(envvars.LD_PRELOAD)
+ spec.setenv(envvars.STD_THREAD_PREVENT_TRY_CATCH, "true")
+ spec.setenv(envvars.GLIBCXX_FORCE_NEW, "1")
+ spec.setenv(envvars.LD_LIBRARY_PATH, vespa.FindHome()+"/lib64:/opt/vespa-deps/lib64")
+ spec.setenv(envvars.MALLOC_ARENA_MAX, "1")
+
+ // fallback from old env.vars:
+ spec.considerEnvFallback(envvars.VESPA_USE_HUGEPAGES_LIST, envvars.HUGEPAGES_LIST)
+ spec.considerEnvFallback(envvars.VESPA_USE_MADVISE_LIST, envvars.MADVISE_LIST)
+ spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC, envvars.VESPAMALLOC_LIST)
+ spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_D, envvars.VESPAMALLOCD_LIST)
+ spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_DST, envvars.VESPAMALLOCDST_LIST)
+ spec.considerEnvFallback(envvars.VESPA_USE_NO_VESPAMALLOC, envvars.NO_VESPAMALLOC_LIST)
+ // other fallbacks:
+ spec.considerFallback(envvars.ROOT, vespa.FindHome())
+ spec.considerFallback(envvars.VESPA_USER, vespa.FindVespaUser())
+ spec.considerFallback(envvars.VESPA_USE_HUGEPAGES_LIST, "all")
+ spec.considerFallback(envvars.VESPA_USE_VESPAMALLOC, "all")
+ spec.considerFallback(envvars.VESPA_USE_NO_VESPAMALLOC, strings.Join([]string{
+ "vespa-rpc-invoke",
+ "vespa-get-config",
+ "vespa-sentinel-cmd",
+ "vespa-route",
+ "vespa-proton-cmd",
+ "vespa-configproxy-cmd",
+ "vespa-config-status",
+ }, " "))
+
+}
+
+func (spec *ProgSpec) configureHugePages() {
+ if spec.matchesListEnv(envvars.VESPA_USE_HUGEPAGES_LIST) {
+ spec.setenv(envvars.VESPA_USE_HUGEPAGES, "yes")
+ }
+}
+
+func (spec *ProgSpec) configureUseMadvise() {
+ limit := spec.valueFromListEnv(envvars.VESPA_USE_MADVISE_LIST)
+ if limit != "" {
+ trace.Trace("shall use madvise with limit", limit, "as set in", envvars.VESPA_USE_MADVISE_LIST)
+ spec.setenv(envvars.VESPA_MALLOC_MADVISE_LIMIT, limit)
+ return
+ }
+}
+
+func (spec *ProgSpec) configurePath() {
+ // Prefer newer gdb and pstack:
+ spec.prependPath("/opt/rh/gcc-toolset-12/root/usr/bin")
+ // Maven is needed for tester applications:
+ spec.prependPath(vespa.FindHome() + "/local/maven/bin")
+ spec.prependPath(vespa.FindHome() + "/bin64")
+ spec.prependPath(vespa.FindHome() + "/bin")
+ // how to find the "java" program?
+ // should be available in $VESPA_HOME/bin or JAVA_HOME
+ if javaHome := spec.getenv(envvars.JAVA_HOME); javaHome != "" {
+ spec.prependPath(javaHome + "/bin")
+ }
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/mockbin/bad-numactl b/client/go/internal/admin/script-utils/startcbinary/mockbin/bad-numactl
new file mode 100755
index 00000000000..1b08bb19b95
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/mockbin/bad-numactl
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# emulate numactl which says hardware is available but fails anyway
+
+case $#.$* in
+ 1.--hardware)
+ cat << 'EOF'
+available: 2 nodes (0-1)
+node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46
+node 0 size: 128604 MB
+node 0 free: 107167 MB
+node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47
+node 1 size: 128976 MB
+node 1 free: 110921 MB
+node distances:
+node 0 1
+ 0: 10 21
+ 1: 21 10
+EOF
+ ;;
+ *)
+ echo "numactl failure"
+ ;;
+esac
+exit 0
diff --git a/client/go/internal/admin/script-utils/startcbinary/mockbin/good-numactl b/client/go/internal/admin/script-utils/startcbinary/mockbin/good-numactl
new file mode 100755
index 00000000000..f861809b382
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/mockbin/good-numactl
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# emulate normal numactl operations
+
+case $#.$* in
+ 1.--hardware)
+ cat << 'EOF'
+available: 2 nodes (0-1)
+node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46
+node 0 size: 128604 MB
+node 0 free: 107167 MB
+node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47
+node 1 size: 128976 MB
+node 1 free: 110921 MB
+node distances:
+node 0 1
+ 0: 10 21
+ 1: 21 10
+EOF
+ ;;
+ *)
+ echo "foo"
+ ;;
+esac
+exit 0
diff --git a/client/go/internal/admin/script-utils/startcbinary/mockbin/has-valgrind b/client/go/internal/admin/script-utils/startcbinary/mockbin/has-valgrind
new file mode 100755
index 00000000000..8716209928e
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/mockbin/has-valgrind
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# emulate "which" that finds valgrind
+
+echo "/opt/vespa-deps/bin/valgrind"
+exit 0
diff --git a/client/go/internal/admin/script-utils/startcbinary/mockbin/no-numactl b/client/go/internal/admin/script-utils/startcbinary/mockbin/no-numactl
new file mode 100755
index 00000000000..dfe583abaca
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/mockbin/no-numactl
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# emulate inoperational numactl
+
+echo "No NUMA available on this system"
+exit 1
diff --git a/client/go/internal/admin/script-utils/startcbinary/mockbin/no-valgrind b/client/go/internal/admin/script-utils/startcbinary/mockbin/no-valgrind
new file mode 100755
index 00000000000..7a74e29b84b
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/mockbin/no-valgrind
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# emulate "which" that does not find valgrind
+
+echo "which: no valgrind in ($PATH)" >&2
+exit 1
diff --git a/client/go/internal/admin/script-utils/startcbinary/numactl.go b/client/go/internal/admin/script-utils/startcbinary/numactl.go
new file mode 100644
index 00000000000..fe091dedba9
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/numactl.go
@@ -0,0 +1,72 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+)
+
+func (p *ProgSpec) configureNumaCtl() {
+ p.shouldUseNumaCtl = false
+ p.numaSocket = -1
+ if p.getenv(envvars.VESPA_NO_NUMACTL) != "" {
+ return
+ }
+ backticks := util.BackTicksIgnoreStderr
+ out, err := backticks.Run("numactl", "--hardware")
+ trace.Debug("numactl --hardware says:", out)
+ if err != nil {
+ trace.Trace("numactl error:", err)
+ return
+ }
+ outfoo, errfoo := backticks.Run("numactl", "--interleave", "all", "echo", "foo")
+ if errfoo != nil {
+ trace.Trace("cannot run with numactl:", errfoo)
+ return
+ }
+ if outfoo != "foo\n" {
+ trace.Trace("bad numactl output:", outfoo)
+ return
+ }
+ p.shouldUseNumaCtl = true
+ if affinity := p.getenv(envvars.VESPA_AFFINITY_CPU_SOCKET); affinity != "" {
+ wantSocket, _ := strconv.Atoi(affinity)
+ trace.Debug("want socket:", wantSocket)
+ parts := strings.Fields(out)
+ for idx := 0; idx+2 < len(parts); idx++ {
+ if parts[idx] == "available:" && parts[idx+2] == "nodes" {
+ numSockets, _ := strconv.Atoi(parts[idx+1])
+ trace.Debug("numSockets:", numSockets)
+ if numSockets > 1 {
+ p.numaSocket = (wantSocket % numSockets)
+ return
+ }
+ }
+ }
+ }
+}
+
+func (p *ProgSpec) numaCtlBinary() string {
+ return "numactl"
+}
+
+func (p *ProgSpec) prependNumaCtl(args []string) []string {
+ v := util.NewArrayList[string](5 + len(args))
+ v.Append("numactl")
+ if p.numaSocket >= 0 {
+ v.Append(fmt.Sprintf("--cpunodebind=%d", p.numaSocket))
+ v.Append(fmt.Sprintf("--membind=%d", p.numaSocket))
+ } else {
+ v.Append("--interleave")
+ v.Append("all")
+ }
+ v.AppendAll(args...)
+ return v
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/numactl_test.go b/client/go/internal/admin/script-utils/startcbinary/numactl_test.go
new file mode 100644
index 00000000000..65f52be988e
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/numactl_test.go
@@ -0,0 +1,89 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package startcbinary
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+)
+
+func setup(t *testing.T, testFileName string) {
+ trace.AdjustVerbosity(1)
+ mockBinParent = strings.TrimSuffix(testFileName, "/numactl_test.go")
+ tmpBin = t.TempDir() + "/mock.bin.numactl_test"
+ err := os.MkdirAll(tmpBin, 0755)
+ assert.Nil(t, err)
+ t.Setenv("PATH", fmt.Sprintf("%s:%s", tmpBin, os.Getenv("PATH")))
+}
+
+func TestNumaCtlDetection(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ return
+ }
+ _, tfn, _, _ := runtime.Caller(0)
+ setup(t, tfn)
+ orig := []string{"/bin/myprog", "-c", "cfgid"}
+ spec := NewProgSpec(orig)
+
+ useMock("no-numactl", "numactl")
+ spec.configureNumaCtl()
+ assert.Equal(t, false, spec.shouldUseNumaCtl)
+
+ useMock("bad-numactl", "numactl")
+ spec.configureNumaCtl()
+ assert.Equal(t, false, spec.shouldUseNumaCtl)
+
+ t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "")
+ useMock("good-numactl", "numactl")
+ spec.configureNumaCtl()
+ assert.Equal(t, true, spec.shouldUseNumaCtl)
+ assert.Equal(t, -1, spec.numaSocket)
+ argv := spec.prependNumaCtl(orig)
+ trace.Trace("argv:", argv)
+ assert.Equal(t, 6, len(argv))
+ assert.Equal(t, "numactl", argv[0])
+ assert.Equal(t, "--interleave", argv[1])
+ assert.Equal(t, "all", argv[2])
+ assert.Equal(t, "/bin/myprog-bin", argv[3])
+ assert.Equal(t, "-c", argv[4])
+ assert.Equal(t, "cfgid", argv[5])
+
+ t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "0")
+ spec.configureNumaCtl()
+ assert.Equal(t, true, spec.shouldUseNumaCtl)
+ assert.Equal(t, 0, spec.numaSocket)
+ argv = spec.prependNumaCtl(orig)
+ trace.Trace("argv:", argv)
+ assert.Equal(t, 6, len(argv))
+ assert.Equal(t, "numactl", argv[0])
+ assert.Equal(t, "--cpunodebind=0", argv[1])
+ assert.Equal(t, "--membind=0", argv[2])
+ assert.Equal(t, "/bin/myprog-bin", argv[3])
+ assert.Equal(t, "-c", argv[4])
+ assert.Equal(t, "cfgid", argv[5])
+
+ t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "1")
+ spec.configureNumaCtl()
+ assert.Equal(t, true, spec.shouldUseNumaCtl)
+ assert.Equal(t, 1, spec.numaSocket)
+ argv = spec.prependNumaCtl(orig)
+ trace.Trace("argv:", argv)
+ assert.Equal(t, 6, len(argv))
+ assert.Equal(t, "numactl", argv[0])
+ assert.Equal(t, "--cpunodebind=1", argv[1])
+ assert.Equal(t, "--membind=1", argv[2])
+ assert.Equal(t, "/bin/myprog-bin", argv[3])
+ assert.Equal(t, "-c", argv[4])
+ assert.Equal(t, "cfgid", argv[5])
+
+ t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "2")
+ spec.configureNumaCtl()
+ assert.Equal(t, true, spec.shouldUseNumaCtl)
+ assert.Equal(t, 0, spec.numaSocket)
+
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/progspec.go b/client/go/internal/admin/script-utils/startcbinary/progspec.go
new file mode 100644
index 00000000000..9975f6c3c90
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/progspec.go
@@ -0,0 +1,144 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "os"
+ "strings"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+)
+
+type ProgSpec struct {
+ Program string
+ Args []string
+ BaseName string
+ Env map[string]string
+ numaSocket int
+ shouldUseCallgrind bool
+ shouldUseValgrind bool
+ shouldUseNumaCtl bool
+ shouldUseVespaMalloc bool
+ vespaMallocPreload string
+}
+
+func NewProgSpec(argv []string) *ProgSpec {
+ progName := argv[0]
+ binProg := progName + "-bin"
+ p := ProgSpec{
+ Program: binProg,
+ Args: argv,
+ BaseName: baseNameOf(progName),
+ Env: make(map[string]string),
+ numaSocket: -1,
+ }
+ p.Args[0] = binProg
+ return &p
+}
+
+func baseNameOf(s string) string {
+ idx := strings.LastIndex(s, "/")
+ idx++
+ return s[idx:]
+}
+
+func (p *ProgSpec) setenv(k, v string) {
+ p.Env[k] = v
+}
+
+func (p *ProgSpec) getenv(k string) string {
+ if v, ok := p.Env[k]; ok {
+ return v
+ }
+ return os.Getenv(k)
+}
+
+func (p *ProgSpec) prependPath(dirName string) {
+ pathList := []string{dirName}
+ oldPath := p.getenv(envvars.PATH)
+ if oldPath == "" {
+ oldPath = "/usr/bin"
+ }
+ for _, part := range strings.Split(oldPath, ":") {
+ if part != dirName {
+ pathList = append(pathList, part)
+ }
+ }
+ newPath := strings.Join(pathList, ":")
+ p.setenv(envvars.PATH, newPath)
+ os.Setenv(envvars.PATH, newPath)
+}
+
+func (p *ProgSpec) matchesListEnv(envVarName string) bool {
+ return p.matchesListString(p.getenv(envVarName))
+}
+
+func (p *ProgSpec) matchesListString(env string) bool {
+ if env == "all" {
+ trace.Debug(p.Program, "always matching in:", env)
+ return true
+ }
+ parts := strings.Fields(env)
+ for _, part := range parts {
+ if p.BaseName == part {
+ trace.Debug(p.Program, "has basename matching in:", env)
+ return true
+ }
+ trace.Debug("checking matching:", p.BaseName, "!=", part)
+ }
+ return false
+}
+
+func (p *ProgSpec) valueFromListEnv(envVarName string) string {
+ return p.valueFromListString(p.getenv(envVarName))
+}
+
+func (p *ProgSpec) valueFromListString(env string) string {
+ parts := strings.Fields(env)
+ for _, part := range parts {
+ idx := strings.Index(part, "=")
+ if idx <= 0 {
+ trace.Trace("expected key=value, but got:", part)
+ continue
+ }
+ partName := part[:idx]
+ idx++
+ partValue := part[idx:]
+ if p.BaseName == partName || partName == "all" {
+ trace.Debug(p.Program, "has basename matching in:", env)
+ return partValue
+ }
+ trace.Debug("checking matching:", p.BaseName, "!=", part)
+ }
+ return ""
+}
+
+func (spec *ProgSpec) effectiveEnv() []string {
+ env := make(map[string]string)
+ for _, entry := range os.Environ() {
+ addInMap := func(kv string) bool {
+ for idx, elem := range kv {
+ if elem == '=' {
+ k := kv[:idx]
+ env[k] = kv
+ return true
+ }
+ }
+ return false
+ }
+ if !addInMap(entry) {
+ env[entry] = ""
+ }
+ }
+ for k, v := range spec.Env {
+ trace.Trace("add to environment:", k, "=", v)
+ env[k] = k + "=" + v
+ }
+ envv := make([]string, 0, len(env))
+ for _, v := range env {
+ envv = append(envv, v)
+ }
+ return envv
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/progspec_test.go b/client/go/internal/admin/script-utils/startcbinary/progspec_test.go
new file mode 100644
index 00000000000..be113e4e350
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/progspec_test.go
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package startcbinary
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestProgSpec(t *testing.T) {
+ spec := NewProgSpec([]string{"/opt/vespa/bin/foobar"})
+ var b bool
+
+ b = spec.matchesListString("")
+ assert.Equal(t, false, b)
+ b = spec.matchesListString("foobar")
+ assert.Equal(t, true, b)
+ b = spec.matchesListString("foo bar")
+ assert.Equal(t, false, b)
+ b = spec.matchesListString("one foobar")
+ assert.Equal(t, true, b)
+ b = spec.matchesListString("foobar two")
+ assert.Equal(t, true, b)
+ b = spec.matchesListString("one foobar two")
+ assert.Equal(t, true, b)
+ b = spec.matchesListString("all")
+ assert.Equal(t, true, b)
+
+ var s string
+ s = spec.valueFromListString("")
+ assert.Equal(t, "", s)
+ s = spec.valueFromListString("foobar=123")
+ assert.Equal(t, "123", s)
+ s = spec.valueFromListString("one=1 foobar=123 two=2")
+ assert.Equal(t, "123", s)
+ s = spec.valueFromListString("one=1 all=123")
+ assert.Equal(t, "123", s)
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/startcbinary.go b/client/go/internal/admin/script-utils/startcbinary/startcbinary.go
new file mode 100644
index 00000000000..f5e58e59808
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/startcbinary.go
@@ -0,0 +1,47 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+)
+
+func startCbinary(spec *ProgSpec) int {
+ spec.configureCommonEnv()
+ spec.configurePath()
+ spec.configureTuning()
+ spec.configureValgrind()
+ spec.configureNumaCtl()
+ spec.configureHugePages()
+ spec.configureUseMadvise()
+ spec.configureVespaMalloc()
+ err := spec.run()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return 1
+ } else {
+ return 0
+ }
+}
+
+func (spec *ProgSpec) run() error {
+ prog := spec.Program
+ args := spec.Args
+ if spec.shouldUseValgrind {
+ args = spec.prependValgrind(args)
+ prog = spec.valgrindBinary()
+ } else if spec.shouldUseNumaCtl {
+ args = spec.prependNumaCtl(args)
+ prog = spec.numaCtlBinary()
+ }
+ if spec.shouldUseVespaMalloc {
+ spec.setenv(envvars.LD_PRELOAD, spec.vespaMallocPreload)
+ }
+ envv := spec.effectiveEnv()
+ return util.Execvpe(prog, args, envv)
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/tuning.go b/client/go/internal/admin/script-utils/startcbinary/tuning.go
new file mode 100644
index 00000000000..57230629d7a
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/tuning.go
@@ -0,0 +1,13 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+)
+
+func (spec *ProgSpec) configureTuning() {
+ util.OptionallyReduceTimerFrequency()
+ util.TuneResourceLimits()
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/valgrind.go b/client/go/internal/admin/script-utils/startcbinary/valgrind.go
new file mode 100644
index 00000000000..43a1ed602bd
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/valgrind.go
@@ -0,0 +1,82 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
+)
+
+func (p *ProgSpec) configureValgrind() {
+ p.shouldUseValgrind = false
+ p.shouldUseCallgrind = false
+ env := p.getenv(envvars.VESPA_USE_VALGRIND)
+ parts := strings.Split(env, " ")
+ for _, part := range parts {
+ if p.BaseName == part {
+ trace.Trace("using valgrind as", p.Program, "has basename in", envvars.VESPA_USE_VALGRIND, "=>", env)
+ backticks := util.BackTicksWithStderr
+ out, err := backticks.Run("which", "valgrind")
+ if err != nil {
+ trace.Trace("no valgrind, 'which' fails:", err, "=>", out)
+ return
+ }
+ if opts := p.getenv(envvars.VESPA_VALGRIND_OPT); strings.Contains(opts, "callgrind") {
+ p.shouldUseCallgrind = true
+ }
+ p.shouldUseValgrind = true
+ return
+ }
+ trace.Debug("checking", envvars.VESPA_USE_VALGRIND, ":", p.BaseName, "!=", part)
+ }
+}
+
+func (p *ProgSpec) valgrindBinary() string {
+ return "valgrind"
+}
+
+func (p *ProgSpec) valgrindOptions() []string {
+ env := p.getenv(envvars.VESPA_VALGRIND_OPT)
+ if env != "" {
+ return strings.Fields(env)
+ }
+ result := []string{
+ "--num-callers=32",
+ "--run-libc-freeres=yes",
+ "--track-origins=yes",
+ "--freelist-vol=1000000000",
+ "--leak-check=full",
+ "--show-reachable=yes",
+ }
+ result = addValgrindSuppression(result, "etc/vespa/valgrind-suppressions.txt")
+ result = addValgrindSuppression(result, "etc/vespa/suppressions.txt")
+ return result
+}
+
+func addValgrindSuppression(r []string, fn string) []string {
+ existsOk, fileName := vespa.HasFileUnderVespaHome(fn)
+ if existsOk {
+ r = append(r, fmt.Sprintf("--suppressions=%s", fileName))
+ }
+ return r
+}
+
+func (p *ProgSpec) valgrindLogOption() string {
+ return fmt.Sprintf("--log-file=%s/tmp/valgrind.%s.log.%d", vespa.FindHome(), p.BaseName, os.Getpid())
+}
+
+func (p *ProgSpec) prependValgrind(args []string) []string {
+ v := util.NewArrayList[string](15 + len(args))
+ v.Append(p.valgrindBinary())
+ v.AppendAll(p.valgrindOptions()...)
+ v.Append(p.valgrindLogOption())
+ v.AppendAll(args...)
+ return v
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/valgrind_test.go b/client/go/internal/admin/script-utils/startcbinary/valgrind_test.go
new file mode 100644
index 00000000000..48cc78474ed
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/valgrind_test.go
@@ -0,0 +1,92 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package startcbinary
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+)
+
+var tmpBin string
+var mockBinParent string
+
+func useMock(prog, target string) {
+ mock := fmt.Sprintf("%s/mockbin/%s", mockBinParent, prog)
+ symlink := fmt.Sprintf("%s/%s", tmpBin, target)
+ os.Remove(symlink)
+ err := os.Symlink(mock, symlink)
+ if err != nil {
+ util.JustExitWith(err)
+ }
+}
+
+func setupValgrind(t *testing.T, testFileName string) {
+ trace.AdjustVerbosity(1)
+ t.Setenv("VESPA_HOME", mockBinParent+"/mock_vespahome")
+ mockBinParent = strings.TrimSuffix(testFileName, "/valgrind_test.go")
+ tmpBin = t.TempDir() + "/mock.bin.valgrind_test"
+ err := os.MkdirAll(tmpBin, 0755)
+ assert.Nil(t, err)
+ t.Setenv("PATH", fmt.Sprintf("%s:%s", tmpBin, os.Getenv("PATH")))
+}
+
+func TestValgrindDetection(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ return
+ }
+ _, tfn, _, _ := runtime.Caller(0)
+ setupValgrind(t, tfn)
+ spec := NewProgSpec([]string{"/opt/vespa/bin/foobar"})
+ var argv []string
+
+ useMock("has-valgrind", "which")
+
+ t.Setenv("VESPA_USE_VALGRIND", "")
+ spec.configureValgrind()
+ assert.Equal(t, false, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+
+ t.Setenv("VESPA_USE_VALGRIND", "foo bar")
+ spec.configureValgrind()
+ assert.Equal(t, false, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+
+ t.Setenv("VESPA_USE_VALGRIND", "foobar")
+ spec.configureValgrind()
+ assert.Equal(t, true, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+
+ argv = spec.prependValgrind([]string{"/bin/myprog", "-c", "cfgid"})
+ trace.Trace("argv:", argv)
+ assert.Equal(t, 11, len(argv))
+ assert.Equal(t, "valgrind", argv[0])
+ assert.Equal(t, "/bin/myprog", argv[8])
+
+ t.Setenv("VESPA_USE_VALGRIND", "another foobar yetmore")
+ spec.configureValgrind()
+ assert.Equal(t, true, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+
+ t.Setenv("VESPA_VALGRIND_OPT", "--tool=callgrind")
+ spec.configureValgrind()
+ assert.Equal(t, true, spec.shouldUseValgrind)
+ assert.Equal(t, true, spec.shouldUseCallgrind)
+
+ argv = spec.prependValgrind([]string{"/bin/myprog", "-c", "cfgid"})
+ trace.Trace("argv:", argv)
+ assert.Equal(t, 6, len(argv))
+ assert.Equal(t, "valgrind", argv[0])
+ assert.Equal(t, "--tool=callgrind", argv[1])
+ assert.Equal(t, "/bin/myprog", argv[3])
+
+ useMock("no-valgrind", "which")
+ spec.configureValgrind()
+ assert.Equal(t, false, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+}
diff --git a/client/go/internal/admin/script-utils/startcbinary/vespamalloc.go b/client/go/internal/admin/script-utils/startcbinary/vespamalloc.go
new file mode 100644
index 00000000000..c6d53e1d03c
--- /dev/null
+++ b/client/go/internal/admin/script-utils/startcbinary/vespamalloc.go
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package startcbinary
+
+import (
+ "fmt"
+
+ "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
+)
+
+func vespaMallocLib(suf string) string {
+ prefixes := []string{"lib64", "lib"}
+ for _, pre := range prefixes {
+ fn := fmt.Sprintf("%s/vespa/malloc/%s", pre, suf)
+ existsOk, fileName := vespa.HasFileUnderVespaHome(fn)
+ if existsOk {
+ trace.Debug("found library:", fileName)
+ return fileName
+ }
+ trace.Debug("bad or missing library:", fn)
+ }
+ return ""
+}
+
+func (p *ProgSpec) configureVespaMalloc() {
+ p.shouldUseVespaMalloc = false
+ if p.matchesListEnv(envvars.VESPA_USE_NO_VESPAMALLOC) {
+ trace.Trace("use no vespamalloc:", p.BaseName)
+ return
+ }
+ if p.shouldUseValgrind && !p.shouldUseCallgrind {
+ trace.Trace("use valgrind, so no vespamalloc:", p.BaseName)
+ return
+ }
+ var useFile string
+ if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_DST) {
+ useFile = vespaMallocLib("libvespamallocdst16.so")
+ } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_D) {
+ useFile = vespaMallocLib("libvespamallocd.so")
+ } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC) {
+ useFile = vespaMallocLib("libvespamalloc.so")
+ }
+ trace.Trace("use file:", useFile)
+ if useFile == "" {
+ return
+ }
+ if loadAsHuge := p.getenv(envvars.VESPA_LOAD_CODE_AS_HUGEPAGES); loadAsHuge != "" {
+ otherFile := vespaMallocLib("libvespa_load_as_huge.so")
+ useFile = fmt.Sprintf("%s:%s", useFile, otherFile)
+ }
+ p.considerEnvFallback(envvars.VESPA_MALLOC_HUGEPAGES, envvars.VESPA_USE_HUGEPAGES)
+ p.vespaMallocPreload = useFile
+ p.shouldUseVespaMalloc = true
+}