Add SoongExecutionMetrics to ninja run

This adds metrics for partial compilation to Soong.

- Soong_ui names the directory SOONG_METRICS_AGGREGATION_DIR, which it
  empties before each ninja run.
- Find_input_delta writes metrics there
- At the end of the build, Soong_ui aggregates the metrics that were
  written to generate the aggregated metrics.

Bug: b/376287012
Test: Manual, TH
Change-Id: I123df654f5b963fcd213b5c4d815173051f5d72e
diff --git a/cmd/find_input_delta/find_input_delta/main.go b/cmd/find_input_delta/find_input_delta/main.go
index a864584..036b239 100644
--- a/cmd/find_input_delta/find_input_delta/main.go
+++ b/cmd/find_input_delta/find_input_delta/main.go
@@ -80,15 +80,16 @@
 		panic(err)
 	}
 
-	file_list := *fid_lib.CompareInternalState(prior_state, new_state, target)
+	file_list := fid_lib.CompareInternalState(prior_state, new_state, target)
 
 	if err = file_list.Format(os.Stdout, template); err != nil {
 		panic(err)
 	}
 
-	metrics_file := os.Getenv("SOONG_METRICS_AGGREGATION_FILE")
-	if metrics_file != "" {
-		if err = file_list.SendMetrics(metrics_file); err != nil {
+	metrics_dir := os.Getenv("SOONG_METRICS_AGGREGATION_DIR")
+	out_dir := os.Getenv("OUT_DIR")
+	if metrics_dir != "" {
+		if err = file_list.WriteMetrics(metrics_dir, out_dir); err != nil {
 			panic(err)
 		}
 	}
diff --git a/cmd/find_input_delta/find_input_delta_lib/file_list.go b/cmd/find_input_delta/find_input_delta_lib/file_list.go
index 01242a0..f1d588b 100644
--- a/cmd/find_input_delta/find_input_delta_lib/file_list.go
+++ b/cmd/find_input_delta/find_input_delta_lib/file_list.go
@@ -16,10 +16,11 @@
 
 import (
 	"fmt"
+	"hash/fnv"
 	"io"
 	"os"
 	"path/filepath"
-	"slices"
+	"strings"
 	"text/template"
 
 	fid_exp "android/soong/cmd/find_input_delta/find_input_delta_proto"
@@ -106,95 +107,75 @@
 	fl.ExtCountMap[ext].Changes += 1
 }
 
-func (fl FileList) ToProto() (*fid_exp.FileList, error) {
-	var count uint32
-	return fl.toProto(&count)
-}
-
-func (fl FileList) toProto(count *uint32) (*fid_exp.FileList, error) {
-	ret := &fid_exp.FileList{
-		Name: proto.String(fl.Name),
+// Write a SoongExecutionMetrics FileList proto to `dir`.
+//
+// Path
+// Prune any paths that
+// begin with `pruneDir` (usually ${OUT_DIR}).  The file is only written if any
+// non-pruned changes are present.
+func (fl *FileList) WriteMetrics(dir, pruneDir string) (err error) {
+	if dir == "" {
+		return fmt.Errorf("No directory given")
 	}
+	var needed bool
+
+	if !strings.HasSuffix(pruneDir, "/") {
+		pruneDir += "/"
+	}
+
+	// Hash the dir and `fl.Name` to simplify scanning the metrics
+	// aggregation directory.
+	h := fnv.New128()
+	h.Write([]byte(dir + " " + fl.Name + ".FileList"))
+	path := fmt.Sprintf("%x.pb", h.Sum([]byte{}))
+	path = filepath.Join(dir, path[0:2], path[2:])
+
+	var msg = &fid_exp.FileList{Name: proto.String(fl.Name)}
 	for _, a := range fl.Additions {
-		if *count >= MaxFilesRecorded {
-			break
+		if strings.HasPrefix(a, pruneDir) {
+			continue
 		}
-		ret.Additions = append(ret.Additions, a)
-		*count += 1
+		msg.Additions = append(msg.Additions, a)
+		needed = true
 	}
 	for _, ch := range fl.Changes {
-		if *count >= MaxFilesRecorded {
-			break
-		} else {
-			// Pre-increment to limit what the call adds.
-			*count += 1
-			change, err := ch.toProto(count)
-			if err != nil {
-				return nil, err
-			}
-			ret.Changes = append(ret.Changes, change)
+		if strings.HasPrefix(ch.Name, pruneDir) {
+			continue
 		}
+		msg.Changes = append(msg.Changes, ch.Name)
+		needed = true
 	}
 	for _, d := range fl.Deletions {
-		if *count >= MaxFilesRecorded {
-			break
+		if strings.HasPrefix(d, pruneDir) {
+			continue
 		}
-		ret.Deletions = append(ret.Deletions, d)
+		msg.Deletions = append(msg.Deletions, d)
+		needed = true
 	}
-	ret.TotalDelta = proto.Uint32(*count)
-	exts := []string{}
-	for k := range fl.ExtCountMap {
-		exts = append(exts, k)
+	if !needed {
+		return nil
 	}
-	slices.Sort(exts)
-	for _, k := range exts {
-		v := fl.ExtCountMap[k]
-		ret.Counts = append(ret.Counts, &fid_exp.FileCount{
-			Extension:     proto.String(k),
-			Additions:     proto.Uint32(v.Additions),
-			Deletions:     proto.Uint32(v.Deletions),
-			Modifications: proto.Uint32(v.Changes),
-		})
-	}
-	return ret, nil
-}
-
-func (fl FileList) SendMetrics(path string) error {
-	if path == "" {
-		return fmt.Errorf("No path given")
-	}
-	message, err := fl.ToProto()
-	if err != nil {
-		return err
-	}
-
-	// Marshal the message wrapped in SoongCombinedMetrics.
 	data := protowire.AppendVarint(
 		[]byte{},
 		protowire.EncodeTag(
 			protowire.Number(fid_exp.FieldNumbers_FIELD_NUMBERS_FILE_LIST),
 			protowire.BytesType))
-	size := uint64(proto.Size(message))
+	size := uint64(proto.Size(msg))
 	data = protowire.AppendVarint(data, size)
-	data, err = proto.MarshalOptions{UseCachedSize: true}.MarshalAppend(data, message)
+	data, err = proto.MarshalOptions{UseCachedSize: true}.MarshalAppend(data, msg)
 	if err != nil {
 		return err
 	}
 
-	out, err := os.Create(path)
+	err = os.MkdirAll(filepath.Dir(path), 0777)
 	if err != nil {
 		return err
 	}
-	defer func() {
-		if err := out.Close(); err != nil {
-			fmt.Fprintf(os.Stderr, "Failed to close %s: %v\n", path, err)
-		}
-	}()
-	_, err = out.Write(data)
-	return err
+
+	return os.WriteFile(path, data, 0644)
 }
 
-func (fl FileList) Format(wr io.Writer, format string) error {
+func (fl *FileList) Format(wr io.Writer, format string) error {
 	tmpl, err := template.New("filelist").Parse(format)
 	if err != nil {
 		return err
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state.go b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
index 2b8c395..bf4f866 100644
--- a/cmd/find_input_delta/find_input_delta_lib/internal_state.go
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
@@ -51,6 +51,9 @@
 	for _, input := range inputs {
 		stat, err := fs.Stat(fsys, input)
 		if err != nil {
+			if errors.Is(err, fs.ErrNotExist) {
+				continue
+			}
 			return ret, err
 		}
 		pci := &fid_proto.PartialCompileInput{
diff --git a/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
index 745de2d..b6adc45 100644
--- a/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
@@ -96,20 +96,14 @@
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The name of the file.
-	// In the outermost message, this is the name of the Ninja target.
-	// When used in `changes`, this is the name of the changed file.
+	// The name of the output file (Ninja target).
 	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
 	// The added files.
 	Additions []string `protobuf:"bytes,2,rep,name=additions" json:"additions,omitempty"`
 	// The changed files.
-	Changes []*FileList `protobuf:"bytes,3,rep,name=changes" json:"changes,omitempty"`
+	Changes []string `protobuf:"bytes,3,rep,name=changes" json:"changes,omitempty"`
 	// The deleted files.
 	Deletions []string `protobuf:"bytes,4,rep,name=deletions" json:"deletions,omitempty"`
-	// Count of files added/changed/deleted.
-	TotalDelta *uint32 `protobuf:"varint,5,opt,name=total_delta,json=totalDelta" json:"total_delta,omitempty"`
-	// Counts by extension.
-	Counts []*FileCount `protobuf:"bytes,6,rep,name=counts" json:"counts,omitempty"`
 }
 
 func (x *FileList) Reset() {
@@ -158,7 +152,7 @@
 	return nil
 }
 
-func (x *FileList) GetChanges() []*FileList {
+func (x *FileList) GetChanges() []string {
 	if x != nil {
 		return x.Changes
 	}
@@ -172,135 +166,28 @@
 	return nil
 }
 
-func (x *FileList) GetTotalDelta() uint32 {
-	if x != nil && x.TotalDelta != nil {
-		return *x.TotalDelta
-	}
-	return 0
-}
-
-func (x *FileList) GetCounts() []*FileCount {
-	if x != nil {
-		return x.Counts
-	}
-	return nil
-}
-
-type FileCount struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The file extension
-	Extension *string `protobuf:"bytes,1,opt,name=extension" json:"extension,omitempty"`
-	// Number of added files with this extension.
-	Additions *uint32 `protobuf:"varint,2,opt,name=additions" json:"additions,omitempty"`
-	// Number of modified files with this extension.
-	Modifications *uint32 `protobuf:"varint,3,opt,name=modifications" json:"modifications,omitempty"`
-	// Number of deleted files with this extension.
-	Deletions *uint32 `protobuf:"varint,4,opt,name=deletions" json:"deletions,omitempty"`
-}
-
-func (x *FileCount) Reset() {
-	*x = FileCount{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_file_list_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *FileCount) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*FileCount) ProtoMessage() {}
-
-func (x *FileCount) ProtoReflect() protoreflect.Message {
-	mi := &file_file_list_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use FileCount.ProtoReflect.Descriptor instead.
-func (*FileCount) Descriptor() ([]byte, []int) {
-	return file_file_list_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *FileCount) GetExtension() string {
-	if x != nil && x.Extension != nil {
-		return *x.Extension
-	}
-	return ""
-}
-
-func (x *FileCount) GetAdditions() uint32 {
-	if x != nil && x.Additions != nil {
-		return *x.Additions
-	}
-	return 0
-}
-
-func (x *FileCount) GetModifications() uint32 {
-	if x != nil && x.Modifications != nil {
-		return *x.Modifications
-	}
-	return 0
-}
-
-func (x *FileCount) GetDeletions() uint32 {
-	if x != nil && x.Deletions != nil {
-		return *x.Deletions
-	}
-	return 0
-}
-
 var File_file_list_proto protoreflect.FileDescriptor
 
 var file_file_list_proto_rawDesc = []byte{
 	0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x12, 0x1e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f,
 	0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x22, 0x82, 0x02, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12,
-	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
-	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
-	0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x12, 0x42, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64,
-	0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, 0x63, 0x68, 0x61,
-	0x6e, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f,
-	0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74,
-	0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x65,
-	0x6c, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69,
-	0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06,
-	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x43,
-	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
-	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
-	0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69,
-	0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74,
-	0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x4a, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x75, 0x6d,
-	0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55,
-	0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
-	0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55, 0x4d,
-	0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01,
-	0x42, 0x3b, 0x5a, 0x39, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
-	0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74,
-	0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75,
-	0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x6f, 0x22, 0x74, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
+	0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c,
+	0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65,
+	0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x4a, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64,
+	0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x49, 0x45, 0x4c, 0x44,
+	0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+	0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f,
+	0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x4c, 0x49, 0x53,
+	0x54, 0x10, 0x01, 0x42, 0x3b, 0x5a, 0x39, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e,
+	0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69,
+	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
@@ -316,20 +203,17 @@
 }
 
 var file_file_list_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
 var file_file_list_proto_goTypes = []interface{}{
 	(FieldNumbers)(0), // 0: android.find_input_delta_proto.FieldNumbers
 	(*FileList)(nil),  // 1: android.find_input_delta_proto.FileList
-	(*FileCount)(nil), // 2: android.find_input_delta_proto.FileCount
 }
 var file_file_list_proto_depIdxs = []int32{
-	1, // 0: android.find_input_delta_proto.FileList.changes:type_name -> android.find_input_delta_proto.FileList
-	2, // 1: android.find_input_delta_proto.FileList.counts:type_name -> android.find_input_delta_proto.FileCount
-	2, // [2:2] is the sub-list for method output_type
-	2, // [2:2] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
 }
 
 func init() { file_file_list_proto_init() }
@@ -350,18 +234,6 @@
 				return nil
 			}
 		}
-		file_file_list_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FileCount); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -369,7 +241,7 @@
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_file_list_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   2,
+			NumMessages:   1,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/cmd/find_input_delta/find_input_delta_proto/file_list.proto b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
index 7180358..5309d66 100644
--- a/cmd/find_input_delta/find_input_delta_proto/file_list.proto
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
@@ -23,37 +23,15 @@
 }
 
 message FileList {
-  // The name of the file.
-  // In the outermost message, this is the name of the Ninja target.
-  // When used in `changes`, this is the name of the changed file.
+  // The name of the output file (Ninja target).
   optional string name = 1;
 
   // The added files.
   repeated string additions = 2;
 
   // The changed files.
-  repeated FileList changes = 3;
+  repeated string changes = 3;
 
   // The deleted files.
   repeated string deletions = 4;
-
-  // Count of files added/changed/deleted.
-  optional uint32 total_delta = 5;
-
-  // Counts by extension.
-  repeated FileCount counts = 6;
-}
-
-message FileCount {
-  // The file extension
-  optional string extension = 1;
-
-  // Number of added files with this extension.
-  optional uint32 additions = 2;
-
-  // Number of modified files with this extension.
-  optional uint32 modifications = 3;
-
-  // Number of deleted files with this extension.
-  optional uint32 deletions = 4;
 }
diff --git a/cmd/find_input_delta/find_input_delta_proto/regen.sh b/cmd/find_input_delta/find_input_delta_proto/regen.sh
old mode 100644
new mode 100755
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index c7134d7..9721794 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -27,6 +27,7 @@
 
 	"android/soong/shared"
 	"android/soong/ui/build"
+	"android/soong/ui/execution_metrics"
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
 	"android/soong/ui/signal"
@@ -170,14 +171,16 @@
 		stat.Finish()
 	})
 	criticalPath := status.NewCriticalPath()
+	emet := execution_metrics.NewExecutionMetrics(log)
 	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
-		Context:      ctx,
-		Logger:       log,
-		Metrics:      met,
-		Tracer:       trace,
-		Writer:       output,
-		Status:       stat,
-		CriticalPath: criticalPath,
+		Context:          ctx,
+		Logger:           log,
+		Metrics:          met,
+		ExecutionMetrics: emet,
+		Tracer:           trace,
+		Writer:           output,
+		Status:           stat,
+		CriticalPath:     criticalPath,
 	}}
 
 	freshConfig := func() build.Config {
@@ -194,6 +197,7 @@
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
 	soongBuildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_build_metrics.pb")
 	buildTraceFile := filepath.Join(logsDir, c.logsPrefix+"build.trace.gz")
+	executionMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_execution_metrics.pb")
 
 	metricsFiles := []string{
 		buildErrorFile,        // build error strings
@@ -204,9 +208,16 @@
 	}
 
 	defer func() {
+		emet.Finish(buildCtx)
 		stat.Finish()
 		criticalPath.WriteToMetrics(met)
 		met.Dump(soongMetricsFile)
+		emet.Dump(executionMetricsFile, args)
+		// If there are execution metrics, upload them.
+		if _, err := os.Stat(executionMetricsFile); err == nil {
+			// TODO: Upload the metrics file.
+			// metricsFiles = append(metricsFiles, executionMetricsFile)
+		}
 		if !config.SkipMetricsUpload() {
 			build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, metricsFiles...)
 		}