veyron/tools/debug: Allow multiple values + cosmetic changes.
Rename the 'stats value' command to 'stats read' and allow multiple glob
patterns to be used, instead of a single object name.
Rename the 'stats watchglob' command to 'stats watch', and automatically
reconnect when a stream drops.
Change-Id: I224c46aa8ae4b7c5a0026a5413360d6b0781e5f7
diff --git a/tools/debug/impl.go b/tools/debug/impl.go
index 0f49e85..4448abe 100644
--- a/tools/debug/impl.go
+++ b/tools/debug/impl.go
@@ -9,6 +9,7 @@
"regexp"
"sort"
"strings"
+ "sync"
"time"
"veyron.io/veyron/veyron/lib/cmdline"
@@ -43,18 +44,18 @@
vom.Register(istats.HistogramValue{})
// logs read flags
- cmdRead.Flags.BoolVar(&follow, "f", false, "When true, read will wait for new log entries when it reaches the end of the file.")
- cmdRead.Flags.BoolVar(&verbose, "v", false, "When true, read will be more verbose.")
- cmdRead.Flags.IntVar(&numEntries, "n", int(logtypes.AllEntries), "The number of log entries to read.")
- cmdRead.Flags.Int64Var(&startPos, "o", 0, "The position, in bytes, from which to start reading the log file.")
+ cmdLogsRead.Flags.BoolVar(&follow, "f", false, "When true, read will wait for new log entries when it reaches the end of the file.")
+ cmdLogsRead.Flags.BoolVar(&verbose, "v", false, "When true, read will be more verbose.")
+ cmdLogsRead.Flags.IntVar(&numEntries, "n", int(logtypes.AllEntries), "The number of log entries to read.")
+ cmdLogsRead.Flags.Int64Var(&startPos, "o", 0, "The position, in bytes, from which to start reading the log file.")
- // stats value flags
- cmdValue.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
- cmdValue.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
+ // stats read flags
+ cmdStatsRead.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
+ cmdStatsRead.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
- // stats watchglob flags
- cmdWatchGlob.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
- cmdWatchGlob.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
+ // stats watch flags
+ cmdStatsWatch.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
+ cmdStatsWatch.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
// pprof flags
cmdPProfRun.Flags.StringVar(&pprofCmd, "pprofcmd", "veyron go tool pprof", "The pprof command to use.")
@@ -63,7 +64,7 @@
var cmdGlob = &cmdline.Command{
Run: runGlob,
Name: "glob",
- Short: "Returns all matching entries from the namespace",
+ Short: "Returns all matching entries from the namespace.",
Long: "Returns all matching entries from the namespace.",
ArgsName: "<pattern> ...",
ArgsLong: `
@@ -72,57 +73,62 @@
}
func runGlob(cmd *cmdline.Command, args []string) error {
- if want, got := 1, len(args); got < want {
- return cmd.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, want)
+ if min, got := 1, len(args); got < min {
+ return cmd.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min)
}
results := make(chan naming.MountEntry)
errors := make(chan error)
- ctx := rt.R().NewContext()
- for _, a := range args {
- go doGlob(ctx, a, results, errors)
- }
+ doGlobs(rt.R().NewContext(), args, results, errors)
var lastErr error
- count := 0
-L:
for {
select {
- case r := <-results:
- fmt.Fprint(cmd.Stdout(), r.Name)
- for _, s := range r.Servers {
+ case err := <-errors:
+ lastErr = err
+ fmt.Fprintln(cmd.Stderr(), "Error:", err)
+ case me, ok := <-results:
+ if !ok {
+ return lastErr
+ }
+ fmt.Fprint(cmd.Stdout(), me.Name)
+ for _, s := range me.Servers {
fmt.Fprintf(cmd.Stdout(), " %s (TTL %s)", s.Server, s.TTL)
}
fmt.Fprintln(cmd.Stdout())
- case e := <-errors:
- if e != nil {
- lastErr = e
- fmt.Fprintln(cmd.Stderr(), e)
- }
- count++
- if count == len(args) {
- break L
- }
}
}
- return lastErr
}
-func doGlob(ctx context.T, pattern string, results chan<- naming.MountEntry, errors chan<- error) {
- ns := rt.R().Namespace()
- ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
+// doGlobs calls Glob on multiple patterns in parallel and sends all the results
+// on the results channel and all the errors on the errors channel. It closes
+// the results channel when all the results have been sent.
+func doGlobs(ctx context.T, patterns []string, results chan<- naming.MountEntry, errors chan<- error) {
+ var wg sync.WaitGroup
+ wg.Add(len(patterns))
+ for _, p := range patterns {
+ go doGlob(ctx, p, results, errors, &wg)
+ }
+ go func() {
+ wg.Wait()
+ close(results)
+ }()
+}
+
+func doGlob(ctx context.T, pattern string, results chan<- naming.MountEntry, errors chan<- error, wg *sync.WaitGroup) {
+ defer wg.Done()
+ ctx, cancel := ctx.WithTimeout(time.Minute)
defer cancel()
- c, err := ns.Glob(ctx, pattern)
+ c, err := rt.R().Namespace().Glob(ctx, pattern)
if err != nil {
- errors <- err
+ errors <- fmt.Errorf("%s: %v", pattern, err)
return
}
- for res := range c {
- results <- res
+ for me := range c {
+ results <- me
}
- errors <- nil
}
-var cmdRead = &cmdline.Command{
- Run: runRead,
+var cmdLogsRead = &cmdline.Command{
+ Run: runLogsRead,
Name: "read",
Short: "Reads the content of a log file object.",
Long: "Reads the content of a log file object.",
@@ -132,7 +138,7 @@
`,
}
-func runRead(cmd *cmdline.Command, args []string) error {
+func runLogsRead(cmd *cmdline.Command, args []string) error {
if want, got := 1, len(args); want != got {
return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want %d", got, want)
}
@@ -167,18 +173,18 @@
return nil
}
-var cmdSize = &cmdline.Command{
- Run: runSize,
+var cmdLogsSize = &cmdline.Command{
+ Run: runLogsSize,
Name: "size",
- Short: "Returns the size of the a log file object",
- Long: "Returns the size of the a log file object.",
+ Short: "Returns the size of a log file object.",
+ Long: "Returns the size of a log file object.",
ArgsName: "<name>",
ArgsLong: `
<name> is the name of the log file object.
`,
}
-func runSize(cmd *cmdline.Command, args []string) error {
+func runLogsSize(cmd *cmdline.Command, args []string) error {
if want, got := 1, len(args); want != got {
return cmd.UsageErrorf("size: incorrect number of arguments, got %d, want %d", got, want)
}
@@ -195,82 +201,119 @@
return nil
}
-var cmdValue = &cmdline.Command{
- Run: runValue,
- Name: "value",
- Short: "Returns the value of the a stats object",
- Long: "Returns the value of the a stats object.",
- ArgsName: "<name>",
+var cmdStatsRead = &cmdline.Command{
+ Run: runStatsRead,
+ Name: "read",
+ Short: "Returns the value of stats objects.",
+ Long: "Returns the value of stats objects.",
+ ArgsName: "<name> ...",
ArgsLong: `
-<name> is the name of the stats object.
+<name> is the name of a stats object, or a glob pattern to match against stats
+object names.
`,
}
-func runValue(cmd *cmdline.Command, args []string) error {
- if want, got := 1, len(args); want != got {
- return cmd.UsageErrorf("value: incorrect number of arguments, got %d, want %d", got, want)
+func runStatsRead(cmd *cmdline.Command, args []string) error {
+ if min, got := 1, len(args); got < min {
+ return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want >=%d", got, min)
}
- name := args[0]
- lf, err := stats.BindStats(name)
- if err != nil {
- return err
+ ctx := rt.R().NewContext()
+ globResults := make(chan naming.MountEntry)
+ errors := make(chan error)
+ doGlobs(ctx, args, globResults, errors)
+
+ output := make(chan string)
+ go func() {
+ var wg sync.WaitGroup
+ for me := range globResults {
+ wg.Add(1)
+ go doValue(ctx, me.Name, output, errors, &wg)
+ }
+ wg.Wait()
+ close(output)
+ }()
+
+ var lastErr error
+ for {
+ select {
+ case err := <-errors:
+ lastErr = err
+ fmt.Fprintln(cmd.Stderr(), err)
+ case out, ok := <-output:
+ if !ok {
+ return lastErr
+ }
+ fmt.Fprintln(cmd.Stdout(), out)
+ }
}
- v, err := lf.Value(rt.R().NewContext())
- if err != nil {
- return err
- }
- fmt.Fprintln(cmd.Stdout(), formatValue(v))
- return nil
}
-var cmdWatchGlob = &cmdline.Command{
- Run: runWatchGlob,
- Name: "watchglob",
- Short: "Returns a stream of all matching entries and their values",
- Long: "Returns a stream of all matching entries and their values",
+func doValue(ctx context.T, name string, output chan<- string, errors chan<- error, wg *sync.WaitGroup) {
+ defer wg.Done()
+ s, err := stats.BindStats(name)
+ if err != nil {
+ errors <- fmt.Errorf("%s: %v", name, err)
+ return
+ }
+ ctx, cancel := ctx.WithTimeout(time.Minute)
+ defer cancel()
+ v, err := s.Value(ctx)
+ if err != nil {
+ errors <- fmt.Errorf("%s: %v", name, err)
+ return
+ }
+ output <- fmt.Sprintf("%s: %v", name, formatValue(v))
+}
+
+var cmdStatsWatch = &cmdline.Command{
+ Run: runStatsWatch,
+ Name: "watch",
+ Short: "Returns a stream of all matching entries and their values as they change.",
+ Long: "Returns a stream of all matching entries and their values as they change.",
ArgsName: "<pattern> ...",
ArgsLong: `
<pattern> is a glob pattern to match.
`,
}
-func runWatchGlob(cmd *cmdline.Command, args []string) error {
+func runStatsWatch(cmd *cmdline.Command, args []string) error {
if want, got := 1, len(args); got < want {
- return cmd.UsageErrorf("watchglob: incorrect number of arguments, got %d, want >=%d", got, want)
+ return cmd.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want)
}
results := make(chan string)
errors := make(chan error)
ctx := rt.R().NewContext()
- for _, a := range args {
- go doWatchGlob(ctx, a, results, errors)
+ var wg sync.WaitGroup
+ wg.Add(len(args))
+ for _, arg := range args {
+ go doWatch(ctx, arg, results, errors, &wg)
}
+ go func() {
+ wg.Wait()
+ close(results)
+ }()
var lastErr error
- count := 0
-L:
for {
select {
- case r := <-results:
+ case err := <-errors:
+ lastErr = err
+ fmt.Fprintln(cmd.Stderr(), "Error:", err)
+ case r, ok := <-results:
+ if !ok {
+ return lastErr
+ }
fmt.Fprintln(cmd.Stdout(), r)
- case e := <-errors:
- if e != nil {
- lastErr = e
- fmt.Fprintln(cmd.Stderr(), e)
- }
- count++
- if count == len(args) {
- break L
- }
}
}
- return lastErr
}
-func doWatchGlob(ctx context.T, pattern string, results chan<- string, errors chan<- error) {
+func doWatch(ctx context.T, pattern string, results chan<- string, errors chan<- error, wg *sync.WaitGroup) {
+ defer wg.Done()
root, globPattern := naming.SplitAddressName(pattern)
g, err := glob.Parse(globPattern)
if err != nil {
- errors <- fmt.Errorf("glob.Parse(%s): %v", globPattern, err)
+ errors <- fmt.Errorf("%s: %v", globPattern, err)
return
}
var prefixElems []string
@@ -281,27 +324,31 @@
}
c, err := watch.BindGlobWatcher(name)
if err != nil {
- errors <- err
+ errors <- fmt.Errorf("%s: %v", name, err)
return
}
- stream, err := c.WatchGlob(ctx, watchtypes.GlobRequest{Pattern: g.String()})
- if err != nil {
- errors <- err
- return
+ for retry := false; ; retry = true {
+ if retry {
+ time.Sleep(10 * time.Second)
+ }
+ stream, err := c.WatchGlob(ctx, watchtypes.GlobRequest{Pattern: g.String()})
+ if err != nil {
+ errors <- fmt.Errorf("%s: %v", name, err)
+ continue
+ }
+ iterator := stream.RecvStream()
+ for iterator.Advance() {
+ v := iterator.Value()
+ results <- fmt.Sprintf("%s: %s", naming.Join(name, v.Name), formatValue(v.Value))
+ }
+ if err = iterator.Err(); err != nil {
+ errors <- fmt.Errorf("%s: %v", name, err)
+ continue
+ }
+ if err = stream.Finish(); err != nil {
+ errors <- fmt.Errorf("%s: %v", name, err)
+ }
}
- iterator := stream.RecvStream()
- for iterator.Advance() {
- v := iterator.Value()
- results <- fmt.Sprintf("%s: %s", naming.Join(name, v.Name), formatValue(v.Value))
- }
- if err = iterator.Err(); err != nil {
- errors <- err
- return
- }
- if err = stream.Finish(); err != nil {
- errors <- err
- }
- errors <- nil
}
func formatValue(value interface{}) string {
@@ -352,8 +399,8 @@
var cmdPProfRun = &cmdline.Command{
Run: runPProf,
Name: "run",
- Short: "Runs the pprof tool",
- Long: "Runs the pprof tool",
+ Short: "Runs the pprof tool.",
+ Long: "Runs the pprof tool.",
ArgsName: "<name> <profile> [passthru args] ...",
ArgsLong: `
<name> is the name of the pprof object.
@@ -361,7 +408,7 @@
All the [passthru args] are passed to the pprof tool directly, e.g.
-$ debug pprof run a/b/c heap --text --lines
+$ debug pprof run a/b/c heap --text
$ debug pprof run a/b/c profile -gv
`,
}
@@ -426,8 +473,8 @@
var cmdPProfRunProxy = &cmdline.Command{
Run: runPProfProxy,
Name: "proxy",
- Short: "Runs an http proxy to a pprof object",
- Long: "Runs an http proxy to a pprof object",
+ Short: "Runs an http proxy to a pprof object.",
+ Long: "Runs an http proxy to a pprof object.",
ArgsName: "<name>",
ArgsLong: `
<name> is the name of the pprof object.
@@ -456,7 +503,7 @@
var cmdRoot = cmdline.Command{
Name: "debug",
- Short: "Command-line tool for interacting with the debug server",
+ Short: "Command-line tool for interacting with the debug server.",
Long: "Command-line tool for interacting with the debug server.",
Children: []*cmdline.Command{
cmdGlob,
@@ -464,13 +511,13 @@
Name: "logs",
Short: "Accesses log files",
Long: "Accesses log files",
- Children: []*cmdline.Command{cmdRead, cmdSize},
+ Children: []*cmdline.Command{cmdLogsRead, cmdLogsSize},
},
&cmdline.Command{
Name: "stats",
Short: "Accesses stats",
Long: "Accesses stats",
- Children: []*cmdline.Command{cmdValue, cmdWatchGlob},
+ Children: []*cmdline.Command{cmdStatsRead, cmdStatsWatch},
},
&cmdline.Command{
Name: "pprof",
diff --git a/tools/debug/test.sh b/tools/debug/test.sh
index d8d834c..888e481 100755
--- a/tools/debug/test.sh
+++ b/tools/debug/test.sh
@@ -24,6 +24,8 @@
local GOT WANT
cd "${WORKDIR}"
+ mkdir "tmp"
+ export TMPDIR="${WORKDIR}/tmp"
build
export VEYRON_CREDENTIALS=$(shell::tmp_dir)
@@ -33,7 +35,7 @@
# Test top level glob.
local -r DBGLOG="${WORKDIR}/debug.log"
- GOT=$(./debug -v=2 glob "${EP}/__debug/*" 2> "${DBGLOG}") \
+ GOT=$(./debug glob "${EP}/__debug/*" 2> "${DBGLOG}") \
|| (dumplogs "${DBGLOG}"; shell_test::fail "line ${LINENO}: failed to run debug")
WANT="${EP}/__debug/logs
${EP}/__debug/pprof
@@ -58,14 +60,19 @@
WANT="This is a log file"
shell_test::assert_eq "${GOT}" "${WANT}" "${LINENO}"
- # Test stats watchglob.
+ # Test stats read.
+ GOT=$(./debug stats read "${EP}/__debug/stats/ipc/server/*/ReadLog/latency-ms" 2> "${DBGLOG}" | wc -l) \
+ || (dumplogs "${DBGLOG}"; shell_test::fail "line ${LINENO}: failed to run debug")
+ shell_test::assert_gt "${GOT}" "0" "${LINENO}"
+
+ # Test stats watch.
local TMP=$(shell::tmp_file)
touch "${TMP}"
local -r DEBUG_PID=$(shell::run_server "${shell_test_DEFAULT_SERVER_TIMEOUT}" "${TMP}" "${DBGLOG}" \
- ./debug stats watchglob -raw "${EP}/__debug/stats/ipc/server/*/ReadLog/latency-ms")
+ ./debug stats watch -raw "${EP}/__debug/stats/ipc/server/*/ReadLog/latency-ms")
shell::timed_wait_for "${shell_test_DEFAULT_MESSAGE_TIMEOUT}" "${TMP}" "ReadLog/latency-ms"
kill "${DEBUG_PID}"
- grep "Count:1 " "${TMP}" || (dumplogs "${TMP}"; shell_test::fail "line ${LINENO}: failed to find expected output")
+ grep -q "Count:1 " "${TMP}" || (dumplogs "${TMP}"; shell_test::fail "line ${LINENO}: failed to find expected output")
# Test pprof.
if ! ./debug pprof run "${EP}/__debug/pprof" heap --text &> "${DBGLOG}"; then