Skip to content
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
42 changes: 29 additions & 13 deletions internal/report/benchmarking_table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,33 +223,49 @@ func avxTurboFrequenciesFromOutput(output string) (instructionFreqs map[string][
return
}

// bucketsToCounts expands the core frequency buckets to a list of core counts (from column 1) and associated spec sse frequencies (from column 2)
func bucketsToCoresFreqs(specCoreFreqs [][]string) (cores []string, freqs []string, err error) {
// the first column is the core count, the second column is the spec sse frequency
// frequencyBucketsToFrequencies creates a slice of SSE frequencies from the spec core frequency buckets
// input: the first column is the bucket range, e.g. 1-44, the second (or third) column is the spec sse frequency
func frequencyBucketsToFrequencies(specCoreFreqs [][]string) (freqs []string, err error) {
if len(specCoreFreqs) < 2 || len(specCoreFreqs[0]) < 2 {
err = fmt.Errorf("unable to parse core frequency buckets")
return
}
rangeIdx := 0 // the first column is the bucket, e.g., 1-44
var sseIdx int
for i := range specCoreFreqs[0] {
if strings.Contains(strings.ToUpper(specCoreFreqs[0][i]), "SSE") {
sseIdx = i
break
}
}
if sseIdx == 0 {
err = fmt.Errorf("unable to find SSE frequency column")
return
}
for i := 1; i < len(specCoreFreqs); i++ {
// parse the bucket into start/end parts
bucket := strings.Split(specCoreFreqs[i][0], "-")
if len(bucket) != 2 {
err = fmt.Errorf("unable to parse bucket %s", specCoreFreqs[i][0])
bucketRange := strings.TrimSpace(specCoreFreqs[i][rangeIdx])
Comment thread
harp-intel marked this conversation as resolved.
// parse the bucketParts into start/end parts
bucketParts := strings.Split(bucketRange, "-")
if len(bucketParts) != 2 {
err = fmt.Errorf("unable to parse bucket range %s", bucketRange)
return
}
// parse the start and end parts into integers
var start int
var end int
start, err = strconv.Atoi(strings.TrimSpace(bucket[0]))
start, err = strconv.Atoi(strings.TrimSpace(bucketParts[0]))
if err != nil {
err = fmt.Errorf("unable to parse start %s", bucket[0])
err = fmt.Errorf("unable to parse start %s", bucketParts[0])
return
}
end, err = strconv.Atoi(strings.TrimSpace(bucket[1]))
end, err = strconv.Atoi(strings.TrimSpace(bucketParts[1]))
if err != nil {
err = fmt.Errorf("unable to parse end %s", bucket[1])
err = fmt.Errorf("unable to parse end %s", bucketParts[1])
return
}
// add the core count to the list
for j := start; j <= end; j++ {
cores = append(cores, strconv.Itoa(j))
freqs = append(freqs, specCoreFreqs[i][1])
freqs = append(freqs, specCoreFreqs[i][sseIdx])
}
}
return
Expand Down
88 changes: 59 additions & 29 deletions internal/report/table_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ var tableDefinitions = map[string]TableDefinition{
PrefetcherTableName: {
Name: PrefetcherTableName,
HasRows: true,
// TODO: Vendors: []string{"GenuineIntel"},
Vendors: []string{"GenuineIntel"},
ScriptNames: []string{
script.LscpuScriptName,
script.LspciBitsScriptName,
Expand All @@ -280,6 +280,7 @@ var tableDefinitions = map[string]TableDefinition{
AcceleratorTableName: {
Name: AcceleratorTableName,
Vendors: []string{"GenuineIntel"},
Models: []string{"143", "207", "173", "175", "221"}, // Sapphire Rapids, Emerald Rapids, Granite Rapids, Sierra Forest, Clearwater Forest
HasRows: true,
ScriptNames: []string{
script.LshwScriptName,
Expand Down Expand Up @@ -1246,13 +1247,13 @@ func elcTableInsights(outputs map[string]script.ScriptOutput, tableValues TableV
}

func maximumFrequencyTableValues(outputs map[string]script.ScriptOutput) []Field {
specCoreFrequencies, err := getSpecCoreFrequenciesFromOutput(outputs)
frequencyBuckets, err := getSpecFrequencyBuckets(outputs)
if err != nil {
slog.Warn("unable to get spec core frequencies", slog.String("error", err.Error()))
return []Field{}
}
var fields []Field
for i, row := range specCoreFrequencies {
for i, row := range frequencyBuckets {
// first row is field names
if i == 0 {
for _, fieldName := range row {
Expand Down Expand Up @@ -2016,48 +2017,77 @@ func cpuTemperatureTableValues(outputs map[string]script.ScriptOutput) []Field {
}

func cpuFrequencyTableValues(outputs map[string]script.ScriptOutput) []Field {
specCoreFrequencies, err := getSpecCoreFrequenciesFromOutput(outputs)
if err != nil {
slog.Warn("unable to get spec core frequencies", slog.String("error", err.Error()))
return []Field{}
}
// get the core counts from the buckets
cores, specSSEFreqs, err := bucketsToCoresFreqs(specCoreFrequencies)
if err != nil {
slog.Error("unable to convert buckets to counts", slog.String("error", err.Error()))
return []Field{}
}
// get the sse, avx256, and avx512 frequencies from the avx-turbo output
instructionFreqs, err := avxTurboFrequenciesFromOutput(outputs[script.TurboFrequenciesScriptName].Stdout)
if err != nil {
slog.Error("unable to get avx turbo frequencies", slog.String("error", err.Error()))
return []Field{}
}
// we're expecting scalar_iadd, avx256_fma, avx512_fma, all associated slices should be of the same length
// we're expecting scalar_iadd, avx256_fma, avx512_fma
scalarIaddFreqs := instructionFreqs["scalar_iadd"]
avx256FmaFreqs := instructionFreqs["avx256_fma"]
avx512FmaFreqs := instructionFreqs["avx512_fma"]
// check that we have the same number of frequencies as cores (sanity check)
numCores := len(cores)
if len(specSSEFreqs) != numCores || len(scalarIaddFreqs) != numCores || len(avx256FmaFreqs) != numCores || len(avx512FmaFreqs) != numCores {
slog.Error("mismatched number of frequencies and core counts", slog.Int("numCores", numCores), slog.Int("numSpecSSEFreqs", len(specSSEFreqs)), slog.Int("numScalarIaddFreqs", len(scalarIaddFreqs)), slog.Int("numAvx256FmaFreqs", len(avx256FmaFreqs)), slog.Int("numAvx512FmaFreqs", len(avx512FmaFreqs)))
// stop if we don't have any scalar_iadd frequencies
if len(scalarIaddFreqs) == 0 {
slog.Error("no scalar_iadd frequencies found")
return []Field{}
}
// get the spec core frequencies from the spec output
var specSSEFreqs []string
frequencyBuckets, err := getSpecFrequencyBuckets(outputs)
if err == nil && len(frequencyBuckets) > 2 {
// get the frequencies from the buckets
specSSEFreqs, err = frequencyBucketsToFrequencies(frequencyBuckets)
if err != nil {
slog.Error("unable to convert buckets to counts", slog.String("error", err.Error()))
return []Field{}
}
// trim the spec frequencies to the length of the scalar_iadd frequencies
// this can happen when the actual core count is less than the number of cores in the spec
if len(scalarIaddFreqs) < len(specSSEFreqs) {
specSSEFreqs = specSSEFreqs[:len(scalarIaddFreqs)]
}
}
// create the fields
fields := []Field{
{Name: "cores"},
{Name: "sse (expected)"},
{Name: "sse"}, // scalar_iadd
{Name: "avx2"}, // avx256_fma
{Name: "avx512"}, // avx512_fma
}
coresIdx := 0 // always the first field
var specSSEFieldIdx int
var scalarIaddFieldIdx int
var avx2FieldIdx int
var avx512FieldIdx int
if len(specSSEFreqs) > 0 {
fields = append(fields, Field{Name: "SSE (expected)"})
specSSEFieldIdx = len(fields) - 1
}
if len(scalarIaddFreqs) > 0 {
fields = append(fields, Field{Name: "SSE"})
scalarIaddFieldIdx = len(fields) - 1
}
if len(avx256FmaFreqs) > 0 {
fields = append(fields, Field{Name: "AVX2"})
avx2FieldIdx = len(fields) - 1
}
if len(avx512FmaFreqs) > 0 {
fields = append(fields, Field{Name: "AVX512"})
avx512FieldIdx = len(fields) - 1
}
// add the data to the fields
fields[0].Values = cores
fields[1].Values = specSSEFreqs
for i := range cores {
fields[2].Values = append(fields[2].Values, fmt.Sprintf("%.1f", scalarIaddFreqs[i]))
fields[3].Values = append(fields[3].Values, fmt.Sprintf("%.1f", avx256FmaFreqs[i]))
fields[4].Values = append(fields[4].Values, fmt.Sprintf("%.1f", avx512FmaFreqs[i]))
for i := range scalarIaddFreqs { // scalarIaddFreqs is required
fields[coresIdx].Values = append(fields[coresIdx].Values, fmt.Sprintf("%d", i+1))
if specSSEFieldIdx > 0 {
fields[specSSEFieldIdx].Values = append(fields[specSSEFieldIdx].Values, specSSEFreqs[i])
}
if scalarIaddFieldIdx > 0 {
fields[scalarIaddFieldIdx].Values = append(fields[scalarIaddFieldIdx].Values, fmt.Sprintf("%.1f", scalarIaddFreqs[i]))
}
if avx2FieldIdx > 0 {
fields[avx2FieldIdx].Values = append(fields[avx2FieldIdx].Values, fmt.Sprintf("%.1f", avx256FmaFreqs[i]))
}
if avx512FieldIdx > 0 {
fields[avx512FieldIdx].Values = append(fields[avx512FieldIdx].Values, fmt.Sprintf("%.1f", avx512FmaFreqs[i]))
}
}
return fields
}
Expand Down
69 changes: 43 additions & 26 deletions internal/report/table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,37 +197,38 @@ func baseFrequencyFromOutput(outputs map[string]script.ScriptOutput) string {
return ""
}

func convertMsrToDecimals(msr string) (decVals []int64, err error) {
func convertMsrToDecimals(msr string) (decVals []int, err error) {
re := regexp.MustCompile(`[0-9a-fA-F][0-9a-fA-F]`)
hexVals := re.FindAll([]byte(msr), -1)
if hexVals == nil {
err = fmt.Errorf("no hex values found in msr")
return
}
decVals = make([]int64, len(hexVals))
decVals = make([]int, len(hexVals))
decValsIndex := len(decVals) - 1
for _, hexVal := range hexVals {
var decVal int64
decVal, err = strconv.ParseInt(string(hexVal), 16, 64)
decVal, err = strconv.ParseInt(string(hexVal), 16, 0)
if err != nil {
return
}
decVals[decValsIndex] = decVal
decVals[decValsIndex] = int(decVal)
decValsIndex--
}
return
}

// getSpecCoreFrequenciesFromOutput
// getSpecFrequencyBuckets
// returns slice of rows
// first row is header
// each row is a slice of strings
// "cores", "sse", "avx2", "avx512", "avx512h", "amx"
// "0-41", "3.5", "3.5", "3.3", "3.2", "3.1"
// "42-63", "3.5", "3.5", "3.3", "3.2", "3.1"
// "64-85", "3.5", "3.5", "3.3", "3.2", "3.1"
// "cores", "cores per die", "sse", "avx2", "avx512", "avx512h", "amx"
// "0-41", "0-20", "3.5", "3.5", "3.3", "3.2", "3.1"
// "42-63", "21-31", "3.5", "3.5", "3.3", "3.2", "3.1"
// "64-85", "32-43", "3.5", "3.5", "3.3", "3.2", "3.1"
// ...
func getSpecCoreFrequenciesFromOutput(outputs map[string]script.ScriptOutput) ([][]string, error) {
// the "cores per die" column is only present for some architectures
func getSpecFrequencyBuckets(outputs map[string]script.ScriptOutput) ([][]string, error) {
arch := uarchFromOutput(outputs)
if arch == "" {
return nil, fmt.Errorf("uarch is required")
Expand All @@ -254,7 +255,9 @@ func getSpecCoreFrequenciesFromOutput(outputs map[string]script.ScriptOutput) ([
// get list of buckets
bucketCoreCounts, _ := convertMsrToDecimals(values[0])
// create buckets
var buckets []string
var totalCoreBuckets []string // only for multi-die architectures
var dieCoreBuckets []string
totalCoreStartRange := 1
startRange := 1
var archMultiplier int
if strings.Contains(arch, "SRF") || strings.Contains(arch, "CWF") {
Expand All @@ -267,19 +270,22 @@ func getSpecCoreFrequenciesFromOutput(outputs map[string]script.ScriptOutput) ([
archMultiplier = 1
}
for _, count := range bucketCoreCounts {
adjustedCount := count * int64(archMultiplier)
if startRange > int(adjustedCount) {
break
if archMultiplier > 1 {
totalCoreCount := count * archMultiplier
if totalCoreStartRange > int(totalCoreCount) {
break
}
totalCoreBuckets = append(totalCoreBuckets, fmt.Sprintf("%d-%d", totalCoreStartRange, totalCoreCount))
totalCoreStartRange = int(totalCoreCount) + 1
}
bucketRange := fmt.Sprintf("%d-%d", startRange, adjustedCount)
buckets = append(buckets, bucketRange)
startRange = int(adjustedCount) + 1
dieCoreBuckets = append(dieCoreBuckets, fmt.Sprintf("%d-%d", startRange, count))
startRange = int(count) + 1
Comment thread Fixed
}
// get the frequencies for each isa
var allIsaFreqs [][]string
for _, isaHex := range values[1:] {
var isaFreqs []string
var freqs []int64
var freqs []int
if isaHex != "0" {
var err error
freqs, err = convertMsrToDecimals(isaHex)
Expand All @@ -288,7 +294,7 @@ func getSpecCoreFrequenciesFromOutput(outputs map[string]script.ScriptOutput) ([
}
} else {
// if the ISA is not supported, set the frequency to zero for all buckets
freqs = make([]int64, len(bucketCoreCounts))
freqs = make([]int, len(bucketCoreCounts))
for i := range freqs {
freqs[i] = 0
}
Expand All @@ -302,17 +308,28 @@ func getSpecCoreFrequenciesFromOutput(outputs map[string]script.ScriptOutput) ([
}
// format the output
var specCoreFreqs [][]string
// add bucket field name
specCoreFreqs = append(specCoreFreqs, []string{fieldNames[0]})
specCoreFreqs = make([][]string, 1, len(dieCoreBuckets)+1)
// add bucket field name(s)
specCoreFreqs[0] = append(specCoreFreqs[0], "Cores")
if archMultiplier > 1 {
specCoreFreqs[0] = append(specCoreFreqs[0], "Cores per Die")
}
// add fieldNames for ISAs that have frequencies
for i := range allIsaFreqs {
if allIsaFreqs[i][0] == "0.0" {
continue
}
specCoreFreqs[0] = append(specCoreFreqs[0], fieldNames[i+1])
specCoreFreqs[0] = append(specCoreFreqs[0], strings.ToUpper(fieldNames[i+1]))
}
for i, bucket := range buckets {
row := []string{bucket}
for i, bucket := range dieCoreBuckets {
row := make([]string, 0, len(allIsaFreqs)+2)
// add the total core buckets for multi-die architectures
if archMultiplier > 1 {
row = append(row, totalCoreBuckets[i])
}
// add the die core buckets
row = append(row, bucket)
// add the frequencies for each ISA
for _, isaFreqs := range allIsaFreqs {
if isaFreqs[0] == "0.0" {
continue
Expand Down Expand Up @@ -340,15 +357,15 @@ func maxFrequencyFromOutput(outputs map[string]script.ScriptOutput) string {
}
}
// get the max frequency from the MSR/tpmi
specCoreFrequencies, err := getSpecCoreFrequenciesFromOutput(outputs)
specCoreFrequencies, err := getSpecFrequencyBuckets(outputs)
if err == nil && len(specCoreFrequencies) > 1 && len(specCoreFrequencies[1]) > 1 {
return specCoreFrequencies[1][1] + "GHz"
}
return valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "4", `Max Speed:\s(.*)`)
}

func allCoreMaxFrequencyFromOutput(outputs map[string]script.ScriptOutput) string {
specCoreFrequencies, err := getSpecCoreFrequenciesFromOutput(outputs)
specCoreFrequencies, err := getSpecFrequencyBuckets(outputs)
if err == nil && len(specCoreFrequencies) >= 2 && len(specCoreFrequencies[1]) > 1 {
// the last entry in the 2nd column is the max all-core frequency
return specCoreFrequencies[len(specCoreFrequencies)-1][1] + "GHz"
Expand Down