Skip to content

Commit 66f285b

Browse files
Merge pull request #76 from AlexsanderHamir/refactors
Refactors
2 parents a290af5 + b5945e1 commit 66f285b

File tree

3 files changed

+118
-25
lines changed

3 files changed

+118
-25
lines changed

cli/helpers.go

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/AlexsanderHamir/prof/engine/collector"
1414

1515
"github.com/AlexsanderHamir/prof/engine/tools/benchstats"
16+
"github.com/AlexsanderHamir/prof/engine/tools/qcachegrind"
1617
"github.com/AlexsanderHamir/prof/engine/tracker"
1718
"github.com/AlexsanderHamir/prof/internal"
1819
"github.com/spf13/cobra"
@@ -40,8 +41,10 @@ const (
4041
minTagsForComparison = 2
4142

4243
// 3 occurrences requires a const
43-
base = "base"
44-
current = "current"
44+
baseTagFlag = "base"
45+
currentTagFlag = "current"
46+
benchNameFlag = "bench-name"
47+
tagFlag = "tag"
4548
)
4649

4750
// CreateRootCmd creates and returns the root cobra command.
@@ -69,30 +72,53 @@ func createToolsCmd() *cobra.Command {
6972
}
7073

7174
cmd.AddCommand(createBenchStatCmd())
75+
cmd.AddCommand(createQCacheGrindCmd())
76+
77+
return cmd
78+
}
79+
80+
func createQCacheGrindCmd() *cobra.Command {
81+
profilesFlag := "profiles"
82+
shortExplanation := "runs benchstat on txt collected data."
83+
84+
cmd := &cobra.Command{
85+
Use: "qcachegrind",
86+
Short: shortExplanation,
87+
Example: "prof tools qcachegrind --tag `current` --profiles `cpu` --bench-name `BenchmarkGenPool`",
88+
RunE: func(_ *cobra.Command, _ []string) error {
89+
return qcachegrind.RunQcacheGrind(tag, benchmarkName, profiles[0])
90+
},
91+
}
92+
93+
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
94+
cmd.Flags().StringSliceVar(&profiles, profilesFlag, []string{}, `Profiles to use (e.g., "cpu,memory,mutex")`)
95+
cmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
96+
97+
_ = cmd.MarkFlagRequired(benchNameFlag)
98+
_ = cmd.MarkFlagRequired(profilesFlag)
99+
_ = cmd.MarkFlagRequired(tagFlag)
72100

73101
return cmd
74102
}
75103

76104
func createBenchStatCmd() *cobra.Command {
77-
baseTagFlag := base
78-
currentFlag := current
79-
benchNameFlag := "bench-name"
80105
shortExplanation := "runs benchstat on txt collected data."
81106

82107
cmd := &cobra.Command{
83-
Use: "benchstat",
84-
Short: shortExplanation,
108+
Use: "benchstat",
109+
Short: shortExplanation,
110+
Example: "prof tools benchstat --base `baseline` --current `current` --bench-name `BenchmarkGenPool`",
85111
RunE: func(_ *cobra.Command, _ []string) error {
86112
return benchstats.RunBenchStats(Baseline, Current, benchmarkName)
87113
},
88114
}
89115

90116
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
91-
cmd.Flags().StringVar(&Current, currentFlag, "", "Name of the current tag")
117+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
92118
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
93119

94120
_ = cmd.MarkFlagRequired(baseTagFlag)
95-
_ = cmd.MarkFlagRequired(currentFlag)
121+
_ = cmd.MarkFlagRequired(currentTagFlag)
96122
_ = cmd.MarkFlagRequired(benchNameFlag)
97123

98124
return cmd
@@ -109,7 +135,6 @@ func createProfManual() *cobra.Command {
109135
},
110136
}
111137

112-
tagFlag := "tag"
113138
manualCmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
114139
_ = manualCmd.MarkFlagRequired(tagFlag)
115140

