From 89abaf5ef4331a8b69d205c2a758bea917cab1c3 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 27 Jun 2017 11:54:15 -0700 Subject: [PATCH 1/4] Don't fail on first error detecting cpu stats Since cpu.Counts() never returns an error this doesn't functionally change anything today. --- helper/stats/cpu.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/helper/stats/cpu.go b/helper/stats/cpu.go index 7326deda6..e6d3bf26c 100644 --- a/helper/stats/cpu.go +++ b/helper/stats/cpu.go @@ -5,6 +5,7 @@ import ( "math" "sync" + multierror "github.com/hashicorp/go-multierror" "github.com/shirou/gopsutil/cpu" ) @@ -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 { @@ -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 } @@ -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 } From ecf090e9805376462f1d63a59e631c33453620c3 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 27 Jun 2017 11:56:52 -0700 Subject: [PATCH 2/4] Fix cpu_total_compute override --- client/fingerprint/cpu.go | 58 +++++++++++++++++++--------------- client/fingerprint/cpu_test.go | 36 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/client/fingerprint/cpu.go b/client/fingerprint/cpu.go index 421614824..a75ee0d8b 100644 --- a/client/fingerprint/cpu.go +++ b/client/fingerprint/cpu.go @@ -31,39 +31,47 @@ 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) - - 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 + f.logger.Printf("[WARN] fingerprint.cpu: %v", err) } - modelName := stats.CPUModelName() - if modelName != "" { + if cfg.CpuCompute != 0 { + setResources(cfg.CpuCompute) + return true, nil + } + + if modelName := stats.CPUModelName(); modelName != "" { node.Attributes["cpu.modelname"] = modelName } - mhz := stats.CPUMHzPerCore() - node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz) - f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz) + if mhz := stats.CPUMHzPerCore(); mhz > 0 { + node.Attributes["cpu.frequency"] = fmt.Sprintf("%.0f", mhz) + f.logger.Printf("[DEBUG] fingerprint.cpu: frequency: %.0f MHz", mhz) + } - numCores := stats.CPUNumCores() - node.Attributes["cpu.numcores"] = fmt.Sprintf("%d", numCores) - f.logger.Printf("[DEBUG] fingerprint.cpu: core count: %d", numCores) + if numCores := stats.CPUNumCores(); numCores > 0 { + node.Attributes["cpu.numcores"] = fmt.Sprintf("%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) + // Set the node compute resources if they are detected/configured + if tt > 0 { + node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%d", tt) - setResources(int(tt)) + if node.Resources == nil { + node.Resources = &structs.Resources{} + } + + node.Resources.CPU = tt + } else { + f.logger.Printf("[INFO] fingerprint.cpu: cannot detect cpu total compute. "+ + "CPU compute must be set manually using the client config option %q", + "cpu_total_compute") + } return true, nil } diff --git a/client/fingerprint/cpu_test.go b/client/fingerprint/cpu_test.go index 55c1e1008..238d55770 100644 --- a/client/fingerprint/cpu_test.go +++ b/client/fingerprint/cpu_test.go @@ -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) + } +} From 0f65a566270090de0cb97b2af0b89fa61165552b Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 27 Jun 2017 11:58:01 -0700 Subject: [PATCH 3/4] Document cpu.totalcompute attribute --- .../source/docs/runtime/interpolation.html.md.erb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/website/source/docs/runtime/interpolation.html.md.erb b/website/source/docs/runtime/interpolation.html.md.erb index b5f9cb6b8..b414e9158 100644 --- a/website/source/docs/runtime/interpolation.html.md.erb +++ b/website/source/docs/runtime/interpolation.html.md.erb @@ -114,14 +114,20 @@ Below is a table documenting common node properties: ${attr.cpu.arch} CPU architecture of the client (e.g. amd64, 386) - - ${attr.consul.datacenter} - The Consul datacenter of the client (if Consul is found) - ${attr.cpu.numcores} Number of CPU cores on the client + + ${attr.cpu.totalcompute} + + cpu.frequency × cpu.numcores but may be overridden by client.cpu_total_compute + + + + ${attr.consul.datacenter} + The Consul datacenter of the client (if Consul is found) + ${attr.driver.<property>} See the [task drivers](/docs/drivers/index.html) for property documentation From b2382f99f207aefc25da5892e65f3d316a767907 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Mon, 3 Jul 2017 13:25:33 -0700 Subject: [PATCH 4/4] 0 compute == error --- client/fingerprint/cpu.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/client/fingerprint/cpu.go b/client/fingerprint/cpu.go index a75ee0d8b..0c9b4c25d 100644 --- a/client/fingerprint/cpu.go +++ b/client/fingerprint/cpu.go @@ -59,19 +59,20 @@ func (f *CPUFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bo tt = cfg.CpuCompute } - // Set the node compute resources if they are detected/configured - if tt > 0 { - node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%d", tt) - - if node.Resources == nil { - node.Resources = &structs.Resources{} - } - - node.Resources.CPU = tt - } else { - f.logger.Printf("[INFO] fingerprint.cpu: cannot detect cpu total compute. "+ + // 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") } + + node.Attributes["cpu.totalcompute"] = fmt.Sprintf("%d", tt) + + if node.Resources == nil { + node.Resources = &structs.Resources{} + } + + node.Resources.CPU = tt return true, nil }