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
79 changes: 72 additions & 7 deletions cli/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,31 @@ func createRunCmd() *cobra.Command {

// createTrackCmd creates the track subcommand
func createTrackCmd() *cobra.Command {
shortExplanation := "Compare performance between two benchmark runs to detect regressions and improvements"
cmd := &cobra.Command{
Use: "track",
Short: shortExplanation,
}

cmd.AddCommand(createTrackAutoCmd())
cmd.AddCommand(createTrackManualCmd())

return cmd
}

func createTrackAutoCmd() *cobra.Command {
baseTagFlag := "base-tag"
currentTagFlag := "current-tag"
benchNameFlag := "bench-name"
profileTypeFlag := "profile-type"
outputFormatFlag := "output-format"
example := fmt.Sprintf(`prof track --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
shortExplanation := "Compare performance between two benchmark runs to detect regressions and improvements"
longExplanation := "This command only works if the run command was used to collect and organize the benchmark and profile data, as it expects a specific directory structure generated by that process."
example := fmt.Sprintf(`prof track auto --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
longExplanation := fmt.Sprintf("This command only works if the %s command was used to collect and organize the benchmark and profile data, as it expects a specific directory structure generated by that process.", shared.AUTOCMD)

cmd := &cobra.Command{
Use: "track",
Short: shortExplanation,
Use: shared.TrackAutoCMD,
Long: longExplanation,
RunE: runTrack,
RunE: runTrackAuto,
Example: example,
}

Expand All @@ -125,6 +136,30 @@ func createTrackCmd() *cobra.Command {
return cmd
}

func createTrackManualCmd() *cobra.Command {
baseFlag := "base"
currentFlag := "current"
outputFormatFlag := "output-format"
example := fmt.Sprintf(`prof track %s --%s "path/to/profile_file.txt" --%s "path/to/profile_file.txt" --%s "summary"`, shared.TrackManualCMD, baseFlag, currentFlag, outputFormatFlag)

cmd := &cobra.Command{
Use: shared.TrackManualCMD,
Short: "Manually specify the paths to the profile text files you want to compare.",
RunE: runTrackManual,
Example: example,
}

cmd.Flags().StringVar(&baselineTag, baseFlag, "", "Name of the baseline tag")
cmd.Flags().StringVar(&currentTag, currentFlag, "", "Name of the current tag")
cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "", "Output format choice choice")

_ = cmd.MarkFlagRequired(baseFlag)
_ = cmd.MarkFlagRequired(currentFlag)
_ = cmd.MarkFlagRequired(outputFormatFlag)

return cmd
}

func createVersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Expand Down Expand Up @@ -201,7 +236,7 @@ func runSetup(_ *cobra.Command, _ []string) error {
}

// runTrack handles the track command execution
func runTrack(_ *cobra.Command, _ []string) error {
func runTrackAuto(_ *cobra.Command, _ []string) error {
validFormats := map[string]bool{
"summary": true,
"detailed": true,
Expand Down Expand Up @@ -231,3 +266,33 @@ func runTrack(_ *cobra.Command, _ []string) error {

return nil
}

func runTrackManual(_ *cobra.Command, _ []string) error {
validFormats := map[string]bool{
"summary": true,
"detailed": true,
}

if !validFormats[outputFormat] {
return fmt.Errorf("invalid output format '%s'. Valid formats: summary, detailed", outputFormat)
}

report, err := tracker.CheckPerformanceDifferencesManual(baselineTag, currentTag)
if err != nil {
return fmt.Errorf("failed to track performance differences: %w", err)
}

noFunctionChanges := len(report.FunctionChanges) == 0
if noFunctionChanges {
slog.Info("No function changes detected between the two runs")
return nil
}

switch outputFormat {
case "summary":
printSummary(report)
case "detailed":
printDetailedReport(report)
}
return nil
}
22 changes: 7 additions & 15 deletions parser/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type LineObj struct {
CumPercentage float64
}

func TurnLinesIntoObjects(profilePath, profileType string) ([]*LineObj, error) {
func TurnLinesIntoObjects(profilePath string) ([]*LineObj, error) {
var lines []string

scanner, file, err := shared.GetScanner(profilePath)
Expand All @@ -61,8 +61,7 @@ func TurnLinesIntoObjects(profilePath, profileType string) ([]*LineObj, error) {
}
defer file.Close()

shouldRemove := true
CollectOrRemoveHeader(scanner, profileType, &lines, shouldRemove)
CollectOrRemoveHeader(scanner)

GetAllProfileLines(scanner, &lines)

Expand Down Expand Up @@ -152,18 +151,11 @@ func GetAllProfileLines(scanner *bufio.Scanner, lines *[]string) {
}
}

func CollectOrRemoveHeader(scanner *bufio.Scanner, profileType string, lines *[]string, shouldRemove bool) {
lineCount := 0

headerIndex := 6
if profileType != "cpu" {
headerIndex = 5
}

for lineCount < headerIndex && scanner.Scan() {
if !shouldRemove {
*lines = append(*lines, scanner.Text())
func CollectOrRemoveHeader(scanner *bufio.Scanner) {
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, header) {
break
}
lineCount++
}
}
3 changes: 1 addition & 2 deletions parser/tests/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (

func TestLinesIntoObjs(t *testing.T) {
profilePath := filepath.Join("testFiles", "BenchmarkGenPool_cpu.txt")
profileType := "cpu"

lineObjs, err := parser.TurnLinesIntoObjects(profilePath, profileType)
lineObjs, err := parser.TurnLinesIntoObjects(profilePath)
if err != nil {
t.Error(err)
}
Expand Down
6 changes: 4 additions & 2 deletions shared/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (

// CLI Commands
const (
AUTOCMD = "auto"
MANUALCMD = "manual"
AUTOCMD = "auto"
MANUALCMD = "manual"
TrackAutoCMD = AUTOCMD
TrackManualCMD = MANUALCMD
)

const (
Expand Down
37 changes: 35 additions & 2 deletions tracker/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ func CheckPerformanceDifferences(baselineTag, currentTag, benchName, profileType
textFilePath1BaseLine := filepath.Join(shared.MainDirOutput, baselineTag, shared.ProfileTextDir, benchName, fileName)
textFilePath2Current := filepath.Join(shared.MainDirOutput, currentTag, shared.ProfileTextDir, benchName, fileName)

lineObjsBaseline, err := parser.TurnLinesIntoObjects(textFilePath1BaseLine, profileType)
lineObjsBaseline, err := parser.TurnLinesIntoObjects(textFilePath1BaseLine)
if err != nil {
return nil, fmt.Errorf("couldn't get objs for path: %s, error: %w", textFilePath1BaseLine, err)
}

lineObjsCurrent, err := parser.TurnLinesIntoObjects(textFilePath2Current, profileType)
lineObjsCurrent, err := parser.TurnLinesIntoObjects(textFilePath2Current)
if err != nil {
return nil, fmt.Errorf("couldn't get objs for path: %s, error: %w", textFilePath2Current, err)
}
Expand All @@ -44,3 +44,36 @@ func CheckPerformanceDifferences(baselineTag, currentTag, benchName, profileType

return pgp, nil
}

// CheckPerformanceDifferences creates the profile report by comparing data from prof's auto run.
func CheckPerformanceDifferencesManual(baselineProfile, currentProfile string) (*ProfileChangeReport, error) {
lineObjsBaseline, err := parser.TurnLinesIntoObjects(baselineProfile)
if err != nil {
return nil, fmt.Errorf("couldn't get objs for path: %s, error: %w", baselineProfile, err)
}

lineObjsCurrent, err := parser.TurnLinesIntoObjects(currentProfile)
if err != nil {
return nil, fmt.Errorf("couldn't get objs for path: %s, error: %w", currentProfile, err)
}

matchingMap := createHashFromLineObjects(lineObjsBaseline)

pgp := &ProfileChangeReport{}
for _, currentObj := range lineObjsCurrent {
baseLineObj, matchNotFound := matchingMap[currentObj.FnName]
if !matchNotFound {
continue
}

var changeResult *FunctionChangeResult
changeResult, err = DetectChange(baseLineObj, currentObj)
if err != nil {
return nil, fmt.Errorf("DetectChange failed: %w", err)
}

pgp.FunctionChanges = append(pgp.FunctionChanges, changeResult)
}

return pgp, nil
}
Loading