@@ -3,6 +3,11 @@ package cmd
33import (
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
1318var (
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
1729var 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
35113func Execute () {
@@ -61,4 +139,5 @@ func Execute() {
61139func 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