• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AlexsanderHamir / prof / 16577619537

28 Jul 2025 06:51PM UTC coverage: 17.498% (-0.1%) from 17.625%
16577619537

Pull #13

github

web-flow
Merge aeb362e56 into 174638ed0
Pull Request #13: small improvements

0 of 9 new or added lines in 2 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

277 of 1583 relevant lines covered (17.5%)

14.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cli/api.go
1
package cli
2

3
import (
4
        "errors"
5
        "fmt"
6
        "log/slog"
7

8
        "github.com/AlexsanderHamir/prof/engine/benchmark"
9
        "github.com/AlexsanderHamir/prof/engine/collector"
10
        "github.com/AlexsanderHamir/prof/engine/tracker"
11
        "github.com/AlexsanderHamir/prof/engine/version"
12
        "github.com/AlexsanderHamir/prof/internal/args"
13
        "github.com/AlexsanderHamir/prof/internal/config"
14
        "github.com/AlexsanderHamir/prof/internal/shared"
15
        "github.com/spf13/cobra"
16
)
17

18
var (
19
        // Root command flags.
20
        benchmarks []string
21
        profiles   []string
22
        tag        string
23
        count      int
24

25
        // Track command flags.
26
        baselineTag   string
27
        currentTag    string
28
        benchmarkName string
29
        profileType   string
30
        outputFormat  string
31
)
32

33
// CreateRootCmd creates and returns the root cobra command.
34
func CreateRootCmd() *cobra.Command {
×
35
        rootCmd := &cobra.Command{
×
36
                Use:   "prof",
×
37
                Short: "CLI tool for organizing pprof generated data, and analyzing performance differences at the profile level.",
×
38
                RunE:  runBenchmarks,
×
39
        }
×
40

×
NEW
41
        rootCmd.AddCommand(createProfManual())
×
NEW
42
        rootCmd.AddCommand(createProfAuto())
×
43
        rootCmd.AddCommand(createSetupCmd())
×
44
        rootCmd.AddCommand(createTrackCmd())
×
45
        rootCmd.AddCommand(createVersionCmd())
×
46

×
47
        return rootCmd
×
48
}
×
49

NEW
50
func createProfManual() *cobra.Command {
×
51
        manualCmd := &cobra.Command{
×
52
                Use:     shared.MANUALCMD,
×
53
                Short:   "Receives profile files and performs data collection and organization. (doesn't wrap go test)",
×
54
                Args:    cobra.MinimumNArgs(1),
×
55
                Example: fmt.Sprintf("prof %s --tag tagName cpu.prof memory.prof block.prof mutex.prof", shared.MANUALCMD),
×
56
                RunE: func(_ *cobra.Command, args []string) error {
×
57
                        return collector.RunCollector(args, tag)
×
58
                },
×
59
        }
60

61
        tagFlag := "tag"
×
62
        manualCmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
×
63
        _ = manualCmd.MarkFlagRequired(tagFlag)
×
64

×
65
        return manualCmd
×
66
}
67

NEW
68
func createProfAuto() *cobra.Command {
×
69
        benchFlag := "benchmarks"
×
70
        profileFlag := "profiles"
×
71
        tagFlag := "tag"
×
72
        countFlag := "count"
×
73
        example := fmt.Sprintf(`prof %s --%s BenchmarkGenPool --%s cpu,memory --%s 10 --%s "tag1"`, shared.AUTOCMD, benchFlag, profileFlag, countFlag, tagFlag)
×
74

×
75
        cmd := &cobra.Command{
×
76
                Use:     shared.AUTOCMD,
×
77
                Short:   "Wraps `go test` and `pprof` to benchmark code and gather profiling data for performance investigations.",
×
78
                RunE:    runBenchmarks,
×
79
                Example: example,
×
80
        }
×
81

×
82
        cmd.Flags().StringSliceVar(&benchmarks, benchFlag, []string{}, `Benchmarks to run (e.g., "[BenchmarkGenPool]")"`)
×
83
        cmd.Flags().StringSliceVar(&profiles, profileFlag, []string{}, `Profiles to use (e.g., "[cpu,memory,mutex]")`)
×
84
        cmd.Flags().StringVar(&tag, tagFlag, "", "The tag is used to organize the results")
×
85
        cmd.Flags().IntVar(&count, countFlag, 0, "Number of runs")
×
86

×
87
        _ = cmd.MarkFlagRequired(benchFlag)
×
88
        _ = cmd.MarkFlagRequired(profileFlag)
×
89
        _ = cmd.MarkFlagRequired(tagFlag)
×
90
        _ = cmd.MarkFlagRequired(countFlag)
×
91

×
92
        return cmd
×
93
}
×
94

