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
17package com.android.layoutlib.bridge.intensive.util.perf;
18
19import com.android.layoutlib.bridge.intensive.util.TestUtils;
20
21import org.junit.runners.model.Statement;
22
23import java.io.File;
24import java.io.FileInputStream;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.util.Arrays;
28import java.util.Random;
29import java.util.concurrent.Executors;
30import java.util.concurrent.ScheduledExecutorService;
31import java.util.concurrent.TimeUnit;
32import java.util.concurrent.atomic.AtomicBoolean;
33import java.util.function.Consumer;
34
35import com.google.common.hash.HashCode;
36import com.google.common.hash.HashFunction;
37import com.google.common.hash.Hashing;
38
39/**
40 * JUnit {@link Statement} used to measure some statistics about the test method.
41 */
42public 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
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     */
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     */
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
126    private long getUsedMemory() {
127        return mRuntime.totalMemory() - mRuntime.freeMemory();
128    }
129
130
131    @Override
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