Merge pull request #2745 from hashicorp/b-2638-cpu_total_compute

Fix cpu_total_compute override
This commit is contained in:
Michael Schurter 2017-07-03 15:13:51 -07:00 committed by GitHub
commit 6b7d7a41f8
4 changed files with 91 additions and 37 deletions

View File

@ -31,39 +31,48 @@ func (f *CPUFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bo
} }
if err := stats.Init(); err != nil { if err := stats.Init(); err != nil {
err := fmt.Errorf("Unable to obtain CPU information: %v", err) f.logger.Printf("[WARN] fingerprint.cpu: %v", err)
if cfg.CpuCompute != 0 {
f.logger.Printf("[DEBUG] fingerprint.cpu: %v. Using specified cpu compute %d", err, cfg.CpuCompute)
setResources(cfg.CpuCompute)
return true, nil
}
f.logger.Printf("[ERR] fingerprint.cpu: %v", err)
f.logger.Printf("[INFO] fingerprint.cpu: cpu compute may be set manually"+
" using the client config option %q on machines where cpu information"+
" can not be automatically detected.", "cpu_total_compute")
return false, err
} }
modelName := stats.CPUModelName() if cfg.CpuCompute != 0 {
if modelName != "" { setResources(cfg.CpuCompute)
return true, nil
}
if modelName := stats.CPUModelName(); modelName != "" {
node.Attributes["cpu.modelname"] = modelName node.Attributes["cpu.modelname"] = modelName
} }
mhz := stats.CPUMHzPerCore() if mhz := stats.CPUMHzPerCore(); mhz > 0 {
node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz) node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz)
f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz) f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz)
}
numCores := stats.CPUNumCores() if numCores := stats.CPUNumCores(); numCores > 0 {
node.Attributes["cpu.numcores"] = fmt.Sprintf("%d", numCores) node.Attributes["cpu.numcores"] = fmt.Sprintf("%d", numCores)
f.logger.Printf("[DEBUG] fingerprint.cpu: core count: %d", numCores) f.logger.Printf("[DEBUG] fingerprint.cpu: core count: %d", numCores)
}
tt := stats.TotalTicksAvailable() tt := int(stats.TotalTicksAvailable())
if cfg.CpuCompute > 0 {
f.logger.Printf("[DEBUG] fingerprint.cpu: Using specified cpu compute %d", cfg.CpuCompute)
tt = cfg.CpuCompute
}
node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%.0f", tt) // Return an error if no cpu was detected or explicitly set as this
// node would be unable to receive any allocations.
if tt == 0 {
return false, fmt.Errorf("cannot detect cpu total compute. "+
"CPU compute must be set manually using the client config option %q",
"cpu_total_compute")
}
setResources(int(tt)) node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%d", tt)
if node.Resources == nil {
node.Resources = &structs.Resources{}
}
node.Resources.CPU = tt
return true, nil return true, nil
} }

View File

@ -40,3 +40,39 @@ func TestCPUFingerprint(t *testing.T) {
} }
} }
// TestCPUFingerprint_OverrideCompute asserts that setting cpu_total_compute in
// the client config overrides the detected CPU freq (if any).
func TestCPUFingerprint_OverrideCompute(t *testing.T) {
f := NewCPUFingerprint(testLogger())
node := &structs.Node{
Attributes: make(map[string]string),
}
cfg := &config.Config{}
ok, err := f.Fingerprint(cfg, node)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should apply")
}
// Get actual system CPU
origCPU := node.Resources.CPU
// Override it with a setting
cfg.CpuCompute = origCPU + 123
// Make sure the Fingerprinter applies the override
ok, err = f.Fingerprint(cfg, node)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should apply")
}
if node.Resources.CPU != cfg.CpuCompute {
t.Fatalf("expected override cpu of %d but found %d", cfg.CpuCompute, node.Resources.CPU)
}
}

View File

@ -5,6 +5,7 @@ import (
"math" "math"
"sync" "sync"
multierror "github.com/hashicorp/go-multierror"
"github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/cpu"
) )
@ -20,15 +21,15 @@ var (
func Init() error { func Init() error {
onceLer.Do(func() { onceLer.Do(func() {
if cpuNumCores, initErr = cpu.Counts(true); initErr != nil { var merrs *multierror.Error
initErr = fmt.Errorf("Unable to determine the number of CPU cores available: %v", initErr) var err error
return if cpuNumCores, err = cpu.Counts(true); err != nil {
merrs = multierror.Append(merrs, fmt.Errorf("Unable to determine the number of CPU cores available: %v", err))
} }
var cpuInfo []cpu.InfoStat var cpuInfo []cpu.InfoStat
if cpuInfo, initErr = cpu.Info(); initErr != nil { if cpuInfo, err = cpu.Info(); err != nil {
initErr = fmt.Errorf("Unable to obtain CPU information: %v", initErr) merrs = multierror.Append(merrs, fmt.Errorf("Unable to obtain CPU information: %v", initErr))
return
} }
for _, cpu := range cpuInfo { for _, cpu := range cpuInfo {
@ -41,6 +42,9 @@ func Init() error {
// node to fall into a unique computed node class // node to fall into a unique computed node class
cpuMhzPerCore = math.Floor(cpuMhzPerCore) cpuMhzPerCore = math.Floor(cpuMhzPerCore)
cpuTotalTicks = math.Floor(float64(cpuNumCores) * cpuMhzPerCore) cpuTotalTicks = math.Floor(float64(cpuNumCores) * cpuMhzPerCore)
// Set any errors that occurred
initErr = merrs.ErrorOrNil()
}) })
return initErr return initErr
} }
@ -60,8 +64,7 @@ func CPUModelName() string {
return cpuModelName return cpuModelName
} }
// TotalTicksAvailable calculates the total frequency available across all // TotalTicksAvailable calculates the total Mhz available across all cores
// cores
func TotalTicksAvailable() float64 { func TotalTicksAvailable() float64 {
return cpuTotalTicks return cpuTotalTicks
} }

View File

@ -114,14 +114,20 @@ Below is a table documenting common node properties:
<td><tt>${attr.cpu.arch}</tt></td> <td><tt>${attr.cpu.arch}</tt></td>
<td>CPU architecture of the client (e.g. <tt>amd64</tt>, <tt>386</tt>)</td> <td>CPU architecture of the client (e.g. <tt>amd64</tt>, <tt>386</tt>)</td>
</tr> </tr>
<tr>
<td><tt>${attr.consul.datacenter}</tt></td>
<td>The Consul datacenter of the client (if Consul is found)</td>
</tr>
<tr> <tr>
<td><tt>${attr.cpu.numcores}</tt></td> <td><tt>${attr.cpu.numcores}</tt></td>
<td>Number of CPU cores on the client</td> <td>Number of CPU cores on the client</td>
</tr> </tr>
<tr>
<td><tt>${attr.cpu.totalcompute}</tt></td>
<td>
<tt>cpu.frequency &times; cpu.numcores</tt> but may be overridden by <tt>client.cpu_total_compute</tt>
</td>
</tr>
<tr>
<td><tt>${attr.consul.datacenter}</tt></td>
<td>The Consul datacenter of the client (if Consul is found)</td>
</tr>
<tr> <tr>
<td><tt>${attr.driver.&lt;property&gt;}</tt></td> <td><tt>${attr.driver.&lt;property&gt;}</tt></td>
<td>See the [task drivers](/docs/drivers/index.html) for property documentation</td> <td>See the [task drivers](/docs/drivers/index.html) for property documentation</td>