95
// createTrackCmd creates the track subcommand
96
func createTrackCmd() *cobra.Command {
×
97
        shortExplanation := "Compare performance between two benchmark runs to detect regressions and improvements"
×
98
        cmd := &cobra.Command{
×
99
                Use:   "track",
×
100
                Short: shortExplanation,
×
101
        }
×
102

×
103
        cmd.AddCommand(createTrackAutoCmd())
×
104
        cmd.AddCommand(createTrackManualCmd())
×
105

×
106
        return cmd
×
107
}
×
108

109
func createTrackAutoCmd() *cobra.Command {
×
110
        baseTagFlag := "base"
×
111
        currentTagFlag := "current"
×
112
        benchNameFlag := "bench-name"
×
113
        profileTypeFlag := "profile-type"
×
114
        outputFormatFlag := "output-format"
×
115
        example := fmt.Sprintf(`prof track auto --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
×
116
        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)
×
117

×
118
        cmd := &cobra.Command{
×
119
                Use:     shared.TrackAutoCMD,
×
120
                Long:    longExplanation,
×
121
                RunE:    runTrackAuto,
×
122
                Example: example,
×
123
        }
×
124

×
125
        cmd.Flags().StringVar(&baselineTag, baseTagFlag, "", "Name of the baseline tag")
×
126
        cmd.Flags().StringVar(&currentTag, currentTagFlag, "", "Name of the current tag")
×
127
        cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
×
128
        cmd.Flags().StringVar(&profileType, profileTypeFlag, "", "Profile type (cpu, memory, mutex, block)")
×
129
        cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "detailed", `Output format: "summary" or "detailed"`)
×
130

×
131
        _ = cmd.MarkFlagRequired(baseTagFlag)
×
132
        _ = cmd.MarkFlagRequired(currentTagFlag)
×
133
        _ = cmd.MarkFlagRequired(benchNameFlag)
×
134
        _ = cmd.MarkFlagRequired(profileTypeFlag)
×
135

×
136
        return cmd
×
137
}
×
138

139
func createTrackManualCmd() *cobra.Command {
×
140
        baseFlag := "base"
×
141
        currentFlag := "current"
×
142
        outputFormatFlag := "output-format"
×
143
        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)
×
144

×
145
        cmd := &cobra.Command{
×
146
                Use:     shared.TrackManualCMD,
×
147
                Short:   "Manually specify the paths to the profile text files you want to compare.",
×
148
                RunE:    runTrackManual,
×
149
                Example: example,
×
150
        }
×
151

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

×
156
        _ = cmd.MarkFlagRequired(baseFlag)
×
157
        _ = cmd.MarkFlagRequired(currentFlag)
×
158
        _ = cmd.MarkFlagRequired(outputFormatFlag)
×
159

×
160
        return cmd
×
161
}
×
162

163
func createVersionCmd() *cobra.Command {
×
164
        cmd := &cobra.Command{
×
165
                Use:                   "version",
×
166
                Short:                 "Shows the current version of prof and checks for updates.",
×
167
                RunE:                  runVersion,
×
168
                DisableFlagsInUseLine: true,
×
169
        }
×
170

×
171
        return cmd
×
172
}
×
173

174
// createSetupCmd creates the setup subcommand
175
func createSetupCmd() *cobra.Command {
×
176
        cmd := &cobra.Command{
×
177
                Use:                   "setup",
×
178
                Short:                 "Generates the template configuration file.",
×
179
                RunE:                  runSetup,
×
180
                DisableFlagsInUseLine: true,
×
181
        }
×
182

×
183
        return cmd
×
184
}
×
185

186
// Execute runs the CLI application
187
func Execute() error {
×
188
        return CreateRootCmd().Execute()
×
189
}
×
190

191
func runBenchmarks(_ *cobra.Command, _ []string) error {
×
192
        if len(benchmarks) == 0 {
×
193
                return errors.New("benchmarks flag is empty")
×
194
        }
×
195

196
        if len(profiles) == 0 {
×
197
                return errors.New("profiles flag is empty")
×
198
        }
×
199

200
        cfg, err := config.LoadFromFile(shared.ConfigFilename)
×
201
        if err != nil {
×
202
                cfg = &config.Config{}
×
203
        }
×
204

205
        if err = benchmark.SetupDirectories(tag, benchmarks, profiles); err != nil {
×
206
                return fmt.Errorf("failed to setup directories: %w", err)
×
207
        }
×
208

209
        benchArgs := &args.BenchArgs{
×
210
                Benchmarks: benchmarks,
×
211
                Profiles:   profiles,
×
212
                Count:      count,
×
213
                Tag:        tag,
×
214
        }
×
215

×
216
        printConfiguration(benchArgs, cfg.FunctionFilter)
×
217

×
218
        if err = runBenchAndGetProfiles(benchArgs, cfg.FunctionFilter); err != nil {
×
219
                return err
×
220
        }
×
221

222
        return nil
×
223
}
224

225
// runVersion handles the version command execution
226
func runVersion(_ *cobra.Command, _ []string) error {
×
227
        current, latest := version.Check()
×
228
        output := version.FormatOutput(current, latest)
×
229
        fmt.Print(output)
×
230
        return nil
×
231
}
×
232

233
// runSetup handles the setup command execution
234
func runSetup(_ *cobra.Command, _ []string) error {
×
235
        return config.CreateTemplate()
×
236
}
×
237

238
var validFormats = map[string]bool{
239
        "summary":  true,
240
        "detailed": true,
241
}
242

243
// runTrack handles the track command execution
244
func runTrackAuto(_ *cobra.Command, _ []string) error {
×
245
        if !validFormats[outputFormat] {
×
246
                return fmt.Errorf("invalid output format '%s'. Valid formats: summary, detailed", outputFormat)
×
247
        }
×
248

249
        report, err := tracker.CheckPerformanceDifferences(baselineTag, currentTag, benchmarkName, profileType)
×
250
        if err != nil {
×
251
                return fmt.Errorf("failed to track performance differences: %w", err)
×
252
        }
×
253

254
        noFunctionChanges := len(report.FunctionChanges) == 0
×
255
        if noFunctionChanges {
×
256
                slog.Info("No function changes detected between the two runs")
×
257
                return nil
×
258
        }
×
259

260
        switch outputFormat {
×
261
        case "summary":
×
262
                printSummary(report)
×
263
        case "detailed":
×
264
                printDetailedReport(report)
×
265
        }
266

267
        return nil
×
268
}
269

270
func runTrackManual(_ *cobra.Command, _ []string) error {
×
271
        if !validFormats[outputFormat] {
×
272
                return fmt.Errorf("invalid output format '%s'. Valid formats: summary, detailed", outputFormat)
×
273
        }
×
274

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

280
        noFunctionChanges := len(report.FunctionChanges) == 0
×
281
        if noFunctionChanges {
×
282
                slog.Info("No function changes detected between the two runs")
×
283
                return nil
×
284
        }
×
285

286
        switch outputFormat {
×
287
        case "summary":
×
288
                printSummary(report)
×
289
        case "detailed":
×
290
                printDetailedReport(report)
×
291
        }
292
        return nil
×
293
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc