Skip to content

Commit d12df0e

Browse files
Merge pull request #3 from AlexsanderHamir/refactors
Core non-wrapping feature implemented
2 parents 1effc40 + 7b9e4d6 commit d12df0e

File tree

10 files changed

+245
-121
lines changed

10 files changed

+245
-121
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
tasks.md
1+
tasks.md
2+
*.out
3+
*.prof
4+
*prof

benchmark/api.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path/filepath"
99

1010
"github.com/AlexsanderHamir/prof/args"
11+
"github.com/AlexsanderHamir/prof/collector"
1112
"github.com/AlexsanderHamir/prof/parser"
1213
"github.com/AlexsanderHamir/prof/shared"
1314
)
@@ -18,11 +19,11 @@ func SetupDirectories(tag string, benchmarks, profiles []string) error {
1819
if err != nil {
1920
return err
2021
}
21-
tagDir := filepath.Join(currentDir, shared.MainDirOutput, tag)
2222

23-
err = deleteContents(tagDir)
23+
tagDir := filepath.Join(currentDir, shared.MainDirOutput, tag)
24+
err = shared.CleanOrCreateDir(tagDir)
2425
if err != nil {
25-
return fmt.Errorf("deleteContents failed: %w", err)
26+
return fmt.Errorf("CleanOrCreateDir failed: %w", err)
2627
}
2728

2829
if err = createBenchDirectories(tagDir, benchmarks); err != nil {
@@ -39,9 +40,9 @@ func RunBenchmark(benchmarkName string, profiles []string, count int, tag string
3940
return err
4041
}
4142

42-
textDir, binDir := getOrCreateOutputDirectories(benchmarkName, tag)
43+
textDir, binDir := getOutputDirectories(benchmarkName, tag)
4344

44-
outputFile := filepath.Join(textDir, fmt.Sprintf("%s.%s", benchmarkName, textExtension))
45+
outputFile := filepath.Join(textDir, fmt.Sprintf("%s.%s", benchmarkName, shared.TextExtension))
4546
if err = runBenchmarkCommand(cmd, outputFile); err != nil {
4647
return err
4748
}
@@ -69,15 +70,15 @@ func ProcessProfiles(benchmarkName string, profiles []string, tag string) error
6970
return fmt.Errorf("failed to stat profile file %s: %w", profileFile, err)
7071
}
7172

72-
outputFile := filepath.Join(textDir, fmt.Sprintf("%s_%s.%s", benchmarkName, profile, textExtension))
73+
outputFile := filepath.Join(textDir, fmt.Sprintf("%s_%s.%s", benchmarkName, profile, shared.TextExtension))
7374
profileFunctionsDir := filepath.Join(tagDir, profile+shared.FunctionsDirSuffix, benchmarkName)
7475

75-
if err := generateTextProfile(profileFile, outputFile); err != nil {
76+
if err := collector.GenerateProfileTextOutput(profileFile, outputFile); err != nil {
7677
return fmt.Errorf("failed to generate text profile for %s: %w", profile, err)
7778
}
7879

79-
pngFile := filepath.Join(profileFunctionsDir, fmt.Sprintf("%s_%s.png", benchmarkName, profile))
80-
if err := generatePNGVisualization(profileFile, pngFile); err != nil {
80+
pngDesiredFilePath := filepath.Join(profileFunctionsDir, fmt.Sprintf("%s_%s.png", benchmarkName, profile))
81+
if err := collector.GeneratePNGVisualization(profileFile, pngDesiredFilePath); err != nil {
8182
return fmt.Errorf("failed to generate PNG visualization for %s: %w", profile, err)
8283
}
8384

@@ -95,17 +96,12 @@ func CollectProfileFunctions(args *args.CollectionArgs) error {
9596
return fmt.Errorf("failed to create output directory: %w", err)
9697
}
9798

98-
filter := parser.ProfileFilter{
99-
FunctionPrefixes: args.BenchmarkConfig.IncludePrefixes,
100-
IgnoreFunctions: args.BenchmarkConfig.IgnoreFunctions,
101-
}
102-
103-
functions, err := parser.GetAllFunctionNames(paths.ProfileTextFile, filter)
99+
functions, err := parser.GetAllFunctionNames(paths.ProfileTextFile, args.BenchmarkConfig)
104100
if err != nil {
105101
return fmt.Errorf("failed to extract function names: %w", err)
106102
}
107103

108-
if err = saveAllFunctionsPprofContents(functions, paths); err != nil {
104+
if err = collector.SaveAllFunctionsPprofContents(functions, paths.ProfileBinaryFile, paths.FunctionDirectory); err != nil {
109105
return fmt.Errorf("getAllFunctionsPprofContents failed: %w", err)
110106
}
111107
}

benchmark/helpers.go

Lines changed: 2 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,7 @@ func getProfileFlags() map[string]string {
2222
}
2323
}
2424

25-
func getPprofTextParams() []string {
26-
return []string{
27-
"-cum",
28-
"-edgefraction=0",
29-
"-nodefraction=0",
30-
"-top",
31-
}
32-
}
33-
3425
const (
35-
textExtension = "txt"
3626
binExtension = "out"
3727
descriptionFileName = "description.txt"
3828
moduleNotFoundMsg = "go: cannot find main module"
@@ -115,36 +105,8 @@ func buildBenchmarkCommand(benchmarkName string, profiles []string, count int) (
115105
return cmd, nil
116106
}
117107

118-
func deleteContents(dir string) error {
119-
info, err := os.Stat(dir)
120-
121-
if os.IsNotExist(err) {
122-
return nil
123-
} else if err != nil {
124-
return err
125-
}
126-
127-
if !info.IsDir() {
128-
return fmt.Errorf("path is not a directory: %s", dir)
129-
}
130-
131-
entries, err := os.ReadDir(dir)
132-
if err != nil {
133-
return fmt.Errorf("failed to read directory: %w", err)
134-
}
135-
136-
for _, entry := range entries {
137-
path := filepath.Join(dir, entry.Name())
138-
if err = os.RemoveAll(path); err != nil {
139-
return fmt.Errorf("failed to remove %s: %w", path, err)
140-
}
141-
}
142-
143-
return nil
144-
}
145-
146108
// getOutputDirectories gets or creates the output directories.
147-
func getOrCreateOutputDirectories(benchmarkName, tag string) (textDir string, binDir string) {
109+
func getOutputDirectories(benchmarkName, tag string) (textDir string, binDir string) {
148110
tagDir := filepath.Join(shared.MainDirOutput, tag)
149111
textDir = filepath.Join(tagDir, shared.ProfileTextDir, benchmarkName)
150112
binDir = filepath.Join(tagDir, shared.ProfileBinDir, benchmarkName)
@@ -212,34 +174,6 @@ func moveTestFiles(benchmarkName, binDir string) error {
212174
return nil
213175
}
214176

215-
func generateTextProfile(profileFile, outputFile string) error {
216-
pprofTextParams := getPprofTextParams()
217-
cmd := append([]string{"go", "tool", "pprof"}, pprofTextParams...)
218-
cmd = append(cmd, profileFile)
219-
220-
// #nosec G204 -- cmd is constructed internally by generateTextProfile(), not from user input
221-
execCmd := exec.Command(cmd[0], cmd[1:]...)
222-
output, err := execCmd.Output()
223-
if err != nil {
224-
return fmt.Errorf("pprof command failed: %w", err)
225-
}
226-
227-
return os.WriteFile(outputFile, output, shared.PermFile)
228-
}
229-
230-
func generatePNGVisualization(profileFile, outputFile string) error {
231-
cmd := []string{"go", "tool", "pprof", "-png", profileFile}
232-
233-
// #nosec G204 -- cmd is constructed internally by generatePNGVisualization(), not from user input
234-
execCmd := exec.Command(cmd[0], cmd[1:]...)
235-
output, err := execCmd.Output()
236-
if err != nil {
237-
return fmt.Errorf("pprof PNG generation failed: %w", err)
238-
}
239-
240-
return os.WriteFile(outputFile, output, shared.PermFile)
241-
}
242-
243177
// ProfilePaths holds paths for profile text, binary, and output directories.
244178
type ProfilePaths struct {
245179
// Desired file path for specified profile
@@ -265,7 +199,7 @@ type ProfilePaths struct {
265199
// - bench/v1.0/cpu_functions/BenchmarkPool/function1.txt
266200
func getProfilePaths(tag, benchmarkName, profile string) ProfilePaths {
267201
tagDir := filepath.Join("bench", tag)
268-
profileTextFile := fmt.Sprintf("%s_%s.%s", benchmarkName, profile, textExtension)
202+
profileTextFile := fmt.Sprintf("%s_%s.%s", benchmarkName, profile, shared.TextExtension)
269203
profileBinFile := fmt.Sprintf("%s_%s.%s", benchmarkName, profile, binExtension)
270204

271205
return ProfilePaths{
@@ -274,35 +208,3 @@ func getProfilePaths(tag, benchmarkName, profile string) ProfilePaths {
274208
FunctionDirectory: filepath.Join(tagDir, profile+shared.FunctionsDirSuffix, benchmarkName),
275209
}
276210
}
277-
278-
// saveAllFunctionsPprofContents calls [getFunctionPprofContent] sequentially.
279-
func saveAllFunctionsPprofContents(functions []string, paths ProfilePaths) error {
280-
for _, function := range functions {
281-
if err := getFunctionPprofContent(function, paths); err != nil {
282-
return fmt.Errorf("failed to extract function content for %s: %w", function, err)
283-
}
284-
}
285-
286-
return nil
287-
}
288-
289-
// getFunctionPprofContent gets code line level mapping of specified function
290-
// and writes the data to a file named after the function.
291-
func getFunctionPprofContent(function string, paths ProfilePaths) error {
292-
outputFile := filepath.Join(paths.FunctionDirectory, function+"."+textExtension)
293-
cmd := []string{"go", "tool", "pprof", fmt.Sprintf("-list=%s", function), paths.ProfileBinaryFile}
294-
295-
// #nosec ProfileTextDir04 -- cmd is constructed internally by getFunctionPprofContent(), not from user input
296-
execCmd := exec.Command(cmd[0], cmd[1:]...)
297-
output, err := execCmd.Output()
298-
if err != nil {
299-
return fmt.Errorf("pprof list command failed: %w", err)
300-
}
301-
302-
if err = os.WriteFile(outputFile, output, shared.PermFile); err != nil {
303-
return fmt.Errorf("failed to write function content: %w", err)
304-
}
305-
306-
slog.Info("Collected function", "function", function)
307-
return nil
308-
}

cli/api.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"log/slog"
77

88
"github.com/AlexsanderHamir/prof/args"
9+
"github.com/AlexsanderHamir/prof/collector"
910
"github.com/AlexsanderHamir/prof/config"
1011
"github.com/AlexsanderHamir/prof/tracker"
1112
"github.com/AlexsanderHamir/prof/version"
@@ -35,6 +36,7 @@ func CreateRootCmd() *cobra.Command {
3536
RunE: runBenchmarks,
3637
}
3738

39+
rootCmd.AddCommand(createManualCmd())
3840
rootCmd.AddCommand(createRunCmd())
3941
rootCmd.AddCommand(createSetupCmd())
4042
rootCmd.AddCommand(createTrackCmd())
@@ -43,6 +45,24 @@ func CreateRootCmd() *cobra.Command {
4345
return rootCmd
4446
}
4547

48+
func createManualCmd() *cobra.Command {
49+
manualCmd := &cobra.Command{
50+
Use: "manual",
51+
Short: "Receives profile files and performs data collection and organization.",
52+
Args: cobra.MinimumNArgs(1),
53+
Example: "prof manual cpu.prof memory.prof block.prof",
54+
RunE: func(_ *cobra.Command, args []string) error {
55+
return collector.RunCollector(args, tag)
56+
},
57+
}
58+
59+
tagFlag := "tag"
60+
manualCmd.Flags().StringVar(&tag, tagFlag, "", "Tag for organization")
61+
_ = manualCmd.MarkFlagRequired(tagFlag)
62+
63+
return manualCmd
64+
}
65+
4666
func createRunCmd() *cobra.Command {
4767
benchFlag := "benchmarks"
4868
profileFlag := "profiles"
@@ -142,7 +162,7 @@ func runBenchmarks(_ *cobra.Command, _ []string) error {
142162
cfg = &config.Config{}
143163
}
144164

145-
benchmarkList, profileList, err := parseBenchmarkConfig(benchmarks, profiles)
165+
benchmarkList, profileList, err := parseAndValidateBenchmarkParams(benchmarks, profiles)
146166
if err != nil {
147167
return fmt.Errorf("failed to parse benchmark config: %w", err)
148168
}

cli/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func parseListArgument(arg string) []string {
5959
}
6060

6161
// Rest of the functions remain the same...
62-
func parseBenchmarkConfig(benchmarks, profiles string) ([]string, []string, error) {
62+
func parseAndValidateBenchmarkParams(benchmarks, profiles string) ([]string, []string, error) {
6363
if err := validateListArguments(benchmarks, profiles); err != nil {
6464
return nil, nil, err
6565
}

0 commit comments

Comments
 (0)