Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cpu_total_compute override #2745

Merged
merged 4 commits into from
Jul 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 32 additions & 23 deletions client/fingerprint/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,39 +31,48 @@ func (f *CPUFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bo
}

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
}
if cfg.CpuCompute != 0 {
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")
if modelName := stats.CPUModelName(); modelName != "" {
node.Attributes["cpu.modelname"] = modelName
}

return false, err
if mhz := stats.CPUMHzPerCore(); mhz > 0 {
node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz)
f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz)
}

modelName := stats.CPUModelName()
if modelName != "" {
node.Attributes["cpu.modelname"] = modelName
if numCores := stats.CPUNumCores(); numCores > 0 {
node.Attributes["cpu.numcores"] = fmt.Sprintf("%d", numCores)
f.logger.Printf("[DEBUG] fingerprint.cpu: core count: %d", numCores)
}

mhz := stats.CPUMHzPerCore()
node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz)
f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz)
tt := int(stats.TotalTicksAvailable())
if cfg.CpuCompute > 0 {
f.logger.Printf("[DEBUG] fingerprint.cpu: Using specified cpu compute %d", cfg.CpuCompute)
tt = cfg.CpuCompute
}

numCores := stats.CPUNumCores()
node.Attributes["cpu.numcores"] = fmt.Sprintf("%d", numCores)
f.logger.Printf("[DEBUG] fingerprint.cpu: core count: %d", numCores)
// 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")
}

tt := stats.TotalTicksAvailable()
node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%d", tt)

node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%.0f", tt)
if node.Resources == nil {
node.Resources = &structs.Resources{}
}

setResources(int(tt))
node.Resources.CPU = tt
return true, nil
}
36 changes: 36 additions & 0 deletions client/fingerprint/cpu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
19 changes: 11 additions & 8 deletions helper/stats/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math"
"sync"

multierror "github.com/hashicorp/go-multierror"
"github.com/shirou/gopsutil/cpu"
)

Expand All @@ -20,15 +21,15 @@ var (

func Init() error {
onceLer.Do(func() {
if cpuNumCores, initErr = cpu.Counts(true); initErr != nil {
initErr = fmt.Errorf("Unable to determine the number of CPU cores available: %v", initErr)
return
var merrs *multierror.Error
var err error
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
if cpuInfo, initErr = cpu.Info(); initErr != nil {
initErr = fmt.Errorf("Unable to obtain CPU information: %v", initErr)
return
if cpuInfo, err = cpu.Info(); err != nil {
merrs = multierror.Append(merrs, fmt.Errorf("Unable to obtain CPU information: %v", initErr))
}

for _, cpu := range cpuInfo {
Expand All @@ -41,6 +42,9 @@ func Init() error {
// node to fall into a unique computed node class
cpuMhzPerCore = math.Floor(cpuMhzPerCore)
cpuTotalTicks = math.Floor(float64(cpuNumCores) * cpuMhzPerCore)

// Set any errors that occurred
initErr = merrs.ErrorOrNil()
})
return initErr
}
Expand All @@ -60,8 +64,7 @@ func CPUModelName() string {
return cpuModelName
}

// TotalTicksAvailable calculates the total frequency available across all
// cores
// TotalTicksAvailable calculates the total Mhz available across all cores
func TotalTicksAvailable() float64 {
return cpuTotalTicks
}
14 changes: 10 additions & 4 deletions website/source/docs/runtime/interpolation.html.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,20 @@ Below is a table documenting common node properties:
<td><tt>${attr.cpu.arch}</tt></td>
<td>CPU architecture of the client (e.g. <tt>amd64</tt>, <tt>386</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>
<td><tt>${attr.cpu.numcores}</tt></td>
<td>Number of CPU cores on the client</td>
</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>
<td><tt>${attr.driver.&lt;property&gt;}</tt></td>
<td>See the [task drivers](/docs/drivers/index.html) for property documentation</td>
Expand Down