Merge "Fix: vendor and odm are restricted to use public platform properties"
diff --git a/cc/config/clang.go b/cc/config/clang.go
index eddc341..8618d09 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -132,6 +132,10 @@
 		// Disable -Winconsistent-missing-override until we can clean up the existing
 		// codebase for it.
 		"-Wno-inconsistent-missing-override",
+
+		// Warnings from clang-10
+		// Nested and array designated initialization is nice to have.
+		"-Wno-c99-designator",
 	}, " "))
 
 	pctx.StaticVariable("ClangExtraCppflags", strings.Join([]string{
@@ -161,6 +165,10 @@
 		// new warnings are fixed.
 		"-Wno-tautological-constant-compare",
 		"-Wno-tautological-type-limit-compare",
+		// http://b/145210666
+		"-Wno-reorder-init-list",
+		// http://b/145211066
+		"-Wno-implicit-int-float-conversion",
 	}, " "))
 
 	// Extra cflags for external third-party projects to disable warnings that
@@ -176,6 +184,13 @@
 		// Bug: http://b/29823425 Disable -Wnull-dereference until the
 		// new instances detected by this warning are fixed.
 		"-Wno-null-dereference",
+
+		// http://b/145211477
+		"-Wno-pointer-compare",
+		// http://b/145211022
+		"-Wno-xor-used-as-pow",
+		// http://b/145211022
+		"-Wno-final-dtor-non-final-class",
 	}, " "))
 }
 
diff --git a/cc/config/global.go b/cc/config/global.go
index 0943126..f18f950 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -124,8 +124,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r365631b"
-	ClangDefaultShortVersion = "9.0.7"
+	ClangDefaultVersion      = "clang-r370808"
+	ClangDefaultShortVersion = "10.0.1"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/fuzz.go b/cc/fuzz.go
index bb89bb4..c2b0ff4 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -345,6 +345,11 @@
 			return
 		}
 
+		// Discard modules that are in an unavailable namespace.
+		if !ccModule.ExportedToMake() {
+			return
+		}
+
 		s.fuzzTargets[module.Name()] = true
 
 		hostOrTargetString := "target"
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index c47cbf0..da94d33 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -261,6 +261,7 @@
 		// We're knowingly doing some otherwise unsightly things with builtin
 		// functions here. We're just generating stub libraries, so ignore it.
 		"-Wno-incompatible-library-redeclaration",
+		"-Wno-incomplete-setjmp-declaration",
 		"-Wno-builtin-requires-header",
 		"-Wno-invalid-noreturn",
 		"-Wall",
