Skip to content
Open
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
23 changes: 16 additions & 7 deletions cmd/metrics/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import (

// MetricDefinition is the common (across loader implementations) representation of a single metric
type MetricDefinition struct {
Name string `json:"name"`
Expression string `json:"expression"`
Description string `json:"description"`
Name string
LegacyName string
Expression string
Description string
Category string
Level int
ThresholdExpression string
// Evaluation fields - used during metric expression evaluation
//
// Variables - map of variable names found in Expression to the indices of the event
Expand All @@ -26,14 +30,19 @@ type MetricDefinition struct {
// definitions are loaded and parsed, so that the expression does not need to be
// parsed each time the metric is evaluated.
Evaluable *govaluate.EvaluableExpression
// ThresholdVariables - list of variable names found in ThresholdExpression.
ThresholdVariables []string
// ThresholdEvaluable - parsed threshold expression from govaluate. These are set once when the metric
// definitions are loaded and parsed, so that the expression does not need to be
// parsed each time the metric is evaluated.
ThresholdEvaluable *govaluate.EvaluableExpression
}

// EventDefinition is the common (across loader implementations) representation of a single perf event
type EventDefinition struct {
Raw string // the event string in perf format
Name string // the event name
Device string // the event device (e.g., "cpu" from cpu/event=0x3c/,umask=...)
Description string // the event description
Raw string // the event string in perf format
Name string // the event name
Device string // the event device (e.g., "cpu" from cpu/event=0x3c/,umask=...), currently used by legacy loader only
}

// GroupDefinition represents a group of perf events
Expand Down
55 changes: 41 additions & 14 deletions cmd/metrics/loader_perfmon.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ type MetricsConfigHeader struct {
}
type PerfspectMetric struct {
MetricName string `json:"MetricName"`
LegacyName string `json:"LegacyName"`
Origin string `json:"Origin"`
}
type MetricsConfig struct {
Expand Down Expand Up @@ -236,7 +235,7 @@ func filterReportMetrics(reportMetrics []PerfspectMetric, selectedMetricNames []
for _, metricName := range selectedMetricNames {
found := false
for _, metric := range reportMetrics {
if metric.LegacyName == "metric_"+metricName {
if metric.MetricName == metricName {
filteredMetrics = append(filteredMetrics, metric)
found = true
break
Expand All @@ -256,13 +255,13 @@ func loadPerfmonMetrics(reportMetrics []PerfspectMetric, perfmonMetrics []Perfmo
var perfmonMetric *PerfmonMetric
var found bool
if !metadata.SupportsFixedTMA {
perfmonMetric, found = findPerfmonMetric(alternateTMAMetrics, metric.LegacyName)
perfmonMetric, found = findPerfmonMetric(alternateTMAMetrics, metric.MetricName)
}
if !found {
perfmonMetric, found = findPerfmonMetric(allPerfmonMetrics, metric.LegacyName)
perfmonMetric, found = findPerfmonMetric(allPerfmonMetrics, metric.MetricName)
}
if !found {
slog.Warn("Metric not found in metric definitions", "metric", metric.LegacyName, "origin", metric.Origin)
slog.Warn("Metric not found in metric definitions", "metric", metric.MetricName, "origin", metric.Origin)
continue
}
// Add the metric to the list of metrics to return
Expand Down Expand Up @@ -332,20 +331,48 @@ func getExpression(perfmonMetric PerfmonMetric) (string, error) {
return expression, nil
}

func getThresholdExpression(perfmonMetric PerfmonMetric) (string, error) {
if perfmonMetric.Threshold == nil {
return "", nil // no threshold defined
}
expression := perfmonMetric.Threshold.Formula
if expression == "" {
return "", nil // no threshold defined
}
replacers := make(map[string]string)
for _, thresholdMetric := range perfmonMetric.Threshold.ThresholdMetrics {
replacers[thresholdMetric["Alias"]] = fmt.Sprintf("[%s]", thresholdMetric["Value"])
}
for alias, replacement := range replacers {
// Replace alias as whole words only (not substrings)
expression = util.ReplaceWholeWord(expression, alias, replacement)
}
return expression, nil
}

func perfmonToMetricDefs(perfmonMetrics []PerfmonMetric) ([]MetricDefinition, error) {
var metrics []MetricDefinition
for _, perfmonMetric := range perfmonMetrics {
// get the expression for the metric
expression, err := getExpression(perfmonMetric)
if err != nil {
slog.Warn("Failed getting expression for metric", "metric", perfmonMetric.LegacyName, "error", err)
slog.Warn("Failed getting expression for metric", "metric", perfmonMetric.MetricName, "error", err)
continue
}
thresholdExpression, err := getThresholdExpression(perfmonMetric)
if err != nil {
slog.Warn("Failed getting threshold expression for metric", "metric", perfmonMetric.MetricName, "error", err)
continue
}
// create a MetricDefinition from the perfmon metric
metric := MetricDefinition{
Name: perfmonMetric.LegacyName,
Description: perfmonMetric.BriefDescription,
Expression: expression,
Name: perfmonMetric.MetricName,
LegacyName: perfmonMetric.LegacyName,
Description: perfmonMetric.BriefDescription,
Category: perfmonMetric.Category,
Level: perfmonMetric.Level,
Expression: expression,
ThresholdExpression: thresholdExpression,
}
// add the metric to the list of metrics
metrics = append(metrics, metric)
Expand All @@ -363,7 +390,7 @@ func removeUncollectableMetrics(perfmonMetrics []PerfmonMetric, coreEvents CoreE
}
uncollectableEvents := getUncollectableEvents(eventNames, coreEvents, uncoreEvents, otherEvents, metadata)
if len(uncollectableEvents) > 0 {
slog.Warn("Metric contains uncollectable events", "metric", perfmonMetric.LegacyName, "uncollectableEvents", uncollectableEvents)
slog.Warn("Metric contains uncollectable events", "metric", perfmonMetric.MetricName, "uncollectableEvents", uncollectableEvents)
continue
}
// if the metric is collectable, add it to the list of collectable metrics
Expand All @@ -389,19 +416,19 @@ func loadEventGroupsFromMetrics(perfmonMetrics []PerfmonMetric, coreEvents CoreE
uncollectableEvents = util.UniqueAppend(uncollectableEvents, uncollectableMetricEvents...)
// skip metrics that have uncollectable events
if len(uncollectableMetricEvents) > 0 {
slog.Warn("Metric contains uncollectable events", "metric", perfmonMetric.LegacyName, "uncollectableEvents", uncollectableMetricEvents)
slog.Warn("Metric contains uncollectable events", "metric", perfmonMetric.MetricName, "uncollectableEvents", uncollectableMetricEvents)
continue
}
metricCoreGroups, metricUncoreGroups, metricOtherGroups, err := groupsFromEventNames(
perfmonMetric.LegacyName,
perfmonMetric.MetricName,
metricEventNames,
coreEvents,
uncoreEvents,
otherEvents,
metadata,
)
if err != nil {
slog.Error("Error creating groups from event names", "metric", perfmonMetric.LegacyName, "error", err)
slog.Error("Error creating groups from event names", "metric", perfmonMetric.MetricName, "error", err)
continue
}
// Add the groups to the main lists
Expand Down Expand Up @@ -565,7 +592,7 @@ func groupsFromEventNames(metricName string, eventNames []string, coreEvents Cor
// findPerfmonMetric -- Helper function to find a metric by name
func findPerfmonMetric(metricsList []PerfmonMetric, metricName string) (*PerfmonMetric, bool) {
for _, metric := range metricsList {
if metric.LegacyName == metricName {
if metric.MetricName == metricName {
return &metric, true
}
}
Expand Down
67 changes: 59 additions & 8 deletions cmd/metrics/loader_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package metrics
import (
"fmt"
"log/slog"
"perfspect/internal/util"
"regexp"
"strings"

Expand Down Expand Up @@ -54,6 +55,12 @@ func configureMetrics(metrics []MetricDefinition, uncollectableEvents []string,
return nil, fmt.Errorf("failed to initialize metric variables: %w", err)
}

// initialize threshold variables
metrics, err = initializeThresholdVariables(metrics)
if err != nil {
return nil, fmt.Errorf("failed to initialize threshold variables: %w", err)
}

// remove "metric_" prefix from metric names
metrics, err = removeMetricsPrefix(metrics)
if err != nil {
Expand Down Expand Up @@ -200,23 +207,37 @@ func removeIfUncollectableEvents(metrics []MetricDefinition, uncollectableEvents
return filteredMetrics, nil
}

func transformExpression(expression string) (string, error) {
// transform if/else to ?/:
transformed, err := perfmonToPerfspectConditional(expression)
if err != nil {
return "", fmt.Errorf("failed to transform metric expression: %w", err)
}
// replace "> =" with ">=" and "< =" with "<="
transformed = strings.ReplaceAll(transformed, "> =", ">=")
transformed = strings.ReplaceAll(transformed, "< =", "<=")
// replace "&" with "&&" and "|" with "||"
transformed = strings.ReplaceAll(transformed, " & ", " && ")
transformed = strings.ReplaceAll(transformed, " | ", " || ")
return transformed, nil
}

// transformMetricExpressions transforms metric expressions from perfmon format to perfspect format
// it replaces if/else with ternary conditional, replaces "> =" with ">=", and "< =" with "<="
func transformMetricExpressions(metrics []MetricDefinition) ([]MetricDefinition, error) {
// transform if/else to ?/:
var transformedMetrics []MetricDefinition
for i := range metrics {
metric := &metrics[i]
transformed, err := perfmonToPerfspectConditional(metric.Expression)
var err error
metric.Expression, err = transformExpression(metric.Expression)
if err != nil {
return nil, fmt.Errorf("failed to transform metric expression: %w", err)
}
// replace "> =" with ">=" and "< =" with "<="
transformed = strings.ReplaceAll(transformed, "> =", ">=")
transformed = strings.ReplaceAll(transformed, "< =", "<=")
if transformed != metric.Expression {
slog.Debug("transformed metric", slog.String("name", metric.Name), slog.String("transformed", transformed))
metric.Expression = transformed
if metric.ThresholdExpression != "" {
metric.ThresholdExpression, err = transformExpression(metric.ThresholdExpression)
if err != nil {
return nil, fmt.Errorf("failed to transform threshold expression: %w", err)
}
}
// add the transformed metric to the list
transformedMetrics = append(transformedMetrics, *metric)
Expand All @@ -235,6 +256,12 @@ func setEvaluableExpressions(metrics []MetricDefinition) ([]MetricDefinition, er
slog.Error("failed to create evaluable expression for metric", slog.String("error", err.Error()), slog.String("name", metric.Name), slog.String("expression", metric.Expression))
return nil, err
}
if metric.ThresholdExpression != "" {
if metric.ThresholdEvaluable, err = govaluate.NewEvaluableExpressionWithFunctions(metric.ThresholdExpression, evaluatorFunctions); err != nil {
slog.Error("failed to create threshold evaluable expression for metric", slog.String("error", err.Error()), slog.String("name", metric.Name), slog.String("threshold_expression", metric.ThresholdExpression))
return nil, err
}
}
}
return metrics, nil
}
Expand Down Expand Up @@ -287,6 +314,30 @@ func initializeMetricVariables(metrics []MetricDefinition) ([]MetricDefinition,
return metrics, nil
}

func initializeThresholdVariables(metrics []MetricDefinition) ([]MetricDefinition, error) {
// get a list of the variables in the threshold expression
for i := range metrics {
metric := &metrics[i]
metric.ThresholdVariables = []string{}
expressionIdx := 0
for {
startVar := strings.IndexRune(metric.ThresholdExpression[expressionIdx:], '[')
if startVar == -1 { // no more vars in this expression
break
}
endVar := strings.IndexRune(metric.ThresholdExpression[expressionIdx:], ']')
if endVar == -1 {
return nil, fmt.Errorf("didn't find end of variable indicator (]) in threshold expression: %s", metric.ThresholdExpression[expressionIdx:])
}
// add the variable name to the list if it is not already present
varName := metric.ThresholdExpression[expressionIdx:][startVar+1 : endVar]
metric.ThresholdVariables = util.UniqueAppend(metric.ThresholdVariables, varName)
expressionIdx += endVar + 1
}
}
return metrics, nil
}

// removeMetricsPrefix removes the "metric_" prefix from metric names
// this is done to make the names more readable and consistent with other metrics
// it is assumed that all metric names start with "metric_"
Expand Down
13 changes: 10 additions & 3 deletions cmd/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ var (
flagTransactionRate float64
// advanced options
flagShowMetricNames bool
flagLegacyNames bool
flagMetricsList []string
flagEventFilePath string
flagMetricFilePath string
Expand Down Expand Up @@ -177,6 +178,7 @@ const (
flagTransactionRateName = "txnrate"

flagShowMetricNamesName = "list"
flagLegacyNamesName = "legacy-names"
flagMetricsListName = "metrics"
flagEventFilePathName = "eventfile"
flagMetricFilePathName = "metricfile"
Expand Down Expand Up @@ -231,6 +233,7 @@ func init() {
Cmd.Flags().Float64Var(&flagTransactionRate, flagTransactionRateName, 0, "")

Cmd.Flags().BoolVar(&flagShowMetricNames, flagShowMetricNamesName, false, "")
Cmd.Flags().BoolVar(&flagLegacyNames, flagLegacyNamesName, false, "")
Cmd.Flags().StringSliceVar(&flagMetricsList, flagMetricsListName, []string{}, "")
Cmd.Flags().StringVar(&flagEventFilePath, flagEventFilePathName, "", "")
Cmd.Flags().StringVar(&flagMetricFilePath, flagMetricFilePathName, "", "")
Expand Down Expand Up @@ -353,6 +356,10 @@ func getFlagGroups() []common.FlagGroup {
Name: flagShowMetricNamesName,
Help: "show metric names available on this platform and exit",
},
{
Name: flagLegacyNamesName,
Help: "use legacy metric names where applicable (Deprecated, will be removed in a future release)",
},
{
Name: flagMetricsListName,
Help: "a comma separated list of quoted metric names to include in output",
Expand Down Expand Up @@ -819,10 +826,10 @@ func processRawData(localOutputDir string) error {
if err != nil {
return err
}
filesWritten = printMetrics(metricFrames, frameCount, metadata.Hostname, metadata.CollectionStartTime, localOutputDir)
filesWritten = printMetrics(metricFrames, frameCount, metricDefinitions, metadata.Hostname, metadata.CollectionStartTime, localOutputDir)
frameCount += len(metricFrames)
}
summaryFiles, err := summarizeMetrics(localOutputDir, metadata.Hostname, metadata)
summaryFiles, err := summarizeMetrics(localOutputDir, metadata.Hostname, metadata, metricDefinitions)
if err != nil {
return err
}
Expand Down Expand Up @@ -1103,7 +1110,7 @@ func runCmd(cmd *cobra.Command, args []string) error {
_ = multiSpinner.Status(targetContext.target.GetName(), "no metrics collected")
} else {
targetContext.metadata.PerfSpectVersion = appContext.Version
summaryFiles, err := summarizeMetrics(localOutputDir, targetContext.target.GetName(), targetContext.metadata)
summaryFiles, err := summarizeMetrics(localOutputDir, targetContext.target.GetName(), targetContext.metadata, targetContext.metricDefinitions)
if err != nil {
err = fmt.Errorf("failed to summarize metrics: %w", err)
exitErrs = append(exitErrs, err)
Expand Down
Loading