1 /*
2  * Copyright (C) 2016 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 
17 package com.android.layoutlib.bridge.intensive.util.perf;
18 
19 import com.android.layoutlib.bridge.intensive.util.TestUtils;
20 
21 import org.junit.runners.model.Statement;
22 
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.util.Arrays;
28 import java.util.Random;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.ScheduledExecutorService;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.function.Consumer;
34 
35 import com.google.common.hash.HashCode;
36 import com.google.common.hash.HashFunction;
37 import com.google.common.hash.Hashing;
38 
39 /**
40  * JUnit {@link Statement} used to measure some statistics about the test method.
41  */
42 public class TimedStatement extends Statement {
43     private static final int CALIBRATION_WARMUP_ITERATIONS = 50;
44     private static final int CALIBRATION_RUNS = 100;
45 
46     private static boolean sIsCalibrated;
47     private static double sCalibrated;
48 
49     private final Statement mStatement;
50     private final int mWarmUpIterations;
51     private final int mRuns;
52     private final Runtime mRuntime = Runtime.getRuntime();
53     private final Consumer<TimedStatementResult> mCallback;
54 
TimedStatement(Statement statement, int warmUpIterations, int runs, Consumer<TimedStatementResult> finishedCallback)55     TimedStatement(Statement statement, int warmUpIterations, int runs,
56             Consumer<TimedStatementResult> finishedCallback) {
57         mStatement = statement;
58         mWarmUpIterations = warmUpIterations;
59         mRuns = runs;
60         mCallback = finishedCallback;
61     }
62 
63     /**
64      * The calibrate method tries to do some work that involves IO, memory allocations and some
65      * operations on the randomly generated data to calibrate the speed of the machine with
66      * something that resembles the execution of a test case.
67      */
calibrateMethod()68     private static void calibrateMethod() throws IOException {
69         File tmpFile = File.createTempFile("test", "file");
70         Random rnd = new Random();
71         HashFunction hashFunction = Hashing.sha512();
72         for (int i = 0; i < 5 + rnd.nextInt(5); i++) {
73             FileOutputStream stream = new FileOutputStream(tmpFile);
74             int bytes = 30000 + rnd.nextInt(60000);
75             byte[] buffer = new byte[bytes];
76 
77             rnd.nextBytes(buffer);
78             byte acc = 0;
79             for (int j = 0; j < bytes; j++) {
80                 acc += buffer[i];
81             }
82             buffer[0] = acc;
83             stream.write(buffer);
84             System.gc();
85             stream.close();
86             FileInputStream input = new FileInputStream(tmpFile);
87             byte[] readBuffer = new byte[bytes];
88             //noinspection ResultOfMethodCallIgnored
89             input.read(readBuffer);
90             buffer = readBuffer;
91             HashCode code1 = hashFunction.hashBytes(buffer);
92             Arrays.sort(buffer);
93             HashCode code2 = hashFunction.hashBytes(buffer);
94             input.close();
95 
96             FileOutputStream hashStream = new FileOutputStream(tmpFile);
97             hashStream.write(code1.asBytes());
98             hashStream.write(code2.asBytes());
99             hashStream.close();
100         }
101     }
102 
103     /**
104      * Runs the calibration process and sets the calibration measure in {@link #sCalibrated}
105      */
doCalibration()106     private static void doCalibration() throws IOException {
107         System.out.println("Calibrating ...");
108         TestUtils.gc();
109         for (int i = 0; i < CALIBRATION_WARMUP_ITERATIONS; i++) {
110             calibrateMethod();
111         }
112 
113         LongStatsCollector stats = new LongStatsCollector(CALIBRATION_RUNS);
114         for (int i = 0; i < CALIBRATION_RUNS; i++) {
115             TestUtils.gc();
116             long start = System.currentTimeMillis();
117             calibrateMethod();
118             stats.accept(System.currentTimeMillis() - start);
119         }
120 
121         sCalibrated = stats.getStats().getMedian();
122         sIsCalibrated = true;
123         System.out.printf("  DONE %fms\n", sCalibrated);
124     }
125 
getUsedMemory()126     private long getUsedMemory() {
127         return mRuntime.totalMemory() - mRuntime.freeMemory();
128     }
129 
130 
131     @Override
evaluate()132     public void evaluate() throws Throwable {
133         if (!sIsCalibrated) {
134             doCalibration();
135         }
136 
137         for (int i = 0; i < mWarmUpIterations; i++) {
138             mStatement.evaluate();
139         }
140 
141         LongStatsCollector timeStats = new LongStatsCollector(mRuns);
142         LongStatsCollector memoryUseStats = new LongStatsCollector(mRuns);
143         AtomicBoolean collectSamples = new AtomicBoolean(false);
144 
145         ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
146         TestUtils.gc();
147         executorService.scheduleAtFixedRate(() -> {
148             if (!collectSamples.get()) {
149                 return;
150             }
151             memoryUseStats.accept(getUsedMemory());
152         }, 0, 200, TimeUnit.MILLISECONDS);
153 
154         try {
155             for (int i = 0; i < mRuns; i++) {
156                 TestUtils.gc();
157                 collectSamples.set(true);
158                 long startTimeMs = System.currentTimeMillis();
159                 mStatement.evaluate();
160                 long stopTimeMs = System.currentTimeMillis();
161                 collectSamples.set(true);
162                 timeStats.accept(stopTimeMs - startTimeMs);
163 
164             }
165         } finally {
166             executorService.shutdownNow();
167         }
168 
169         TimedStatementResult result = new TimedStatementResult(
170                 mWarmUpIterations,
171                 mRuns,
172                 sCalibrated,
173                 timeStats.getStats(),
174                 memoryUseStats.getStats());
175         mCallback.accept(result);
176     }
177 
178 }
179