From: Diego Perez Date: Wed, 14 Dec 2016 17:03:15 +0000 (+0000) Subject: Adds test to collect performance metrics X-Git-Tag: android-x86-8.1-r1~5378^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=373259f2960b2fa508e524a035818b390f99e574;p=android-x86%2Fframeworks-base.git Adds test to collect performance metrics Test: Adds performance tests Change-Id: I7d112ddad4efb2c1c84ac177d230225cc031f7f3 --- diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 9ee416a3b4ca..33d55dea6f79 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -30,7 +30,8 @@ LOCAL_JAVA_LIBRARIES := layoutlib \ layoutlib_api-prebuilt \ tools-common-prebuilt \ sdk-common \ - junit-host + junit-host \ + guavalib include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java new file mode 100644 index 000000000000..c90c26aad8ec --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java @@ -0,0 +1,55 @@ +/* + * 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"); + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java index f622c218dbdd..3e5f9e074fef 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java @@ -35,6 +35,7 @@ import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback; 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; @@ -50,7 +51,6 @@ import android.annotation.Nullable; 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; @@ -272,7 +272,7 @@ public class RenderTestBase { * 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)); @@ -304,21 +304,6 @@ public class RenderTestBase { } } - 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; @@ -327,7 +312,7 @@ public class RenderTestBase { sLogger = null; sBridge = null; - gc(); + TestUtils.gc(); System.out.println("Objects still linked from the DelegateManager:"); DelegateManager.dump(System.out); @@ -435,6 +420,27 @@ public class RenderTestBase { 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() { @@ -500,7 +506,7 @@ public class RenderTestBase { 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); diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java new file mode 100644 index 000000000000..1df8e7978ba8 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java @@ -0,0 +1,36 @@ +/* + * 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(); + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java new file mode 100644 index 000000000000..ee98b4ba3b65 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java @@ -0,0 +1,97 @@ +/* + * 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; + } + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java new file mode 100644 index 000000000000..7225a10dbed4 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java @@ -0,0 +1,80 @@ +/* + * 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; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java new file mode 100644 index 000000000000..77a2b0e50920 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java @@ -0,0 +1,178 @@ +/* + * 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 mCallback; + + TimedStatement(Statement statement, int warmUpIterations, int runs, + Consumer 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); + } + +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java new file mode 100644 index 000000000000..59f90d295436 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java @@ -0,0 +1,64 @@ +/* + * 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()); + } +}