Robin Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 1 | package main |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "fmt" |
| 6 | "io" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "regexp" |
| 10 | "sort" |
| 11 | "strings" |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 12 | "sync" |
Robin Thellend | a930f5a | 2014-10-07 09:54:25 -0700 | [diff] [blame] | 13 | "time" |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 14 | |
Jiri Simsa | bc26d69 | 2014-11-19 18:30:55 -0800 | [diff] [blame] | 15 | "veyron.io/lib/cmdline" |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 16 | "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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 20 | "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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 27 | "veyron.io/veyron/veyron2/services/watch" |
| 28 | watchtypes "veyron.io/veyron/veyron2/services/watch/types" |
| 29 | "veyron.io/veyron/veyron2/vom" |
| 30 | ) |
| 31 | |
| 32 | var ( |
| 33 | follow bool |
| 34 | verbose bool |
| 35 | numEntries int |
| 36 | startPos int64 |
| 37 | raw bool |
| 38 | showType bool |
| 39 | pprofCmd string |
| 40 | ) |
| 41 | |
| 42 | func init() { |
| 43 | vom.Register(istats.HistogramValue{}) |
| 44 | |
| 45 | // logs read flags |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 46 | 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 50 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 51 | // 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 54 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 55 | // 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 58 | |
| 59 | // pprof flags |
| 60 | cmdPProfRun.Flags.StringVar(&pprofCmd, "pprofcmd", "veyron go tool pprof", "The pprof command to use.") |
| 61 | } |
| 62 | |
| 63 | var cmdGlob = &cmdline.Command{ |
| 64 | Run: runGlob, |
| 65 | Name: "glob", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 66 | Short: "Returns all matching entries from the namespace.", |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 67 | Long: "Returns all matching entries from the namespace.", |
| 68 | ArgsName: "<pattern> ...", |
| 69 | ArgsLong: ` |
| 70 | <pattern> is a glob pattern to match. |
| 71 | `, |
| 72 | } |
| 73 | |
| 74 | func runGlob(cmd *cmdline.Command, args []string) error { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 75 | if min, got := 1, len(args); got < min { |
| 76 | return cmd.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 77 | } |
Robin Thellend | a930f5a | 2014-10-07 09:54:25 -0700 | [diff] [blame] | 78 | results := make(chan naming.MountEntry) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 79 | errors := make(chan error) |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 80 | doGlobs(rt.R().NewContext(), args, results, errors) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 81 | var lastErr error |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 82 | for { |
| 83 | select { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 84 | 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 Presotto | 59a254c | 2014-10-30 13:09:29 -0700 | [diff] [blame] | 93 | fmt.Fprintf(cmd.Stdout(), " %s (Expires %s)", s.Server, s.Expires) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 94 | } |
| 95 | fmt.Fprintln(cmd.Stdout()) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 96 | } |
| 97 | } |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 98 | } |
| 99 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 100 | // 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. |
| 103 | func 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 | |
| 115 | func 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 Thellend | a930f5a | 2014-10-07 09:54:25 -0700 | [diff] [blame] | 118 | defer cancel() |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 119 | c, err := rt.R().Namespace().Glob(ctx, pattern) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 120 | if err != nil { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 121 | errors <- fmt.Errorf("%s: %v", pattern, err) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 122 | return |
| 123 | } |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 124 | for me := range c { |
| 125 | results <- me |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 126 | } |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 127 | } |
| 128 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 129 | var cmdLogsRead = &cmdline.Command{ |
| 130 | Run: runLogsRead, |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 131 | 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 Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 140 | func runLogsRead(cmd *cmdline.Command, args []string) error { |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 141 | 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 Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 145 | lf := logreader.LogFileClient(name) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 146 | 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 Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 172 | var cmdLogsSize = &cmdline.Command{ |
| 173 | Run: runLogsSize, |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 174 | Name: "size", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 175 | Short: "Returns the size of a log file object.", |
| 176 | Long: "Returns the size of a log file object.", |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 177 | ArgsName: "<name>", |
| 178 | ArgsLong: ` |
| 179 | <name> is the name of the log file object. |
| 180 | `, |
| 181 | } |
| 182 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 183 | func runLogsSize(cmd *cmdline.Command, args []string) error { |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 184 | 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 Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 188 | lf := logreader.LogFileClient(name) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 189 | 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 Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 197 | var 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 203 | ArgsLong: ` |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 204 | <name> is the name of a stats object, or a glob pattern to match against stats |
| 205 | object names. |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 206 | `, |
| 207 | } |
| 208 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 209 | func 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 212 | } |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 213 | 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 241 | } |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 242 | } |
| 243 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 244 | func doValue(ctx context.T, name string, output chan<- string, errors chan<- error, wg *sync.WaitGroup) { |
| 245 | defer wg.Done() |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 246 | ctx, cancel := ctx.WithTimeout(time.Minute) |
| 247 | defer cancel() |
Todd Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 248 | v, err := stats.StatsClient(name).Value(ctx) |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 249 | 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 | |
| 256 | var 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 261 | ArgsName: "<pattern> ...", |
| 262 | ArgsLong: ` |
| 263 | <pattern> is a glob pattern to match. |
| 264 | `, |
| 265 | } |
| 266 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 267 | func runStatsWatch(cmd *cmdline.Command, args []string) error { |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 268 | if want, got := 1, len(args); got < want { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 269 | return cmd.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 270 | } |
| 271 | |
| 272 | results := make(chan string) |
| 273 | errors := make(chan error) |
| 274 | ctx := rt.R().NewContext() |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 275 | var wg sync.WaitGroup |
| 276 | wg.Add(len(args)) |
| 277 | for _, arg := range args { |
| 278 | go doWatch(ctx, arg, results, errors, &wg) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 279 | } |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 280 | go func() { |
| 281 | wg.Wait() |
| 282 | close(results) |
| 283 | }() |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 284 | var lastErr error |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 285 | for { |
| 286 | select { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 287 | 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 294 | fmt.Fprintln(cmd.Stdout(), r) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 295 | } |
| 296 | } |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 297 | } |
| 298 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 299 | func doWatch(ctx context.T, pattern string, results chan<- string, errors chan<- error, wg *sync.WaitGroup) { |
| 300 | defer wg.Done() |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 301 | root, globPattern := naming.SplitAddressName(pattern) |
| 302 | g, err := glob.Parse(globPattern) |
| 303 | if err != nil { |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 304 | errors <- fmt.Errorf("%s: %v", globPattern, err) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 305 | 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 Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 313 | c := watch.GlobWatcherClient(name) |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 314 | 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 Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 335 | } |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 336 | } |
| 337 | |
| 338 | func 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 | |
| 356 | func 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 | |
| 383 | var cmdPProfRun = &cmdline.Command{ |
| 384 | Run: runPProf, |
| 385 | Name: "run", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 386 | Short: "Runs the pprof tool.", |
| 387 | Long: "Runs the pprof tool.", |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 388 | 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 | |
| 393 | All the [passthru args] are passed to the pprof tool directly, e.g. |
| 394 | |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 395 | $ debug pprof run a/b/c heap --text |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 396 | $ debug pprof run a/b/c profile -gv |
| 397 | `, |
| 398 | } |
| 399 | |
| 400 | func 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 | |
| 431 | func showPProfProfiles(cmd *cmdline.Command, name string) error { |
Todd Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 432 | v, err := pprof.PProfClient(name).Profiles(rt.R().NewContext()) |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 433 | 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 | |
| 445 | func 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 | |
| 453 | var cmdPProfRunProxy = &cmdline.Command{ |
| 454 | Run: runPProfProxy, |
| 455 | Name: "proxy", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 456 | Short: "Runs an http proxy to a pprof object.", |
| 457 | Long: "Runs an http proxy to a pprof object.", |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 458 | ArgsName: "<name>", |
| 459 | ArgsLong: ` |
| 460 | <name> is the name of the pprof object. |
| 461 | `, |
| 462 | } |
| 463 | |
| 464 | func 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 | |
| 484 | var cmdRoot = cmdline.Command{ |
| 485 | Name: "debug", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 486 | Short: "Command-line tool for interacting with the debug server.", |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 487 | 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 Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 494 | Children: []*cmdline.Command{cmdLogsRead, cmdLogsSize}, |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 495 | }, |
| 496 | &cmdline.Command{ |
| 497 | Name: "stats", |
| 498 | Short: "Accesses stats", |
| 499 | Long: "Accesses stats", |
Robin Thellend | faa083a | 2014-10-22 13:41:18 -0700 | [diff] [blame] | 500 | Children: []*cmdline.Command{cmdStatsRead, cmdStatsWatch}, |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 501 | }, |
| 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 Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 511 | func root() *cmdline.Command { |
Robin Thellend | 663bf48 | 2014-10-01 10:27:10 -0700 | [diff] [blame] | 512 | return &cmdRoot |
| 513 | } |