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