func (pse *PipestanceOverrides) getThreads(pqn string, phase string, def float64) float64 {
	for pqn != "" {
		val := pse.overridesbystage[pqn].GetThreads(phase)
		if val == nil {
			pqn = getParent(pqn)
		} else {
			util.LogInfo("overide", "At [%s.threads:%s] replace %g with %g",
				phase, pqn, def, *val)
			return *val
		}
	}
	return def
}
Compute the value to use for a stage's thread reservation, which might be
overridden.

pqn is the partially qualified node name.

def  is the default value to use if the value is not overridden.
package core
import (
	"encoding/json"
	"fmt"
	"os"
	"github.com/martian-lang/martian/martian/util"
)
func partiallyQualifiedName(n string) string {
	for count := 0; count < 2 && len(n) > 0; n = n[1:] {
		if n[0] == '.' {
			count++
		}
	}
	return n
}
type StageOverride struct {
	ForceVolatile *bool `json:"force_volatile,omitempty"`
	JoinThreads *float64     `json:"join.threads,omitempty"`
	JoinMem     *float64     `json:"join.mem_gb,omitempty"`
	JoinVMem    *float64     `json:"join.vmem_gb,omitempty"`
	JoinProfile *ProfileMode `json:"join.profile,omitempty"`
	ChunkThreads *float64     `json:"chunk.threads,omitempty"`
	ChunkMem     *float64     `json:"chunk.mem_gb,omitempty"`
	ChunkVMem    *float64     `json:"chunk.vmem_gb,omitempty"`
	ChunkProfile *ProfileMode `json:"chunk.profile,omitempty"`
	SplitThreads *float64     `json:"split.threads,omitempty"`
	SplitMem     *float64     `json:"split.mem_gb,omitempty"`
	SplitVMem    *float64     `json:"split.vmem_gb,omitempty"`
	SplitProfile *ProfileMode `json:"split.profile,omitempty"`
}
type PipestanceOverrides struct {
	filename         string
	overridesbystage map[string]*StageOverride
}
func ReadOverrides(path string) (*PipestanceOverrides, error) {
	pse := new(PipestanceOverrides)
	return pse, pse.Set(path)
}
func (*PipestanceOverrides) Type() string {
	return "pipestance overrides json"
}
func (pse *PipestanceOverrides) String() string {
	return pse.filename
}
func (pse *PipestanceOverrides) Set(path string) error {
	return pse.ReadFile(path)
}
func (pse *PipestanceOverrides) ReadFile(path string) error {
	pse.filename = path
	if path == "" {
		return nil
	}
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	dec := json.NewDecoder(f)
	dec.DisallowUnknownFields()
	err = dec.Decode(&pse.overridesbystage)
	if err != nil {
		return fmt.Errorf("decoding overrides content: %w", err)
	}
	util.Println("Loaded %v overrides from %v", len(pse.overridesbystage), path)
	return nil
}
func getParent(n string) string {
	for len(n) > 0 {
		if n[len(n)-1] == '.' {
			return n[:len(n)-1]
		}
		n = n[:len(n)-1]
	}
	return ""
}
func (pse *PipestanceOverrides) GetForceVolatile(node string, def bool) bool {
	if pse == nil {
		return def
	}
	pqn := partiallyQualifiedName(node)
	for pqn != "" {
		so := pse.overridesbystage[pqn]
		if so == nil || so.ForceVolatile == nil {
			pqn = getParent(pqn)
		} else {
			util.LogInfo("overide", "At [force_volatile:%v] replace %v with %v",
				pqn, def, *so.ForceVolatile)
			return *so.ForceVolatile
		}
	}
	return def
}
func (pse *PipestanceOverrides) GetResources(node string, phase string, res *JobResources) {
	if pse == nil {
		return
	}
	pqn := partiallyQualifiedName(node)
	res.Threads = pse.getThreads(pqn, phase, res.Threads)
	res.MemGB = pse.getMem(pqn, phase, res.MemGB)
	res.VMemGB = pse.getVMem(pqn, phase, res.VMemGB)
}