@@ -119,7 +144,6 @@ func createProfManual() *cobra.Command {
119144
func createProfAuto() *cobra.Command {
120145
benchFlag := "benchmarks"
121146
profileFlag := "profiles"
122-
tagFlag := "tag"
123147
countFlag := "count"
124148
example := fmt.Sprintf(`prof %s --%s "BenchmarkGenPool" --%s "cpu,memory" --%s 10 --%s "tag1"`, internal.AUTOCMD, benchFlag, profileFlag, countFlag, tagFlag)
125149

@@ -159,14 +183,11 @@ func createTrackCmd() *cobra.Command {
159183
}
160184

161185
func createTrackAutoCmd() *cobra.Command {
162-
baseTagFlag := "base"
163-
currentFlag := "current"
164-
benchNameFlag := "bench-name"
165186
profileTypeFlag := "profile-type"
166187
outputFormatFlag := "output-format"
167188
failFlag := "fail-on-regression"
168189
thresholdFlag := "regression-threshold"
169-
example := fmt.Sprintf(`prof track auto --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
190+
example := fmt.Sprintf(`prof track auto --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
170191
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.", internal.AUTOCMD)
171192
shortExplanation := "If prof auto was used to collect the data, track auto can be used to analyze it, you just have to pass the tag name."
172193

@@ -190,28 +211,26 @@ func createTrackAutoCmd() *cobra.Command {
190211
}
191212

192213
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
193-
cmd.Flags().StringVar(&Current, currentFlag, "", "Name of the current tag")
214+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
194215
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
195216
cmd.Flags().StringVar(&profileType, profileTypeFlag, "", "Profile type (cpu, memory, mutex, block)")
196217
cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "detailed", `Output format: "summary" or "detailed"`)
197218
cmd.Flags().BoolVar(&failOnRegression, failFlag, false, "Exit with non-zero code if regression exceeds threshold")
198219
cmd.Flags().Float64Var(&regressionThreshold, thresholdFlag, 0.0, "Fail when worst flat regression exceeds this percent (e.g., 5.0)")
199220

200221
_ = cmd.MarkFlagRequired(baseTagFlag)
201-
_ = cmd.MarkFlagRequired(currentFlag)
222+
_ = cmd.MarkFlagRequired(currentTagFlag)
202223
_ = cmd.MarkFlagRequired(benchNameFlag)
203224
_ = cmd.MarkFlagRequired(profileTypeFlag)
204225

205226
return cmd
206227
}
207228

208229
func createTrackManualCmd() *cobra.Command {
209-
baseFlag := "base"
210-
currentFlag := "current"
211230
outputFormatFlag := "output-format"
212231
failFlag := "fail-on-regression"
213232
thresholdFlag := "regression-threshold"
214-
example := fmt.Sprintf(`prof track %s --%s "path/to/profile_file.txt" --%s "path/to/profile_file.txt" --%s "summary"`, internal.TrackManualCMD, baseFlag, currentFlag, outputFormatFlag)
233+
example := fmt.Sprintf(`prof track %s --%s "path/to/profile_file.txt" --%s "path/to/profile_file.txt" --%s "summary"`, internal.TrackManualCMD, baseTagFlag, currentTagFlag, outputFormatFlag)
215234

216235
cmd := &cobra.Command{
217236
Use: internal.TrackManualCMD,
@@ -232,14 +251,14 @@ func createTrackManualCmd() *cobra.Command {
232251
Example: example,
233252
}
234253

235-
cmd.Flags().StringVar(&Baseline, baseFlag, "", "Name of the baseline tag")
236-
cmd.Flags().StringVar(&Current, currentFlag, "", "Name of the current tag")
254+
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
255+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
237256
cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "", "Output format choice choice")
238257
cmd.Flags().BoolVar(&failOnRegression, failFlag, false, "Exit with non-zero code if regression exceeds threshold")
239258
cmd.Flags().Float64Var(&regressionThreshold, thresholdFlag, 0.0, "Fail when worst flat regression exceeds this percent (e.g., 5.0)")
240259

241-
_ = cmd.MarkFlagRequired(baseFlag)
242-
_ = cmd.MarkFlagRequired(currentFlag)
260+
_ = cmd.MarkFlagRequired(baseTagFlag)
261+
_ = cmd.MarkFlagRequired(currentTagFlag)
243262
_ = cmd.MarkFlagRequired(outputFormatFlag)
244263

245264
return cmd

engine/tools/benchstats/helpers.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

engine/tools/qcachegrind/api.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package qcachegrind
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
10+
"github.com/AlexsanderHamir/prof/internal"
11+
)
12+
13+
// go tool pprof -callgrind {benchmarkName}_{profileName}.out > {benchmarkName}_{profileName}.callgrind
14+
// qcachegrind profile.callgrind
15+
16+
func RunQcacheGrind(tag, benchName, profileName string) error {
17+
// 1. find the binary file given the parameters of the function, it will be located under bench/tag/bin/benchName/{benchmarkName}_{profileName}.out
18+
binaryFilePath := filepath.Join(internal.MainDirOutput, tag, internal.ProfileBinDir, benchName, fmt.Sprintf("%s_%s.out", benchName, profileName))
19+
20+
if _, err := os.Stat(binaryFilePath); os.IsNotExist(err) {
21+
return fmt.Errorf("binary file not found: %s", binaryFilePath)
22+
}
23+
24+
// 2. Create a callgrind file out of the binary by running the following command (EXAMPLE):
25+
// a. go tool pprof -callgrind {benchmarkName}_{profileName}.out > {benchmarkName}_{profileName}.callgrind
26+
// b. save the command under tools/qcachegrind/{benchmarkName}_results.callgrind
27+
28+
// Create the output directory for qcachegrind results
29+
outputDir := filepath.Join(internal.MainDirOutput, internal.ToolDir, "qcachegrind")
30+
if err := os.MkdirAll(outputDir, internal.PermDir); err != nil {
31+
return fmt.Errorf("failed to create output directory: %w", err)
32+
}
33+
34+
// Generate the callgrind file
35+
callgrindOutputPath := filepath.Join(outputDir, fmt.Sprintf("%s_%s.callgrind", benchName, profileName))
36+
37+
cmd := exec.Command("go", "tool", "pprof", "-callgrind", binaryFilePath)
38+
outputFile, err := os.Create(callgrindOutputPath)
39+
if err != nil {
40+
return fmt.Errorf("failed to create callgrind output file: %w", err)
41+
}
42+
defer outputFile.Close()
43+
44+
cmd.Stdout = outputFile
45+
cmd.Stderr = os.Stderr
46+
47+
if err = cmd.Run(); err != nil {
48+
return fmt.Errorf("failed to generate callgrind file: %w", err)
49+
}
50+
51+
fmt.Printf("Generated callgrind file: %s\n", callgrindOutputPath)
52+
53+
// 3. Use the output to call qcachegrind profile.callgrind and launch it for the user to analyze.
54+
55+
// Check if qcachegrind is installed
56+
if _, err = exec.LookPath("qcachegrind"); err != nil {
57+
return errors.New("qcachegrind command not found. Please install it first: sudo apt-get install qcachegrind (Ubuntu/Debian) or brew install qcachegrind (macOS)")
58+
}
59+
60+
// Launch qcachegrind with the generated callgrind file
61+
launchCmd := exec.Command("qcachegrind", callgrindOutputPath)
62+
launchCmd.Stdout = os.Stdout
63+
launchCmd.Stderr = os.Stderr
64+
65+
fmt.Printf("Launching qcachegrind with file: %s\n", callgrindOutputPath)
66+
67+
// Run qcachegrind in the background so the user can interact with it
68+
if err = launchCmd.Start(); err != nil {
69+
return fmt.Errorf("failed to launch qcachegrind: %w", err)
70+
}
71+
72+
fmt.Println("qcachegrind launched successfully. You can now analyze the profile data.")
73+
74+
return nil
75+
}

0 commit comments

Comments
 (0)