diff --git a/cuj/Android.bp b/cuj/Android.bp
new file mode 100644
index 0000000..21d667f
--- /dev/null
+++ b/cuj/Android.bp
@@ -0,0 +1,12 @@
+blueprint_go_binary {
+    name: "cuj_tests",
+    deps: [
+        "soong-ui-build",
+        "soong-ui-logger",
+        "soong-ui-terminal",
+        "soong-ui-tracer",
+    ],
+    srcs: [
+        "cuj.go",
+    ],
+}
diff --git a/cuj/cuj.go b/cuj/cuj.go
new file mode 100644
index 0000000..c7ff8ff
--- /dev/null
+++ b/cuj/cuj.go
@@ -0,0 +1,190 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This executable runs a series of build commands to test and benchmark some critical user journeys.
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"android/soong/ui/build"
+	"android/soong/ui/logger"
+	"android/soong/ui/metrics"
+	"android/soong/ui/status"
+	"android/soong/ui/terminal"
+	"android/soong/ui/tracer"
+)
+
+type Test struct {
+	name string
+	args []string
+
+	results TestResults
+}
+
+type TestResults struct {
+	metrics *metrics.Metrics
+	err     error
+}
+
+// Run runs a single build command.  It emulates the "m" command line by calling into Soong UI directly.
+func (t *Test) Run(logsDir string) {
+	output := terminal.NewStatusOutput(os.Stdout, "", false, false)
+
+	log := logger.New(output)
+	defer log.Cleanup()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	trace := tracer.New(log)
+	defer trace.Close()
+
+	met := metrics.New()
+
+	stat := &status.Status{}
+	defer stat.Finish()
+	stat.AddOutput(output)
+	stat.AddOutput(trace.StatusTracer())
+
+	build.SetupSignals(log, cancel, func() {
+		trace.Close()
+		log.Cleanup()
+		stat.Finish()
+	})
+
+	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
+		Context: ctx,
+		Logger:  log,
+		Metrics: met,
+		Tracer:  trace,
+		Writer:  output,
+		Status:  stat,
+	}}
+
+	defer logger.Recover(func(err error) {
+		t.results.err = err
+	})
+
+	config := build.NewConfig(buildCtx, t.args...)
+	build.SetupOutDir(buildCtx, config)
+
+	os.MkdirAll(logsDir, 0777)
+	log.SetOutput(filepath.Join(logsDir, "soong.log"))
+	trace.SetOutput(filepath.Join(logsDir, "build.trace"))
+	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
+	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
+	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
+	stat.AddOutput(status.NewCriticalPath(log))
+
+	defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
+
+	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
+		if !strings.HasSuffix(start, "N") {
+			if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
+				log.Verbosef("Took %dms to start up.",
+					time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
+				buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
+			}
+		}
+
+		if executable, err := os.Executable(); err == nil {
+			trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
+		}
+	}
+
+	f := build.NewSourceFinder(buildCtx, config)
+	defer f.Shutdown()
+	build.FindSources(buildCtx, config, f)
+
+	build.Build(buildCtx, config, build.BuildAll)
+
+	t.results.metrics = met
+}
+
+func main() {
+	outDir := os.Getenv("OUT_DIR")
+	if outDir == "" {
+		outDir = "out"
+	}
+
+	cujDir := filepath.Join(outDir, "cuj_tests")
+
+	// Use a subdirectory for the out directory for the tests to keep them isolated.
+	os.Setenv("OUT_DIR", filepath.Join(cujDir, "out"))
+
+	// Each of these tests is run in sequence without resetting the output tree.  The state of the output tree will
+	// affect each successive test.  To maintain the validity of the benchmarks across changes, care must be taken
+	// to avoid changing the state of the tree when a test is run.  This is most easily accomplished by adding tests
+	// at the end.
+	tests := []Test{
+		{
+			// Reset the out directory to get reproducible results.
+			name: "clean",
+			args: []string{"clean"},
+		},
+		{
+			// Parse the build files.
+			name: "nothing",
+			args: []string{"nothing"},
+		},
+		{
+			// Parse the build files again to monitor issues like globs rerunning.
+			name: "nothing_rebuild",
+			args: []string{"nothing"},
+		},
+		{
+			// Parse the build files again, this should always be very short.
+			name: "nothing_rebuild_twice",
+			args: []string{"nothing"},
+		},
+		{
+			// Build the framework as a common developer task and one that keeps getting longer.
+			name: "framework",
+			args: []string{"framework"},
+		},
+		{
+			// Build the framework again to make sure it doesn't rebuild anything.
+			name: "framework_rebuild",
+			args: []string{"framework"},
+		},
+		{
+			// Build the framework again to make sure it doesn't rebuild anything even if it did the second time.
+			name: "framework_rebuild_twice",
+			args: []string{"framework"},
+		},
+	}
+
+	cujMetrics := metrics.NewCriticalUserJourneysMetrics()
+	defer cujMetrics.Dump(filepath.Join(cujDir, "logs", "cuj_metrics.pb"))
+
+	for i, t := range tests {
+		logsSubDir := fmt.Sprintf("%02d_%s", i, t.name)
+		logsDir := filepath.Join(cujDir, "logs", logsSubDir)
+		t.Run(logsDir)
+		if t.results.err != nil {
+			fmt.Printf("error running test %q: %s\n", t.name, t.results.err)
+			break
+		}
+		if t.results.metrics != nil {
+			cujMetrics.Add(t.name, t.results.metrics)
+		}
+	}
+}
diff --git a/cuj/run_cuj_tests.sh b/cuj/run_cuj_tests.sh
new file mode 100755
index 0000000..b4f9f88
--- /dev/null
+++ b/cuj/run_cuj_tests.sh
@@ -0,0 +1,29 @@
+#!/bin/bash -e
+
+readonly UNAME="$(uname)"
+case "$UNAME" in
+Linux)
+    readonly OS='linux'
+    ;;
+Darwin)
+    readonly OS='darwin'
+    ;;
+*)
+    echo "Unsupported OS '$UNAME'"
+    exit 1
+    ;;
+esac
+
+readonly ANDROID_TOP="$(cd $(dirname $0)/../../..; pwd)"
+cd "$ANDROID_TOP"
+
+export OUT_DIR="${OUT_DIR:-out}"
+readonly SOONG_OUT="${OUT_DIR}/soong"
+
+build/soong/soong_ui.bash --make-mode "${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests"
+
+"${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests" || true
+
+if [ -n "${DIST_DIR}" ]; then
+  cp -r "${OUT_DIR}/cuj_tests/logs" "${DIST_DIR}"
+fi
diff --git a/java/dex.go b/java/dex.go
index 5b25b21..cd6d90d 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -115,7 +115,6 @@
 	r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars"))
 	r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars"))
 	r8Flags = append(r8Flags, flags.classpath.FormJavaClassPath("-libraryjars"))
-	r8Flags = append(r8Flags, "-forceprocessing")
 
 	r8Deps = append(r8Deps, proguardRaiseDeps...)
 	r8Deps = append(r8Deps, flags.bootClasspath...)
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 5e72cee..280ce8c 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -217,7 +217,7 @@
 	// Always create the default boot image first, to get a unique profile rule for all images.
 	d.defaultBootImage = buildBootImage(ctx, defaultBootImageConfig(ctx))
 	// Create boot image for the ART apex (build artifacts are accessed via the global boot image config).
-	buildBootImage(ctx, artBootImageConfig(ctx))
+	d.otherImages = append(d.otherImages, buildBootImage(ctx, artBootImageConfig(ctx)))
 	if global.GenerateApexImage {
 		// Create boot images for the JIT-zygote experiment.
 		d.otherImages = append(d.otherImages, buildBootImage(ctx, apexBootImageConfig(ctx)))
@@ -598,7 +598,6 @@
 		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPathsDeps.Strings(), " "))
 		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.dexLocationsDeps, " "))
 		ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS", strings.Join(image.imageLocations, ":"))
-		ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+image.name, image.zip.String())
 
 		var imageNames []string
 		for _, current := range append(d.otherImages, image) {
@@ -619,6 +618,8 @@
 				if current.zip != nil {
 				}
 			}
+
+			ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+current.name, current.zip.String())
 		}
 		ctx.Strict("DEXPREOPT_IMAGE_NAMES", strings.Join(imageNames, " "))
 	}
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 57a770e..fd1cfd4 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -226,12 +226,13 @@
 				c.images[arch] = imageDir.Join(ctx, imageName)
 				c.imagesDeps[arch] = c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex")
 			}
