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
16 changes: 9 additions & 7 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"perfspect/internal/progress"
"perfspect/internal/report"
"perfspect/internal/script"
"perfspect/internal/table"
"perfspect/internal/target"
"perfspect/internal/util"
"slices"
Expand Down Expand Up @@ -283,9 +284,9 @@ func setOnTarget(cmd *cobra.Command, myTarget target.Target, flagGroups []flagGr

// getConfig collects the configuration data from the target(s)
func getConfig(myTargets []target.Target, localTempDir string) ([]common.TargetScriptOutputs, error) {
scriptNames := report.GetScriptNamesForTable(report.ConfigurationTableName)

var scriptsToRun []script.ScriptDefinition
for _, scriptName := range scriptNames {
for _, scriptName := range tableDefinitions[ConfigurationTableName].ScriptNames {
scriptsToRun = append(scriptsToRun, script.GetScriptByName(scriptName))
}
multiSpinner := progress.NewMultiSpinner()
Expand Down Expand Up @@ -317,7 +318,6 @@ func getConfig(myTargets []target.Target, localTempDir string) ([]common.TargetS
for _, target := range myTargets {
for _, targetScriptOutputs := range allTargetScriptOutputs {
if targetScriptOutputs.TargetName == target.GetName() {
targetScriptOutputs.TableNames = []string{report.ConfigurationTableName}
orderedTargetScriptOutputs = append(orderedTargetScriptOutputs, targetScriptOutputs)
break
}
Expand All @@ -333,15 +333,17 @@ func processConfig(targetScriptOutputs []common.TargetScriptOutputs) (map[string
var err error
for _, targetScriptOutput := range targetScriptOutputs {
// process the tables, i.e., get field values from raw script output
tableNames := []string{report.ConfigurationTableName}
var tableValues []report.TableValues
if tableValues, err = report.ProcessTables(tableNames, targetScriptOutput.ScriptOutputs); err != nil {
tables := []table.TableDefinition{tableDefinitions[ConfigurationTableName]}
var tableValues []table.TableValues
if tableValues, err = table.ProcessTables(tables, targetScriptOutput.ScriptOutputs); err != nil {
err = fmt.Errorf("failed to process collected data: %v", err)
return nil, err
}
// create the report for this single table
var reportBytes []byte
if reportBytes, err = report.Create("txt", tableValues, targetScriptOutput.TargetName); err != nil {
report.RegisterTextRenderer(ConfigurationTableName, configurationTableTextRenderer)

if reportBytes, err = report.Create("txt", tableValues, targetScriptOutput.TargetName, ""); err != nil {
err = fmt.Errorf("failed to create report: %v", err)
return nil, err
}
Expand Down
233 changes: 233 additions & 0 deletions cmd/config/config_tables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package config

// Copyright (C) 2021-2025 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

import (
"fmt"
"log/slog"
"perfspect/internal/common"
"perfspect/internal/cpus"
"perfspect/internal/script"
"perfspect/internal/table"
"slices"
"strings"
)

const (
ConfigurationTableName = "Configuration"
)

var tableDefinitions = map[string]table.TableDefinition{
ConfigurationTableName: {
Name: ConfigurationTableName,
Vendors: []string{cpus.IntelVendor},
HasRows: false,
ScriptNames: []string{
script.LscpuScriptName,
script.LscpuCacheScriptName,
script.LspciBitsScriptName,
script.LspciDevicesScriptName,
script.L3CacheWayEnabledName,
script.PackagePowerLimitName,
script.EpbScriptName,
script.EppScriptName,
script.EppValidScriptName,
script.EppPackageControlScriptName,
script.EppPackageScriptName,
script.ScalingGovernorScriptName,
script.UncoreMaxFromMSRScriptName,
script.UncoreMinFromMSRScriptName,
script.UncoreMaxFromTPMIScriptName,
script.UncoreMinFromTPMIScriptName,
script.UncoreDieTypesFromTPMIScriptName,
script.SpecCoreFrequenciesScriptName,
script.ElcScriptName,
script.PrefetchControlName,
script.PrefetchersName,
script.PrefetchersAtomName,
script.CstatesScriptName,
script.C1DemotionScriptName,
},
FieldsFunc: configurationTableValues},
}

func configurationTableValues(outputs map[string]script.ScriptOutput) []table.Field {
uarch := common.UarchFromOutput(outputs)
if uarch == "" {
slog.Error("failed to get uarch from script outputs")
return []table.Field{}
}
// This table is only shown in text mode on stdout for the config command. The config
// command implements its own print logic and uses the Description field to show the command line
// argument for each config item.
fields := []table.Field{
{Name: "Cores per Socket", Description: "--cores <N>", Values: []string{common.ValFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`)}},
{Name: "L3 Cache", Description: "--llc <MB>", Values: []string{l3InstanceFromOutput(outputs)}},
{Name: "Package Power / TDP", Description: "--tdp <Watts>", Values: []string{common.TDPFromOutput(outputs)}},
{Name: "Core SSE Frequency", Description: "--core-max <GHz>", Values: []string{sseFrequenciesFromOutput(outputs)}},
}
if strings.Contains(uarch, "SRF") || strings.Contains(uarch, "GNR") || strings.Contains(uarch, "CWF") {
fields = append(fields, []table.Field{
{Name: "Uncore Max Frequency (Compute)", Description: "--uncore-max-compute <GHz>", Values: []string{common.UncoreMinMaxDieFrequencyFromOutput(true, true, outputs)}},
{Name: "Uncore Min Frequency (Compute)", Description: "--uncore-min-compute <GHz>", Values: []string{common.UncoreMinMaxDieFrequencyFromOutput(false, true, outputs)}},
{Name: "Uncore Max Frequency (I/O)", Description: "--uncore-max-io <GHz>", Values: []string{common.UncoreMinMaxDieFrequencyFromOutput(true, false, outputs)}},
{Name: "Uncore Min Frequency (I/O)", Description: "--uncore-min-io <GHz>", Values: []string{common.UncoreMinMaxDieFrequencyFromOutput(false, false, outputs)}},
}...)
} else {
fields = append(fields, []table.Field{
{Name: "Uncore Max Frequency", Description: "--uncore-max <GHz>", Values: []string{common.UncoreMaxFrequencyFromOutput(outputs)}},
{Name: "Uncore Min Frequency", Description: "--uncore-min <GHz>", Values: []string{common.UncoreMinFrequencyFromOutput(outputs)}},
}...)
}
fields = append(fields, []table.Field{
{Name: "Energy Performance Bias", Description: "--epb <0-15>", Values: []string{common.EPBFromOutput(outputs)}},
{Name: "Energy Performance Preference", Description: "--epp <0-255>", Values: []string{common.EPPFromOutput(outputs)}},
{Name: "Scaling Governor", Description: "--gov <performance|powersave>", Values: []string{strings.TrimSpace(outputs[script.ScalingGovernorScriptName].Stdout)}},
}...)
// add ELC (for SRF, CWF and GNR only)
if strings.Contains(uarch, "SRF") || strings.Contains(uarch, "GNR") || strings.Contains(uarch, "CWF") {
fields = append(fields, table.Field{Name: "Efficiency Latency Control", Description: "--elc <default|latency-optimized>", Values: []string{common.ELCSummaryFromOutput(outputs)}})
}
// add prefetchers
for _, pf := range common.PrefetcherDefinitions {
if slices.Contains(pf.Uarchs, "all") || slices.Contains(pf.Uarchs, uarch[:3]) {
var scriptName string
switch pf.Msr {
case common.MsrPrefetchControl:
scriptName = script.PrefetchControlName
case common.MsrPrefetchers:
scriptName = script.PrefetchersName
case common.MsrAtomPrefTuning1:
scriptName = script.PrefetchersAtomName
default:
slog.Error("unknown msr for prefetcher", slog.String("msr", fmt.Sprintf("0x%x", pf.Msr)))
continue
}
msrVal := common.ValFromRegexSubmatch(outputs[scriptName].Stdout, `^([0-9a-fA-F]+)`)
var enabledDisabled string
enabled, err := common.IsPrefetcherEnabled(msrVal, pf.Bit)
if err != nil {
slog.Warn("error checking prefetcher enabled status", slog.String("error", err.Error()))
continue
}
if enabled {
enabledDisabled = "Enabled"
} else {
enabledDisabled = "Disabled"
}
fields = append(fields,
table.Field{
Name: pf.ShortName + " prefetcher",
Description: "--" + "pref-" + strings.ReplaceAll(strings.ToLower(pf.ShortName), " ", "") + " <enable|disable>",
Values: []string{enabledDisabled}},
)
}
}
// add C6
c6 := common.C6FromOutput(outputs)
if c6 != "" {
fields = append(fields, table.Field{Name: "C6", Description: "--c6 <enable|disable>", Values: []string{c6}})
}
// add C1 Demotion
c1Demotion := strings.TrimSpace(outputs[script.C1DemotionScriptName].Stdout)
if c1Demotion != "" {
fields = append(fields, table.Field{Name: "C1 Demotion", Description: "--c1-demotion <enable|disable>", Values: []string{c1Demotion}})
}
return fields
}

// l3InstanceFromOutput retrieves the L3 cache size per instance (per socket on Intel) in megabytes
func l3InstanceFromOutput(outputs map[string]script.ScriptOutput) string {
l3InstanceMB, _, err := common.GetL3MSRMB(outputs)
if err != nil {
slog.Info("Could not get L3 size from MSR, falling back to lscpu", slog.String("error", err.Error()))
l3InstanceMB, _, err = common.GetL3LscpuMB(outputs)
if err != nil {
slog.Error("Could not get L3 size from lscpu", slog.String("error", err.Error()))
return ""
}
}
return common.FormatCacheSizeMB(l3InstanceMB)
}

// sseFrequenciesFromOutput gets the bucketed SSE frequencies from the output
// and returns a compact string representation with consolidated ranges, e.g.:
// "1-40/3.5, 41-60/3.4, 61-86/3.2"
func sseFrequenciesFromOutput(outputs map[string]script.ScriptOutput) string {
specCoreFrequencies, err := common.GetSpecFrequencyBuckets(outputs)
if err != nil {
return ""
}
sseFreqs := common.GetSSEFreqsFromBuckets(specCoreFrequencies)
if len(sseFreqs) < 1 {
return ""
}

var result []string
i := 1
for i < len(specCoreFrequencies) {
startIdx := i
currentFreq := sseFreqs[i-1]

// Find consecutive buckets with the same frequency
for i < len(specCoreFrequencies) && sseFreqs[i-1] == currentFreq {
i++
}
endIdx := i - 1

// Extract start and end core numbers from the ranges
startRange := strings.Split(specCoreFrequencies[startIdx][0], "-")[0]
endRange := strings.Split(specCoreFrequencies[endIdx][0], "-")[1]

// Format the consolidated range
if startRange == endRange {
result = append(result, fmt.Sprintf("%s/%s", startRange, currentFreq))
} else {
result = append(result, fmt.Sprintf("%s-%s/%s", startRange, endRange, currentFreq))
}
}

return strings.Join(result, ", ")
}

// configurationTableTextRenderer renders the configuration table for text reports.
// It's similar to the default text table renderer, but uses the Description field
// to show the command line argument for each config item.
// Example output:
// Configuration
// =============
// Cores per Socket: 86 --cores <N>
// L3 Cache: 336M --llc <MB>
// Package Power / TDP: 350W --tdp <Watts>
// All-Core Max Frequency: 3.2GHz --core-max <GHz>
func configurationTableTextRenderer(tableValues table.TableValues) string {
var sb strings.Builder

// Find the longest field name and value for formatting
maxFieldNameLen := 0
maxValueLen := 0
for _, field := range tableValues.Fields {
if len(field.Name) > maxFieldNameLen {
maxFieldNameLen = len(field.Name)
}
if len(field.Values) > 0 && len(field.Values[0]) > maxValueLen {
maxValueLen = len(field.Values[0])
}
}

// Print each field with name, value, and description (command-line arg)
for _, field := range tableValues.Fields {
var value string
if len(field.Values) > 0 {
value = field.Values[0]
}
// Format: "Field Name: Value Description"
sb.WriteString(fmt.Sprintf("%-*s %-*s %s\n",
maxFieldNameLen+1, field.Name+":",
maxValueLen, value,
field.Description))
}

return sb.String()
}
3 changes: 1 addition & 2 deletions cmd/config/flag_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package config
import (
"fmt"
"perfspect/internal/common"
"perfspect/internal/report"
"perfspect/internal/target"
"regexp"
"slices"
Expand Down Expand Up @@ -193,7 +192,7 @@ func initializeFlags(cmd *cobra.Command) {
flagGroups = append(flagGroups, group)
// prefetcher options
group = flagGroup{name: flagGroupPrefetcherName, flags: []flagDefinition{}}
for _, pref := range report.GetPrefetcherDefinitions() {
for _, pref := range common.GetPrefetcherDefinitions() {
group.flags = append(group.flags,
newStringFlag(cmd,
// flag name
Expand Down
12 changes: 6 additions & 6 deletions cmd/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
"log/slog"
"math"
"perfspect/internal/common"
"perfspect/internal/cpus"
"perfspect/internal/report"
"perfspect/internal/script"
"perfspect/internal/target"
"perfspect/internal/util"
Expand Down Expand Up @@ -131,19 +131,19 @@ func setLlcSize(desiredLlcSize float64, myTarget target.Target, localTempDir str
return fmt.Errorf("failed to run scripts on target: %w", err)
}

uarch := report.UarchFromOutput(outputs)
uarch := common.UarchFromOutput(outputs)
cpu, err := cpus.GetCPUByMicroArchitecture(uarch)
if err != nil {
return fmt.Errorf("failed to get CPU by microarchitecture: %w", err)
}
if cpu.CacheWayCount == 0 {
return fmt.Errorf("cache way count is zero")
}
maximumLlcSize, _, err := report.GetL3LscpuMB(outputs)
maximumLlcSize, _, err := common.GetL3LscpuMB(outputs)
if err != nil {
return fmt.Errorf("failed to get maximum LLC size: %w", err)
}
currentLlcSize, _, err := report.GetL3MSRMB(outputs)
currentLlcSize, _, err := common.GetL3MSRMB(outputs)
if err != nil {
return fmt.Errorf("failed to get current LLC size: %w", err)
}
Expand Down Expand Up @@ -823,15 +823,15 @@ func getUarch(myTarget target.Target, localTempDir string) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to run scripts on target: %w", err)
}
uarch := report.UarchFromOutput(outputs)
uarch := common.UarchFromOutput(outputs)
if uarch == "" {
return "", fmt.Errorf("failed to get microarchitecture")
}
return uarch, nil
}

func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir string, prefetcherType string) error {
pf, err := report.GetPrefetcherDefByName(prefetcherType)
pf, err := common.GetPrefetcherDefByName(prefetcherType)
if err != nil {
return fmt.Errorf("failed to get prefetcher definition: %w", err)
}
Expand Down
12 changes: 8 additions & 4 deletions cmd/flame/flame.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"perfspect/internal/common"
"perfspect/internal/report"
"perfspect/internal/table"
"perfspect/internal/util"
"slices"
"strconv"
Expand Down Expand Up @@ -175,11 +176,11 @@ func validateFlags(cmd *cobra.Command, args []string) error {
}

func runCmd(cmd *cobra.Command, args []string) error {
var tableNames []string
var tables []table.TableDefinition
if !flagNoSystemSummary {
tableNames = append(tableNames, report.BriefSysSummaryTableName)
tables = append(tables, common.TableDefinitions[common.BriefSysSummaryTableName])
}
tableNames = append(tableNames, report.CallStackFrequencyTableName)
tables = append(tables, tableDefinitions[CallStackFrequencyTableName])
reportingCommand := common.ReportingCommand{
Cmd: cmd,
ReportNamePost: "flame",
Expand All @@ -189,7 +190,10 @@ func runCmd(cmd *cobra.Command, args []string) error {
"PIDs": strings.Join(util.IntSliceToStringSlice(flagPids), ","),
"MaxDepth": strconv.Itoa(flagMaxDepth),
},
TableNames: tableNames,
Tables: tables,
}

report.RegisterHTMLRenderer(CallStackFrequencyTableName, callStackFrequencyTableHTMLRenderer)

return reportingCommand.Run()
}
Loading