Run 'pstree' if ninja_log hasn't updated recently

This doesn't catch all the possible causes of timeouts,
(like if Ninja is only partially stuck or if Kati is stuck)
but it should clarify some causes of stuckness

Bug: 62065855
Test: m -j showcommands NINJA_HEARTBEAT_INTERVAL=500ms
Change-Id: I73a792ae91873b19d7b336166a2d47f37c549906
diff --git a/ui/build/exec.go b/ui/build/exec.go
index c8c5c9a..79310dc 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -30,6 +30,9 @@
 	ctx    Context
 	config Config
 	name   string
+
+	// doneChannel closes to signal the command's termination
+	doneChannel chan bool
 }
 
 func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd {
@@ -38,9 +41,10 @@
 		Environment: config.Environment().Copy(),
 		Sandbox:     noSandbox,
 
-		ctx:    ctx,
-		config: config,
-		name:   name,
+		ctx:         ctx,
+		config:      config,
+		name:        name,
+		doneChannel: make(chan bool),
 	}
 
 	return ret
@@ -57,6 +61,10 @@
 	c.ctx.Verboseln(c.Path, c.Args)
 }
 
+func (c *Cmd) teardown() {
+	close(c.doneChannel)
+}
+
 func (c *Cmd) Start() error {
 	c.prepare()
 	return c.Cmd.Start()
@@ -64,17 +72,23 @@
 
 func (c *Cmd) Run() error {
 	c.prepare()
-	return c.Cmd.Run()
+	defer c.teardown()
+	err := c.Cmd.Run()
+	return err
 }
 
 func (c *Cmd) Output() ([]byte, error) {
 	c.prepare()
-	return c.Cmd.Output()
+	defer c.teardown()
+	bytes, err := c.Cmd.Output()
+	return bytes, err
 }
 
 func (c *Cmd) CombinedOutput() ([]byte, error) {
 	c.prepare()
-	return c.Cmd.CombinedOutput()
+	defer c.teardown()
+	bytes, err := c.Cmd.CombinedOutput()
+	return bytes, err
 }
 
 // StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal
@@ -119,3 +133,13 @@
 	c.reportError(err)
 	return ret
 }
+
+// Done() tells whether this command has finished executing
+func (c *Cmd) Done() bool {
+	select {
+	case <-c.doneChannel:
+		return true
+	default:
+		return false
+	}
+}