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
10 changes: 4 additions & 6 deletions cmd/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var (
flagCPU bool
flagFrequency bool
flagIPC bool
flagC6 bool
flagCstate bool
flagIRQRate bool
flagMemory bool
flagNetwork bool
Expand All @@ -82,7 +82,7 @@ const (
flagCPUName = "cpu"
flagFrequencyName = "frequency"
flagIPCName = "ipc"
flagC6Name = "c6"
flagCstateName = "cstate"
flagIRQRateName = "irqrate"
flagMemoryName = "memory"
flagNetworkName = "network"
Expand All @@ -105,7 +105,7 @@ var telemetrySummaryTableName = "Telemetry Summary"
var categories = []app.Category{
{FlagName: flagCPUName, FlagVar: &flagCPU, DefaultValue: false, Help: "monitor cpu utilization", Tables: []table.TableDefinition{tableDefinitions[CPUUtilizationTelemetryTableName], tableDefinitions[UtilizationCategoriesTelemetryTableName]}},
{FlagName: flagIPCName, FlagVar: &flagIPC, DefaultValue: false, Help: "monitor IPC", Tables: []table.TableDefinition{tableDefinitions[IPCTelemetryTableName]}},
{FlagName: flagC6Name, FlagVar: &flagC6, DefaultValue: false, Help: "monitor C6 residency", Tables: []table.TableDefinition{tableDefinitions[C6TelemetryTableName]}},
{FlagName: flagCstateName, FlagVar: &flagCstate, DefaultValue: false, Help: "monitor C-States residency", Tables: []table.TableDefinition{tableDefinitions[CstatesTelemetryTableName]}},
{FlagName: flagFrequencyName, FlagVar: &flagFrequency, DefaultValue: false, Help: "monitor cpu frequency", Tables: []table.TableDefinition{tableDefinitions[FrequencyTelemetryTableName]}},
{FlagName: flagPowerName, FlagVar: &flagPower, DefaultValue: false, Help: "monitor power", Tables: []table.TableDefinition{tableDefinitions[PowerTelemetryTableName]}},
{FlagName: flagTemperatureName, FlagVar: &flagTemperature, DefaultValue: false, Help: "monitor temperature", Tables: []table.TableDefinition{tableDefinitions[TemperatureTelemetryTableName]}},
Expand Down Expand Up @@ -336,7 +336,7 @@ func runCmd(cmd *cobra.Command, args []string) error {
report.RegisterHTMLRenderer(CPUUtilizationTelemetryTableName, cpuUtilizationTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(UtilizationCategoriesTelemetryTableName, utilizationCategoriesTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(IPCTelemetryTableName, ipcTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(C6TelemetryTableName, c6TelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(CstatesTelemetryTableName, cstatesTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(FrequencyTelemetryTableName, averageFrequencyTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(IRQRateTelemetryTableName, irqRateTelemetryTableHTMLRenderer)
report.RegisterHTMLRenderer(DriveTelemetryTableName, driveTelemetryTableHTMLRenderer)
Expand Down Expand Up @@ -364,7 +364,6 @@ func getTableValues(allTableValues []table.TableValues, tableName string) table.
func summaryFromTableValues(allTableValues []table.TableValues, _ map[string]script.ScriptOutput) table.TableValues {
cpuUtil := getCPUAveragePercentage(getTableValues(allTableValues, UtilizationCategoriesTelemetryTableName), "%idle", true)
ipc := getCPUAveragePercentage(getTableValues(allTableValues, IPCTelemetryTableName), "Core (Avg.)", false)
c6 := getCPUAveragePercentage(getTableValues(allTableValues, C6TelemetryTableName), "Core (Avg.)", false)
avgCoreFreq := getMetricAverage(getTableValues(allTableValues, FrequencyTelemetryTableName), []string{"Core (Avg.)"}, "Time")
pkgPower := getPkgAveragePower(allTableValues)
pkgTemperature := getPkgAverageTemperature(allTableValues)
Expand All @@ -386,7 +385,6 @@ func summaryFromTableValues(allTableValues []table.TableValues, _ map[string]scr
Fields: []table.Field{
{Name: "CPU Utilization (%)", Values: []string{cpuUtil}},
{Name: "IPC", Values: []string{ipc}},
{Name: "C6 Core Residency (%)", Values: []string{c6}},
{Name: "Core Frequency (MHz)", Values: []string{avgCoreFreq}},
{Name: "Package Power (Watts)", Values: []string{pkgPower}},
{Name: "Package Temperature (C)", Values: []string{pkgTemperature}},
Expand Down
4 changes: 2 additions & 2 deletions cmd/telemetry/telemetry_renderers.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ func ipcTelemetryTableHTMLRenderer(tableValues table.TableValues, targetName str
return telemetryTableHTMLRenderer(tableValues, data, datasetNames, chartConfig, nil)
}

func c6TelemetryTableHTMLRenderer(tableValues table.TableValues, targetName string) string {
func cstatesTelemetryTableHTMLRenderer(tableValues table.TableValues, targetName string) string {
if len(tableValues.Fields) < 2 {
slog.Error("insufficient fields in table, expected at least 2", slog.String("table", tableValues.Name), slog.Int("fields", len(tableValues.Fields)))
return ""
Expand Down Expand Up @@ -559,7 +559,7 @@ func c6TelemetryTableHTMLRenderer(tableValues table.TableValues, targetName stri
chartConfig := report.ChartTemplateStruct{
ID: fmt.Sprintf("%s%d", tableValues.Name, util.RandUint(10000)),
XaxisText: "Time",
YaxisText: "% C6 Residency",
YaxisText: "% Residency",
TitleText: "",
DisplayTitle: "false",
DisplayLegend: "true",
Expand Down
44 changes: 25 additions & 19 deletions cmd/telemetry/telemetry_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const (
CPUUtilizationTelemetryTableName = "CPU Utilization Telemetry"
UtilizationCategoriesTelemetryTableName = "Utilization Categories Telemetry"
IPCTelemetryTableName = "IPC Telemetry"
C6TelemetryTableName = "C6 Telemetry"
CstatesTelemetryTableName = "C-States Telemetry"
FrequencyTelemetryTableName = "Frequency Telemetry"
IRQRateTelemetryTableName = "IRQ Rate Telemetry"
InstructionTelemetryTableName = "Instruction Telemetry"
Expand All @@ -41,7 +41,7 @@ const (
CPUUtilizationTelemetryMenuLabel = "CPU Utilization"
UtilizationCategoriesTelemetryMenuLabel = "Utilization Categories"
IPCTelemetryMenuLabel = "IPC"
C6TelemetryMenuLabel = "C6"
CstatesTelemetryMenuLabel = "C-States"
FrequencyTelemetryMenuLabel = "Frequency"
IRQRateTelemetryMenuLabel = "IRQ Rate"
InstructionTelemetryMenuLabel = "Instruction"
Expand Down Expand Up @@ -84,15 +84,15 @@ var tableDefinitions = map[string]table.TableDefinition{
script.TurbostatTelemetryScriptName,
},
FieldsFunc: ipcTelemetryTableValues},
C6TelemetryTableName: {
Name: C6TelemetryTableName,
MenuLabel: C6TelemetryMenuLabel,
CstatesTelemetryTableName: {
Name: CstatesTelemetryTableName,
MenuLabel: CstatesTelemetryMenuLabel,
Architectures: []string{cpus.X86Architecture},
HasRows: true,
ScriptNames: []string{
script.TurbostatTelemetryScriptName,
},
FieldsFunc: c6TelemetryTableValues},
FieldsFunc: cstatesTelemetryTableValues},
FrequencyTelemetryTableName: {
Name: FrequencyTelemetryTableName,
MenuLabel: FrequencyTelemetryMenuLabel,
Expand Down Expand Up @@ -510,13 +510,12 @@ func ipcTelemetryTableValues(outputs map[string]script.ScriptOutput) []table.Fie
return fields
}

func c6TelemetryTableValues(outputs map[string]script.ScriptOutput) []table.Field {
fields := []table.Field{
{Name: "Time"},
{Name: "Package (Avg.)"},
{Name: "Core (Avg.)"},
}
platformRows, err := extract.TurbostatPlatformRows(outputs[script.TurbostatTelemetryScriptName].Stdout, []string{"C6%", "CPU%c6"})
func cstatesTelemetryTableValues(outputs map[string]script.ScriptOutput) []table.Field {
fields := []table.Field{}
// e.g., SRF - C1% C1E% C6S% C6SP% CPU%c1 CPU%c6 Mod%c6
// e.g., GNR - POLL% C1% C1E% C6% C6P% CPU%c1 CPU%c6
reCstate := regexp.MustCompile(`^(C\d+\w*%|CPU%c\d+|POLL%|Mod%c\d+)$`) // matches C1%, C1E%, C6%, C6S%, C6P%, CPU%c1, CPU%c6, C1E%, C6P%, POLL%, Mod%c6
platformRows, err := extract.TurbostatPlatformRowsByRegexMatch(outputs[script.TurbostatTelemetryScriptName].Stdout, []*regexp.Regexp{reCstate})
if err != nil {
slog.Warn(err.Error())
return []table.Field{}
Expand All @@ -525,14 +524,21 @@ func c6TelemetryTableValues(outputs map[string]script.ScriptOutput) []table.Fiel
slog.Warn("no platform rows found in turbostat telemetry output")
return []table.Field{}
}
// dynamically build the fields based on the cstate-related fields we found in the platform rows
// e.g., if we found "C6%" and "CPU%c6", we'll create two fields, "C6%" and "CPU%c6"
// for each platform row
for i := range platformRows {
// append the timestamp to the fields
fields[0].Values = append(fields[0].Values, platformRows[i][0]) // Timestamp
// append the C6 residency values to the fields
fields[1].Values = append(fields[1].Values, platformRows[i][1]) // C6%
// append the CPU C6 residency values to the fields
fields[2].Values = append(fields[2].Values, platformRows[i][2]) // CPU%c6
// the first row contains the field names, so use that to build the fields, including the timestamp field
if i == 0 {
for _, fieldName := range platformRows[i] {
fields = append(fields, table.Field{Name: fieldName})
}
} else {
// append the values to the corresponding fields
for j := range platformRows[i] {
fields[j].Values = append(fields[j].Values, platformRows[i][j])
}
}
}
return fields
}
Expand Down
72 changes: 72 additions & 0 deletions internal/extract/turbostat.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package extract
import (
"fmt"
"log/slog"
"regexp"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -77,6 +78,77 @@ func parseTurbostatOutput(output string) ([]map[string]string, error) {
return rows, nil
}

// TurbostatPlatformRowsByRegexMatch parses the output of the turbostat script and returns the rows
// for the platform (summary) only, matching fields by regex.
// Multiple fields may match the regex, all matching fields, and their values, will be returned in
// alphabetical order.
// Returns:
// - [][]string: first row is the header with "timestamp" followed by matched field names, subsequent
// rows contain the corresponding values for each platform row in the output.
func TurbostatPlatformRowsByRegexMatch(turboStatScriptOutput string, fieldRegexs []*regexp.Regexp) ([][]string, error) {
if turboStatScriptOutput == "" {
return nil, fmt.Errorf("turbostat output is empty")
}
if len(fieldRegexs) == 0 {
return nil, fmt.Errorf("no field regexes provided")
}
rows, err := parseTurbostatOutput(turboStatScriptOutput)
if err != nil {
return nil, fmt.Errorf("unable to parse turbostat output: %w", err)
}
if len(rows) == 0 {
return nil, fmt.Errorf("no rows found in turbostat output")
}
// Build our list of field names
var matchedFields []string
foundPlatformRow := false
for _, row := range rows {
if !isPlatformRow(row) {
continue
}
foundPlatformRow = true
for field := range row {
for _, re := range fieldRegexs {
if re.MatchString(field) {
if !slices.Contains(matchedFields, field) {
matchedFields = append(matchedFields, field)
}
break
}
}
}
break // only need the first platform row to discover fields
}
if !foundPlatformRow {
return nil, fmt.Errorf("no platform rows found in turbostat output")
}
if len(matchedFields) == 0 {
return nil, fmt.Errorf("no fields matched the provided regexes in turbostat output")
}
// Sort alphabetically for deterministic output since map iteration is unordered.
slices.Sort(matchedFields)
// First row is the header: timestamp followed by matched field names.
header := make([]string, len(matchedFields)+1)
header[0] = "timestamp"
copy(header[1:], matchedFields)
fieldValues := [][]string{header}
for _, row := range rows {
if !isPlatformRow(row) {
continue
}
rowValues := make([]string, len(matchedFields)+1)
rowValues[0] = row["timestamp"]
for i, field := range matchedFields {
rowValues[i+1] = row[field]
}
fieldValues = append(fieldValues, rowValues)
}
if len(fieldValues) == 1 { // only the header row, no data
return nil, fmt.Errorf("no platform data found in turbostat output for the provided regexes")
}
return fieldValues, nil
}

// TurbostatPlatformRows parses the output of the turbostat script and returns the rows
// for the platform (summary) only.
func TurbostatPlatformRows(turboStatScriptOutput string, fieldNames []string) ([][]string, error) {
Expand Down