+
+			c.zip = c.dir.Join(ctx, c.name+".zip")
 		}
 
 		// specific to the framework config
 		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
 		frameworkCfg.imageLocations = append(artCfg.imageLocations, frameworkCfg.imageLocations...)
-		frameworkCfg.zip = frameworkCfg.dir.Join(ctx, frameworkCfg.stem+".zip")
 
 		return configs
 	}).(map[string]*bootImageConfig)
diff --git a/python/tests/par_test.py b/python/tests/par_test.py
index 1fafe0f..56a5063 100644
--- a/python/tests/par_test.py
+++ b/python/tests/par_test.py
@@ -44,6 +44,13 @@
 assert_equal("sys.path[1]", sys.path[1], os.path.join(archive, "internal"))
 assert_equal("sys.path[2]", sys.path[2], os.path.join(archive, "internal", "stdlib"))
 
+if os.getenv('ARGTEST', False):
+    assert_equal("len(sys.argv)", len(sys.argv), 3)
+    assert_equal("sys.argv[1]", sys.argv[1], "--arg1")
+    assert_equal("sys.argv[2]", sys.argv[2], "arg2")
+else:
+    assert_equal("len(sys.argv)", len(sys.argv), 1)
+
 if failed:
     sys.exit(1)
 
diff --git a/python/tests/runtest.sh b/python/tests/runtest.sh
index 1ecdebc..21187ed 100755
--- a/python/tests/runtest.sh
+++ b/python/tests/runtest.sh
@@ -36,8 +36,12 @@
 PYTHONHOME=/usr $ANDROID_HOST_OUT/nativetest64/par_test/par_test
 PYTHONPATH=/usr $ANDROID_HOST_OUT/nativetest64/par_test/par_test
 
+ARGTEST=true $ANDROID_HOST_OUT/nativetest64/par_test/par_test --arg1 arg2
+
 PYTHONHOME= PYTHONPATH= $ANDROID_HOST_OUT/nativetest64/par_test3/par_test3
 PYTHONHOME=/usr $ANDROID_HOST_OUT/nativetest64/par_test3/par_test3
 PYTHONPATH=/usr $ANDROID_HOST_OUT/nativetest64/par_test3/par_test3
 
+ARGTEST=true $ANDROID_HOST_OUT/nativetest64/par_test3/par_test3 --arg1 arg2
+
 echo "Passed!"
diff --git a/ui/build/build.go b/ui/build/build.go
index 6a19314..7dfb900 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -33,6 +33,15 @@
 	// can be parsed as ninja output.
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
+
+	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
+		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0777)
+		if err != nil {
+			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
+		}
+	} else {
+		ctx.Fatalln("Missing BUILD_DATETIME_FILE")
+	}
 }
 
 var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
diff --git a/ui/build/config.go b/ui/build/config.go
index 876bfe0..565f033 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -15,7 +15,6 @@
 package build
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -30,10 +29,11 @@
 
 type configImpl struct {
 	// From the environment
-	arguments []string
-	goma      bool
-	environ   *Environment
-	distDir   string
+	arguments     []string
+	goma          bool
+	environ       *Environment
+	distDir       string
+	buildDateTime string
 
 	// From the arguments
 	parallel   int
@@ -244,18 +244,14 @@
 
 	outDir := ret.OutDir()
 	buildDateTimeFile := filepath.Join(outDir, "build_date.txt")
-	var content string
 	if buildDateTime, ok := ret.environ.Get("BUILD_DATETIME"); ok && buildDateTime != "" {
-		content = buildDateTime
+		ret.buildDateTime = buildDateTime
 	} else {
-		content = strconv.FormatInt(time.Now().Unix(), 10)
+		ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
 	}
+
 	if ctx.Metrics != nil {
-		ctx.Metrics.SetBuildDateTime(content)
-	}
-	err := ioutil.WriteFile(buildDateTimeFile, []byte(content), 0777)
-	if err != nil {
-		ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
+		ctx.Metrics.SetBuildDateTime(ret.buildDateTime)
 	}
 	ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)
 
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index bc86f0a..64bbbf3 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -19,9 +19,9 @@
 	"os"
 	"strconv"
 
-	"android/soong/ui/metrics/metrics_proto"
-
 	"github.com/golang/protobuf/proto"
+
+	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 )
 
 const (
@@ -137,13 +137,32 @@
 	}
 }
 
-func (m *Metrics) Serialize() (data []byte, err error) {
-	return proto.Marshal(&m.metrics)
-}
-
 // exports the output to the file at outputPath
 func (m *Metrics) Dump(outputPath string) (err error) {
-	data, err := m.Serialize()
+	return writeMessageToFile(&m.metrics, outputPath)
+}
+
+type CriticalUserJourneysMetrics struct {
+	cujs soong_metrics_proto.CriticalUserJourneysMetrics
+}
+
+func NewCriticalUserJourneysMetrics() *CriticalUserJourneysMetrics {
+	return &CriticalUserJourneysMetrics{}
+}
+
+func (c *CriticalUserJourneysMetrics) Add(name string, metrics *Metrics) {
+	c.cujs.Cujs = append(c.cujs.Cujs, &soong_metrics_proto.CriticalUserJourneyMetrics{
+		Name:    proto.String(name),
+		Metrics: &metrics.metrics,
+	})
+}
+
+func (c *CriticalUserJourneysMetrics) Dump(outputPath string) (err error) {
+	return writeMessageToFile(&c.cujs, outputPath)
+}
+
+func writeMessageToFile(pb proto.Message, outputPath string) (err error) {
+	data, err := proto.Marshal(pb)
 	if err != nil {
 		return err
 	}
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 5486ec1..0fe5a0d 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -509,6 +509,95 @@
 	return 0
 }
 
