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
80 changes: 69 additions & 11 deletions cmd/metrics/loader_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"perfspect/internal/util"
"regexp"
"slices"
"strconv"
"strings"

"github.com/casbin/govaluate"
Expand Down Expand Up @@ -56,6 +57,10 @@ func (cm *ComponentMetric) getLegacyName() string {
type ComponentEvent struct {
ArchStdEvent string `json:"ArchStdEvent"`
PublicDescription string `json:"PublicDescription"`
EventCode string `json:"EventCode"`
EventName string `json:"EventName"`
BriefDescription string `json:"BriefDescription"`
Errata string `json:"Errata"`
}

func (l *ComponentLoader) loadMetricDefinitions(selectedMetrics []string, metadata Metadata) ([]MetricDefinition, error) {
Expand Down Expand Up @@ -194,10 +199,6 @@ func (l *ComponentLoader) loadEventDefinitions(metadata Metadata) (events []Comp

func (l *ComponentLoader) formEventGroups(metrics []MetricDefinition, events []ComponentEvent, metadata Metadata) (groups []GroupDefinition, err error) {
numGPCounters := metadata.NumGeneralPurposeCounters // groups can have at most this many events (plus fixed counters)
eventNames := make(map[string]bool)
for _, event := range events {
eventNames[event.ArchStdEvent] = true
}

for _, metric := range metrics {
var metricGroups []GroupDefinition
Expand All @@ -220,13 +221,31 @@ func (l *ComponentLoader) formEventGroups(metrics []MetricDefinition, events []C
slices.Sort(variables)

for _, variable := range variables {
// confirm variable is a valid event
if _, exists := eventNames[variable]; !exists {
slog.Warn("Metric variable does not correspond to a known event, skipping variable", slog.String("metric", metric.Name), slog.String("variable", variable))
// Add the event to the current group
var perfRaw string
event := findEventByName(events, variable)
if event == nil {
slog.Warn("Could not find event definition for metric variable, skipping variable", slog.String("metric", metric.Name), slog.String("variable", variable))
continue
}
// Add the event to the current group
currentGroup = append(currentGroup, EventDefinition{Name: variable, Raw: variable, Device: "cpu"})
if event.ArchStdEvent != "" {
perfRaw = event.ArchStdEvent
} else if event.EventName != "" {
// if the event name is supported by perf, use that. Otherwise, fall back to using the event code with the armv8_pmuv3_0/config= raw event format
if strings.Contains(metadata.PerfSupportedEvents, event.EventName) {
perfRaw = event.EventName
} else if event.EventCode != "" {
perfRaw = fmt.Sprintf("armv8_pmuv3_0/config=%s,name=%s/", event.EventCode, variable)
} else {
slog.Warn("Event definition for metric variable does not have ArchStdEvent, EventName supported by perf, or EventCode, skipping variable", slog.String("metric", metric.Name), slog.String("variable", variable))
continue
}
} else {
// this shouldn't happen since the variable should match either ArchStdEvent or EventName, but log just in case
slog.Warn("Event definition for metric variable does not have EventName or ArchStdEvent, skipping variable", slog.String("metric", metric.Name), slog.String("variable", variable))
continue
}
currentGroup = append(currentGroup, EventDefinition{Name: variable, Raw: perfRaw, Device: "cpu"})

// Only increment the GP counter count if this isn't a fixed counter
if variable != "CPU_CYCLES" {
Expand Down Expand Up @@ -256,6 +275,17 @@ func (l *ComponentLoader) formEventGroups(metrics []MetricDefinition, events []C
return groups, nil
}

// findEventByName searches for an event by name in the list of events
// it checks both EventName and ArchStdEvent fields for a match
func findEventByName(events []ComponentEvent, name string) *ComponentEvent {
for i := range events {
if events[i].EventName == name || events[i].ArchStdEvent == name {
return &events[i]
}
}
return nil
}

// mergeSmallGroups merges groups that have few events, ensuring that the merged group does not exceed numGPCounters
// CPU_CYCLES is a fixed counter and does not count against the numGPCounters limit
// events in a group are unique (no duplicates)
Expand Down Expand Up @@ -385,7 +415,8 @@ func initializeComponentMetricVariables(expression string) map[string]int {
}

constants := map[string]bool{
"#slots": true,
"#slots": true,
"duration_time": true,
}

functions := map[string]bool{
Expand All @@ -406,7 +437,7 @@ func initializeComponentMetricVariables(expression string) map[string]int {
}

// Skip some tokens
if isInteger(token) || isHex(token) || operators[token] || constants[token] || functions[token] {
if isInteger(token) || isHex(token) || isExp(token) || operators[token] || constants[token] || functions[token] {
continue
}

Expand All @@ -417,6 +448,19 @@ func initializeComponentMetricVariables(expression string) map[string]int {
return variables
}

func isExp(s string) bool {
// Check if the string is in the format 1eN where N is a positive integer
if len(s) < 3 || s[0] != '1' || s[1] != 'e' {
return false
}
for _, c := range s[2:] {
if c < '0' || c > '9' {
return false
}
}
return true
}

func isHex(s string) bool {
// Check if the string starts with "0x" or "0X"
if len(s) < 3 || !(strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X")) {
Expand Down Expand Up @@ -445,6 +489,8 @@ func isInteger(s string) bool {
func initializeComponentMetricEvaluable(expression string, evaluatorFunctions map[string]govaluate.ExpressionFunction, metadata Metadata) *govaluate.EvaluableExpression {
// replace #slots with metadata.ARMSlots
transformedExpression := strings.ReplaceAll(expression, "#slots", fmt.Sprintf("%d", metadata.ARMSlots))
// replace duration_time with metadata.CollectionInterval.Seconds()
transformedExpression = strings.ReplaceAll(transformedExpression, "duration_time", fmt.Sprintf("%f", metadata.CollectionInterval.Seconds()))
// replace if else with ?:
transformedExpression, err := transformExpression(transformedExpression)
if err != nil {
Expand All @@ -469,6 +515,18 @@ func initializeComponentMetricEvaluable(expression string, evaluatorFunctions ma
}
return match // Should not happen, but return the original match if extraction fails
})
// govaluate doesn't like numeric constants in the format 1eN where N is a positive integer, so we replace them with the equivalent integer value
rxScientific := regexp.MustCompile(`1e(\d+)`)
if rxScientific.MatchString(transformedExpression) {
transformedExpression = rxScientific.ReplaceAllStringFunc(transformedExpression, func(match string) string {
exp, _ := strconv.Atoi(match[2:])
result := 1
for range exp {
result *= 10
}
return fmt.Sprintf("%d", result)
})
}

if transformedExpression != expression {
slog.Debug("Transformed metric expression", slog.String("original", expression), slog.String("transformed", transformedExpression))
Expand Down
1 change: 1 addition & 0 deletions cmd/metrics/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type Metadata struct {
CollectionStartTime time.Time
PerfSpectVersion string
WithWorkload bool // true if metrics were collected with a user-provided workload application
CollectionInterval time.Duration
}

// MetadataCollector defines the interface for architecture-specific metadata collection.
Expand Down
2 changes: 2 additions & 0 deletions cmd/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,8 @@ func prepareMetrics(targetContext *targetContext, localTempDir string, channelEr
channelError <- targetError{target: myTarget, err: err}
return
}
// set the collection interval before loading metrics so that metric expressions using duration_time are compiled with the correct value
targetContext.metadata.CollectionInterval = time.Duration(flagPerfPrintInterval) * time.Second
slog.Debug("metadata: " + targetContext.metadata.String())
if !targetContext.metadata.SupportsInstructions {
slog.Error("Target does not support instructions event collection", slog.String("target", myTarget.GetName()))
Expand Down
17 changes: 9 additions & 8 deletions internal/cpus/cpus.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,15 @@ var cpuIdentifiersARM = []struct {
Identifier CPUIdentifierARM
MicroArchitecture string
}{
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd0c", DmidecodePart: "AWS Graviton2"}, UarchGraviton2}, // AWS Graviton 2 ([m|c|r]6g) Neoverse-N1
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd40", DmidecodePart: "AWS Graviton3"}, UarchGraviton3}, // AWS Graviton 3 ([m|c|r]7g) Neoverse-V1
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd4f", DmidecodePart: "AWS Graviton4"}, UarchGraviton4}, // AWS Graviton 4 ([m|c|r]8g) Neoverse-V2
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd4f", DmidecodePart: "Not Specified"}, UarchAxion}, // GCP Axion (c4a) Neoverse-V2
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd0c", DmidecodePart: "Not Specified"}, UarchAltraFamily}, // Ampere Altra
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac3", DmidecodePart: ""}, UarchAmpereOneAC03}, // AmpereOne AC03
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac4", DmidecodePart: "X"}, UarchAmpereOneAC04}, // AmpereOne AC04
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac4", DmidecodePart: "M"}, UarchAmpereOneAC04_1}, // AmpereOne AC04_1
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd0c", DmidecodePart: "AWS Graviton2"}, UarchGraviton2}, // AWS Graviton 2 ([m|c|r]6g) Neoverse-N1
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd40", DmidecodePart: "AWS Graviton3"}, UarchGraviton3}, // AWS Graviton 3 ([m|c|r]7g) Neoverse-V1
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd4f", DmidecodePart: "AWS Graviton4"}, UarchGraviton4}, // AWS Graviton 4 ([m|c|r]8g) Neoverse-V2
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd4f", DmidecodePart: "Not Specified"}, UarchAxion}, // GCP Axion (c4a) Neoverse-V2
{CPUIdentifierARM{Implementer: "0x41", Part: "0xd0c", DmidecodePart: "Not Specified"}, UarchAltraFamily}, // Ampere Altra
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac3", DmidecodePart: ""}, UarchAmpereOneAC03}, // AmpereOne AC03
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac4", DmidecodePart: "X"}, UarchAmpereOneAC04}, // AmpereOne AC04
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac4", DmidecodePart: "M"}, UarchAmpereOneAC04_1}, // AmpereOne AC04_1
{CPUIdentifierARM{Implementer: "0xc0", Part: "0xac4", DmidecodePart: "Not Specified"}, UarchAmpereOneAC04_1}, // Ampere-1a (VM.Standard.A4.Flex on OCI)
}

// NewCPUIdentifier creates a CPUIdentifier with all data elements
Expand Down