1373259f2960b2fa508e524a035818b390f99e574Diego Perez/*
2373259f2960b2fa508e524a035818b390f99e574Diego Perez * Copyright (C) 2016 The Android Open Source Project
3373259f2960b2fa508e524a035818b390f99e574Diego Perez *
4373259f2960b2fa508e524a035818b390f99e574Diego Perez * Licensed under the Apache License, Version 2.0 (the "License");
5373259f2960b2fa508e524a035818b390f99e574Diego Perez * you may not use this file except in compliance with the License.
6373259f2960b2fa508e524a035818b390f99e574Diego Perez * You may obtain a copy of the License at
7373259f2960b2fa508e524a035818b390f99e574Diego Perez *
8373259f2960b2fa508e524a035818b390f99e574Diego Perez *      http://www.apache.org/licenses/LICENSE-2.0
9373259f2960b2fa508e524a035818b390f99e574Diego Perez *
10373259f2960b2fa508e524a035818b390f99e574Diego Perez * Unless required by applicable law or agreed to in writing, software
11373259f2960b2fa508e524a035818b390f99e574Diego Perez * distributed under the License is distributed on an "AS IS" BASIS,
12373259f2960b2fa508e524a035818b390f99e574Diego Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13373259f2960b2fa508e524a035818b390f99e574Diego Perez * See the License for the specific language governing permissions and
14373259f2960b2fa508e524a035818b390f99e574Diego Perez * limitations under the License.
15373259f2960b2fa508e524a035818b390f99e574Diego Perez */
16373259f2960b2fa508e524a035818b390f99e574Diego Perez
17373259f2960b2fa508e524a035818b390f99e574Diego Perezpackage com.android.layoutlib.bridge.intensive.util.perf;
18373259f2960b2fa508e524a035818b390f99e574Diego Perez
19373259f2960b2fa508e524a035818b390f99e574Diego Perezimport com.android.layoutlib.bridge.intensive.util.TestUtils;
20373259f2960b2fa508e524a035818b390f99e574Diego Perez
21373259f2960b2fa508e524a035818b390f99e574Diego Perezimport org.junit.runners.model.Statement;
22373259f2960b2fa508e524a035818b390f99e574Diego Perez
23373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.io.File;
24373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.io.FileInputStream;
25373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.io.FileOutputStream;
26373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.io.IOException;
27373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.Arrays;
28373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.Random;
29373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.concurrent.Executors;
30373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.concurrent.ScheduledExecutorService;
31373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.concurrent.TimeUnit;
32373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.concurrent.atomic.AtomicBoolean;
33373259f2960b2fa508e524a035818b390f99e574Diego Perezimport java.util.function.Consumer;
34373259f2960b2fa508e524a035818b390f99e574Diego Perez
35373259f2960b2fa508e524a035818b390f99e574Diego Perezimport com.google.common.hash.HashCode;
36373259f2960b2fa508e524a035818b390f99e574Diego Perezimport com.google.common.hash.HashFunction;
37373259f2960b2fa508e524a035818b390f99e574Diego Perezimport com.google.common.hash.Hashing;
38373259f2960b2fa508e524a035818b390f99e574Diego Perez
39373259f2960b2fa508e524a035818b390f99e574Diego Perez/**
40373259f2960b2fa508e524a035818b390f99e574Diego Perez * JUnit {@link Statement} used to measure some statistics about the test method.
41373259f2960b2fa508e524a035818b390f99e574Diego Perez */
42373259f2960b2fa508e524a035818b390f99e574Diego Perezpublic class TimedStatement extends Statement {
43373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static final int CALIBRATION_WARMUP_ITERATIONS = 50;
44373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static final int CALIBRATION_RUNS = 100;
45373259f2960b2fa508e524a035818b390f99e574Diego Perez
46373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static boolean sIsCalibrated;
47373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static double sCalibrated;
48373259f2960b2fa508e524a035818b390f99e574Diego Perez
49373259f2960b2fa508e524a035818b390f99e574Diego Perez    private final Statement mStatement;
50373259f2960b2fa508e524a035818b390f99e574Diego Perez    private final int mWarmUpIterations;
51373259f2960b2fa508e524a035818b390f99e574Diego Perez    private final int mRuns;
52373259f2960b2fa508e524a035818b390f99e574Diego Perez    private final Runtime mRuntime = Runtime.getRuntime();
53373259f2960b2fa508e524a035818b390f99e574Diego Perez    private final Consumer<TimedStatementResult> mCallback;
54373259f2960b2fa508e524a035818b390f99e574Diego Perez
55373259f2960b2fa508e524a035818b390f99e574Diego Perez    TimedStatement(Statement statement, int warmUpIterations, int runs,
56373259f2960b2fa508e524a035818b390f99e574Diego Perez            Consumer<TimedStatementResult> finishedCallback) {
57373259f2960b2fa508e524a035818b390f99e574Diego Perez        mStatement = statement;
58373259f2960b2fa508e524a035818b390f99e574Diego Perez        mWarmUpIterations = warmUpIterations;
59373259f2960b2fa508e524a035818b390f99e574Diego Perez        mRuns = runs;
60373259f2960b2fa508e524a035818b390f99e574Diego Perez        mCallback = finishedCallback;
61373259f2960b2fa508e524a035818b390f99e574Diego Perez    }
62373259f2960b2fa508e524a035818b390f99e574Diego Perez
63373259f2960b2fa508e524a035818b390f99e574Diego Perez    /**
64373259f2960b2fa508e524a035818b390f99e574Diego Perez     * The calibrate method tries to do some work that involves IO, memory allocations and some
65373259f2960b2fa508e524a035818b390f99e574Diego Perez     * operations on the randomly generated data to calibrate the speed of the machine with
66373259f2960b2fa508e524a035818b390f99e574Diego Perez     * something that resembles the execution of a test case.
67373259f2960b2fa508e524a035818b390f99e574Diego Perez     */
68373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static void calibrateMethod() throws IOException {
69373259f2960b2fa508e524a035818b390f99e574Diego Perez        File tmpFile = File.createTempFile("test", "file");
70373259f2960b2fa508e524a035818b390f99e574Diego Perez        Random rnd = new Random();
71373259f2960b2fa508e524a035818b390f99e574Diego Perez        HashFunction hashFunction = Hashing.sha512();
72373259f2960b2fa508e524a035818b390f99e574Diego Perez        for (int i = 0; i < 5 + rnd.nextInt(5); i++) {
73373259f2960b2fa508e524a035818b390f99e574Diego Perez            FileOutputStream stream = new FileOutputStream(tmpFile);
74373259f2960b2fa508e524a035818b390f99e574Diego Perez            int bytes = 30000 + rnd.nextInt(60000);
75373259f2960b2fa508e524a035818b390f99e574Diego Perez            byte[] buffer = new byte[bytes];
76373259f2960b2fa508e524a035818b390f99e574Diego Perez
77373259f2960b2fa508e524a035818b390f99e574Diego Perez            rnd.nextBytes(buffer);
78373259f2960b2fa508e524a035818b390f99e574Diego Perez            byte acc = 0;
79373259f2960b2fa508e524a035818b390f99e574Diego Perez            for (int j = 0; j < bytes; j++) {
80373259f2960b2fa508e524a035818b390f99e574Diego Perez                acc += buffer[i];
81373259f2960b2fa508e524a035818b390f99e574Diego Perez            }
82373259f2960b2fa508e524a035818b390f99e574Diego Perez            buffer[0] = acc;
83373259f2960b2fa508e524a035818b390f99e574Diego Perez            stream.write(buffer);
84373259f2960b2fa508e524a035818b390f99e574Diego Perez            System.gc();
85373259f2960b2fa508e524a035818b390f99e574Diego Perez            stream.close();
86373259f2960b2fa508e524a035818b390f99e574Diego Perez            FileInputStream input = new FileInputStream(tmpFile);
87373259f2960b2fa508e524a035818b390f99e574Diego Perez            byte[] readBuffer = new byte[bytes];
88373259f2960b2fa508e524a035818b390f99e574Diego Perez            //noinspection ResultOfMethodCallIgnored
89373259f2960b2fa508e524a035818b390f99e574Diego Perez            input.read(readBuffer);
90373259f2960b2fa508e524a035818b390f99e574Diego Perez            buffer = readBuffer;
91373259f2960b2fa508e524a035818b390f99e574Diego Perez            HashCode code1 = hashFunction.hashBytes(buffer);
92373259f2960b2fa508e524a035818b390f99e574Diego Perez            Arrays.sort(buffer);
93373259f2960b2fa508e524a035818b390f99e574Diego Perez            HashCode code2 = hashFunction.hashBytes(buffer);
94373259f2960b2fa508e524a035818b390f99e574Diego Perez            input.close();
95373259f2960b2fa508e524a035818b390f99e574Diego Perez
96373259f2960b2fa508e524a035818b390f99e574Diego Perez            FileOutputStream hashStream = new FileOutputStream(tmpFile);
97373259f2960b2fa508e524a035818b390f99e574Diego Perez            hashStream.write(code1.asBytes());
98373259f2960b2fa508e524a035818b390f99e574Diego Perez            hashStream.write(code2.asBytes());
99373259f2960b2fa508e524a035818b390f99e574Diego Perez            hashStream.close();
100373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
101373259f2960b2fa508e524a035818b390f99e574Diego Perez    }
102373259f2960b2fa508e524a035818b390f99e574Diego Perez
103373259f2960b2fa508e524a035818b390f99e574Diego Perez    /**
104373259f2960b2fa508e524a035818b390f99e574Diego Perez     * Runs the calibration process and sets the calibration measure in {@link #sCalibrated}
105373259f2960b2fa508e524a035818b390f99e574Diego Perez     */
106373259f2960b2fa508e524a035818b390f99e574Diego Perez    private static void doCalibration() throws IOException {
107373259f2960b2fa508e524a035818b390f99e574Diego Perez        System.out.println("Calibrating ...");
108373259f2960b2fa508e524a035818b390f99e574Diego Perez        TestUtils.gc();
109373259f2960b2fa508e524a035818b390f99e574Diego Perez        for (int i = 0; i < CALIBRATION_WARMUP_ITERATIONS; i++) {
110373259f2960b2fa508e524a035818b390f99e574Diego Perez            calibrateMethod();
111373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
112373259f2960b2fa508e524a035818b390f99e574Diego Perez
113373259f2960b2fa508e524a035818b390f99e574Diego Perez        LongStatsCollector stats = new LongStatsCollector(CALIBRATION_RUNS);
114373259f2960b2fa508e524a035818b390f99e574Diego Perez        for (int i = 0; i < CALIBRATION_RUNS; i++) {
115373259f2960b2fa508e524a035818b390f99e574Diego Perez            TestUtils.gc();
116373259f2960b2fa508e524a035818b390f99e574Diego Perez            long start = System.currentTimeMillis();
117373259f2960b2fa508e524a035818b390f99e574Diego Perez            calibrateMethod();
118373259f2960b2fa508e524a035818b390f99e574Diego Perez            stats.accept(System.currentTimeMillis() - start);
119373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
120373259f2960b2fa508e524a035818b390f99e574Diego Perez
121373259f2960b2fa508e524a035818b390f99e574Diego Perez        sCalibrated = stats.getStats().getMedian();
122373259f2960b2fa508e524a035818b390f99e574Diego Perez        sIsCalibrated = true;
123373259f2960b2fa508e524a035818b390f99e574Diego Perez        System.out.printf("  DONE %fms\n", sCalibrated);
124373259f2960b2fa508e524a035818b390f99e574Diego Perez    }
125373259f2960b2fa508e524a035818b390f99e574Diego Perez
126373259f2960b2fa508e524a035818b390f99e574Diego Perez    private long getUsedMemory() {
127373259f2960b2fa508e524a035818b390f99e574Diego Perez        return mRuntime.totalMemory() - mRuntime.freeMemory();
128373259f2960b2fa508e524a035818b390f99e574Diego Perez    }
129373259f2960b2fa508e524a035818b390f99e574Diego Perez
130373259f2960b2fa508e524a035818b390f99e574Diego Perez
131373259f2960b2fa508e524a035818b390f99e574Diego Perez    @Override
132373259f2960b2fa508e524a035818b390f99e574Diego Perez    public void evaluate() throws Throwable {
133373259f2960b2fa508e524a035818b390f99e574Diego Perez        if (!sIsCalibrated) {
134373259f2960b2fa508e524a035818b390f99e574Diego Perez            doCalibration();
135373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
136373259f2960b2fa508e524a035818b390f99e574Diego Perez
137373259f2960b2fa508e524a035818b390f99e574Diego Perez        for (int i = 0; i < mWarmUpIterations; i++) {
138373259f2960b2fa508e524a035818b390f99e574Diego Perez            mStatement.evaluate();
139373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
140373259f2960b2fa508e524a035818b390f99e574Diego Perez
141373259f2960b2fa508e524a035818b390f99e574Diego Perez        LongStatsCollector timeStats = new LongStatsCollector(mRuns);
142373259f2960b2fa508e524a035818b390f99e574Diego Perez        LongStatsCollector memoryUseStats = new LongStatsCollector(mRuns);
143373259f2960b2fa508e524a035818b390f99e574Diego Perez        AtomicBoolean collectSamples = new AtomicBoolean(false);
144373259f2960b2fa508e524a035818b390f99e574Diego Perez
145373259f2960b2fa508e524a035818b390f99e574Diego Perez        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
146373259f2960b2fa508e524a035818b390f99e574Diego Perez        TestUtils.gc();
147373259f2960b2fa508e524a035818b390f99e574Diego Perez        executorService.scheduleAtFixedRate(() -> {
148373259f2960b2fa508e524a035818b390f99e574Diego Perez            if (!collectSamples.get()) {
149373259f2960b2fa508e524a035818b390f99e574Diego Perez                return;
150373259f2960b2fa508e524a035818b390f99e574Diego Perez            }
151373259f2960b2fa508e524a035818b390f99e574Diego Perez            memoryUseStats.accept(getUsedMemory());
152373259f2960b2fa508e524a035818b390f99e574Diego Perez        }, 0, 200, TimeUnit.MILLISECONDS);
153373259f2960b2fa508e524a035818b390f99e574Diego Perez
154373259f2960b2fa508e524a035818b390f99e574Diego Perez        try {
155373259f2960b2fa508e524a035818b390f99e574Diego Perez            for (int i = 0; i < mRuns; i++) {
156373259f2960b2fa508e524a035818b390f99e574Diego Perez                TestUtils.gc();
157373259f2960b2fa508e524a035818b390f99e574Diego Perez                collectSamples.set(true);
158373259f2960b2fa508e524a035818b390f99e574Diego Perez                long startTimeMs = System.currentTimeMillis();
159373259f2960b2fa508e524a035818b390f99e574Diego Perez                mStatement.evaluate();
160373259f2960b2fa508e524a035818b390f99e574Diego Perez                long stopTimeMs = System.currentTimeMillis();
161373259f2960b2fa508e524a035818b390f99e574Diego Perez                collectSamples.set(true);
162373259f2960b2fa508e524a035818b390f99e574Diego Perez                timeStats.accept(stopTimeMs - startTimeMs);
163373259f2960b2fa508e524a035818b390f99e574Diego Perez
164373259f2960b2fa508e524a035818b390f99e574Diego Perez            }
165373259f2960b2fa508e524a035818b390f99e574Diego Perez        } finally {
166373259f2960b2fa508e524a035818b390f99e574Diego Perez            executorService.shutdownNow();
167373259f2960b2fa508e524a035818b390f99e574Diego Perez        }
168373259f2960b2fa508e524a035818b390f99e574Diego Perez
169373259f2960b2fa508e524a035818b390f99e574Diego Perez        TimedStatementResult result = new TimedStatementResult(
170373259f2960b2fa508e524a035818b390f99e574Diego Perez                mWarmUpIterations,
171373259f2960b2fa508e524a035818b390f99e574Diego Perez                mRuns,
172373259f2960b2fa508e524a035818b390f99e574Diego Perez                sCalibrated,
173373259f2960b2fa508e524a035818b390f99e574Diego Perez                timeStats.getStats(),
174373259f2960b2fa508e524a035818b390f99e574Diego Perez                memoryUseStats.getStats());
175373259f2960b2fa508e524a035818b390f99e574Diego Perez        mCallback.accept(result);
176373259f2960b2fa508e524a035818b390f99e574Diego Perez    }
177373259f2960b2fa508e524a035818b390f99e574Diego Perez
178373259f2960b2fa508e524a035818b390f99e574Diego Perez}
179