+type CriticalUserJourneyMetrics struct {
+	// The name of a critical user journey test.
+	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// The metrics produced when running the critical user journey test.
+	Metrics              *MetricsBase `protobuf:"bytes,2,opt,name=metrics" json:"metrics,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *CriticalUserJourneyMetrics) Reset()         { *m = CriticalUserJourneyMetrics{} }
+func (m *CriticalUserJourneyMetrics) String() string { return proto.CompactTextString(m) }
+func (*CriticalUserJourneyMetrics) ProtoMessage()    {}
+func (*CriticalUserJourneyMetrics) Descriptor() ([]byte, []int) {
+	return fileDescriptor_6039342a2ba47b72, []int{3}
+}
+
+func (m *CriticalUserJourneyMetrics) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CriticalUserJourneyMetrics.Unmarshal(m, b)
+}
+func (m *CriticalUserJourneyMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CriticalUserJourneyMetrics.Marshal(b, m, deterministic)
+}
+func (m *CriticalUserJourneyMetrics) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CriticalUserJourneyMetrics.Merge(m, src)
+}
+func (m *CriticalUserJourneyMetrics) XXX_Size() int {
+	return xxx_messageInfo_CriticalUserJourneyMetrics.Size(m)
+}
+func (m *CriticalUserJourneyMetrics) XXX_DiscardUnknown() {
+	xxx_messageInfo_CriticalUserJourneyMetrics.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CriticalUserJourneyMetrics proto.InternalMessageInfo
+
+func (m *CriticalUserJourneyMetrics) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *CriticalUserJourneyMetrics) GetMetrics() *MetricsBase {
+	if m != nil {
+		return m.Metrics
+	}
+	return nil
+}
+
+type CriticalUserJourneysMetrics struct {
+	// A set of metrics from a run of the critical user journey tests.
+	Cujs                 []*CriticalUserJourneyMetrics `protobuf:"bytes,1,rep,name=cujs" json:"cujs,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                      `json:"-"`
+	XXX_unrecognized     []byte                        `json:"-"`
+	XXX_sizecache        int32                         `json:"-"`
+}
+
+func (m *CriticalUserJourneysMetrics) Reset()         { *m = CriticalUserJourneysMetrics{} }
+func (m *CriticalUserJourneysMetrics) String() string { return proto.CompactTextString(m) }
+func (*CriticalUserJourneysMetrics) ProtoMessage()    {}
+func (*CriticalUserJourneysMetrics) Descriptor() ([]byte, []int) {
+	return fileDescriptor_6039342a2ba47b72, []int{4}
+}
+
+func (m *CriticalUserJourneysMetrics) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CriticalUserJourneysMetrics.Unmarshal(m, b)
+}
+func (m *CriticalUserJourneysMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CriticalUserJourneysMetrics.Marshal(b, m, deterministic)
+}
+func (m *CriticalUserJourneysMetrics) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CriticalUserJourneysMetrics.Merge(m, src)
+}
+func (m *CriticalUserJourneysMetrics) XXX_Size() int {
+	return xxx_messageInfo_CriticalUserJourneysMetrics.Size(m)
+}
+func (m *CriticalUserJourneysMetrics) XXX_DiscardUnknown() {
+	xxx_messageInfo_CriticalUserJourneysMetrics.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CriticalUserJourneysMetrics proto.InternalMessageInfo
+
+func (m *CriticalUserJourneysMetrics) GetCujs() []*CriticalUserJourneyMetrics {
+	if m != nil {
+		return m.Cujs
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterEnum("soong_build_metrics.MetricsBase_BuildVariant", MetricsBase_BuildVariant_name, MetricsBase_BuildVariant_value)
 	proto.RegisterEnum("soong_build_metrics.MetricsBase_Arch", MetricsBase_Arch_name, MetricsBase_Arch_value)
@@ -516,59 +605,65 @@
 	proto.RegisterType((*MetricsBase)(nil), "soong_build_metrics.MetricsBase")
 	proto.RegisterType((*PerfInfo)(nil), "soong_build_metrics.PerfInfo")
 	proto.RegisterType((*ModuleTypeInfo)(nil), "soong_build_metrics.ModuleTypeInfo")
+	proto.RegisterType((*CriticalUserJourneyMetrics)(nil), "soong_build_metrics.CriticalUserJourneyMetrics")
+	proto.RegisterType((*CriticalUserJourneysMetrics)(nil), "soong_build_metrics.CriticalUserJourneysMetrics")
 }
 
 func init() { proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72) }
 
 var fileDescriptor_6039342a2ba47b72 = []byte{
-	// 769 bytes of a gzipped FileDescriptorProto
+	// 834 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x6f, 0x6b, 0xdb, 0x46,
-	0x18, 0xaf, 0x62, 0x25, 0x96, 0x1e, 0xc5, 0xae, 0x7a, 0xc9, 0xa8, 0xca, 0x08, 0x33, 0x66, 0x1d,
-	0x7e, 0xb1, 0xba, 0xc5, 0x14, 0x53, 0x4c, 0x19, 0xd8, 0x89, 0x29, 0x25, 0xd8, 0x2e, 0x4a, 0xdc,
-	0x95, 0xed, 0xc5, 0xa1, 0x4a, 0xe7, 0x46, 0x9b, 0xa5, 0x13, 0x77, 0xa7, 0x32, 0x7f, 0x88, 0x7d,
-	0x93, 0x7d, 0xad, 0x7d, 0x8f, 0x71, 0xcf, 0x49, 0x8e, 0x02, 0x81, 0x85, 0xbe, 0x3b, 0x3d, 0xbf,
-	0x3f, 0xf7, 0x7b, 0x4e, 0xba, 0x47, 0xd0, 0xc9, 0x98, 0x12, 0x69, 0x2c, 0x87, 0x85, 0xe0, 0x8a,
-	0x93, 0x13, 0xc9, 0x79, 0xfe, 0x85, 0x7e, 0x2e, 0xd3, 0x6d, 0x42, 0x2b, 0xa8, 0xff, 0x8f, 0x0b,
-	0xde, 0xc2, 0xac, 0x67, 0x91, 0x64, 0xe4, 0x15, 0x9c, 0x1a, 0x42, 0x12, 0x29, 0x46, 0x55, 0x9a,
-	0x31, 0xa9, 0xa2, 0xac, 0x08, 0xac, 0x9e, 0x35, 0x68, 0x85, 0x04, 0xb1, 0x8b, 0x48, 0xb1, 0xeb,
-	0x1a, 0x21, 0xcf, 0xc0, 0x31, 0x8a, 0x34, 0x09, 0x0e, 0x7a, 0xd6, 0xc0, 0x0d, 0xdb, 0xf8, 0xfc,
-	0x3e, 0x21, 0x13, 0x78, 0x56, 0x6c, 0x23, 0xb5, 0xe1, 0x22, 0xa3, 0x5f, 0x99, 0x90, 0x29, 0xcf,
-	0x69, 0xcc, 0x13, 0x96, 0x47, 0x19, 0x0b, 0x5a, 0xc8, 0x7d, 0x5a, 0x13, 0x3e, 0x1a, 0xfc, 0xbc,
-	0x82, 0xc9, 0x73, 0xe8, 0xaa, 0x48, 0x7c, 0x61, 0x8a, 0x16, 0x82, 0x27, 0x65, 0xac, 0x02, 0x1b,
-	0x05, 0x1d, 0x53, 0xfd, 0x60, 0x8a, 0x24, 0x81, 0xd3, 0x8a, 0x66, 0x42, 0x7c, 0x8d, 0x44, 0x1a,
-	0xe5, 0x2a, 0x38, 0xec, 0x59, 0x83, 0xee, 0xe8, 0xc5, 0xf0, 0x9e, 0x9e, 0x87, 0x8d, 0x7e, 0x87,
-	0x33, 0x8d, 0x7c, 0x34, 0xa2, 0x49, 0x6b, 0xbe, 0x7c, 0x17, 0x12, 0xe3, 0xd7, 0x04, 0xc8, 0x0a,
-	0xbc, 0x6a, 0x97, 0x48, 0xc4, 0x37, 0xc1, 0x11, 0x9a, 0x3f, 0xff, 0x5f, 0xf3, 0xa9, 0x88, 0x6f,
-	0x26, 0xed, 0xf5, 0xf2, 0x72, 0xb9, 0xfa, 0x75, 0x19, 0x82, 0xb1, 0xd0, 0x45, 0x32, 0x84, 0x93,
-	0x86, 0xe1, 0x3e, 0x75, 0x1b, 0x5b, 0x7c, 0x72, 0x4b, 0xac, 0x03, 0xfc, 0x0c, 0x55, 0x2c, 0x1a,
-	0x17, 0xe5, 0x9e, 0xee, 0x20, 0xdd, 0x37, 0xc8, 0x79, 0x51, 0xd6, 0xec, 0x4b, 0x70, 0x6f, 0xb8,
-	0xac, 0xc2, 0xba, 0xdf, 0x14, 0xd6, 0xd1, 0x06, 0x18, 0x35, 0x84, 0x0e, 0x9a, 0x8d, 0xf2, 0xc4,
-	0x18, 0xc2, 0x37, 0x19, 0x7a, 0xda, 0x64, 0x94, 0x27, 0xe8, 0xf9, 0x14, 0xda, 0xe8, 0xc9, 0x65,
-	0xe0, 0x61, 0x0f, 0x47, 0xfa, 0x71, 0x25, 0x49, 0xbf, 0xda, 0x8c, 0x4b, 0xca, 0xfe, 0x52, 0x22,
-	0x0a, 0x8e, 0x11, 0xf6, 0x0c, 0x3c, 0xd7, 0xa5, 0x3d, 0x27, 0x16, 0x5c, 0x4a, 0x6d, 0xd1, 0xb9,
-	0xe5, 0x9c, 0xeb, 0xda, 0x4a, 0x92, 0x9f, 0xe0, 0x71, 0x83, 0x83, 0xb1, 0xbb, 0xe6, 0xf3, 0xd9,
-	0xb3, 0x30, 0xc8, 0x0b, 0x38, 0x69, 0xf0, 0xf6, 0x2d, 0x3e, 0x36, 0x07, 0xbb, 0xe7, 0x36, 0x72,
-	0xf3, 0x52, 0xd1, 0x24, 0x15, 0x81, 0x6f, 0x72, 0xf3, 0x52, 0x5d, 0xa4, 0x82, 0xfc, 0x02, 0x9e,
-	0x64, 0xaa, 0x2c, 0xa8, 0xe2, 0x7c, 0x2b, 0x83, 0x27, 0xbd, 0xd6, 0xc0, 0x1b, 0x9d, 0xdd, 0x7b,
-	0x44, 0x1f, 0x98, 0xd8, 0xbc, 0xcf, 0x37, 0x3c, 0x04, 0x54, 0x5c, 0x6b, 0x01, 0x99, 0x80, 0xfb,
-	0x67, 0xa4, 0x52, 0x2a, 0xca, 0x5c, 0x06, 0xe4, 0x21, 0x6a, 0x47, 0xf3, 0xc3, 0x32, 0x97, 0xe4,
-	0x2d, 0x80, 0x61, 0xa2, 0xf8, 0xe4, 0x21, 0x62, 0x17, 0xd1, 0x5a, 0x9d, 0xa7, 0xf9, 0x1f, 0x91,
-	0x51, 0x9f, 0x3e, 0x48, 0x8d, 0x02, 0xad, 0xee, 0xbf, 0x82, 0xe3, 0x3b, 0x17, 0xc5, 0x01, 0x7b,
-	0x7d, 0x35, 0x0f, 0xfd, 0x47, 0xa4, 0x03, 0xae, 0x5e, 0x5d, 0xcc, 0x67, 0xeb, 0x77, 0xbe, 0x45,
-	0xda, 0xa0, 0x2f, 0x97, 0x7f, 0xd0, 0x7f, 0x0b, 0x36, 0x1e, 0xa5, 0x07, 0xf5, 0xa7, 0xe1, 0x3f,
-	0xd2, 0xe8, 0x34, 0x5c, 0xf8, 0x16, 0x71, 0xe1, 0x70, 0x1a, 0x2e, 0xc6, 0xaf, 0xfd, 0x03, 0x5d,
-	0xfb, 0xf4, 0x66, 0xec, 0xb7, 0x08, 0xc0, 0xd1, 0xa7, 0x37, 0x63, 0x3a, 0x7e, 0xed, 0xdb, 0xfd,
-	0xbf, 0x2d, 0x70, 0xea, 0x1c, 0x84, 0x80, 0x9d, 0x30, 0x19, 0xe3, 0x6c, 0x72, 0x43, 0x5c, 0xeb,
-	0x1a, 0x4e, 0x17, 0x33, 0x89, 0x70, 0x4d, 0xce, 0x00, 0xa4, 0x8a, 0x84, 0xc2, 0x71, 0x86, 0x73,
-	0xc7, 0x0e, 0x5d, 0xac, 0xe8, 0x29, 0x46, 0xbe, 0x07, 0x57, 0xb0, 0x68, 0x6b, 0x50, 0x1b, 0x51,
-	0x47, 0x17, 0x10, 0x3c, 0x03, 0xc8, 0x58, 0xc6, 0xc5, 0x8e, 0x96, 0x92, 0xe1, 0x54, 0xb1, 0x43,
-	0xd7, 0x54, 0xd6, 0x92, 0xf5, 0xff, 0xb5, 0xa0, 0xbb, 0xe0, 0x49, 0xb9, 0x65, 0xd7, 0xbb, 0x82,
-	0x61, 0xaa, 0xdf, 0xe1, 0xd8, 0x9c, 0x9b, 0xdc, 0x49, 0xc5, 0x32, 0x4c, 0xd7, 0x1d, 0xbd, 0xbc,
-	0xff, 0xba, 0xdc, 0x91, 0x9a, 0x61, 0x74, 0x85, 0xb2, 0xc6, 0xc5, 0xf9, 0x7c, 0x5b, 0x25, 0x3f,
-	0x80, 0x97, 0xa1, 0x86, 0xaa, 0x5d, 0x51, 0x77, 0x09, 0xd9, 0xde, 0x86, 0xfc, 0x08, 0xdd, 0xbc,
-	0xcc, 0x28, 0xdf, 0x50, 0x53, 0x94, 0xd8, 0x6f, 0x27, 0x3c, 0xce, 0xcb, 0x6c, 0xb5, 0x31, 0xfb,
-	0xc9, 0xfe, 0x4b, 0xf0, 0x1a, 0x7b, 0xdd, 0x7d, 0x17, 0x2e, 0x1c, 0x5e, 0xad, 0x56, 0x4b, 0xfd,
-	0xd2, 0x1c, 0xb0, 0x17, 0xd3, 0xcb, 0xb9, 0x7f, 0x30, 0xfb, 0xee, 0xb7, 0xea, 0xef, 0x51, 0x25,
-	0xa7, 0xf8, 0x4b, 0xf9, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x81, 0xd0, 0x84, 0x23, 0x62, 0x06, 0x00,
-	0x00,
+	0x18, 0xaf, 0x62, 0x25, 0xb6, 0x1e, 0xc5, 0xae, 0x7a, 0xc9, 0xa8, 0xba, 0x12, 0x66, 0xc4, 0x3a,
+	0xf2, 0x62, 0x75, 0x8b, 0x29, 0xa1, 0x98, 0x32, 0x48, 0x1c, 0x53, 0xba, 0x60, 0xbb, 0x28, 0x71,
+	0x57, 0xb6, 0x17, 0x87, 0x22, 0x9d, 0x1b, 0x75, 0x96, 0x4e, 0xdc, 0x9d, 0xca, 0xfc, 0x21, 0xf6,
+	0x4d, 0xf6, 0xb5, 0xf6, 0x3d, 0xc6, 0x3d, 0x27, 0x39, 0x0a, 0x78, 0x34, 0xf4, 0xdd, 0xe9, 0xf9,
+	0xfd, 0xb9, 0xdf, 0x73, 0xd2, 0x3d, 0x82, 0x6e, 0xc6, 0x94, 0x48, 0x63, 0x39, 0x28, 0x04, 0x57,
+	0x9c, 0x1c, 0x48, 0xce, 0xf3, 0x4f, 0xf4, 0xba, 0x4c, 0x57, 0x09, 0xad, 0xa0, 0xe0, 0x1f, 0x07,
+	0xdc, 0xa9, 0x59, 0x9f, 0x45, 0x92, 0x91, 0x97, 0x70, 0x68, 0x08, 0x49, 0xa4, 0x18, 0x55, 0x69,
+	0xc6, 0xa4, 0x8a, 0xb2, 0xc2, 0xb7, 0xfa, 0xd6, 0x71, 0x2b, 0x24, 0x88, 0x9d, 0x47, 0x8a, 0x5d,
+	0xd5, 0x08, 0x79, 0x02, 0x1d, 0xa3, 0x48, 0x13, 0x7f, 0xa7, 0x6f, 0x1d, 0x3b, 0x61, 0x1b, 0x9f,
+	0xdf, 0x25, 0x64, 0x04, 0x4f, 0x8a, 0x55, 0xa4, 0x96, 0x5c, 0x64, 0xf4, 0x0b, 0x13, 0x32, 0xe5,
+	0x39, 0x8d, 0x79, 0xc2, 0xf2, 0x28, 0x63, 0x7e, 0x0b, 0xb9, 0x8f, 0x6b, 0xc2, 0x07, 0x83, 0x8f,
+	0x2b, 0x98, 0x3c, 0x83, 0x9e, 0x8a, 0xc4, 0x27, 0xa6, 0x68, 0x21, 0x78, 0x52, 0xc6, 0xca, 0xb7,
+	0x51, 0xd0, 0x35, 0xd5, 0xf7, 0xa6, 0x48, 0x12, 0x38, 0xac, 0x68, 0x26, 0xc4, 0x97, 0x48, 0xa4,
+	0x51, 0xae, 0xfc, 0xdd, 0xbe, 0x75, 0xdc, 0x1b, 0x3e, 0x1f, 0x6c, 0xe9, 0x79, 0xd0, 0xe8, 0x77,
+	0x70, 0xa6, 0x91, 0x0f, 0x46, 0x34, 0x6a, 0x4d, 0x66, 0x6f, 0x43, 0x62, 0xfc, 0x9a, 0x00, 0x99,
+	0x83, 0x5b, 0xed, 0x12, 0x89, 0xf8, 0xc6, 0xdf, 0x43, 0xf3, 0x67, 0x5f, 0x35, 0x3f, 0x15, 0xf1,
+	0xcd, 0xa8, 0xbd, 0x98, 0x5d, 0xcc, 0xe6, 0xbf, 0xcd, 0x42, 0x30, 0x16, 0xba, 0x48, 0x06, 0x70,
+	0xd0, 0x30, 0xdc, 0xa4, 0x6e, 0x63, 0x8b, 0x8f, 0x6e, 0x89, 0x75, 0x80, 0x9f, 0xa1, 0x8a, 0x45,
+	0xe3, 0xa2, 0xdc, 0xd0, 0x3b, 0x48, 0xf7, 0x0c, 0x32, 0x2e, 0xca, 0x9a, 0x7d, 0x01, 0xce, 0x0d,
+	0x97, 0x55, 0x58, 0xe7, 0x9b, 0xc2, 0x76, 0xb4, 0x01, 0x46, 0x0d, 0xa1, 0x8b, 0x66, 0xc3, 0x3c,
+	0x31, 0x86, 0xf0, 0x4d, 0x86, 0xae, 0x36, 0x19, 0xe6, 0x09, 0x7a, 0x3e, 0x86, 0x36, 0x7a, 0x72,
+	0xe9, 0xbb, 0xd8, 0xc3, 0x9e, 0x7e, 0x9c, 0x4b, 0x12, 0x54, 0x9b, 0x71, 0x49, 0xd9, 0x5f, 0x4a,
+	0x44, 0xfe, 0x3e, 0xc2, 0xae, 0x81, 0x27, 0xba, 0xb4, 0xe1, 0xc4, 0x82, 0x4b, 0xa9, 0x2d, 0xba,
+	0xb7, 0x9c, 0xb1, 0xae, 0xcd, 0x25, 0xf9, 0x09, 0x1e, 0x36, 0x38, 0x18, 0xbb, 0x67, 0x3e, 0x9f,
+	0x0d, 0x0b, 0x83, 0x3c, 0x87, 0x83, 0x06, 0x6f, 0xd3, 0xe2, 0x43, 0x73, 0xb0, 0x1b, 0x6e, 0x23,
+	0x37, 0x2f, 0x15, 0x4d, 0x52, 0xe1, 0x7b, 0x26, 0x37, 0x2f, 0xd5, 0x79, 0x2a, 0xc8, 0x2f, 0xe0,
+	0x4a, 0xa6, 0xca, 0x82, 0x2a, 0xce, 0x57, 0xd2, 0x7f, 0xd4, 0x6f, 0x1d, 0xbb, 0xc3, 0xa3, 0xad,
+	0x47, 0xf4, 0x9e, 0x89, 0xe5, 0xbb, 0x7c, 0xc9, 0x43, 0x40, 0xc5, 0x95, 0x16, 0x90, 0x11, 0x38,
+	0x7f, 0x46, 0x2a, 0xa5, 0xa2, 0xcc, 0xa5, 0x4f, 0xee, 0xa3, 0xee, 0x68, 0x7e, 0x58, 0xe6, 0x92,
+	0xbc, 0x01, 0x30, 0x4c, 0x14, 0x1f, 0xdc, 0x47, 0xec, 0x20, 0x5a, 0xab, 0xf3, 0x34, 0xff, 0x1c,
+	0x19, 0xf5, 0xe1, 0xbd, 0xd4, 0x28, 0xd0, 0xea, 0xe0, 0x25, 0xec, 0xdf, 0xb9, 0x28, 0x1d, 0xb0,
+	0x17, 0x97, 0x93, 0xd0, 0x7b, 0x40, 0xba, 0xe0, 0xe8, 0xd5, 0xf9, 0xe4, 0x6c, 0xf1, 0xd6, 0xb3,
+	0x48, 0x1b, 0xf4, 0xe5, 0xf2, 0x76, 0x82, 0x37, 0x60, 0xe3, 0x51, 0xba, 0x50, 0x7f, 0x1a, 0xde,
+	0x03, 0x8d, 0x9e, 0x86, 0x53, 0xcf, 0x22, 0x0e, 0xec, 0x9e, 0x86, 0xd3, 0x93, 0x57, 0xde, 0x8e,
+	0xae, 0x7d, 0x7c, 0x7d, 0xe2, 0xb5, 0x08, 0xc0, 0xde, 0xc7, 0xd7, 0x27, 0xf4, 0xe4, 0x95, 0x67,
+	0x07, 0x7f, 0x5b, 0xd0, 0xa9, 0x73, 0x10, 0x02, 0x76, 0xc2, 0x64, 0x8c, 0xb3, 0xc9, 0x09, 0x71,
+	0xad, 0x6b, 0x38, 0x5d, 0xcc, 0x24, 0xc2, 0x35, 0x39, 0x02, 0x90, 0x2a, 0x12, 0x0a, 0xc7, 0x19,
+	0xce, 0x1d, 0x3b, 0x74, 0xb0, 0xa2, 0xa7, 0x18, 0x79, 0x0a, 0x8e, 0x60, 0xd1, 0xca, 0xa0, 0x36,
+	0xa2, 0x1d, 0x5d, 0x40, 0xf0, 0x08, 0x20, 0x63, 0x19, 0x17, 0x6b, 0x5a, 0x4a, 0x86, 0x53, 0xc5,
+	0x0e, 0x1d, 0x53, 0x59, 0x48, 0x16, 0xfc, 0x6b, 0x41, 0x6f, 0xca, 0x93, 0x72, 0xc5, 0xae, 0xd6,
+	0x05, 0xc3, 0x54, 0x7f, 0xc0, 0xbe, 0x39, 0x37, 0xb9, 0x96, 0x8a, 0x65, 0x98, 0xae, 0x37, 0x7c,
+	0xb1, 0xfd, 0xba, 0xdc, 0x91, 0x9a, 0x61, 0x74, 0x89, 0xb2, 0xc6, 0xc5, 0xb9, 0xbe, 0xad, 0x92,
+	0x1f, 0xc0, 0xcd, 0x50, 0x43, 0xd5, 0xba, 0xa8, 0xbb, 0x84, 0x6c, 0x63, 0x43, 0x7e, 0x84, 0x5e,
+	0x5e, 0x66, 0x94, 0x2f, 0xa9, 0x29, 0x4a, 0xec, 0xb7, 0x1b, 0xee, 0xe7, 0x65, 0x36, 0x5f, 0x9a,
+	0xfd, 0x64, 0xf0, 0x02, 0xdc, 0xc6, 0x5e, 0x77, 0xdf, 0x85, 0x03, 0xbb, 0x97, 0xf3, 0xf9, 0x4c,
+	0xbf, 0xb4, 0x0e, 0xd8, 0xd3, 0xd3, 0x8b, 0x89, 0xb7, 0x13, 0xac, 0xe0, 0xfb, 0xb1, 0x48, 0x55,
+	0x1a, 0x47, 0xab, 0x85, 0x64, 0xe2, 0x57, 0x5e, 0x8a, 0x9c, 0xad, 0xab, 0xdb, 0xbe, 0x39, 0x74,
+	0xab, 0x71, 0xe8, 0x23, 0x68, 0x57, 0x5d, 0x62, 0x4a, 0x77, 0xd8, 0xff, 0xda, 0xc0, 0x08, 0x6b,
+	0x41, 0x70, 0x0d, 0x4f, 0xb7, 0xec, 0x26, 0xeb, 0xed, 0xc6, 0x60, 0xc7, 0xe5, 0x67, 0xe9, 0x5b,
+	0xf8, 0xb1, 0x6e, 0x3f, 0xd9, 0xff, 0x4f, 0x1b, 0xa2, 0xf8, 0xec, 0xbb, 0xdf, 0xab, 0xff, 0x61,
+	0xa5, 0xa0, 0xf8, 0x93, 0xfc, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x20, 0x07, 0xbc, 0xf0, 0x34, 0x07,
+	0x00, 0x00,
 }
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 93034eb..1ea24bf 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -125,3 +125,16 @@
   // The number of logical modules.
   optional uint32 num_of_modules = 3;
 }
+
+message CriticalUserJourneyMetrics {
+  // The name of a critical user journey test.
+  optional string name = 1;
+
+  // The metrics produced when running the critical user journey test.
+  optional MetricsBase metrics = 2;
+}
+
+message CriticalUserJourneysMetrics {
+  // A set of metrics from a run of the critical user journey tests.
+  repeated CriticalUserJourneyMetrics cujs = 1;
+}
\ No newline at end of file
diff --git a/ui/status/log.go b/ui/status/log.go
index 9090f49..d407248 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -180,12 +180,12 @@
 func (e *errorProtoLog) Flush() {
 	data, err := proto.Marshal(&e.errorProto)
 	if err != nil {
-		e.log.Println("Failed to marshal build status proto: %v", err)
+		e.log.Printf("Failed to marshal build status proto: %v\n", err)
 		return
 	}
 	err = ioutil.WriteFile(e.filename, []byte(data), 0644)
 	if err != nil {
-		e.log.Println("Failed to write file %s: %v", e.errorProto, err)
+		e.log.Printf("Failed to write file %s: %v\n", e.filename, err)
 	}
 }