Add ftrace support to DebugService

This allows us to do ad-hoc kernel-level tracing on a running Metropolis node.
Useful for tracking down complex bugs.

Example: `bazel run //metropolis/cli/dbg -- trace -function_graph_filter blkdev_* function_graph`

Test Plan: Debug utility, manually tested

X-Origin-Diff: phab/D748
GitOrigin-RevId: 924eb795250412a73eb30c0eef4a8c1cc726e5fd
diff --git a/metropolis/cli/dbg/main.go b/metropolis/cli/dbg/main.go
index eb9070f..75685e9 100644
--- a/metropolis/cli/dbg/main.go
+++ b/metropolis/cli/dbg/main.go
@@ -24,6 +24,7 @@
 	"io/ioutil"
 	"math/rand"
 	"os"
+	"strings"
 	"time"
 
 	"github.com/spf13/pflag"
@@ -69,6 +70,14 @@
 		fmt.Fprintf(os.Stderr, "Example:\n  %s %s IPAssigned\n", os.Args[0], os.Args[1])
 	}
 
+	traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
+	traceCmd.Usage = func() {
+		fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
+		flag.PrintDefaults()
+	}
+	functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
+	functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
+
 	switch os.Args[1] {
 	case "logs":
 		logsCmd.Parse(os.Args[2:])
@@ -157,5 +166,37 @@
 		if err := command.Execute(); err != nil {
 			os.Exit(1)
 		}
+	case "trace":
+		traceCmd.Parse(os.Args[2:])
+		tracer := traceCmd.Arg(0)
+		var fgf []string
+		var ff []string
+		if len(*functionGraphFilter) > 0 {
+			fgf = strings.Split(*functionGraphFilter, ",")
+		}
+		if len(*functionFilter) > 0 {
+			ff = strings.Split(*functionFilter, ",")
+		}
+		req := apb.TraceRequest{
+			GraphFunctionFilter: fgf,
+			FunctionFilter:      ff,
+			Tracer:              tracer,
+		}
+		traceEvents, err := debugClient.Trace(ctx, &req)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
+			os.Exit(1)
+		}
+		for {
+			traceEvent, err := traceEvents.Recv()
+			if err != nil {
+				if err == io.EOF {
+					break
+				}
+				fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
+				os.Exit(1)
+			}
+			fmt.Println(traceEvent.RawLine)
+		}
 	}
 }