diff options
Diffstat (limited to 'client/go/internal/admin/script-utils/startcbinary')
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 +} |