From 73033b74bdd1eea8987d6a55ab55d8bd6cb041d0 Mon Sep 17 00:00:00 2001 From: johnsonlee Date: Sat, 18 Jul 2020 19:40:46 +0800 Subject: [PATCH] Improves thread renaming and optimization --- booster-android-api/build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../build.gradle | 4 +- .../NamedForkJoinWorkerThreadFactory.java | 34 ++ .../instrument/NamedThreadFactory.java | 23 +- .../booster/instrument/ShadowExecutors.java | 37 +++ .../instrument/ShadowHandlerThread.java | 26 +- .../booster/instrument/ShadowThread.java | 97 +++++- .../instrument/ShadowThreadPoolExecutor.java | 273 +++++++++++++++- .../booster/instrument/ShadowTimer.java | 45 ++- booster-android-instrument-toast/build.gradle | 4 +- .../build.gradle | 4 +- booster-android-instrument/build.gradle | 4 +- .../booster/transform/TransformException.kt | 15 + .../transform/thread/ThreadTransformer.kt | 309 ++++++++++++++---- build.gradle | 2 +- 21 files changed, 810 insertions(+), 95 deletions(-) create mode 100644 booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedForkJoinWorkerThreadFactory.java create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformException.kt diff --git a/booster-android-api/build.gradle b/booster-android-api/build.gradle index 16ddebc..ab97a31 100644 --- a/booster-android-api/build.gradle +++ b/booster-android-api/build.gradle @@ -1,2 +1,2 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/booster-android-instrument-activity-thread/build.gradle b/booster-android-instrument-activity-thread/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-activity-thread/build.gradle +++ b/booster-android-instrument-activity-thread/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-finalizer-watchdog-daemon/build.gradle b/booster-android-instrument-finalizer-watchdog-daemon/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-finalizer-watchdog-daemon/build.gradle +++ b/booster-android-instrument-finalizer-watchdog-daemon/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-logcat/build.gradle b/booster-android-instrument-logcat/build.gradle index 22a4e38..174bd61 100644 --- a/booster-android-instrument-logcat/build.gradle +++ b/booster-android-instrument-logcat/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-media-player/build.gradle b/booster-android-instrument-media-player/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-media-player/build.gradle +++ b/booster-android-instrument-media-player/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-res-check/build.gradle b/booster-android-instrument-res-check/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-res-check/build.gradle +++ b/booster-android-instrument-res-check/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-shared-preferences/build.gradle b/booster-android-instrument-shared-preferences/build.gradle index fdb94b2..4ad9560 100644 --- a/booster-android-instrument-shared-preferences/build.gradle +++ b/booster-android-instrument-shared-preferences/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-thread/build.gradle b/booster-android-instrument-thread/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-thread/build.gradle +++ b/booster-android-instrument-thread/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedForkJoinWorkerThreadFactory.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedForkJoinWorkerThreadFactory.java new file mode 100644 index 0000000..c473ab6 --- /dev/null +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedForkJoinWorkerThreadFactory.java @@ -0,0 +1,34 @@ +package com.didiglobal.booster.instrument; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; + +import static com.didiglobal.booster.instrument.ShadowThread.makeThreadName; + +/** + * @author johnsonlee + */ +public class NamedForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + + private final String name; + private final ForkJoinPool.ForkJoinWorkerThreadFactory factory; + + public NamedForkJoinWorkerThreadFactory(final ForkJoinPool.ForkJoinWorkerThreadFactory factory, final String name) { + this.factory = factory; + this.name = name; + } + + public NamedForkJoinWorkerThreadFactory(final String name) { + this(null, name); + } + + @Override + public ForkJoinWorkerThread newThread(final ForkJoinPool pool) { + final ForkJoinWorkerThread thread = (null != this.factory) + ? factory.newThread(pool) + : new ForkJoinWorkerThread(pool) {}; + thread.setName(makeThreadName(thread.getName(), this.name)); + return thread; + } + +} diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedThreadFactory.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedThreadFactory.java index d59a9b6..48b2487 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedThreadFactory.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/NamedThreadFactory.java @@ -5,7 +5,28 @@ import java.util.concurrent.atomic.AtomicInteger; import static com.didiglobal.booster.instrument.ShadowThread.setThreadName; -class NamedThreadFactory implements ThreadFactory { +public class NamedThreadFactory implements ThreadFactory { + + /** + * Used by {@code ThreadTransformer} for thread renaming + * + * @param prefix the prefix of name + * @return an instance of ThreadFactory + */ + public static ThreadFactory newInstance(final String prefix) { + return new NamedThreadFactory(prefix); + } + + /** + * Used by {@code ThreadTransformer} for thread renaming + * + * @param factory the factory delegate + * @param prefix the prefix of name + * @return an instance of ThreadFactory + */ + public static ThreadFactory newInstance(final ThreadFactory factory, final String prefix) { + return new NamedThreadFactory(factory, prefix); + } private final String name; private final AtomicInteger counter = new AtomicInteger(1); diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowExecutors.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowExecutors.java index 5cd31ce..6ba92e4 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowExecutors.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowExecutors.java @@ -7,6 +7,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; @@ -107,6 +108,30 @@ public class ShadowExecutors { // + // + + public static ExecutorService newWorkStealingPool(final String name) { + return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), new NamedForkJoinWorkerThreadFactory(name), null, true); + } + + public static ExecutorService newWorkStealingPool(final int parallelism, final String name) { + return new ForkJoinPool(parallelism, new NamedForkJoinWorkerThreadFactory(name), null, true); + } + + // + + // + + public static ExecutorService newOptimizedFixedThreadPool(final int nThreads, final String name) { + return Executors.newFixedThreadPool(nThreads, new NamedThreadFactory(name)); + } + + public static ExecutorService newOptimizedFixedThreadPool(final int nThreads, final ThreadFactory factory, final String name) { + return Executors.newFixedThreadPool(nThreads, new NamedThreadFactory(factory, name)); + } + + // + // public static ScheduledExecutorService newOptimizedSingleThreadScheduledExecutor(final String name) { @@ -175,6 +200,18 @@ public class ShadowExecutors { // + // + + public static ExecutorService newOptimizedWorkStealingPool(final String name) { + return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), new NamedForkJoinWorkerThreadFactory(name), null, true); + } + + public static ExecutorService newOptimizedWorkStealingPool(final int parallelism, final String name) { + return new ForkJoinPool(parallelism, new NamedForkJoinWorkerThreadFactory(name), null, true); + } + + // + private static class DelegatedExecutorService extends AbstractExecutorService { private final ExecutorService executor; diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowHandlerThread.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowHandlerThread.java index c9d524d..3d226f6 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowHandlerThread.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowHandlerThread.java @@ -5,7 +5,10 @@ import android.os.HandlerThread; import static com.didiglobal.booster.instrument.ShadowThread.makeThreadName; import static java.lang.Math.min; -public class ShadowHandlerThread { +/** + * @author johnsonlee + */ +public class ShadowHandlerThread extends HandlerThread { public static HandlerThread newHandlerThread(final String name, final String prefix) { return new HandlerThread(makeThreadName(name, prefix)); @@ -15,4 +18,25 @@ public class ShadowHandlerThread { return new HandlerThread(makeThreadName(name, prefix), min(android.os.Process.THREAD_PRIORITY_DEFAULT, priority)); } + /** + * Initialize {@code HandlerThread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowHandlerThread(final String name, final String prefix) { + super(makeThreadName(name, prefix)); + } + + /** + * Initialize {@code HandlerThread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param name the original name + * @param priority the thread priority + * @param prefix the prefix of new name + */ + public ShadowHandlerThread(final String name, final int priority, final String prefix) { + super(makeThreadName(name, prefix), priority); + } + } diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThread.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThread.java index c81d381..4a1c727 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThread.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThread.java @@ -1,6 +1,9 @@ package com.didiglobal.booster.instrument; -public class ShadowThread { +/** + * @author johnsonlee + */ +public class ShadowThread extends Thread { /** * {@code U+200B}: Zero-Width Space @@ -52,4 +55,96 @@ public class ShadowThread { return name == null ? prefix : (name.startsWith(MARK) ? name : (prefix + "#" + name)); } + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param prefix the new name + */ + public ShadowThread(final String prefix) { + super(makeThreadName(prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param target the object whose {@code run} method is invoked when this thread is started. + * If {@code null}, this thread's run method is invoked. + * @param prefix the new name + */ + public ShadowThread(final Runnable target, final String prefix) { + super(target, makeThreadName(prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param group the thread group + * @param target the object whose {@code run} method is invoked when this thread is started. + * If {@code null}, this thread's run method is invoked. + * @param prefix the new name + */ + public ShadowThread(final ThreadGroup group, final Runnable target, final String prefix) { + super(group, target, makeThreadName(prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowThread(final String name, final String prefix) { + super(makeThreadName(name, prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param group the thread group + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowThread(final ThreadGroup group, final String name, final String prefix) { + super(group, makeThreadName(name, prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param target the object whose {@code run} method is invoked when this thread is started. + * If {@code null}, this thread's run method is invoked. + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowThread(final Runnable target, final String name, final String prefix) { + super(target, makeThreadName(name, prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param group the thread group + * @param target the object whose {@code run} method is invoked when this thread is started. + * If {@code null}, this thread's run method is invoked. + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowThread(final ThreadGroup group, final Runnable target, final String name, final String prefix) { + super(group, target, makeThreadName(name, prefix)); + } + + /** + * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming + * + * @param group the thread group + * @param target the object whose {@code run} method is invoked when this thread is started. + * If {@code null}, this thread's run method is invoked. + * @param name the original name + * @param stackSize the desired stack size for the new thread, or zero to indicate that this parameter is to be ignored. + * @param prefix the prefix of new name + */ + public ShadowThread(final ThreadGroup group, final Runnable target, final String name, final long stackSize, final String prefix) { + super(group, target, makeThreadName(name, prefix), stackSize); + } + } diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThreadPoolExecutor.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThreadPoolExecutor.java index 267760a..76a8b5d 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThreadPoolExecutor.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowThreadPoolExecutor.java @@ -6,7 +6,10 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -public class ShadowThreadPoolExecutor { +/** + * @author johnsonlee + */ +public class ShadowThreadPoolExecutor extends ThreadPoolExecutor { // @@ -84,4 +87,272 @@ public class ShadowThreadPoolExecutor { // + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param prefix the prefix of new thread + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final String prefix + ) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, prefix, false); + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param prefix the prefix of new thread + * @param optimize the value indicates that the thread pool optimization should be applied + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final String prefix, + final boolean optimize + ) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NamedThreadFactory(prefix)); + if (optimize) { + allowCoreThreadTimeOut(getKeepAliveTime(unit) > 0); + } + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param threadFactory the factory to use when the executor creates a new thread + * @param prefix the prefix of new thread + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final ThreadFactory threadFactory, + final String prefix + ) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, prefix, false); + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param threadFactory the factory to use when the executor creates a new thread + * @param prefix the prefix of new thread + * @param optimize the value indicates that the thread pool optimization should be applied + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final ThreadFactory threadFactory, + final String prefix, + final boolean optimize + ) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NamedThreadFactory(threadFactory, prefix)); + if (optimize) { + allowCoreThreadTimeOut(getKeepAliveTime(unit) > 0); + } + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached + * @param prefix the prefix of new thread + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final RejectedExecutionHandler handler, + final String prefix + ) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler, prefix, false); + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached + * @param prefix the prefix of new thread + * @param optimize the value indicates that the thread pool optimization should be applied + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final RejectedExecutionHandler handler, + final String prefix, + final boolean optimize + ) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NamedThreadFactory(prefix), handler); + if (optimize) { + allowCoreThreadTimeOut(getKeepAliveTime(unit) > 0); + } + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param threadFactory the factory to use when the executor creates a new thread + * @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached + * @param prefix the prefix of new thread + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final ThreadFactory threadFactory, + final RejectedExecutionHandler handler, + final String prefix + ) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler, prefix, false); + } + + /** + * Initialize {@code ThreadPoolExecutor} with new thread name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param corePoolSize the number of threads to keep in the pool, even if they are idle, + * unless {@code allowCoreThreadTimeOut} is set + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime when the number of threads is greater than the core, + * this is the maximum time that excess idle threads will wait for new tasks before terminating. + * @param unit the time unit for the {@code keepAliveTime} argument + * @param workQueue the queue to use for holding tasks before they are executed. + * This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method. + * @param threadFactory the factory to use when the executor creates a new thread + * @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached + * @param prefix the prefix of new thread + * @param optimize the value indicates that the thread pool optimization should be applied + * @throws IllegalArgumentException if one of the following holds:
+ * {@code corePoolSize < 0}
+ * {@code keepAliveTime < 0}
+ * {@code maximumPoolSize <= 0}
+ * {@code maximumPoolSize < corePoolSize} + */ + public ShadowThreadPoolExecutor( + final int corePoolSize, + final int maximumPoolSize, + final long keepAliveTime, + final TimeUnit unit, + final BlockingQueue workQueue, + final ThreadFactory threadFactory, + final RejectedExecutionHandler handler, + final String prefix, + final boolean optimize + ) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NamedThreadFactory(threadFactory, prefix), handler); + if (optimize) { + allowCoreThreadTimeOut(getKeepAliveTime(unit) > 0); + } + } + } diff --git a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowTimer.java b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowTimer.java index 59a4177..a8908d3 100644 --- a/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowTimer.java +++ b/booster-android-instrument-thread/src/main/java/com/didiglobal/booster/instrument/ShadowTimer.java @@ -4,7 +4,10 @@ import java.util.Timer; import static com.didiglobal.booster.instrument.ShadowThread.makeThreadName; -public class ShadowTimer { +/** + * @author johnsonlee + */ +public class ShadowTimer extends Timer { public static Timer newTimer(final String name) { return new Timer(name); @@ -22,4 +25,44 @@ public class ShadowTimer { return new Timer(makeThreadName(name, prefix), isDaemon); } + /** + * Initialize {@code Timer} with new name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param prefix the prefix of new name + */ + public ShadowTimer(final String prefix) { + super(prefix); + } + + /** + * Initialize {@code Timer} with new name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param isDaemon true if the associated thread should run as a daemon + * @param prefix the prefix of new name + */ + public ShadowTimer(final boolean isDaemon, final String prefix) { + super(prefix, isDaemon); + } + + /** + * Initialize {@code Timer} with new name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param name the original name + * @param prefix the prefix of new name + */ + public ShadowTimer(final String name, final String prefix) { + super(makeThreadName(name, prefix)); + } + + /** + * Initialize {@code Timer} with new name, this constructor is used by {@code ThreadTransformer} for thread renaming + * + * @param name the original name + * @param isDaemon true if the associated thread should run as a daemon + * @param prefix the prefix of new name + */ + public ShadowTimer(final String name, final boolean isDaemon, final String prefix) { + super(makeThreadName(name, prefix), isDaemon); + } + } diff --git a/booster-android-instrument-toast/build.gradle b/booster-android-instrument-toast/build.gradle index 626e9dd..2e34559 100644 --- a/booster-android-instrument-toast/build.gradle +++ b/booster-android-instrument-toast/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument-webview/build.gradle b/booster-android-instrument-webview/build.gradle index c3377ae..30c2ad7 100644 --- a/booster-android-instrument-webview/build.gradle +++ b/booster-android-instrument-webview/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-android-instrument/build.gradle b/booster-android-instrument/build.gradle index 22a4e38..174bd61 100644 --- a/booster-android-instrument/build.gradle +++ b/booster-android-instrument/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly project(':booster-android-api') diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformException.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformException.kt new file mode 100644 index 0000000..b764179 --- /dev/null +++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformException.kt @@ -0,0 +1,15 @@ +package com.didiglobal.booster.transform + +class TransformException : Exception { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + + constructor(cause: Throwable) : super(cause) + + constructor(message: String, cause: Throwable, enableSuppression: Boolean, writableStackTrace: Boolean) + : super(message, cause, enableSuppression, writableStackTrace) +} \ No newline at end of file diff --git a/booster-transform-thread/src/main/kotlin/com/didiglobal/booster/transform/thread/ThreadTransformer.kt b/booster-transform-thread/src/main/kotlin/com/didiglobal/booster/transform/thread/ThreadTransformer.kt index 2c93df6..82cc6dd 100644 --- a/booster-transform-thread/src/main/kotlin/com/didiglobal/booster/transform/thread/ThreadTransformer.kt +++ b/booster-transform-thread/src/main/kotlin/com/didiglobal/booster/transform/thread/ThreadTransformer.kt @@ -5,6 +5,7 @@ import com.didiglobal.booster.kotlinx.file import com.didiglobal.booster.kotlinx.touch import com.didiglobal.booster.transform.ArtifactManager import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.TransformException import com.didiglobal.booster.transform.asm.ClassTransformer import com.didiglobal.booster.transform.asm.className import com.didiglobal.booster.transform.asm.find @@ -50,7 +51,8 @@ class ThreadTransformer : ClassTransformer { } override fun transform(context: TransformContext, klass: ClassNode): ClassNode { - if (klass.name.startsWith(SHADOW)) { + if (klass.name.startsWith(BOOSTER_INSTRUMENT)) { + // ignore booster instrumented classes return klass } @@ -59,7 +61,7 @@ class ThreadTransformer : ClassTransformer { } klass.methods?.forEach { method -> - method.instructions?.iterator()?.asIterable()?.forEach loop@{ + method.instructions?.iterator()?.asIterable()?.forEach { when (it.opcode) { Opcodes.INVOKEVIRTUAL -> (it as MethodInsnNode).transformInvokeVirtual(context, klass, method) Opcodes.INVOKESTATIC -> (it as MethodInsnNode).transformInvokeStatic(context, klass, method) @@ -95,38 +97,211 @@ class ThreadTransformer : ClassTransformer { } } - private fun MethodInsnNode.transformInvokeSpecial(context: TransformContext, klass: ClassNode, method: MethodNode) { - if (this.owner == THREAD && this.name == "") { - when (this.desc) { - "()V", - "(Ljava/lang/Runnable;)V", - "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V" -> { - method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className))) - val r = this.desc.lastIndexOf(')') - val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}" - logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") - logger.println(" * ${this.owner}.${this.name}${this.desc} => ${this.owner}.${this.name}$desc: ${klass.name}.${method.name}${method.desc}") - this.desc = desc + /** + * Transform the `super` call of the following classes: + * + * - `java.lang.Thread` + * - `java.util.Timer` + * - `java.util.concurrent.ThreadPoolExecutor` + * - `android.os.HandlerThread` + */ + private fun MethodInsnNode.transformInvokeSpecial(@Suppress("UNUSED_PARAMETER") context: TransformContext, klass: ClassNode, method: MethodNode) { + if (this.name != "") { + return + } + when (this.owner) { + THREAD -> transformThreadInvokeSpecial(klass, method) + HANDLER_THREAD -> transformHandlerThreadInvokeSpecial(klass, method) + TIMER -> transformTimerInvokeSpecial(klass, method) + THREAD_POOL_EXECUTOR -> transformThreadPoolExecutorInvokeSpecial(klass, method) + } + } + + private fun MethodInsnNode.transformThreadPoolExecutorInvokeSpecial(klass: ClassNode, method: MethodNode, init: MethodInsnNode = this) { + when (this.desc) { + // ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue) + "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;)V" -> { + method.instructions.apply { + // ..., queue => ..., queue, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., queue, prefix => ..., queue, factory + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, NAMED_THREAD_FACTORY, "newInstance", "(Ljava/lang/String;)Ljava/util/concurrent/ThreadFactory;", false)) } - "(Ljava/lang/String;)V", - "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V", - "(Ljava/lang/Runnable;Ljava/lang/String;)V", - "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V" -> { - method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className))) - method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) - logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") + this.desc = "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;)V" + } + // ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue, ThreadFactory) + "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;)V" -> { + method.instructions.apply { + // ..., factory => ..., factory, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., factory, prefix => ..., factory + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, NAMED_THREAD_FACTORY, "newInstance", "(Ljava/util/concurrent/ThreadFactory;Ljava/lang/String;)Ljava/util/concurrent/ThreadFactory;")) } - "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;J)V" -> { - method.instructions.insertBefore(this, InsnNode(Opcodes.POP2)) // discard the last argument: stackSize - method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className))) - method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) - logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") - this.desc = "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V" + } + // ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue, RejectedExecutionHandler) + "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;)V" -> { + method.instructions.apply { + // ..., handler => ..., handler, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., handler, prefix => ..., handler, factory + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, NAMED_THREAD_FACTORY, "newInstance", "(Ljava/lang/String;)Ljava/util/concurrent/ThreadFactory;V", false)) + // ..., handler, factory => ..., factory, handler + insertBefore(init, InsnNode(Opcodes.SWAP)) + } + this.desc = "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V" + } + // ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue, ThreadFactory, RejectedExecutionHandler) + "(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V" -> { + method.instructions.apply { + // ..., factory, handler => ..., handler, factory + insertBefore(init, InsnNode(Opcodes.SWAP)) + // ..., handler, factory => ..., handler, factory, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., handler, factory, prefix => ..., handler, factory + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, NAMED_THREAD_FACTORY, "newInstance", "(Ljava/util/concurrent/ThreadFactory;Ljava/lang/String;)Ljava/util/concurrent/ThreadFactory;")) + // ..., handler, factory => ..., factory, handler + insertBefore(init, InsnNode(Opcodes.SWAP)) + } + } + } + } + + private fun MethodInsnNode.transformTimerInvokeSpecial(klass: ClassNode, method: MethodNode, init: MethodInsnNode = this) { + when (this.desc) { + // Timer() + "()V" -> { + method.instructions.insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + this.desc = "(Ljava/lang/String;)V" + } + // Timer(boolean) + "(Z)V" -> { + method.instructions.apply { + // ..., isDaemon => ..., isDaemon, name + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., isDaemon, name => ..., name, isDaemon + insertBefore(init, InsnNode(Opcodes.SWAP)) + } + this.desc = "(Ljava/lang/String;Z)V" + } + // Timer(String) + "(Ljava/lang/String;)V" -> { + method.instructions.apply { + // ..., name => ..., name, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., name, prefix => ..., name + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + } + } + // Timer(String, boolean) + "(Ljava/lang/String;Z)V" -> { + method.instructions.apply { + // ..., name, isDaemon => ..., isDaemon, name + insertBefore(init, InsnNode(Opcodes.SWAP)) + // ..., isDaemon, name => ..., isDaemon, name, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., isDaemon, name, prefix => ..., isDaemon, name + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + // ..., isDaemon, name => ..., name, isDaemon + insertBefore(init, InsnNode(Opcodes.SWAP)) + } + } + } + } + + private fun MethodInsnNode.transformHandlerThreadInvokeSpecial(klass: ClassNode, method: MethodNode, init: MethodInsnNode = this) { + when (this.desc) { + // HandlerThread(String) + "(Ljava/lang/String;)V" -> { + method.instructions.apply { + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + } + logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${owner}.${name}${desc}: ${klass.name}.${method.name}${method.desc}") + } + // HandlerThread(String, int) + "(Ljava/lang/String;I)V" -> { + method.instructions.apply { + // ..., name, priority => ..., priority, name + insertBefore(init, InsnNode(Opcodes.SWAP)) + // ..., priority, name => ..., priority, name, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., priority, name, prefix => ..., priority, name + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + // ..., priority, name => ..., name, priority + insertBefore(init, InsnNode(Opcodes.SWAP)) + } + logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${owner}.${name}${desc}: ${klass.name}.${method.name}${method.desc}") + } + } + } + + private fun MethodInsnNode.transformThreadInvokeSpecial(klass: ClassNode, method: MethodNode, init: MethodInsnNode = this) { + when (this.desc) { + // Thread() + "()V", + // Thread(Runnable) + "(Ljava/lang/Runnable;)V", + // Thread(ThreaGroup, Runnable) + "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V" -> { + method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className))) + val r = this.desc.lastIndexOf(')') + val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}" + logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") + logger.println(" * ${this.owner}.${this.name}${this.desc} => ${this.owner}.${this.name}$desc: ${klass.name}.${method.name}${method.desc}") + this.desc = desc + } + // Thread(String) + "(Ljava/lang/String;)V", + // Thread(ThreadGroup, String) + "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V", + // Thread(Runnable, String) + "(Ljava/lang/Runnable;Ljava/lang/String;)V", + // Thread(ThreadGroup, Runnable, String) + "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V" -> { + method.instructions.apply { + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + } + logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") + } + // Thread(ThreadGroup, Runnable, String, long) + "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;J)V" -> { + // in order to modify the thread name, the penultimate argument `name` have to be moved on the top + // of operand stack, so that the `ShadowThread.makeThreadName(String, String)` could be invoked to + // consume the `name` on the top of operand stack, and then a new name returned on the top of + // operand stack. + // due to JVM does not support swap long/double on the top of operand stack, so, we have to combine + // DUP* and POP* to swap `name` and `stackSize` + method.instructions.apply { + // ..., name, stackSize => ..., stackSize, name, stackSize + insertBefore(init, InsnNode(Opcodes.DUP2_X1)) + // ..., stackSize, name, stackSize => ..., stackSize, name + insertBefore(init, InsnNode(Opcodes.POP2)) + // ..., stackSize, name => ..., stackSize, name, prefix + insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // ..., stackSize, name, prefix => ..., stackSize, name + insertBefore(init, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false)) + // ..., stackSize, name => ..., stackSize, name, name + insertBefore(init, InsnNode(Opcodes.DUP)) + // ..., stackSize, name, name => ..., name, name, stackSize, name, name + insertBefore(init, InsnNode(Opcodes.DUP2_X2)) + // ..., name, name, stackSize, name, name => ..., name, name, stackSize + insertBefore(init, InsnNode(Opcodes.POP2)) + // ..., name, name, stackSize => ..., name, stackSize, name, stackSize + insertBefore(init, InsnNode(Opcodes.DUP2_X1)) + // ..., name, stackSize, name, stackSize => ..., name, stackSize, name + insertBefore(init, InsnNode(Opcodes.POP2)) + // ..., name, stackSize, name => ..., name, stackSize + insertBefore(init, InsnNode(Opcodes.POP)) } + logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") } } } + /** + * Transform the static calls of [java.util.concurrent.Executors] + */ private fun MethodInsnNode.transformInvokeStatic(context: TransformContext, klass: ClassNode, method: MethodNode) { when (this.owner) { EXECUTORS -> { @@ -140,9 +315,11 @@ class ThreadTransformer : ClassTransformer { method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className))) } "newCachedThreadPool", + "newFixedThreadPool", "newSingleThreadExecutor", "newSingleThreadScheduledExecutor", - "newScheduledThreadPool" -> { + "newScheduledThreadPool", + "newWorkStealingPool"-> { val r = this.desc.lastIndexOf(')') val name = if (optimizationEnabled) this.name.replace("new", "newOptimized") else this.name val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}" @@ -160,42 +337,35 @@ class ThreadTransformer : ClassTransformer { private fun TypeInsnNode.transform(context: TransformContext, klass: ClassNode, method: MethodNode) { when (this.desc) { - /*-*/ HANDLER_THREAD -> this.transformWithName(context, klass, method, SHADOW_HANDLER_THREAD) - /*---------*/ THREAD -> this.transformWithName(context, klass, method, SHADOW_THREAD) - THREAD_POOL_EXECUTOR -> this.transformWithName(context, klass, method, SHADOW_THREAD_POOL_EXECUTOR, if (optimizationEnabled) "Optimized" else "") - /*----------*/ TIMER -> this.transformWithName(context, klass, method, SHADOW_TIMER) + /*-*/ HANDLER_THREAD -> this.transformNew(context, klass, method, SHADOW_HANDLER_THREAD) + /*---------*/ THREAD -> this.transformNew(context, klass, method, SHADOW_THREAD) + THREAD_POOL_EXECUTOR -> this.transformNew(context, klass, method, SHADOW_THREAD_POOL_EXECUTOR, true) + /*----------*/ TIMER -> this.transformNew(context, klass, method, SHADOW_TIMER) } } - private fun TypeInsnNode.transformWithName(context: TransformContext, klass: ClassNode, method: MethodNode, type: String, prefix: String = "") { + private fun TypeInsnNode.transformNew(@Suppress("UNUSED_PARAMETER") context: TransformContext, klass: ClassNode, method: MethodNode, type: String, optimizable: Boolean = false) { this.find { (it.opcode == Opcodes.INVOKESPECIAL) && (it is MethodInsnNode) && (this.desc == it.owner && "" == it.name) }?.isInstanceOf { init: MethodInsnNode -> - val name = "new${prefix.capitalize()}${this.desc.substringAfterLast('/')}" - val desc = "${init.desc.substringBeforeLast(')')}Ljava/lang/String;)L${this.desc};" - logger.println(" * ${init.owner}.${init.name}${init.desc} => $type.$name$desc: ${klass.name}.${method.name}${method.desc}") - // replace NEW with INVOKESTATIC - init.owner = type - init.name = name - init.desc = desc - init.opcode = Opcodes.INVOKESTATIC - init.itf = false - // add name as last parameter - method.instructions.insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + // replace original type with shadowed type + // e.g. android/os/HandlerThread => com/didiglobal/booster/instrument/ShadowHandlerThread + this.desc = type - // remove the next DUP of NEW - val dup = this.next - if (Opcodes.DUP == dup.opcode) { - method.instructions.remove(dup) - } else { - TODO("Unexpected instruction 0x${dup.opcode.toString(16)}: ${klass.name}.${method.name}${method.desc}") + // replace the constructor of original type with the constructor of shadowed type + // e.g. android/os/HandlerThread(Ljava/lang/String;) => com/didiglobal/booster/instrument/ShadowHandlerThread(Ljava/lang/String;Ljava/lang/String;) + val rp = init.desc.lastIndexOf(')') + init.apply { + owner = type + desc = "${desc.substring(0, rp)}Ljava/lang/String;${if (optimizable) "Z" else ""}${desc.substring(rp)}" } - method.instructions.remove(this) - }?: run { - logger.println(" ! failed to match $desc: ${klass.name}.${method.name}${method.desc}") - } + method.instructions.insertBefore(init, LdcInsnNode(makeThreadName(klass.className))) + if (optimizable) { + method.instructions.insertBefore(init, LdcInsnNode(optimizationEnabled)) + } + } ?: throw TransformException("`invokespecial $desc` not found: ${klass.name}.${method.name}${method.desc}") } private fun optimizeAsyncTask(klass: ClassNode) { @@ -227,16 +397,21 @@ private val ClassNode.defaultClinit: MethodNode internal const val MARK = "\u200B" -const val SHADOW = "com/didiglobal/booster/instrument/Shadow" -const val SHADOW_HANDLER_THREAD = "${SHADOW}HandlerThread" -const val SHADOW_THREAD = "${SHADOW}Thread" -const val SHADOW_TIMER = "${SHADOW}Timer" -const val SHADOW_EXECUTORS = "${SHADOW}Executors" -const val SHADOW_THREAD_POOL_EXECUTOR = "${SHADOW}ThreadPoolExecutor" -const val SHADOW_ASYNC_TASK = "${SHADOW}AsyncTask" - -const val HANDLER_THREAD = "android/os/HandlerThread" -const val THREAD = "java/lang/Thread" -const val TIMER = "java/util/Timer" -const val EXECUTORS = "java/util/concurrent/Executors" -const val THREAD_POOL_EXECUTOR = "java/util/concurrent/ThreadPoolExecutor" +internal const val BOOSTER_INSTRUMENT = "com/didiglobal/booster/instrument/" +internal const val SHADOW = "${BOOSTER_INSTRUMENT}Shadow" +internal const val SHADOW_HANDLER_THREAD = "${SHADOW}HandlerThread" +internal const val SHADOW_THREAD = "${SHADOW}Thread" +internal const val SHADOW_TIMER = "${SHADOW}Timer" +internal const val SHADOW_EXECUTORS = "${SHADOW}Executors" +internal const val SHADOW_THREAD_POOL_EXECUTOR = "${SHADOW}ThreadPoolExecutor" +internal const val SHADOW_ASYNC_TASK = "${SHADOW}AsyncTask" +internal const val NAMED_THREAD_FACTORY = "${BOOSTER_INSTRUMENT}/NamedThreadFactory" + + +internal const val JAVA_UTIL = "java/util/" +internal const val JAVA_UTIL_CONCURRENT = "${JAVA_UTIL}concurrent/" +internal const val HANDLER_THREAD = "android/os/HandlerThread" +internal const val THREAD = "java/lang/Thread" +internal const val TIMER = "${JAVA_UTIL}Timer" +internal const val EXECUTORS = "${JAVA_UTIL_CONCURRENT}Executors" +internal const val THREAD_POOL_EXECUTOR = "${JAVA_UTIL_CONCURRENT}ThreadPoolExecutor" diff --git a/build.gradle b/build.gradle index 2a74eeb..3d99c48 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ allprojects { project -> apply plugin: 'de.marcphilipp.nexus-publish' group = 'com.didiglobal.booster' - version = '2.2.0' + version = '2.3.0-SNAPSHOT' repositories { mavenLocal() -- GitLab