1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.multiuser;
17 
18 import android.annotation.Nullable;
19 import android.os.Bundle;
20 import android.os.SystemClock;
21 import android.perftests.utils.ShellHelper;
22 
23 import java.util.ArrayList;
24 
25 // Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
26 public class BenchmarkRunner {
27 
28     private static final long COOL_OFF_PERIOD_MS = 1000;
29 
30     private static final int NUM_ITERATIONS = 4;
31 
32     private static final int NOT_STARTED = 0;  // The benchmark has not started yet.
33     private static final int RUNNING = 1;  // The benchmark is running.
34     private static final int PAUSED = 2; // The benchmark is paused
35     private static final int FINISHED = 3;  // The benchmark has stopped.
36 
37     private final BenchmarkResults mResults = new BenchmarkResults();
38     private int mState = NOT_STARTED;  // Current benchmark state.
39     private int mIteration = 1;
40 
41     public long mStartTimeNs;
42     public long mPausedDurationNs;
43     public long mPausedTimeNs;
44 
45     private Throwable mFirstFailure = null;
46 
47     /**
48      * Starts a new run. Also responsible for finalising the calculations from the previous run,
49      * if there was one; therefore, any previous run must not be {@link #pauseTiming() paused} when
50      * this is called.
51      */
keepRunning()52     public boolean keepRunning() {
53         switch (mState) {
54             case NOT_STARTED:
55                 mState = RUNNING;
56                 prepareForNextRun();
57                 return true;
58             case RUNNING:
59                 mIteration++;
60                 return startNextTestRun();
61             case PAUSED:
62                 throw new IllegalStateException("Benchmarking is in paused state");
63             case FINISHED:
64                 throw new IllegalStateException("Benchmarking is finished");
65             default:
66                 throw new IllegalStateException("BenchmarkRunner is in unknown state");
67         }
68     }
69 
startNextTestRun()70     private boolean startNextTestRun() {
71         mResults.addDuration(System.nanoTime() - mStartTimeNs - mPausedDurationNs);
72         if (mIteration == NUM_ITERATIONS + 1) {
73             mState = FINISHED;
74             return false;
75         } else {
76             prepareForNextRun();
77             return true;
78         }
79     }
80 
prepareForNextRun()81     private void prepareForNextRun() {
82         SystemClock.sleep(COOL_OFF_PERIOD_MS);
83         ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
84         mStartTimeNs = System.nanoTime();
85         mPausedDurationNs = 0;
86     }
87 
pauseTiming()88     public void pauseTiming() {
89         if (mState != RUNNING) {
90             throw new IllegalStateException("Unable to pause the runner: not running currently");
91         }
92         mPausedTimeNs = System.nanoTime();
93         mState = PAUSED;
94     }
95 
96     /**
97      * Resumes the timing after a previous {@link #pauseTiming()}.
98      * First waits for the system to be idle prior to resuming.
99      *
100      * If this is called at the end of the run (so that no further timing is actually desired before
101      * {@link #keepRunning()} is called anyway), use {@link #resumeTimingForNextIteration()} instead
102      * to avoid unnecessary waiting.
103      */
resumeTiming()104     public void resumeTiming() {
105         ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
106         resumeTimer();
107     }
108 
109     /**
110      * Resume timing in preparation for a possible next run (rather than to continue timing the
111      * current run).
112      *
113      * It is equivalent to {@link #resumeTiming()} except that it skips steps that
114      * are unnecessary at the end of a trial (namely, waiting for the system to idle).
115      */
resumeTimingForNextIteration()116     public void resumeTimingForNextIteration() {
117         resumeTimer();
118     }
119 
resumeTimer()120     private void resumeTimer() {
121         if (mState != PAUSED) {
122             throw new IllegalStateException("Unable to resume the runner: already running");
123         }
124         mPausedDurationNs += System.nanoTime() - mPausedTimeNs;
125         mState = RUNNING;
126     }
127 
getStatsToReport()128     public Bundle getStatsToReport() {
129         return mResults.getStatsToReport();
130     }
131 
getStatsToLog()132     public Bundle getStatsToLog() {
133         return mResults.getStatsToLog();
134     }
135 
getAllDurations()136     public ArrayList<Long> getAllDurations() {
137         return mResults.getAllDurations();
138     }
139 
140     /** Returns which iteration (starting at 1) the Runner is currently on. */
getIteration()141     public int getIteration() {
142         return mIteration;
143     }
144 
145     /**
146      * Marks the test run as failed, along with a message of why.
147      * Only the first fail message is retained.
148      */
markAsFailed(Throwable err)149     public void markAsFailed(Throwable err) {
150         if (mFirstFailure == null) {
151             mFirstFailure = err;
152         }
153     }
154 
155     /** Gets the failure message if the test failed; otherwise {@code null}. */
getErrorOrNull()156     public @Nullable Throwable getErrorOrNull() {
157         if (mFirstFailure != null) {
158             return mFirstFailure;
159         }
160         if (mState != FINISHED) {
161             return new AssertionError("BenchmarkRunner state is not FINISHED.");
162         }
163         return null;
164     }
165 }