1
2
3
4
5
6 package tool
7
8 import (
9 "context"
10 "flag"
11 "fmt"
12 "log"
13 "os"
14 "reflect"
15 "runtime"
16 "runtime/pprof"
17 "runtime/trace"
18 "strings"
19 "time"
20 )
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 type Profile struct {
44 CPU string `flag:"profile.cpu" help:"write CPU profile to this file"`
45 Memory string `flag:"profile.mem" help:"write memory profile to this file"`
46 Alloc string `flag:"profile.alloc" help:"write alloc profile to this file"`
47 Trace string `flag:"profile.trace" help:"write trace log to this file"`
48 }
49
50
51 type Application interface {
52
53 Name() string
54
55
56 Usage() string
57
58 ShortHelp() string
59
60
61
62
63
64 DetailedHelp(*flag.FlagSet)
65
66
67 Run(ctx context.Context, args ...string) error
68 }
69
70 type SubCommand interface {
71 Parent() string
72 }
73
74
75
76 type commandLineError string
77
78 func (e commandLineError) Error() string { return string(e) }
79
80
81
82
83 func CommandLineErrorf(message string, args ...interface{}) error {
84 return commandLineError(fmt.Sprintf(message, args...))
85 }
86
87
88
89
90
91 func Main(ctx context.Context, app Application, args []string) {
92 s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
93 if err := Run(ctx, s, app, args); err != nil {
94 fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err)
95 if _, printHelp := err.(commandLineError); printHelp {
96
97
98
99
100 s.Usage()
101 }
102 os.Exit(2)
103 }
104 }
105
106
107
108
109 func Run(ctx context.Context, s *flag.FlagSet, app Application, args []string) (resultErr error) {
110 s.Usage = func() {
111 if app.ShortHelp() != "" {
112 fmt.Fprintf(s.Output(), "%s\n\nUsage:\n ", app.ShortHelp())
113 if sub, ok := app.(SubCommand); ok && sub.Parent() != "" {
114 fmt.Fprintf(s.Output(), "%s [flags] %s", sub.Parent(), app.Name())
115 } else {
116 fmt.Fprintf(s.Output(), "%s [flags]", app.Name())
117 }
118 if usage := app.Usage(); usage != "" {
119 fmt.Fprintf(s.Output(), " %s", usage)
120 }
121 fmt.Fprint(s.Output(), "\n")
122 }
123 app.DetailedHelp(s)
124 }
125 p := addFlags(s, reflect.StructField{}, reflect.ValueOf(app))
126 if err := s.Parse(args); err != nil {
127 return err
128 }
129
130 if p != nil && p.CPU != "" {
131 f, err := os.Create(p.CPU)
132 if err != nil {
133 return err
134 }
135 if err := pprof.StartCPUProfile(f); err != nil {
136 f.Close()
137 return err
138 }
139 defer func() {
140 pprof.StopCPUProfile()
141 if closeErr := f.Close(); resultErr == nil {
142 resultErr = closeErr
143 }
144 }()
145 }
146
147 if p != nil && p.Trace != "" {
148 f, err := os.Create(p.Trace)
149 if err != nil {
150 return err
151 }
152 if err := trace.Start(f); err != nil {
153 f.Close()
154 return err
155 }
156 defer func() {
157 trace.Stop()
158 if closeErr := f.Close(); resultErr == nil {
159 resultErr = closeErr
160 }
161 log.Printf("To view the trace, run:\n$ go tool trace view %s", p.Trace)
162 }()
163 }
164
165 if p != nil && p.Memory != "" {
166 f, err := os.Create(p.Memory)
167 if err != nil {
168 return err
169 }
170 defer func() {
171 runtime.GC()
172 if err := pprof.WriteHeapProfile(f); err != nil {
173 log.Printf("Writing memory profile: %v", err)
174 }
175 f.Close()
176 }()
177 }
178
179 if p != nil && p.Alloc != "" {
180 f, err := os.Create(p.Alloc)
181 if err != nil {
182 return err
183 }
184 defer func() {
185 if err := pprof.Lookup("allocs").WriteTo(f, 0); err != nil {
186 log.Printf("Writing alloc profile: %v", err)
187 }
188 f.Close()
189 }()
190 }
191
192 return app.Run(ctx, s.Args()...)
193 }
194
195
196
197 func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *Profile {
198
199 if field.PkgPath != "" {
200 return nil
201 }
202
203 flagNames, isFlag := field.Tag.Lookup("flag")
204 help := field.Tag.Get("help")
205 if isFlag {
206 nameList := strings.Split(flagNames, ",")
207
208 addFlag(f, value, nameList[0], help)
209 if len(nameList) > 1 {
210
211 fv := f.Lookup(nameList[0]).Value
212 for _, flagName := range nameList[1:] {
213 f.Var(fv, flagName, help)
214 }
215 }
216 return nil
217 }
218
219 value = resolve(value.Elem())
220 if value.Kind() != reflect.Struct {
221 return nil
222 }
223
224
225
226 p, _ := value.Addr().Interface().(*Profile)
227
228 for i := 0; i < value.Type().NumField(); i++ {
229 child := value.Type().Field(i)
230 v := value.Field(i)
231
232 if v.Kind() != reflect.Ptr {
233 v = v.Addr()
234 }
235
236 if fp := addFlags(f, child, v); fp != nil {
237 p = fp
238 }
239 }
240 return p
241 }
242
243 func addFlag(f *flag.FlagSet, value reflect.Value, flagName string, help string) {
244 switch v := value.Interface().(type) {
245 case flag.Value:
246 f.Var(v, flagName, help)
247 case *bool:
248 f.BoolVar(v, flagName, *v, help)
249 case *time.Duration:
250 f.DurationVar(v, flagName, *v, help)
251 case *float64:
252 f.Float64Var(v, flagName, *v, help)
253 case *int64:
254 f.Int64Var(v, flagName, *v, help)
255 case *int:
256 f.IntVar(v, flagName, *v, help)
257 case *string:
258 f.StringVar(v, flagName, *v, help)
259 case *uint:
260 f.UintVar(v, flagName, *v, help)
261 case *uint64:
262 f.Uint64Var(v, flagName, *v, help)
263 default:
264 log.Fatalf("Cannot understand flag of type %T", v)
265 }
266 }
267
268 func resolve(v reflect.Value) reflect.Value {
269 for {
270 switch v.Kind() {
271 case reflect.Interface, reflect.Ptr:
272 v = v.Elem()
273 default:
274 return v
275 }
276 }
277 }
278
View as plain text