Skip to content

potential solution to hyperthreading reporting error #415

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

Merged
merged 8 commits into from
Jul 15, 2025
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
27 changes: 27 additions & 0 deletions internal/report/table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,11 +453,25 @@ func hyperthreadingFromOutput(outputs map[string]script.ScriptOutput) string {
sockets := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`)
coresPerSocket := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`)
cpus := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU\(.*:\s*(.+?)$`)
onlineCpus := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^On-line CPU\(s\) list:\s*(.+)$`)
threadsPerCore := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Thread\(s\) per core:\s*(.+)$`)

numCPUs, err := strconv.Atoi(cpus) // logical CPUs
if err != nil {
slog.Error("error parsing cpus from lscpu")
return ""
}
onlineCpusList, err := util.SelectiveIntRangeToIntList(onlineCpus) // logical online CPUs
numOnlineCpus := len(onlineCpusList)
if err != nil {
slog.Error("error parsing online cpus from lscpu")
numOnlineCpus = 0 // set to 0 to indicate parsing failed, will use numCPUs instead
}
numThreadsPerCore, err := strconv.Atoi(threadsPerCore) // logical threads per core
if err != nil {
slog.Error("error parsing threads per core from lscpu")
numThreadsPerCore = 0
}
numSockets, err := strconv.Atoi(sockets)
if err != nil {
slog.Error("error parsing sockets from lscpu")
Expand All @@ -472,9 +486,22 @@ func hyperthreadingFromOutput(outputs map[string]script.ScriptOutput) string {
if err != nil {
return ""
}
if numOnlineCpus > 0 && numOnlineCpus < numCPUs {
// if online CPUs list is available, use it to determine the number of CPUs
// supersedes lscpu output of numCPUs which counts CPUs on the system, not online CPUs
numCPUs = numOnlineCpus
}
if cpu.LogicalThreadCount < 2 {
return "N/A"
} else if numThreadsPerCore == 1 {
// if threads per core is 1, hyperthreading is disabled
return "Disabled"
} else if numThreadsPerCore >= 2 {
// if threads per core is greater than or equal to 2, hyperthreading is enabled
return "Enabled"
} else if numCPUs > numCoresPerSocket*numSockets {
// if the threads per core attribute is not available, we can still check if hyperthreading is enabled
// by checking if the number of logical CPUs is greater than the number of physical cores
return "Enabled"
} else {
return "Disabled"
Expand Down
259 changes: 259 additions & 0 deletions internal/report/table_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,269 @@ package report
// SPDX-License-Identifier: BSD-3-Clause

import (
"perfspect/internal/script"
"reflect"
"testing"
)

func TestHyperthreadingFromOutput(t *testing.T) {
tests := []struct {
name string
lscpuOutput string
wantResult string
}{
{
name: "Hyperthreading enabled - 2 threads per core",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: 0-15
`,
wantResult: "Enabled",
},
{
name: "Hyperthreading disabled - 1 thread per core",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 8
Thread(s) per core: 1
On-line CPU(s) list: 0-7
`,
wantResult: "Disabled",
},
{
name: "Hyperthreading enabled - detected by CPU count vs core count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 2
Core(s) per socket: 8
CPU(s): 32
On-line CPU(s) list: 0-31
`,
wantResult: "Enabled",
},
{
name: "Hyperthreading disabled - CPU count equals core count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 2
Core(s) per socket: 8
CPU(s): 16
On-line CPU(s) list: 0-15
`,
wantResult: "Disabled",
},
{
name: "Online CPUs less than total CPUs - use online count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: 0-7
`,
wantResult: "Enabled",
},
{
name: "Missing threads per core - fallback to CPU vs core comparison",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 16
On-line CPU(s) list: 0-15
`,
wantResult: "Enabled",
},
{
name: "Error parsing CPU count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): invalid
Thread(s) per core: 2
On-line CPU(s) list: 0-15
`,
wantResult: "",
},
{
name: "Error parsing socket count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): invalid
Core(s) per socket: 8
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: 0-15
`,
wantResult: "",
},
{
name: "Error parsing cores per socket",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: invalid
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: 0-15
`,
wantResult: "",
},
{
name: "Invalid online CPU list - should continue with total CPU count",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: invalid-range
`,
wantResult: "Enabled",
},
{
name: "Single core CPU - disabled result",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 1
CPU(s): 1
Thread(s) per core: 1
On-line CPU(s) list: 0
`,
wantResult: "Disabled",
},
{
name: "4 threads per core - enabled",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 32
Thread(s) per core: 4
On-line CPU(s) list: 0-31
`,
wantResult: "Enabled",
},
{
name: "Missing CPU family - getCPUExtended will fail",
lscpuOutput: `
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 16
Thread(s) per core: 2
On-line CPU(s) list: 0-15
`,
wantResult: "",
},
{
name: "Dual socket system with hyperthreading",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 2
Core(s) per socket: 16
CPU(s): 64
Thread(s) per core: 2
On-line CPU(s) list: 0-63
`,
wantResult: "Enabled",
},
{
name: "Quad socket system without hyperthreading",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 4
Core(s) per socket: 12
CPU(s): 48
Thread(s) per core: 1
On-line CPU(s) list: 0-47
`,
wantResult: "Disabled",
},
{
name: "Offlined cores with hyperthreading disabled and no threads per core",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 64
On-line CPU(s) list: 0-7
`,
wantResult: "Disabled",
},
{
name: "Offlined cores with hyperthreading enabled and no threads per core",
lscpuOutput: `
CPU family: 6
Model: 143
Stepping: 8
Socket(s): 1
Core(s) per socket: 8
CPU(s): 64
On-line CPU(s) list: 0-7,32-39
`,
wantResult: "Enabled",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outputs := map[string]script.ScriptOutput{
script.LscpuScriptName: {
Stdout: tt.lscpuOutput,
Stderr: "",
Exitcode: 0,
},
}

result := hyperthreadingFromOutput(outputs)
if result != tt.wantResult {
t.Errorf("hyperthreadingFromOutput() = %q, want %q", result, tt.wantResult)
}
})
}
}

func TestGetFrequenciesFromMSR(t *testing.T) {
tests := []struct {
name string
Expand Down