Skip to content

Commit c60ed3c

Browse files
Merge pull request #77 from AlexsanderHamir/refactors
Implements Parts of #75
2 parents 66f285b + fe0ca6d commit c60ed3c

File tree

16 files changed

+1231
-1176
lines changed

16 files changed

+1231
-1176
lines changed

cli/commands.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/AlexsanderHamir/prof/engine/benchmark"
7+
"github.com/AlexsanderHamir/prof/engine/collector"
8+
"github.com/AlexsanderHamir/prof/engine/tools/benchstats"
9+
"github.com/AlexsanderHamir/prof/engine/tools/qcachegrind"
10+
"github.com/AlexsanderHamir/prof/engine/tracker"
11+
"github.com/AlexsanderHamir/prof/internal"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
// CreateRootCmd creates and returns the root cobra command.
16+
func CreateRootCmd() *cobra.Command {
17+
rootCmd := &cobra.Command{
18+
Use: "prof",
19+
Short: "CLI tool for organizing pprof generated data, and analyzing performance differences at the profile level.",
20+
}
21+
22+
rootCmd.AddCommand(createProfManual())
23+
rootCmd.AddCommand(createProfAuto())
24+
rootCmd.AddCommand(createTuiCmd())
25+
rootCmd.AddCommand(createSetupCmd())
26+
rootCmd.AddCommand(createTrackCmd())
27+
rootCmd.AddCommand(createToolsCmd())
28+
29+
return rootCmd
30+
}
31+
32+
func createToolsCmd() *cobra.Command {
33+
shortExplanation := "Offers many tools that can easily operate on the collected data."
34+
cmd := &cobra.Command{
35+
Use: "tools",
36+
Short: shortExplanation,
37+
}
38+
39+
cmd.AddCommand(createBenchStatCmd())
40+
cmd.AddCommand(createQCacheGrindCmd())
41+
42+
return cmd
43+
}
44+
45+
func createQCacheGrindCmd() *cobra.Command {
46+
profilesFlag := "profiles"
47+
shortExplanation := "runs benchstat on txt collected data."
48+
49+
cmd := &cobra.Command{
50+
Use: "qcachegrind",
51+
Short: shortExplanation,
52+
Example: "prof tools qcachegrind --tag `current` --profiles `cpu` --bench-name `BenchmarkGenPool`",
53+
RunE: func(_ *cobra.Command, _ []string) error {
54+
return qcachegrind.RunQcacheGrind(tag, benchmarkName, profiles[0])
55+
},
56+
}
57+
58+
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
59+
cmd.Flags().StringSliceVar(&profiles, profilesFlag, []string{}, `Profiles to use (e.g., "cpu,memory,mutex")`)
60+
cmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
61+
62+
_ = cmd.MarkFlagRequired(benchNameFlag)
63+
_ = cmd.MarkFlagRequired(profilesFlag)
64+
_ = cmd.MarkFlagRequired(tagFlag)
65+
66+
return cmd
67+
}
68+
69+
func createBenchStatCmd() *cobra.Command {
70+
shortExplanation := "runs benchstat on txt collected data."
71+
72+
cmd := &cobra.Command{
73+
Use: "benchstat",
74+
Short: shortExplanation,
75+
Example: "prof tools benchstat --base `baseline` --current `current` --bench-name `BenchmarkGenPool`",
76+
RunE: func(_ *cobra.Command, _ []string) error {
77+
return benchstats.RunBenchStats(Baseline, Current, benchmarkName)
78+
},
79+
}
80+
81+
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
82+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
83+
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
84+
85+
_ = cmd.MarkFlagRequired(baseTagFlag)
86+
_ = cmd.MarkFlagRequired(currentTagFlag)
87+
_ = cmd.MarkFlagRequired(benchNameFlag)
88+
89+
return cmd
90+
}
91+
92+
func createProfManual() *cobra.Command {
93+
manualCmd := &cobra.Command{
94+
Use: internal.MANUALCMD,
95+
Short: "Receives profile files and performs data collection and organization. (doesn't wrap go test)",
96+
Args: cobra.MinimumNArgs(1),
97+
Example: fmt.Sprintf("prof %s --tag tagName cpu.prof memory.prof block.prof mutex.prof", internal.MANUALCMD),
98+
RunE: func(_ *cobra.Command, args []string) error {
99+
return collector.RunCollector(args, tag)
100+
},
101+
}
102+
103+
manualCmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
104+
_ = manualCmd.MarkFlagRequired(tagFlag)
105+
106+
return manualCmd
107+
}
108+
109+
func createProfAuto() *cobra.Command {
110+
benchFlag := "benchmarks"
111+
profileFlag := "profiles"
112+
countFlag := "count"
113+
example := fmt.Sprintf(`prof %s --%s "BenchmarkGenPool" --%s "cpu,memory" --%s 10 --%s "tag1"`, internal.AUTOCMD, benchFlag, profileFlag, countFlag, tagFlag)
114+
115+
cmd := &cobra.Command{
116+
Use: internal.AUTOCMD,
117+
Short: "Wraps `go test` and `pprof` to benchmark code and gather profiling data for performance investigations.",
118+
RunE: func(_ *cobra.Command, _ []string) error {
119+
return benchmark.RunBenchmarks(benchmarks, profiles, tag, count)
120+
},
121+
Example: example,
122+
}
123+
124+
cmd.Flags().StringSliceVar(&benchmarks, benchFlag, []string{}, `Benchmarks to run (e.g., "BenchmarkGenPool")"`)
125+
cmd.Flags().StringSliceVar(&profiles, profileFlag, []string{}, `Profiles to use (e.g., "cpu,memory,mutex")`)
126+
cmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
127+
cmd.Flags().IntVar(&count, countFlag, 0, "Number of runs")
128+
129+
_ = cmd.MarkFlagRequired(benchFlag)
130+
_ = cmd.MarkFlagRequired(profileFlag)
131+
_ = cmd.MarkFlagRequired(tagFlag)
132+
_ = cmd.MarkFlagRequired(countFlag)
133+
134+
return cmd
135+
}
136+
137+
func createTrackCmd() *cobra.Command {
138+
shortExplanation := "Compare performance between two benchmark runs to detect regressions and improvements"
139+
cmd := &cobra.Command{
140+
Use: "track",
141+
Short: shortExplanation,
142+
}
143+
144+
cmd.AddCommand(createTrackAutoCmd())
145+
cmd.AddCommand(createTrackManualCmd())
146+
147+
return cmd
148+
}
149+
150+
func createTrackAutoCmd() *cobra.Command {
151+
profileTypeFlag := "profile-type"
152+
outputFormatFlag := "output-format"
153+
failFlag := "fail-on-regression"
154+
thresholdFlag := "regression-threshold"
155+
example := fmt.Sprintf(`prof track auto --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
156+
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)
157+
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."
158+
159+
cmd := &cobra.Command{
160+
Use: internal.TrackAutoCMD,
161+
Short: shortExplanation,
162+
Long: longExplanation,
163+
RunE: func(_ *cobra.Command, _ []string) error {
164+
selections := &tracker.Selections{
165+
OutputFormat: outputFormat,
166+
Baseline: Baseline,
167+
Current: Current,
168+
ProfileType: profileType,
169+
BenchmarkName: benchmarkName,
170+
RegressionThreshold: regressionThreshold,
171+
UseThreshold: failOnRegression,
172+
}
173+
return tracker.RunTrackAuto(selections)
174+
},
175+
Example: example,
176+
}
177+
178+
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
179+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
180+
cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
181+
cmd.Flags().StringVar(&profileType, profileTypeFlag, "", "Profile type (cpu, memory, mutex, block)")
182+
cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "detailed", `Output format: "summary" or "detailed"`)
183+
cmd.Flags().BoolVar(&failOnRegression, failFlag, false, "Exit with non-zero code if regression exceeds threshold")
184+
cmd.Flags().Float64Var(&regressionThreshold, thresholdFlag, 0.0, "Fail when worst flat regression exceeds this percent (e.g., 5.0)")
185+
186+
_ = cmd.MarkFlagRequired(baseTagFlag)
187+
_ = cmd.MarkFlagRequired(currentTagFlag)
188+
_ = cmd.MarkFlagRequired(benchNameFlag)
189+
_ = cmd.MarkFlagRequired(profileTypeFlag)
190+
191+
return cmd
192+
}
193+
194+
func createTrackManualCmd() *cobra.Command {
195+
outputFormatFlag := "output-format"
196+
failFlag := "fail-on-regression"
197+
thresholdFlag := "regression-threshold"
198+
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)
199+
200+
cmd := &cobra.Command{
201+
Use: internal.TrackManualCMD,
202+
Short: "Manually specify the paths to the profile text files you want to compare.",
203+
RunE: func(_ *cobra.Command, _ []string) error {
204+
selections := &tracker.Selections{
205+
OutputFormat: outputFormat,
206+
Baseline: Baseline,
207+
Current: Current,
208+
ProfileType: profileType,
209+
BenchmarkName: benchmarkName,
210+
RegressionThreshold: regressionThreshold,
211+
UseThreshold: failOnRegression,
212+
IsManual: true,
213+
}
214+
return tracker.RunTrackManual(selections)
215+
},
216+
Example: example,
217+
}
218+
219+
cmd.Flags().StringVar(&Baseline, baseTagFlag, "", "Name of the baseline tag")
220+
cmd.Flags().StringVar(&Current, currentTagFlag, "", "Name of the current tag")
221+
cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "", "Output format choice choice")
222+
cmd.Flags().BoolVar(&failOnRegression, failFlag, false, "Exit with non-zero code if regression exceeds threshold")
223+
cmd.Flags().Float64Var(&regressionThreshold, thresholdFlag, 0.0, "Fail when worst flat regression exceeds this percent (e.g., 5.0)")
224+
225+
_ = cmd.MarkFlagRequired(baseTagFlag)
226+
_ = cmd.MarkFlagRequired(currentTagFlag)
227+
_ = cmd.MarkFlagRequired(outputFormatFlag)
228+
229+
return cmd
230+
}
231+
232+
func createSetupCmd() *cobra.Command {
233+
cmd := &cobra.Command{
234+
Use: "setup",
235+
Short: "Generates the template configuration file.",
236+
RunE: func(_ *cobra.Command, _ []string) error {
237+
return internal.CreateTemplate()
238+
},
239+
DisableFlagsInUseLine: true,
240+
}
241+
242+
return cmd
243+
}
244+
245+
func createTuiCmd() *cobra.Command {
246+
cmd := &cobra.Command{
247+
Use: "tui",
248+
Short: "Interactive selection of benchmarks and profiles, then runs prof auto",
249+
RunE: runTUI,
250+
}
251+
252+
cmd.AddCommand(createTuiTrackAutoCmd())
253+
254+
return cmd
255+
}
256+
257+
func createTuiTrackAutoCmd() *cobra.Command {
258+
cmd := &cobra.Command{
259+
Use: "track",
260+
Short: "Interactive tracking with existing benchmark data",
261+
RunE: runTUITrackAuto,
262+
}
263+
264+
return cmd
265+
}

cli/config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cli
2+
3+
import (
4+
"github.com/AlexsanderHamir/prof/engine/tracker"
5+
)
6+
7+
// setGlobalTrackingVariables sets the global CLI variables for tracking
8+
func setGlobalTrackingVariables(selections *tracker.Selections) {
9+
Baseline = selections.Baseline
10+
Current = selections.Current
11+
benchmarkName = selections.BenchmarkName
12+
profileType = selections.ProfileType
13+
outputFormat = selections.OutputFormat
14+
failOnRegression = selections.UseThreshold
15+
regressionThreshold = selections.RegressionThreshold
16+
}

cli/constants_vars.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cli
2+
3+
var (
4+
// Root command flags.
5+
benchmarks []string
6+
profiles []string
7+
tag string
8+
count int
9+
10+
// Track command flags.
11+
Baseline string
12+
Current string
13+
benchmarkName string
14+
profileType string
15+
outputFormat string
16+
failOnRegression bool
17+
regressionThreshold float64
18+
)
19+
20+
const (
21+
tuiPageSize = 20
22+
minTagsForComparison = 2
23+
24+
// 3 occurrences requires a const
25+
baseTagFlag = "base"
26+
currentTagFlag = "current"
27+
benchNameFlag = "bench-name"
28+
tagFlag = "tag"
29+
)

0 commit comments

Comments
 (0)