layoutlib_api-prebuilt \
tools-common-prebuilt \
sdk-common \
- junit-host
+ junit-host \
+ guavalib
include $(BUILD_HOST_JAVA_LIBRARY)
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.util.perf.PerformanceRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.annotation.NonNull;
+
+/**
+ * Set of render tests
+ */
+@RunWith(PerformanceRunner.class)
+public class PerformanceTests extends RenderTestBase {
+
+ @Before
+ public void setUp() {
+ ignoreAllLogging();
+ }
+
+
+ private void render(@NonNull String layoutFileName) throws ClassNotFoundException {
+ SessionParams params = createSessionParams(layoutFileName, ConfigGenerator.NEXUS_5);
+ render(params, 250);
+ }
+
+ @Test
+ public void testActivity() throws ClassNotFoundException {
+ render("activity.xml");
+ }
+
+ @Test
+ public void testAllWidgets() throws ClassNotFoundException {
+ render("allwidgets.xml");
+ }
+}
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.layoutlib.bridge.intensive.util.ImageUtils;
import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.layoutlib.bridge.intensive.util.TestUtils;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.utils.ILogger;
import java.io.File;
import java.io.IOException;
-import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
* Initialize the bridge and the resource maps.
*/
@BeforeClass
- public static void setUp() {
+ public static void beforeClass() {
File data_dir = new File(PLATFORM_DIR, "data");
File res = new File(data_dir, "res");
sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
}
}
- private static void gc() {
- // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
- Object obj = new Object();
- WeakReference ref = new WeakReference<>(obj);
- //noinspection UnusedAssignment
- obj = null;
- while (ref.get() != null) {
- System.gc();
- System.runFinalization();
- }
-
- System.gc();
- System.runFinalization();
- }
-
@AfterClass
public static void tearDown() {
sLayoutLibLog = null;
sLogger = null;
sBridge = null;
- gc();
+ TestUtils.gc();
System.out.println("Objects still linked from the DelegateManager:");
DelegateManager.dump(System.out);
return sLayoutLibLog;
}
+ protected static void ignoreAllLogging() {
+ sLayoutLibLog = new LayoutLog();
+ sLogger = new ILogger() {
+ @Override
+ public void error(Throwable t, String msgFormat, Object... args) {
+ }
+
+ @Override
+ public void warning(String msgFormat, Object... args) {
+ }
+
+ @Override
+ public void info(String msgFormat, Object... args) {
+ }
+
+ @Override
+ public void verbose(String msgFormat, Object... args) {
+ }
+ };
+ }
+
protected static ILogger getLogger() {
if (sLogger == null) {
sLogger = new ILogger() {
return renderAndVerify(params, goldenFileName);
}
- private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
+ protected SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser(layoutFileName);
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util;
+
+import java.lang.ref.WeakReference;
+
+public class TestUtils {
+ public static void gc() {
+ // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
+ Object obj = new Object();
+ WeakReference ref = new WeakReference<>(obj);
+ //noinspection UnusedAssignment
+ obj = null;
+ while (ref.get() != null) {
+ System.gc();
+ System.runFinalization();
+ }
+
+ System.gc();
+ System.runFinalization();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import android.annotation.NonNull;
+import android.util.LongArray;
+
+import java.util.Arrays;
+import java.util.function.LongConsumer;
+
+/**
+ * Class that collect a series of longs and produces the median, min and max values.
+ */
+public class LongStatsCollector implements LongConsumer {
+ private final LongArray mAllValues;
+ private long mMin = Long.MAX_VALUE;
+ private long mMax = Long.MIN_VALUE;
+ public LongStatsCollector(int estimatedRuns) {
+ mAllValues = new LongArray(estimatedRuns);
+ }
+
+ public int size() {
+ return mAllValues.size();
+ }
+
+ @NonNull
+ public Stats getStats() {
+ if (mAllValues.size() == 0) {
+ throw new IndexOutOfBoundsException("No data");
+ }
+
+ double median;
+ int size = mAllValues.size();
+ long[] buffer = new long[size];
+ for (int i = 0; i < size; i++) {
+ buffer[i] = mAllValues.get(i);
+ }
+
+ Arrays.sort(buffer);
+
+ int midPoint = size / 2;
+ median = (size % 2 == 0) ? (buffer[midPoint - 1] + buffer[midPoint]) / 2 : buffer[midPoint];
+
+ return new Stats(mAllValues.size(), mMin, mMax, median);
+ }
+
+ @Override
+ public void accept(long value) {
+ mMin = Math.min(mMin, value);
+ mMax = Math.max(mMax, value);
+ mAllValues.add(value);
+ }
+
+ public static class Stats {
+ private final int mSamples;
+ private final long mMin;
+ private final long mMax;
+ private final double mMedian;
+
+ private Stats(int samples, long min, long max, double median) {
+ mSamples = samples;
+ mMin = min;
+ mMax = max;
+ mMedian = median;
+ }
+
+ public int getSampleCount() {
+ return mSamples;
+ }
+
+ public long getMin() {
+ return mMin;
+ }
+
+ public long getMax() {
+ return mMax;
+ }
+
+ public double getMedian() {
+ return mMedian;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * JUnit {@link Runner} that times the test execution and produces some stats.
+ */
+public class PerformanceRunner extends BlockJUnit4ClassRunner {
+ private static final int DEFAULT_WARMUP_ITERATIONS = 50;
+ private static final int DEFAULT_RUNS = 100;
+
+ private final int mWarmUpIterations;
+ private final int mRuns;
+
+ public PerformanceRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+
+ Configuration classConfig = testClass.getAnnotation(Configuration.class);
+ mWarmUpIterations = classConfig != null && classConfig.warmUpIterations() != -1 ?
+ classConfig.warmUpIterations() :
+ DEFAULT_WARMUP_ITERATIONS;
+ mRuns = classConfig != null && classConfig.runs() != -1 ?
+ classConfig.runs() :
+ DEFAULT_RUNS;
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test) {
+ int warmUpIterations;
+ int runs;
+
+ Configuration methodConfig = method.getAnnotation(Configuration.class);
+ warmUpIterations = methodConfig != null && methodConfig.warmUpIterations() != -1 ?
+ methodConfig.warmUpIterations() :
+ mWarmUpIterations;
+ runs = methodConfig != null && methodConfig.runs() != -1 ?
+ methodConfig.runs() :
+ mRuns;
+ return new TimedStatement(super.methodInvoker(method, test), warmUpIterations, runs,
+ (result) -> System.out.println(result.toString()));
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ super.run(notifier);
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ public @interface Configuration {
+ int warmUpIterations() default -1;
+
+ int runs() default -1;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import com.android.layoutlib.bridge.intensive.util.TestUtils;
+
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+
+/**
+ * JUnit {@link Statement} used to measure some statistics about the test method.
+ */
+public class TimedStatement extends Statement {
+ private static final int CALIBRATION_WARMUP_ITERATIONS = 50;
+ private static final int CALIBRATION_RUNS = 100;
+
+ private static boolean sIsCalibrated;
+ private static double sCalibrated;
+
+ private final Statement mStatement;
+ private final int mWarmUpIterations;
+ private final int mRuns;
+ private final Runtime mRuntime = Runtime.getRuntime();
+ private final Consumer<TimedStatementResult> mCallback;
+
+ TimedStatement(Statement statement, int warmUpIterations, int runs,
+ Consumer<TimedStatementResult> finishedCallback) {
+ mStatement = statement;
+ mWarmUpIterations = warmUpIterations;
+ mRuns = runs;
+ mCallback = finishedCallback;
+ }
+
+ /**
+ * The calibrate method tries to do some work that involves IO, memory allocations and some
+ * operations on the randomly generated data to calibrate the speed of the machine with
+ * something that resembles the execution of a test case.
+ */
+ private static void calibrateMethod() throws IOException {
+ File tmpFile = File.createTempFile("test", "file");
+ Random rnd = new Random();
+ HashFunction hashFunction = Hashing.sha512();
+ for (int i = 0; i < 5 + rnd.nextInt(5); i++) {
+ FileOutputStream stream = new FileOutputStream(tmpFile);
+ int bytes = 30000 + rnd.nextInt(60000);
+ byte[] buffer = new byte[bytes];
+
+ rnd.nextBytes(buffer);
+ byte acc = 0;
+ for (int j = 0; j < bytes; j++) {
+ acc += buffer[i];
+ }
+ buffer[0] = acc;
+ stream.write(buffer);
+ System.gc();
+ stream.close();
+ FileInputStream input = new FileInputStream(tmpFile);
+ byte[] readBuffer = new byte[bytes];
+ //noinspection ResultOfMethodCallIgnored
+ input.read(readBuffer);
+ buffer = readBuffer;
+ HashCode code1 = hashFunction.hashBytes(buffer);
+ Arrays.sort(buffer);
+ HashCode code2 = hashFunction.hashBytes(buffer);
+ input.close();
+
+ FileOutputStream hashStream = new FileOutputStream(tmpFile);
+ hashStream.write(code1.asBytes());
+ hashStream.write(code2.asBytes());
+ hashStream.close();
+ }
+ }
+
+ /**
+ * Runs the calibration process and sets the calibration measure in {@link #sCalibrated}
+ */
+ private static void doCalibration() throws IOException {
+ System.out.println("Calibrating ...");
+ TestUtils.gc();
+ for (int i = 0; i < CALIBRATION_WARMUP_ITERATIONS; i++) {
+ calibrateMethod();
+ }
+
+ LongStatsCollector stats = new LongStatsCollector(CALIBRATION_RUNS);
+ for (int i = 0; i < CALIBRATION_RUNS; i++) {
+ TestUtils.gc();
+ long start = System.currentTimeMillis();
+ calibrateMethod();
+ stats.accept(System.currentTimeMillis() - start);
+ }
+
+ sCalibrated = stats.getStats().getMedian();
+ sIsCalibrated = true;
+ System.out.printf(" DONE %fms\n", sCalibrated);
+ }
+
+ private long getUsedMemory() {
+ return mRuntime.totalMemory() - mRuntime.freeMemory();
+ }
+
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (!sIsCalibrated) {
+ doCalibration();
+ }
+
+ for (int i = 0; i < mWarmUpIterations; i++) {
+ mStatement.evaluate();
+ }
+
+ LongStatsCollector timeStats = new LongStatsCollector(mRuns);
+ LongStatsCollector memoryUseStats = new LongStatsCollector(mRuns);
+ AtomicBoolean collectSamples = new AtomicBoolean(false);
+
+ ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
+ TestUtils.gc();
+ executorService.scheduleAtFixedRate(() -> {
+ if (!collectSamples.get()) {
+ return;
+ }
+ memoryUseStats.accept(getUsedMemory());
+ }, 0, 200, TimeUnit.MILLISECONDS);
+
+ try {
+ for (int i = 0; i < mRuns; i++) {
+ TestUtils.gc();
+ collectSamples.set(true);
+ long startTimeMs = System.currentTimeMillis();
+ mStatement.evaluate();
+ long stopTimeMs = System.currentTimeMillis();
+ collectSamples.set(true);
+ timeStats.accept(stopTimeMs - startTimeMs);
+
+ }
+ } finally {
+ executorService.shutdownNow();
+ }
+
+ TimedStatementResult result = new TimedStatementResult(
+ mWarmUpIterations,
+ mRuns,
+ sCalibrated,
+ timeStats.getStats(),
+ memoryUseStats.getStats());
+ mCallback.accept(result);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import com.android.layoutlib.bridge.intensive.util.perf.LongStatsCollector.Stats;
+
+import java.text.DecimalFormat;
+
+/**
+ * Result value of a {@link TimedStatement}
+ */
+public class TimedStatementResult {
+ private static final DecimalFormat UNITS_FORMAT = new DecimalFormat("#.##");
+
+ private final int mWarmUpIterations;
+ private final int mRuns;
+ private final double mCalibrationTimeMs;
+ private final Stats mTimeStats;
+ private final Stats mMemoryStats;
+
+ TimedStatementResult(int warmUpIterations, int runs,
+ double calibrationTimeMs,
+ Stats timeStats,
+ Stats memoryStats) {
+ mWarmUpIterations = warmUpIterations;
+ mRuns = runs;
+ mCalibrationTimeMs = calibrationTimeMs;
+ mTimeStats = timeStats;
+ mMemoryStats = memoryStats;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Warm up %d. Runs %d\n" + "Time: %s ms (min: %s, max %s)\n" +
+ "Calibration Time: %f ms\n" +
+ "Calibrated Time: %s units (min: %s, max %s)\n" +
+ "Sampled %d times\n" +
+ " Memory used: %d bytes (max %d)\n\n",
+ mWarmUpIterations, mRuns,
+ mTimeStats.getMedian(), mTimeStats.getMin(), mTimeStats.getMax(),
+ mCalibrationTimeMs,
+ UNITS_FORMAT.format((mTimeStats.getMedian() / mCalibrationTimeMs) * 100000),
+ UNITS_FORMAT.format((mTimeStats.getMin() / mCalibrationTimeMs) * 100000),
+ UNITS_FORMAT.format((mTimeStats.getMax() / mCalibrationTimeMs) * 100000),
+ mMemoryStats.getSampleCount(),
+ (long)mMemoryStats.getMedian() - mMemoryStats.getMin(),
+ mMemoryStats.getMax() - mMemoryStats.getMin());
+ }
+}