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

AlexsanderHamir / prof / 16531001195

25 Jul 2025 08:28PM UTC coverage: 16.428% (+0.6%) from 15.814%
16531001195

push

github

web-flow
Merge pull request #4 from AlexsanderHamir/refactors

CLI Refactor

2 of 27 new or added lines in 3 files covered. (7.41%)

3 existing lines in 2 files now uncovered.

241 of 1467 relevant lines covered (16.43%)

15.09 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/args"
9
        "github.com/AlexsanderHamir/prof/benchmark"
10
        "github.com/AlexsanderHamir/prof/collector"
11
        "github.com/AlexsanderHamir/prof/config"
12
        "github.com/AlexsanderHamir/prof/shared"
13
        "github.com/AlexsanderHamir/prof/tracker"
14
        "github.com/AlexsanderHamir/prof/version"
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

×
41
        rootCmd.AddCommand(createManualCmd())
×
42
        rootCmd.AddCommand(createRunCmd())
×
43
        rootCmd.AddCommand(createSetupCmd())
×
44
        rootCmd.AddCommand(createTrackCmd())
×
45
        rootCmd.AddCommand(createVersionCmd())
×
46

×
47
        return rootCmd
×
48
}
×
49

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

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

×
65
        return manualCmd
×
66
}
67

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

×
75
        runCmd := &cobra.Command{
×
76
                Use:     "run",
×
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

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

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

×
92
        return runCmd
×
93
}
×
94

95
// createTrackCmd creates the track subcommand
96
func createTrackCmd() *cobra.Command {
×
97
        baseTagFlag := "base-tag"
×
98
        currentTagFlag := "current-tag"
×
99
        benchNameFlag := "bench-name"
×
100
        profileTypeFlag := "profile-type"
×
101
        outputFormatFlag := "output-format"
×
102
        example := fmt.Sprintf(`prof track --%s "tag1" --%s "tag2" --%s "cpu" --%s "BenchmarkGenPool" --%s "summary"`, baseTagFlag, currentTagFlag, profileTypeFlag, benchNameFlag, outputFormatFlag)
×
103
        shortExplanation := "Compare performance between two benchmark runs to detect regressions and improvements"
×
104
        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."
×
105

×
106
        cmd := &cobra.Command{
×
107
                Use:     "track",
×
108
                Short:   shortExplanation,
×
109
                Long:    longExplanation,
×
110
                RunE:    runTrack,
×
111
                Example: example,
×
112
        }
×
113

×
114
        cmd.Flags().StringVar(&baselineTag, baseTagFlag, "", "Name of the baseline tag")
×
115
        cmd.Flags().StringVar(&currentTag, currentTagFlag, "", "Name of the current tag")
×
116
        cmd.Flags().StringVar(&benchmarkName, benchNameFlag, "", "Name of the benchmark")
×
117
        cmd.Flags().StringVar(&profileType, profileTypeFlag, "", "Profile type (cpu, memory, mutex, block)")
×
118
        cmd.Flags().StringVar(&outputFormat, outputFormatFlag, "detailed", `Output format: "summary" or "detailed"`)
×
119

×
120
        _ = cmd.MarkFlagRequired(baseTagFlag)
×
121
        _ = cmd.MarkFlagRequired(currentTagFlag)
×
122
        _ = cmd.MarkFlagRequired(benchNameFlag)
×
123
        _ = cmd.MarkFlagRequired(profileTypeFlag)
×
124

×
125
        return cmd
×
126
}
×
127

128
func createVersionCmd() *cobra.Command {
×
129
        cmd := &cobra.Command{
×
130
                Use:                   "version",
×
131
                Short:                 "Shows the current version of prof and checks for updates.",
×
132
                RunE:                  runVersion,
×
133
                DisableFlagsInUseLine: true,
×
134
        }
×
135

×
136
        return cmd
×
137
}
×
138

139
// createSetupCmd creates the setup subcommand
140
func createSetupCmd() *cobra.Command {
×
141
        cmd := &cobra.Command{
×
142
                Use:                   "setup",
×
143
                Short:                 "Generates the template configuration file.",
×
144
                RunE:                  runSetup,
×
145
                DisableFlagsInUseLine: true,
×
146
        }
×
147

×
148
        return cmd
×
149
}
×
150

151
// Execute runs the CLI application
152
func Execute() error {
×
153
        return CreateRootCmd().Execute()
×
154
}
×
155

156
func runBenchmarks(_ *cobra.Command, _ []string) error {
×
NEW
157
        if len(benchmarks) == 0 {
×
NEW
158
                return errors.New("benchmarks flag is empty")
×
UNCOV
159
        }
×
160

NEW
161
        if len(profiles) == 0 {
×
NEW
162
                return errors.New("profiles flag is empty")
×
UNCOV
163
        }
×
164

NEW
165
        cfg, err := config.LoadFromFile(shared.ConfigFilename)
×
166
        if err != nil {
×
NEW
167
                cfg = &config.Config{}
×
168
        }
×
169

NEW
170
        if err = benchmark.SetupDirectories(tag, benchmarks, profiles); err != nil {
×
171
                return fmt.Errorf("failed to setup directories: %w", err)
×
172
        }
×
173

174
        benchArgs := &args.BenchArgs{
×
NEW
175
                Benchmarks: benchmarks,
×
NEW
176
                Profiles:   profiles,
×
177
                Count:      count,
×
178
                Tag:        tag,
×
179
        }
×
180

×
181
        printConfiguration(benchArgs, cfg.FunctionFilter)
×
182

×
NEW
183
        if err = runBenchAndGetProfiles(benchArgs, cfg.FunctionFilter); err != nil {
×
184
                return err
×
185
        }
×
186

187
        return nil
×
188
}
189

190
// runVersion handles the version command execution
191
func runVersion(_ *cobra.Command, _ []string) error {
×
192
        current, latest := version.Check()
×
193
        output := version.FormatOutput(current, latest)
×
194
        fmt.Print(output)
×
195
        return nil
×
196
}
×
197

198
// runSetup handles the setup command execution
199
func runSetup(_ *cobra.Command, _ []string) error {
×
200
        return config.CreateTemplate()
×
201
}
×
202

203
// runTrack handles the track command execution
204
func runTrack(_ *cobra.Command, _ []string) error {
×
205
        validFormats := map[string]bool{
×
206
                "summary":  true,
×
207
                "detailed": true,
×
208
        }
×
209

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

NEW
214
        report, err := tracker.CheckPerformanceDifferences(baselineTag, currentTag, benchmarkName, profileType)
×
215
        if err != nil {
×
216
                return fmt.Errorf("failed to track performance differences: %w", err)
×
217
        }
×
218

NEW
219
        noFunctionChanges := len(report.FunctionChanges) == 0
×
NEW
220
        if noFunctionChanges {
×
221
                slog.Info("No function changes detected between the two runs")
×
222
                return nil
×
223
        }
×
224

225
        switch outputFormat {
×
226
        case "summary":
×
227
                printSummary(report)
×
228
        case "detailed":
×
229
                printDetailedReport(report)
×
230
        }
231

232
        return nil
×
233
}
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