blob: 413f4237fd0598d59086e38026a50db99fe855ae [file] [log] [blame]
Colin Crossd0be2102019-11-26 16:16:57 -08001// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This executable runs a series of build commands to test and benchmark some critical user journeys.
16package main
17
18import (
19 "context"
20 "fmt"
21 "os"
22 "path/filepath"
23 "strconv"
24 "strings"
25 "time"
26
27 "android/soong/ui/build"
28 "android/soong/ui/logger"
29 "android/soong/ui/metrics"
Lukacs T. Berkif656b842021-08-11 11:10:28 +020030 "android/soong/ui/signal"
Colin Crossd0be2102019-11-26 16:16:57 -080031 "android/soong/ui/status"
32 "android/soong/ui/terminal"
33 "android/soong/ui/tracer"
34)
35
36type Test struct {
Patrice Arrudaf3261392020-03-23 08:18:36 -070037 name string
38 args []string
39 before func() error
Colin Crossd0be2102019-11-26 16:16:57 -080040
41 results TestResults
42}
43
44type TestResults struct {
45 metrics *metrics.Metrics
46 err error
47}
48
49// Run runs a single build command. It emulates the "m" command line by calling into Soong UI directly.
50func (t *Test) Run(logsDir string) {
51 output := terminal.NewStatusOutput(os.Stdout, "", false, false)
52
53 log := logger.New(output)
54 defer log.Cleanup()
55
56 ctx, cancel := context.WithCancel(context.Background())
57 defer cancel()
58
59 trace := tracer.New(log)
60 defer trace.Close()
61
62 met := metrics.New()
63
64 stat := &status.Status{}
65 defer stat.Finish()
66 stat.AddOutput(output)
67 stat.AddOutput(trace.StatusTracer())
68
Lukacs T. Berkif656b842021-08-11 11:10:28 +020069 signal.SetupSignals(log, cancel, func() {
Colin Crossd0be2102019-11-26 16:16:57 -080070 trace.Close()
71 log.Cleanup()
72 stat.Finish()
73 })
74
75 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
76 Context: ctx,
77 Logger: log,
78 Metrics: met,
79 Tracer: trace,
80 Writer: output,
81 Status: stat,
82 }}
83
84 defer logger.Recover(func(err error) {
85 t.results.err = err
86 })
87
88 config := build.NewConfig(buildCtx, t.args...)
89 build.SetupOutDir(buildCtx, config)
90
91 os.MkdirAll(logsDir, 0777)
92 log.SetOutput(filepath.Join(logsDir, "soong.log"))
93 trace.SetOutput(filepath.Join(logsDir, "build.trace"))
94 stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
95 stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
96 stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
97 stat.AddOutput(status.NewCriticalPath(log))
98
99 defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
100
101 if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
102 if !strings.HasSuffix(start, "N") {
103 if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
104 log.Verbosef("Took %dms to start up.",
105 time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
106 buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
107 }
108 }
109
110 if executable, err := os.Executable(); err == nil {
111 trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
112 }
113 }
114
115 f := build.NewSourceFinder(buildCtx, config)
116 defer f.Shutdown()
117 build.FindSources(buildCtx, config, f)
118
Anton Hansson5a7861a2021-06-04 10:09:01 +0100119 build.Build(buildCtx, config)
Colin Crossd0be2102019-11-26 16:16:57 -0800120
121 t.results.metrics = met
122}
123
Patrice Arrudaf3261392020-03-23 08:18:36 -0700124// Touch the Intent.java file to cause a rebuild of the frameworks to monitor the
125// incremental build speed as mentioned b/152046247. Intent.java file was chosen
126// as it is a key component of the framework and is often modified.
127func touchIntentFile() error {
128 const intentFileName = "frameworks/base/core/java/android/content/Intent.java"
129 currentTime := time.Now().Local()
130 return os.Chtimes(intentFileName, currentTime, currentTime)
131}
132
Colin Crossd0be2102019-11-26 16:16:57 -0800133func main() {
134 outDir := os.Getenv("OUT_DIR")
135 if outDir == "" {
136 outDir = "out"
137 }
138
139 cujDir := filepath.Join(outDir, "cuj_tests")
140
141 // Use a subdirectory for the out directory for the tests to keep them isolated.
142 os.Setenv("OUT_DIR", filepath.Join(cujDir, "out"))
143
144 // Each of these tests is run in sequence without resetting the output tree. The state of the output tree will
145 // affect each successive test. To maintain the validity of the benchmarks across changes, care must be taken
146 // to avoid changing the state of the tree when a test is run. This is most easily accomplished by adding tests
147 // at the end.
148 tests := []Test{
149 {
150 // Reset the out directory to get reproducible results.
151 name: "clean",
152 args: []string{"clean"},
153 },
154 {
155 // Parse the build files.
156 name: "nothing",
157 args: []string{"nothing"},
158 },
159 {
160 // Parse the build files again to monitor issues like globs rerunning.
161 name: "nothing_rebuild",
162 args: []string{"nothing"},
163 },
164 {
165 // Parse the build files again, this should always be very short.
166 name: "nothing_rebuild_twice",
167 args: []string{"nothing"},
168 },
169 {
170 // Build the framework as a common developer task and one that keeps getting longer.
171 name: "framework",
172 args: []string{"framework"},
173 },
174 {
175 // Build the framework again to make sure it doesn't rebuild anything.
176 name: "framework_rebuild",
177 args: []string{"framework"},
178 },
179 {
180 // Build the framework again to make sure it doesn't rebuild anything even if it did the second time.
181 name: "framework_rebuild_twice",
182 args: []string{"framework"},
183 },
Patrice Arrudaf3261392020-03-23 08:18:36 -0700184 {
185 // Scenario major_inc_build (b/152046247): tracking build speed of major incremental build.
186 name: "major_inc_build_droid",
187 args: []string{"droid"},
188 },
189 {
190 name: "major_inc_build_framework_minus_apex_after_droid_build",
191 args: []string{"framework-minus-apex"},
192 before: touchIntentFile,
193 },
194 {
195 name: "major_inc_build_framework_after_droid_build",
196 args: []string{"framework"},
197 before: touchIntentFile,
198 },
199 {
200 name: "major_inc_build_sync_after_droid_build",
201 args: []string{"sync"},
202 before: touchIntentFile,
203 },
204 {
205 name: "major_inc_build_droid_rebuild",
206 args: []string{"droid"},
207 before: touchIntentFile,
208 },
209 {
210 name: "major_inc_build_update_api_after_droid_rebuild",
211 args: []string{"update-api"},
212 before: touchIntentFile,
213 },
Colin Crossd0be2102019-11-26 16:16:57 -0800214 }
215
216 cujMetrics := metrics.NewCriticalUserJourneysMetrics()
217 defer cujMetrics.Dump(filepath.Join(cujDir, "logs", "cuj_metrics.pb"))
218
219 for i, t := range tests {
220 logsSubDir := fmt.Sprintf("%02d_%s", i, t.name)
221 logsDir := filepath.Join(cujDir, "logs", logsSubDir)
Patrice Arrudaf3261392020-03-23 08:18:36 -0700222 if t.before != nil {
223 if err := t.before(); err != nil {
224 fmt.Printf("error running before function on test %q: %v\n", t.name, err)
225 break
226 }
227 }
Colin Crossd0be2102019-11-26 16:16:57 -0800228 t.Run(logsDir)
229 if t.results.err != nil {
230 fmt.Printf("error running test %q: %s\n", t.name, t.results.err)
231 break
232 }
233 if t.results.metrics != nil {
234 cujMetrics.Add(t.name, t.results.metrics)
235 }
236 }
237}