blob: f3dc121903cde2c4ce9b9fadb4ad89f5f9e83834 [file] [log] [blame]
Robin Thellend18205cf2014-10-21 13:53:59 -07001package main
Robin Thellend663bf482014-10-01 10:27:10 -07002
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "os"
8 "os/exec"
9 "regexp"
10 "sort"
11 "strings"
Robin Thellendfaa083a2014-10-22 13:41:18 -070012 "sync"
Robin Thellenda930f5a2014-10-07 09:54:25 -070013 "time"
Robin Thellend663bf482014-10-01 10:27:10 -070014
Jiri Simsabc26d692014-11-19 18:30:55 -080015 "veyron.io/lib/cmdline"
Robin Thellend663bf482014-10-01 10:27:10 -070016 "veyron.io/veyron/veyron/lib/glob"
17 "veyron.io/veyron/veyron/lib/signals"
18 "veyron.io/veyron/veyron/services/mgmt/pprof/client"
19 istats "veyron.io/veyron/veyron/services/mgmt/stats"
Robin Thellend663bf482014-10-01 10:27:10 -070020 "veyron.io/veyron/veyron2/context"
21 "veyron.io/veyron/veyron2/naming"
22 "veyron.io/veyron/veyron2/rt"
23 "veyron.io/veyron/veyron2/services/mgmt/logreader"
24 logtypes "veyron.io/veyron/veyron2/services/mgmt/logreader/types"
25 "veyron.io/veyron/veyron2/services/mgmt/pprof"
26 "veyron.io/veyron/veyron2/services/mgmt/stats"
Robin Thellend663bf482014-10-01 10:27:10 -070027 "veyron.io/veyron/veyron2/services/watch"
28 watchtypes "veyron.io/veyron/veyron2/services/watch/types"
29 "veyron.io/veyron/veyron2/vom"
30)
31
32var (
33 follow bool
34 verbose bool
35 numEntries int
36 startPos int64
37 raw bool
38 showType bool
39 pprofCmd string
40)
41
42func init() {
43 vom.Register(istats.HistogramValue{})
44
45 // logs read flags
Robin Thellendfaa083a2014-10-22 13:41:18 -070046 cmdLogsRead.Flags.BoolVar(&follow, "f", false, "When true, read will wait for new log entries when it reaches the end of the file.")
47 cmdLogsRead.Flags.BoolVar(&verbose, "v", false, "When true, read will be more verbose.")
48 cmdLogsRead.Flags.IntVar(&numEntries, "n", int(logtypes.AllEntries), "The number of log entries to read.")
49 cmdLogsRead.Flags.Int64Var(&startPos, "o", 0, "The position, in bytes, from which to start reading the log file.")
Robin Thellend663bf482014-10-01 10:27:10 -070050
Robin Thellendfaa083a2014-10-22 13:41:18 -070051 // stats read flags
52 cmdStatsRead.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
53 cmdStatsRead.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
Robin Thellend663bf482014-10-01 10:27:10 -070054
Robin Thellendfaa083a2014-10-22 13:41:18 -070055 // stats watch flags
56 cmdStatsWatch.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
57 cmdStatsWatch.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
Robin Thellend663bf482014-10-01 10:27:10 -070058
59 // pprof flags
60 cmdPProfRun.Flags.StringVar(&pprofCmd, "pprofcmd", "veyron go tool pprof", "The pprof command to use.")
61}
62
63var cmdGlob = &cmdline.Command{
64 Run: runGlob,
65 Name: "glob",
Robin Thellendfaa083a2014-10-22 13:41:18 -070066 Short: "Returns all matching entries from the namespace.",
Robin Thellend663bf482014-10-01 10:27:10 -070067 Long: "Returns all matching entries from the namespace.",
68 ArgsName: "<pattern> ...",
69 ArgsLong: `
70<pattern> is a glob pattern to match.
71`,
72}
73
74func runGlob(cmd *cmdline.Command, args []string) error {
Robin Thellendfaa083a2014-10-22 13:41:18 -070075 if min, got := 1, len(args); got < min {
76 return cmd.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min)
Robin Thellend663bf482014-10-01 10:27:10 -070077 }
Robin Thellenda930f5a2014-10-07 09:54:25 -070078 results := make(chan naming.MountEntry)
Robin Thellend663bf482014-10-01 10:27:10 -070079 errors := make(chan error)
Robin Thellendfaa083a2014-10-22 13:41:18 -070080 doGlobs(rt.R().NewContext(), args, results, errors)
Robin Thellend663bf482014-10-01 10:27:10 -070081 var lastErr error
Robin Thellend663bf482014-10-01 10:27:10 -070082 for {
83 select {
Robin Thellendfaa083a2014-10-22 13:41:18 -070084 case err := <-errors:
85 lastErr = err
86 fmt.Fprintln(cmd.Stderr(), "Error:", err)
87 case me, ok := <-results:
88 if !ok {
89 return lastErr
90 }
91 fmt.Fprint(cmd.Stdout(), me.Name)
92 for _, s := range me.Servers {
David Why Use Two When One Will Do Presotto59a254c2014-10-30 13:09:29 -070093 fmt.Fprintf(cmd.Stdout(), " %s (Expires %s)", s.Server, s.Expires)
Robin Thellend663bf482014-10-01 10:27:10 -070094 }
95 fmt.Fprintln(cmd.Stdout())
Robin Thellend663bf482014-10-01 10:27:10 -070096 }
97 }
Robin Thellend663bf482014-10-01 10:27:10 -070098}
99
Robin Thellendfaa083a2014-10-22 13:41:18 -0700100// doGlobs calls Glob on multiple patterns in parallel and sends all the results
101// on the results channel and all the errors on the errors channel. It closes
102// the results channel when all the results have been sent.
103func doGlobs(ctx context.T, patterns []string, results chan<- naming.MountEntry, errors chan<- error) {
104 var wg sync.WaitGroup
105 wg.Add(len(patterns))
106 for _, p := range patterns {
107 go doGlob(ctx, p, results, errors, &wg)
108 }
109 go func() {
110 wg.Wait()
111 close(results)
112 }()
113}
114
115func doGlob(ctx context.T, pattern string, results chan<- naming.MountEntry, errors chan<- error, wg *sync.WaitGroup) {
116 defer wg.Done()
117 ctx, cancel := ctx.WithTimeout(time.Minute)
Robin Thellenda930f5a2014-10-07 09:54:25 -0700118 defer cancel()
Robin Thellendfaa083a2014-10-22 13:41:18 -0700119 c, err := rt.R().Namespace().Glob(ctx, pattern)
Robin Thellend663bf482014-10-01 10:27:10 -0700120 if err != nil {
Robin Thellendfaa083a2014-10-22 13:41:18 -0700121 errors <- fmt.Errorf("%s: %v", pattern, err)
Robin Thellend663bf482014-10-01 10:27:10 -0700122 return
123 }
Robin Thellendfaa083a2014-10-22 13:41:18 -0700124 for me := range c {
125 results <- me
Robin Thellend663bf482014-10-01 10:27:10 -0700126 }
Robin Thellend663bf482014-10-01 10:27:10 -0700127}
128
Robin Thellendfaa083a2014-10-22 13:41:18 -0700129var cmdLogsRead = &cmdline.Command{
130 Run: runLogsRead,
Robin Thellend663bf482014-10-01 10:27:10 -0700131 Name: "read",
132 Short: "Reads the content of a log file object.",
133 Long: "Reads the content of a log file object.",
134 ArgsName: "<name>",
135 ArgsLong: `
136<name> is the name of the log file object.
137`,
138}
139
Robin Thellendfaa083a2014-10-22 13:41:18 -0700140func runLogsRead(cmd *cmdline.Command, args []string) error {
Robin Thellend663bf482014-10-01 10:27:10 -0700141 if want, got := 1, len(args); want != got {
142 return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want %d", got, want)
143 }
144 name := args[0]
Todd Wang702385a2014-11-07 01:54:08 -0800145 lf := logreader.LogFileClient(name)
Robin Thellend663bf482014-10-01 10:27:10 -0700146 stream, err := lf.ReadLog(rt.R().NewContext(), startPos, int32(numEntries), follow)
147 if err != nil {
148 return err
149 }
150 iterator := stream.RecvStream()
151 for iterator.Advance() {
152 entry := iterator.Value()
153 if verbose {
154 fmt.Fprintf(cmd.Stdout(), "[%d] %s\n", entry.Position, entry.Line)
155 } else {
156 fmt.Fprintf(cmd.Stdout(), "%s\n", entry.Line)
157 }
158 }
159 if err = iterator.Err(); err != nil {
160 return err
161 }
162 offset, err := stream.Finish()
163 if err != nil {
164 return err
165 }
166 if verbose {
167 fmt.Fprintf(cmd.Stdout(), "Offset: %d\n", offset)
168 }
169 return nil
170}
171
Robin Thellendfaa083a2014-10-22 13:41:18 -0700172var cmdLogsSize = &cmdline.Command{
173 Run: runLogsSize,
Robin Thellend663bf482014-10-01 10:27:10 -0700174 Name: "size",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700175 Short: "Returns the size of a log file object.",
176 Long: "Returns the size of a log file object.",
Robin Thellend663bf482014-10-01 10:27:10 -0700177 ArgsName: "<name>",
178 ArgsLong: `
179<name> is the name of the log file object.
180`,
181}
182
Robin Thellendfaa083a2014-10-22 13:41:18 -0700183func runLogsSize(cmd *cmdline.Command, args []string) error {
Robin Thellend663bf482014-10-01 10:27:10 -0700184 if want, got := 1, len(args); want != got {
185 return cmd.UsageErrorf("size: incorrect number of arguments, got %d, want %d", got, want)
186 }
187 name := args[0]
Todd Wang702385a2014-11-07 01:54:08 -0800188 lf := logreader.LogFileClient(name)
Robin Thellend663bf482014-10-01 10:27:10 -0700189 size, err := lf.Size(rt.R().NewContext())
190 if err != nil {
191 return err
192 }
193 fmt.Fprintln(cmd.Stdout(), size)
194 return nil
195}
196
Robin Thellendfaa083a2014-10-22 13:41:18 -0700197var cmdStatsRead = &cmdline.Command{
198 Run: runStatsRead,
199 Name: "read",
200 Short: "Returns the value of stats objects.",
201 Long: "Returns the value of stats objects.",
202 ArgsName: "<name> ...",
Robin Thellend663bf482014-10-01 10:27:10 -0700203 ArgsLong: `
Robin Thellendfaa083a2014-10-22 13:41:18 -0700204<name> is the name of a stats object, or a glob pattern to match against stats
205object names.
Robin Thellend663bf482014-10-01 10:27:10 -0700206`,
207}
208
Robin Thellendfaa083a2014-10-22 13:41:18 -0700209func runStatsRead(cmd *cmdline.Command, args []string) error {
210 if min, got := 1, len(args); got < min {
211 return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want >=%d", got, min)
Robin Thellend663bf482014-10-01 10:27:10 -0700212 }
Robin Thellendfaa083a2014-10-22 13:41:18 -0700213 ctx := rt.R().NewContext()
214 globResults := make(chan naming.MountEntry)
215 errors := make(chan error)
216 doGlobs(ctx, args, globResults, errors)
217
218 output := make(chan string)
219 go func() {
220 var wg sync.WaitGroup
221 for me := range globResults {
222 wg.Add(1)
223 go doValue(ctx, me.Name, output, errors, &wg)
224 }
225 wg.Wait()
226 close(output)
227 }()
228
229 var lastErr error
230 for {
231 select {
232 case err := <-errors:
233 lastErr = err
234 fmt.Fprintln(cmd.Stderr(), err)
235 case out, ok := <-output:
236 if !ok {
237 return lastErr
238 }
239 fmt.Fprintln(cmd.Stdout(), out)
240 }
Robin Thellend663bf482014-10-01 10:27:10 -0700241 }
Robin Thellend663bf482014-10-01 10:27:10 -0700242}
243
Robin Thellendfaa083a2014-10-22 13:41:18 -0700244func doValue(ctx context.T, name string, output chan<- string, errors chan<- error, wg *sync.WaitGroup) {
245 defer wg.Done()
Robin Thellendfaa083a2014-10-22 13:41:18 -0700246 ctx, cancel := ctx.WithTimeout(time.Minute)
247 defer cancel()
Todd Wang702385a2014-11-07 01:54:08 -0800248 v, err := stats.StatsClient(name).Value(ctx)
Robin Thellendfaa083a2014-10-22 13:41:18 -0700249 if err != nil {
250 errors <- fmt.Errorf("%s: %v", name, err)
251 return
252 }
253 output <- fmt.Sprintf("%s: %v", name, formatValue(v))
254}
255
256var cmdStatsWatch = &cmdline.Command{
257 Run: runStatsWatch,
258 Name: "watch",
259 Short: "Returns a stream of all matching entries and their values as they change.",
260 Long: "Returns a stream of all matching entries and their values as they change.",
Robin Thellend663bf482014-10-01 10:27:10 -0700261 ArgsName: "<pattern> ...",
262 ArgsLong: `
263<pattern> is a glob pattern to match.
264`,
265}
266
Robin Thellendfaa083a2014-10-22 13:41:18 -0700267func runStatsWatch(cmd *cmdline.Command, args []string) error {
Robin Thellend663bf482014-10-01 10:27:10 -0700268 if want, got := 1, len(args); got < want {
Robin Thellendfaa083a2014-10-22 13:41:18 -0700269 return cmd.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want)
Robin Thellend663bf482014-10-01 10:27:10 -0700270 }
271
272 results := make(chan string)
273 errors := make(chan error)
274 ctx := rt.R().NewContext()
Robin Thellendfaa083a2014-10-22 13:41:18 -0700275 var wg sync.WaitGroup
276 wg.Add(len(args))
277 for _, arg := range args {
278 go doWatch(ctx, arg, results, errors, &wg)
Robin Thellend663bf482014-10-01 10:27:10 -0700279 }
Robin Thellendfaa083a2014-10-22 13:41:18 -0700280 go func() {
281 wg.Wait()
282 close(results)
283 }()
Robin Thellend663bf482014-10-01 10:27:10 -0700284 var lastErr error
Robin Thellend663bf482014-10-01 10:27:10 -0700285 for {
286 select {
Robin Thellendfaa083a2014-10-22 13:41:18 -0700287 case err := <-errors:
288 lastErr = err
289 fmt.Fprintln(cmd.Stderr(), "Error:", err)
290 case r, ok := <-results:
291 if !ok {
292 return lastErr
293 }
Robin Thellend663bf482014-10-01 10:27:10 -0700294 fmt.Fprintln(cmd.Stdout(), r)
Robin Thellend663bf482014-10-01 10:27:10 -0700295 }
296 }
Robin Thellend663bf482014-10-01 10:27:10 -0700297}
298
Robin Thellendfaa083a2014-10-22 13:41:18 -0700299func doWatch(ctx context.T, pattern string, results chan<- string, errors chan<- error, wg *sync.WaitGroup) {
300 defer wg.Done()
Robin Thellend663bf482014-10-01 10:27:10 -0700301 root, globPattern := naming.SplitAddressName(pattern)
302 g, err := glob.Parse(globPattern)
303 if err != nil {
Robin Thellendfaa083a2014-10-22 13:41:18 -0700304 errors <- fmt.Errorf("%s: %v", globPattern, err)
Robin Thellend663bf482014-10-01 10:27:10 -0700305 return
306 }
307 var prefixElems []string
308 prefixElems, g = g.SplitFixedPrefix()
309 name := naming.Join(prefixElems...)
310 if len(root) != 0 {
311 name = naming.JoinAddressName(root, name)
312 }
Todd Wang702385a2014-11-07 01:54:08 -0800313 c := watch.GlobWatcherClient(name)
Robin Thellendfaa083a2014-10-22 13:41:18 -0700314 for retry := false; ; retry = true {
315 if retry {
316 time.Sleep(10 * time.Second)
317 }
318 stream, err := c.WatchGlob(ctx, watchtypes.GlobRequest{Pattern: g.String()})
319 if err != nil {
320 errors <- fmt.Errorf("%s: %v", name, err)
321 continue
322 }
323 iterator := stream.RecvStream()
324 for iterator.Advance() {
325 v := iterator.Value()
326 results <- fmt.Sprintf("%s: %s", naming.Join(name, v.Name), formatValue(v.Value))
327 }
328 if err = iterator.Err(); err != nil {
329 errors <- fmt.Errorf("%s: %v", name, err)
330 continue
331 }
332 if err = stream.Finish(); err != nil {
333 errors <- fmt.Errorf("%s: %v", name, err)
334 }
Robin Thellend663bf482014-10-01 10:27:10 -0700335 }
Robin Thellend663bf482014-10-01 10:27:10 -0700336}
337
338func formatValue(value interface{}) string {
339 var buf bytes.Buffer
340 if showType {
341 fmt.Fprintf(&buf, "%T: ", value)
342 }
343 if raw {
344 fmt.Fprintf(&buf, "%+v", value)
345 return buf.String()
346 }
347 switch v := value.(type) {
348 case istats.HistogramValue:
349 writeASCIIHistogram(&buf, v)
350 default:
351 fmt.Fprintf(&buf, "%v", v)
352 }
353 return buf.String()
354}
355
356func writeASCIIHistogram(w io.Writer, h istats.HistogramValue) {
357 scale := h.Count
358 if scale > 100 {
359 scale = 100
360 }
361 var avg float64
362 if h.Count > 0 {
363 avg = float64(h.Sum) / float64(h.Count)
364 }
365 fmt.Fprintf(w, "Count: %d Sum: %d Avg: %f\n", h.Count, h.Sum, avg)
366 for i, b := range h.Buckets {
367 var r string
368 if i+1 < len(h.Buckets) {
369 r = fmt.Sprintf("[%d,%d[", b.LowBound, h.Buckets[i+1].LowBound)
370 } else {
371 r = fmt.Sprintf("[%d,Inf", b.LowBound)
372 }
373 fmt.Fprintf(w, "%-18s: ", r)
374 if b.Count > 0 && h.Count > 0 {
375 fmt.Fprintf(w, "%s %d (%.1f%%)", strings.Repeat("*", int(b.Count*scale/h.Count)), b.Count, 100*float64(b.Count)/float64(h.Count))
376 }
377 if i+1 < len(h.Buckets) {
378 fmt.Fprintln(w)
379 }
380 }
381}
382
383var cmdPProfRun = &cmdline.Command{
384 Run: runPProf,
385 Name: "run",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700386 Short: "Runs the pprof tool.",
387 Long: "Runs the pprof tool.",
Robin Thellend663bf482014-10-01 10:27:10 -0700388 ArgsName: "<name> <profile> [passthru args] ...",
389 ArgsLong: `
390<name> is the name of the pprof object.
391<profile> the name of the profile to use.
392
393All the [passthru args] are passed to the pprof tool directly, e.g.
394
Robin Thellendfaa083a2014-10-22 13:41:18 -0700395$ debug pprof run a/b/c heap --text
Robin Thellend663bf482014-10-01 10:27:10 -0700396$ debug pprof run a/b/c profile -gv
397`,
398}
399
400func runPProf(cmd *cmdline.Command, args []string) error {
401 if min, got := 1, len(args); got < min {
402 return cmd.UsageErrorf("pprof: incorrect number of arguments, got %d, want >=%d", got, min)
403 }
404 name := args[0]
405 if len(args) == 1 {
406 return showPProfProfiles(cmd, name)
407 }
408 profile := args[1]
409 listener, err := client.StartProxy(rt.R(), name)
410 if err != nil {
411 return err
412 }
413 defer listener.Close()
414
415 // Construct the pprof command line:
416 // <pprofCmd> http://<proxyaddr>/pprof/<profile> [pprof flags]
417 pargs := []string{pprofCmd} // pprofCmd is purposely not escaped.
418 for i := 2; i < len(args); i++ {
419 pargs = append(pargs, shellEscape(args[i]))
420 }
421 pargs = append(pargs, shellEscape(fmt.Sprintf("http://%s/pprof/%s", listener.Addr(), profile)))
422 pcmd := strings.Join(pargs, " ")
423 fmt.Fprintf(cmd.Stdout(), "Running: %s\n", pcmd)
424 c := exec.Command("sh", "-c", pcmd)
425 c.Stdin = os.Stdin
426 c.Stdout = cmd.Stdout()
427 c.Stderr = cmd.Stderr()
428 return c.Run()
429}
430
431func showPProfProfiles(cmd *cmdline.Command, name string) error {
Todd Wang702385a2014-11-07 01:54:08 -0800432 v, err := pprof.PProfClient(name).Profiles(rt.R().NewContext())
Robin Thellend663bf482014-10-01 10:27:10 -0700433 if err != nil {
434 return err
435 }
436 v = append(v, "profile")
437 sort.Strings(v)
438 fmt.Fprintln(cmd.Stdout(), "Available profiles:")
439 for _, p := range v {
440 fmt.Fprintf(cmd.Stdout(), " %s\n", p)
441 }
442 return nil
443}
444
445func shellEscape(s string) string {
446 if !strings.Contains(s, "'") {
447 return "'" + s + "'"
448 }
449 re := regexp.MustCompile("([\"$`\\\\])")
450 return `"` + re.ReplaceAllString(s, "\\$1") + `"`
451}
452
453var cmdPProfRunProxy = &cmdline.Command{
454 Run: runPProfProxy,
455 Name: "proxy",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700456 Short: "Runs an http proxy to a pprof object.",
457 Long: "Runs an http proxy to a pprof object.",
Robin Thellend663bf482014-10-01 10:27:10 -0700458 ArgsName: "<name>",
459 ArgsLong: `
460<name> is the name of the pprof object.
461`,
462}
463
464func runPProfProxy(cmd *cmdline.Command, args []string) error {
465 if want, got := 1, len(args); got != want {
466 return cmd.UsageErrorf("proxy: incorrect number of arguments, got %d, want %d", got, want)
467 }
468 name := args[0]
469 listener, err := client.StartProxy(rt.R(), name)
470 if err != nil {
471 return err
472 }
473 defer listener.Close()
474
475 fmt.Fprintln(cmd.Stdout())
476 fmt.Fprintf(cmd.Stdout(), "The pprof proxy is listening at http://%s/pprof\n", listener.Addr())
477 fmt.Fprintln(cmd.Stdout())
478 fmt.Fprintln(cmd.Stdout(), "Hit CTRL-C to exit")
479
480 <-signals.ShutdownOnSignals()
481 return nil
482}
483
484var cmdRoot = cmdline.Command{
485 Name: "debug",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700486 Short: "Command-line tool for interacting with the debug server.",
Robin Thellend663bf482014-10-01 10:27:10 -0700487 Long: "Command-line tool for interacting with the debug server.",
488 Children: []*cmdline.Command{
489 cmdGlob,
490 &cmdline.Command{
491 Name: "logs",
492 Short: "Accesses log files",
493 Long: "Accesses log files",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700494 Children: []*cmdline.Command{cmdLogsRead, cmdLogsSize},
Robin Thellend663bf482014-10-01 10:27:10 -0700495 },
496 &cmdline.Command{
497 Name: "stats",
498 Short: "Accesses stats",
499 Long: "Accesses stats",
Robin Thellendfaa083a2014-10-22 13:41:18 -0700500 Children: []*cmdline.Command{cmdStatsRead, cmdStatsWatch},
Robin Thellend663bf482014-10-01 10:27:10 -0700501 },
502 &cmdline.Command{
503 Name: "pprof",
504 Short: "Accesses profiling data",
505 Long: "Accesses profiling data",
506 Children: []*cmdline.Command{cmdPProfRun, cmdPProfRunProxy},
507 },
508 },
509}
510
Robin Thellend18205cf2014-10-21 13:53:59 -0700511func root() *cmdline.Command {
Robin Thellend663bf482014-10-01 10:27:10 -0700512 return &cmdRoot
513}