Add build tracing

This creates a rotating build.trace.gz in the out directory that can be
loaded with chrome://tracing. It'll include start and end timings for
make/soong/kati/ninja, and it will import and time-correct the ninja log
files.

Test: m -j; load out/build.trace.gz in chrome://tracing
Test: multiproduct_kati -keep; load out/multiproduct*/build.trace.gz
Change-Id: Ic060fa9515eb88d95dbe16712479dae9dffcf626
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index d6da950..e4044e1 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -17,6 +17,7 @@
     pkgPath: "android/soong/ui/build",
     deps: [
         "soong-ui-logger",
+        "soong-ui-tracer",
     ],
     srcs: [
         "build.go",
diff --git a/ui/build/context.go b/ui/build/context.go
index 59474f5..8144e58 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -18,8 +18,10 @@
 	"context"
 	"io"
 	"os"
+	"time"
 
 	"android/soong/ui/logger"
+	"android/soong/ui/tracer"
 )
 
 type StdioInterface interface {
@@ -55,10 +57,41 @@
 // Context combines a context.Context, logger.Logger, and StdIO redirection.
 // These all are agnostic of the current build, and may be used for multiple
 // builds, while the Config objects contain per-build information.
-type Context *ContextImpl
+type Context struct{ *ContextImpl }
 type ContextImpl struct {
 	context.Context
 	logger.Logger
 
 	StdioInterface
+
+	Thread tracer.Thread
+	Tracer tracer.Tracer
+}
+
+// BeginTrace starts a new Duration Event.
+func (c ContextImpl) BeginTrace(name string) {
+	if c.Tracer != nil {
+		c.Tracer.Begin(name, c.Thread)
+	}
+}
+
+// EndTrace finishes the last Duration Event.
+func (c ContextImpl) EndTrace() {
+	if c.Tracer != nil {
+		c.Tracer.End(c.Thread)
+	}
+}
+
+// CompleteTrace writes a trace with a beginning and end times.
+func (c ContextImpl) CompleteTrace(name string, begin, end uint64) {
+	if c.Tracer != nil {
+		c.Tracer.Complete(name, c.Thread, begin, end)
+	}
+}
+
+// ImportNinjaLog imports a .ninja_log file into the tracer.
+func (c ContextImpl) ImportNinjaLog(filename string, startOffset time.Time) {
+	if c.Tracer != nil {
+		c.Tracer.ImportNinjaLog(c.Thread, filename, startOffset)
+	}
 }
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 6997fbe..423bcbc 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -57,6 +57,9 @@
 }
 
 func runKati(ctx Context, config Config) {
+	ctx.BeginTrace("kati")
+	defer ctx.EndTrace()
+
 	genKatiSuffix(ctx, config)
 
 	executable := "prebuilts/build-tools/" + config.HostPrebuiltTag() + "/bin/ckati"
diff --git a/ui/build/make.go b/ui/build/make.go
index 5880509..89e03f7 100644
--- a/ui/build/make.go
+++ b/ui/build/make.go
@@ -35,6 +35,9 @@
 // vars is the list of variables to read. The values will be put in the
 // returned map.
 func DumpMakeVars(ctx Context, config Config, goals, extra_targets, vars []string) (map[string]string, error) {
+	ctx.BeginTrace("dumpvars")
+	defer ctx.EndTrace()
+
 	cmd := exec.CommandContext(ctx.Context,
 		"make",
 		"--no-print-directory",
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 13e1834..33f9a07 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -16,11 +16,16 @@
 
 import (
 	"os/exec"
+	"path/filepath"
 	"strconv"
 	"strings"
+	"time"
 )
 
 func runNinja(ctx Context, config Config) {
+	ctx.BeginTrace("ninja")
+	defer ctx.EndTrace()
+
 	executable := "prebuilts/build-tools/" + config.HostPrebuiltTag() + "/bin/ninja"
 	args := []string{
 		"-d", "keepdepfile",
@@ -68,6 +73,8 @@
 	cmd.Stdout = ctx.Stdout()
 	cmd.Stderr = ctx.Stderr()
 	ctx.Verboseln(cmd.Path, cmd.Args)
+	startTime := time.Now()
+	defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), startTime)
 	if err := cmd.Run(); err != nil {
 		if e, ok := err.(*exec.ExitError); ok {
 			ctx.Fatalln("ninja failed with:", e.ProcessState.String())
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 88e4161..d017e70 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -20,6 +20,9 @@
 )
 
 func runSoongBootstrap(ctx Context, config Config) {
+	ctx.BeginTrace("bootstrap soong")
+	defer ctx.EndTrace()
+
 	cmd := exec.CommandContext(ctx.Context, "./bootstrap.bash")
 	env := config.Environment().Copy()
 	env.Set("BUILDDIR", config.SoongOutDir())
@@ -37,6 +40,9 @@
 }
 
 func runSoong(ctx Context, config Config) {
+	ctx.BeginTrace("soong")
+	defer ctx.EndTrace()
+
 	cmd := exec.CommandContext(ctx.Context, filepath.Join(config.SoongOutDir(), "soong"), "-w", "dupbuild=err")
 	if config.IsVerbose() {
 		cmd.Args = append(cmd.Args, "-v")