Skip to content

Commit da234e4

Browse files
committed
(feat) add --profile flag to dump runtime profiling data
1 parent d972fff commit da234e4

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ screenshots/
2525
.glide/
2626
.DS_Store
2727
.idea/
28+
29+
# profiles
30+
profiles/

cmd/root.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package cmd
33
import (
44
"fmt"
55
"os"
6+
"path/filepath"
7+
"runtime"
8+
"runtime/pprof"
9+
"runtime/trace"
10+
"time"
611

712
"github.com/sensepost/gowitness/internal/ascii"
813
"github.com/sensepost/gowitness/pkg/log"
@@ -12,6 +17,13 @@ import (
1217

1318
var (
1419
opts = &runner.Options{}
20+
21+
// perf profiling
22+
enableProfiling bool
23+
profileDir string
24+
25+
// hooks to run after command execution
26+
postRunHooks []func()
1527
)
1628

1729
var rootCmd = &cobra.Command{
@@ -28,8 +40,74 @@ var rootCmd = &cobra.Command{
2840
log.Debug("debug logging enabled")
2941
}
3042

43+
if enableProfiling {
44+
ts := time.Now().Format("20060102-150405")
45+
profileDir = filepath.Join("profiles", ts)
46+
47+
if err := os.MkdirAll(profileDir, 0o755); err != nil {
48+
return fmt.Errorf("could not create profile directory %q: %w", profileDir, err)
49+
}
50+
51+
cpuPath := filepath.Join(profileDir, "cpu.pprof")
52+
memPath := filepath.Join(profileDir, "mem.pprof")
53+
tracePath := filepath.Join(profileDir, "trace.out")
54+
55+
// cpu
56+
cpuFile, err := os.Create(cpuPath)
57+
if err != nil {
58+
return fmt.Errorf("could not create CPU profile file: %w", err)
59+
}
60+
if err := pprof.StartCPUProfile(cpuFile); err != nil {
61+
_ = cpuFile.Close()
62+
return fmt.Errorf("could not start CPU profile: %w", err)
63+
}
64+
postRunHooks = append(postRunHooks, func() {
65+
pprof.StopCPUProfile()
66+
_ = cpuFile.Close()
67+
})
68+
69+
// memory
70+
postRunHooks = append(postRunHooks, func() {
71+
memFile, err := os.Create(memPath)
72+
if err != nil {
73+
fmt.Fprintf(os.Stderr, "could not create memory profile file: %v\n", err)
74+
return
75+
}
76+
defer memFile.Close()
77+
78+
runtime.GC() // refresh heap statistics
79+
80+
if err := pprof.WriteHeapProfile(memFile); err != nil {
81+
fmt.Fprintf(os.Stderr, "could not write memory profile: %v\n", err)
82+
}
83+
})
84+
85+
// trace
86+
traceFile, err := os.Create(tracePath)
87+
if err != nil {
88+
return fmt.Errorf("could not create trace file: %w", err)
89+
}
90+
if err := trace.Start(traceFile); err != nil {
91+
_ = traceFile.Close()
92+
return fmt.Errorf("could not start trace: %w", err)
93+
}
94+
postRunHooks = append(postRunHooks, func() {
95+
trace.Stop()
96+
_ = traceFile.Close()
97+
})
98+
99+
// Log where results will be written
100+
log.Info(fmt.Sprintf("profiling enabled: writing profiles to %s", profileDir))
101+
}
102+
31103
return nil
32104
},
105+
106+
PersistentPostRun: func(cmd *cobra.Command, args []string) {
107+
for _, hook := range postRunHooks {
108+
hook()
109+
}
110+
},
33111
}
34112

35113
func Execute() {
@@ -61,4 +139,5 @@ func Execute() {
61139
func init() {
62140
rootCmd.PersistentFlags().BoolVarP(&opts.Logging.Debug, "debug-log", "D", false, "Enable debug logging")
63141
rootCmd.PersistentFlags().BoolVarP(&opts.Logging.Silence, "quiet", "q", false, "Silence (almost all) logging")
142+
rootCmd.PersistentFlags().BoolVar(&enableProfiling, "profile", false, "Enable CPU, memory, and trace profiling (writes to profiles/<timestamp>/)")
64143
}

0 commit comments

Comments
 (0)