diff --git a/README.md b/README.md index 48329f31a..37488b0f5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Matrix-icon](assets/img/readme/header.png) [![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/matrix/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/matrix/pulls) -[![WeChat Approved](https://img.shields.io/badge/Wechat%20Approved-2.0.8-red.svg)](https://github.com/Tencent/matrix/wiki) +[![WeChat Approved](https://img.shields.io/badge/Wechat%20Approved-2.1.0-red.svg)](https://github.com/Tencent/matrix/wiki) [![CircleCI](https://circleci.com/gh/Tencent/matrix.svg?style=shield)](https://app.circleci.com/pipelines/github/Tencent/matrix) (中文版本请参看[这里](#matrix_cn)) @@ -215,7 +215,7 @@ At this point, Matrix has been integrated into the app and is beginning to colle 1. Configure `MATRIX_VERSION` in gradle.properties. ``` gradle - MATRIX_VERSION=2.0.8 + MATRIX_VERSION=2.1.0 ``` 2. Add `matrix-gradle-plugin` in your build.gradle: @@ -364,11 +364,11 @@ Then other components in Matrix could use Quikcen Backtrace to unwind stacktrace #### APK Checker Usage -APK Checker can run independently in Jar ([matrix-apk-canary-2.0.8.jar](https://repo.maven.apache.org/maven2/com/tencent/matrix/matrix-apk-canary/2.0.8/matrix-apk-canary-2.0.8.jar)) mode, usage: +APK Checker can run independently in Jar ([matrix-apk-canary-2.1.0.jar](https://repo.maven.apache.org/maven2/com/tencent/matrix/matrix-apk-canary/2.1.0/matrix-apk-canary-2.1.0.jar)) mode, usage: ```shell -java -jar matrix-apk-canary-2.0.8.jar +java -jar matrix-apk-canary-2.1.0.jar Usages: --config CONFIG-FILE-PATH or @@ -427,7 +427,7 @@ Matrix is under the BSD license. See the [LICENSE](https://github.com/Tencent/Ma # Matrix ![Matrix-icon](assets/img/readme/header.png) -[![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/matrix/blob/master/LICENSE)[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/matrix/pulls) [![WeChat Approved](https://img.shields.io/badge/Wechat%20Approved-2.0.8-red.svg)](https://github.com/Tencent/matrix/wiki) +[![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/matrix/blob/master/LICENSE)[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/matrix/pulls) [![WeChat Approved](https://img.shields.io/badge/Wechat%20Approved-2.1.0-red.svg)](https://github.com/Tencent/matrix/wiki) **Matrix** 是一款微信研发并日常使用的应用性能接入框架,支持iOS, macOS和Android。 Matrix 通过接入各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。 @@ -628,7 +628,7 @@ Matrix-android 当前监控范围包括:应用安装包大小,帧率变化 1. 在你项目根目录下的 gradle.properties 中配置要依赖的 Matrix 版本号,如: ``` gradle - MATRIX_VERSION=2.0.8 + MATRIX_VERSION=2.1.0 ``` 2. 在你项目根目录下的 build.gradle 文件添加 Matrix 依赖,如: @@ -774,10 +774,10 @@ WeChatBacktrace.instance().configure(getApplicationContext()).commit(); #### APK Checker -APK Check 以独立的 jar 包提供 ([matrix-apk-canary-2.0.8.jar](https://repo.maven.apache.org/maven2/com/tencent/matrix/matrix-apk-canary/2.0.8/matrix-apk-canary-2.0.8.jar)),你可以运行: +APK Check 以独立的 jar 包提供 ([matrix-apk-canary-2.1.0.jar](https://repo.maven.apache.org/maven2/com/tencent/matrix/matrix-apk-canary/2.1.0/matrix-apk-canary-2.1.0.jar)),你可以运行: ```cmd -java -jar matrix-apk-canary-2.0.8.jar +java -jar matrix-apk-canary-2.1.0.jar ``` 查看 Usages 来使用它。 diff --git a/matrix/matrix-android/gradle.properties b/matrix/matrix-android/gradle.properties index 8baf7a8d9..55933a473 100644 --- a/matrix/matrix-android/gradle.properties +++ b/matrix/matrix-android/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro # org.gradle.parallel=true #Tue Jun 20 10:24:33 CST 2017 -VERSION_NAME_PREFIX=2.0.8 +VERSION_NAME_PREFIX=2.1.0 VERSION_NAME_SUFFIX= ## two options: Internal (for wechat), External (for public repo) PUBLISH_CHANNEL=Internal diff --git a/matrix/matrix-android/matrix-android-commons/src/main/cpp/libenhance_dlsym/EnhanceDlsym.cpp b/matrix/matrix-android/matrix-android-commons/src/main/cpp/libenhance_dlsym/EnhanceDlsym.cpp index 6eb5b0cd4..15c4fc7a4 100644 --- a/matrix/matrix-android/matrix-android-commons/src/main/cpp/libenhance_dlsym/EnhanceDlsym.cpp +++ b/matrix/matrix-android/matrix-android-commons/src/main/cpp/libenhance_dlsym/EnhanceDlsym.cpp @@ -32,10 +32,15 @@ #include #include #include "EnhanceDlsym.h" -#include "../../../../../matrix-jectl/src/main/cpp/jectl/JeLog.h" #define TAG "Matrix.EnhanceDl" +#include + +#define LOGD(TAG, FMT, args...) //__android_log_print(ANDROID_LOG_DEBUG, TAG, FMT, ##args) +#define LOGI(TAG, FMT, args...) //__android_log_print(ANDROID_LOG_INFO, TAG, FMT, ##args) +#define LOGE(TAG, FMT, args...) //__android_log_print(ANDROID_LOG_ERROR, TAG, FMT, ##args) + namespace enhance { static std::set m_opened_info; diff --git a/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/lifecycle/supervisor/ISubordinateProxy.aidl b/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/lifecycle/supervisor/ISubordinateProxy.aidl index b8b936da1..74e3fbd5b 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/lifecycle/supervisor/ISubordinateProxy.aidl +++ b/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/lifecycle/supervisor/ISubordinateProxy.aidl @@ -2,11 +2,12 @@ package com.tencent.matrix.lifecycle.supervisor; // Declare any non-default types here with import statements +import com.tencent.matrix.util.MemInfo; interface ISubordinateProxy { void dispatchState(in String scene, in String stateName, in boolean state); void dispatchKill(in String scene, in String targetProcess, in int targetPid); void dispatchDeath(in String scene, in String targetProcess, in int targetPid, in boolean isLruKill); - int getPss(); + MemInfo getMemInfo(); } \ No newline at end of file diff --git a/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/util/MemInfo.aidl b/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/util/MemInfo.aidl new file mode 100644 index 000000000..653e6eb45 --- /dev/null +++ b/matrix/matrix-android/matrix-android-lib/src/main/aidl/com/tencent/matrix/util/MemInfo.aidl @@ -0,0 +1,6 @@ +// MemInfo.aidl +package com.tencent.matrix.util; + +// Declare any non-default types here with import statements + +parcelable MemInfo; \ No newline at end of file diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleLogger.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleLogger.kt new file mode 100644 index 000000000..40b2d85ce --- /dev/null +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleLogger.kt @@ -0,0 +1,102 @@ +package com.tencent.matrix.lifecycle + +import android.app.Application +import com.tencent.matrix.lifecycle.owners.* +import com.tencent.matrix.lifecycle.supervisor.* +import com.tencent.matrix.util.MatrixLog +import com.tencent.matrix.util.MatrixUtil + +// @formatter:off +object MatrixLifecycleLogger { + + private var application: Application? = null + private val TAG by lazy { "Matrix.lifecycle.Logger_${String.format("%-10.10s", suffix())}" } + + private fun suffix(): String { + return if (MatrixUtil.isInMainProcess(application!!)) { + "Main" + } else { + val split = MatrixUtil.getProcessName(application!!).split(":").toTypedArray() + if (split.size > 1) { + split[1].takeLast(10) + } else { + "unknown" + } + } + } + + fun init(app: Application, enable: Boolean) { + application = app + if (!enable) { + MatrixLog.i(TAG, "logger disabled") + return + } + + ProcessUIResumedStateOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_PROCESS_UI_RESUMED") + override fun off() = MatrixLog.i(TAG, "ON_PROCESS_UI_PAUSED") + }) + + ProcessUIStartedStateOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_PROCESS_UI_STARTED scene: ${ProcessUILifecycleOwner.recentScene}") + override fun off() = MatrixLog.i(TAG, "ON_PROCESS_UI_STOPPED scene: ${ProcessUILifecycleOwner.recentScene}") + }) + + ProcessExplicitBackgroundOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_PROCESS_ENTER_EXPLICIT_BACKGROUND") + override fun off() = MatrixLog.i(TAG, "ON_PROCESS_EXIT_EXPLICIT_BACKGROUND") + }) + + ProcessStagedBackgroundOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_PROCESS_ENTER_STAGED_BACKGROUND") + override fun off() = MatrixLog.i(TAG, "ON_PROCESS_EXIT_STAGED_BACKGROUND") + }) + + ProcessDeepBackgroundOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_PROCESS_ENTER_DEEP_BACKGROUND") + override fun off() = MatrixLog.i(TAG, "ON_PROCESS_EXIT_DEEP_BACKGROUND") + }) + + AppUIForegroundOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ON_APP_UI_ENTER_FOREGROUND scene: ${ProcessSupervisor.getRecentScene()}") + override fun off() = MatrixLog.i(TAG, "ON_APP_UI_EXIT_FOREGROUND scene: ${ProcessSupervisor.getRecentScene()}") + }) + + AppExplicitBackgroundOwner.observeForever(object : ISerialObserver { + override fun off() = MatrixLog.i(TAG, "ON_APP_EXIT_EXPLICIT_BACKGROUND") + override fun on() = MatrixLog.i(TAG, "ON_APP_ENTER_EXPLICIT_BACKGROUND") + }) + + AppStagedBackgroundOwner.observeForever(object : ISerialObserver { + override fun off() = MatrixLog.i(TAG, "ON_APP_EXIT_STAGED_BACKGROUND") + override fun on() = MatrixLog.i(TAG, "ON_APP_ENTER_STAGED_BACKGROUND") + }) + + AppDeepBackgroundOwner.observeForever(object : ISerialObserver { + override fun off() = MatrixLog.i(TAG, "ON_APP_EXIT_DEEP_BACKGROUND") + override fun on() = MatrixLog.i(TAG, "ON_APP_ENTER_DEEP_BACKGROUND") + }) + + ProcessSupervisor.addDyingListener { scene, processName, pid -> + MatrixLog.i(TAG, "Dying Listener: process $pid-$processName is dying on scene $scene") + false // NOT rescue + } + + ProcessSupervisor.addDeathListener { scene, processName, pid, isLruKill -> + MatrixLog.i( + TAG, + "Death Listener: process $pid-$processName died on scene $scene, is LRU Kill? $isLruKill" + ) + } + + ForegroundServiceLifecycleOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "ForegroundServiceLifecycleOwner: ON") + override fun off() = MatrixLog.i(TAG, "ForegroundServiceLifecycleOwner: OFF") + }) + + OverlayWindowLifecycleOwner.observeForever(object : ISerialObserver { + override fun on() = MatrixLog.i(TAG, "OverlayWindowLifecycleOwner: ON, hasOverlay = ${OverlayWindowLifecycleOwner.hasOverlayWindow()}, hasVisible = ${OverlayWindowLifecycleOwner.hasVisibleWindow()}") + override fun off() = MatrixLog.i(TAG, "OverlayWindowLifecycleOwner: OFF, hasOverlay = ${OverlayWindowLifecycleOwner.hasOverlayWindow()}, hasVisible = ${OverlayWindowLifecycleOwner.hasVisibleWindow()}") + }) + } +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleOwnerInitializer.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleOwnerInitializer.kt index 09e520a67..e65de774c 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleOwnerInitializer.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleOwnerInitializer.kt @@ -25,7 +25,9 @@ data class MatrixLifecycleConfig( */ val enableOverlayWindowMonitor: Boolean = false, - val lifecycleThreadConfig: LifecycleThreadConfig = LifecycleThreadConfig() + val lifecycleThreadConfig: LifecycleThreadConfig = LifecycleThreadConfig(), + + val enableLifecycleLogger: Boolean = false ) /** @@ -61,6 +63,7 @@ class MatrixLifecycleOwnerInitializer { ProcessUILifecycleOwner.init(app) ForegroundServiceLifecycleOwner.init(app, config.enableFgServiceMonitor) OverlayWindowLifecycleOwner.init(config.enableOverlayWindowMonitor) + MatrixLifecycleLogger.init(app, config.enableLifecycleLogger) } @SuppressLint("PrivateApi", "DiscouragedPrivateApi") diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleThread.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleThread.kt index 65163b66c..a87bf12f7 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleThread.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/MatrixLifecycleThread.kt @@ -9,6 +9,7 @@ import java.util.concurrent.* private const val TAG = "Matrix.Lifecycle.Thread" data class LifecycleThreadConfig( + val externalExecutor: Executor? = null, val maxPoolSize: Int = 5, val keepAliveSeconds: Long = 30L, val onHeavyTaskDetected: (task: String, cost: Long) -> Unit = { task, cost -> @@ -45,6 +46,11 @@ internal object MatrixLifecycleThread { } val executor by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + + if (config.externalExecutor != null) { + return@lazy config.externalExecutor!! + } + val idleSynchronousQueue = IdleSynchronousQueue() object : ThreadPoolExecutor( diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ForegroundServiceLifecycleOwner.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ForegroundServiceLifecycleOwner.kt index 737403994..aafee2d7d 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ForegroundServiceLifecycleOwner.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ForegroundServiceLifecycleOwner.kt @@ -10,6 +10,7 @@ import android.os.Handler import android.os.Message import android.os.Process import android.util.ArrayMap +import com.tencent.matrix.lifecycle.MatrixLifecycleThread import com.tencent.matrix.lifecycle.StatefulOwner import com.tencent.matrix.util.MatrixLog import com.tencent.matrix.util.safeApply @@ -19,8 +20,6 @@ import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy -private const val SDK_GUARD = 32 - /** * Created by Yves on 2021/11/30 */ @@ -49,10 +48,6 @@ object ForegroundServiceLifecycleOwner : StatefulOwner() { MatrixLog.i(TAG, "disabled") return } - if (Build.VERSION.SDK_INT > SDK_GUARD) { // for safety - MatrixLog.e(TAG, "NOT support for api-level ${Build.VERSION.SDK_INT} yet!!!") - return - } inject() } @@ -127,8 +122,10 @@ object ForegroundServiceLifecycleOwner : StatefulOwner() { } STOP_SERVICE -> { ActivityThreadmH?.post { - safeApply(TAG) { - hasForegroundService() + MatrixLifecycleThread.handler.post { + safeApply(TAG) { + hasForegroundService() + } } } } @@ -159,15 +156,17 @@ object ForegroundServiceLifecycleOwner : StatefulOwner() { } private fun checkIfAlreadyForegrounded(componentName: ComponentName) { - safeApply(TAG) { - activityManager?.getRunningServices(Int.MAX_VALUE)?.filter { - it.pid == Process.myPid() - && it.uid == Process.myUid() - && it.service == componentName - && it.foreground - }?.forEach { - MatrixLog.i(TAG, "service turned fg when create: ${it.service}") - fgServiceHandler?.onStartForeground(it.service) + MatrixLifecycleThread.handler.post { + safeApply(TAG) { + activityManager?.getRunningServices(Int.MAX_VALUE)?.filter { + it.pid == Process.myPid() + && it.uid == Process.myUid() + && it.service == componentName + && it.foreground + }?.forEach { + MatrixLog.i(TAG, "service turned fg when create: ${it.service}") + fgServiceHandler?.onStartForeground(it.service) + } } } } diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/OverlayWindowLifecycleOwner.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/OverlayWindowLifecycleOwner.kt index 28ed2c031..0dee34cd9 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/OverlayWindowLifecycleOwner.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/OverlayWindowLifecycleOwner.kt @@ -10,8 +10,6 @@ import android.view.WindowManager import com.tencent.matrix.lifecycle.StatefulOwner import com.tencent.matrix.util.* -private const val SDK_GUARD = 32 - /** * Created by Yves on 2021/12/30 */ @@ -39,10 +37,6 @@ object OverlayWindowLifecycleOwner : StatefulOwner() { MatrixLog.i(TAG, "disabled") return } - if (Build.VERSION.SDK_INT > SDK_GUARD) { // for safety - MatrixLog.e(TAG, "NOT support for api-level ${Build.VERSION.SDK_INT} yet!!!") - return - } inject() } diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ProcessBackgroundStateOwner.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ProcessBackgroundStateOwner.kt index c21c21330..dd36e4dbd 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ProcessBackgroundStateOwner.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/owners/ProcessBackgroundStateOwner.kt @@ -68,7 +68,7 @@ object ProcessExplicitBackgroundOwner : StatefulOwner(), IBackgroundStatefulOwne override fun action(): Boolean { val uiForeground by lazy { ProcessUIStartedStateOwner.active() } val fgService by lazy { ForegroundServiceLifecycleOwner.hasForegroundService() } - val visibleWindow by lazy { OverlayWindowLifecycleOwner.hasVisibleWindow() } + val visibleWindow by lazy { OverlayWindowLifecycleOwner.hasOverlayWindow() } if (uiForeground) { MatrixLog.i(TAG, "turn OFF for UI foreground") diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSubordinate.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSubordinate.kt index 4a76ff0ca..7faf4563f 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSubordinate.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSubordinate.kt @@ -97,8 +97,14 @@ internal object ProcessSubordinate { } } - fun getPss(): Int { - return 0 + fun getMemInfo(): Array { + val memInfoList = ArrayList() + subordinateProxies.forEachSafe { + it.value.memInfo?.let { m-> + memInfoList.add(m) + } + } + return memInfoList.toTypedArray() } } @@ -243,9 +249,10 @@ internal object ProcessSubordinate { } } - override fun getPss(): Int { - TODO("Not yet implemented") - } + override fun getMemInfo(): MemInfo = safeLet( + TAG, + defVal = MemInfo(amsPssInfo = PssInfo(), debugPssInfo = PssInfo()) + ) { MemInfo.getCurrentProcessFullMemInfo() } } diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSupervisor.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSupervisor.kt index 98daaacb1..5926389bf 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSupervisor.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/ProcessSupervisor.kt @@ -239,4 +239,16 @@ object ProcessSupervisor : IProcessListener by ProcessSubordinate.processListene MatrixLog.i(tag, "inCharge") } + + fun getAllProcessMemInfo() : Array? { + if (application == null) { + MatrixLog.e(tag, "Supervisor NOT initialized yet or Supervisor is disabled!!!") + return null + } + if (!isSupervisor) { + MatrixLog.e(tag, "Only support for supervisor process") + return null + } + return ProcessSubordinate.manager.getMemInfo() + } } \ No newline at end of file diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/SupervisorService.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/SupervisorService.kt index c54ad448a..90ba8c342 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/SupervisorService.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/lifecycle/supervisor/SupervisorService.kt @@ -37,6 +37,7 @@ class SupervisorService : Service() { } val intent = Intent(context, SupervisorService::class.java) context.startService(intent) + MatrixLog.i(TAG, "start service") } @Volatile @@ -236,7 +237,7 @@ class SupervisorService : Service() { override fun onCreate() { super.onCreate() - MatrixLog.d(TAG, "onCreate") + MatrixLog.i(TAG, "onCreate") isSupervisor = true instance = this @@ -267,6 +268,12 @@ class SupervisorService : Service() { return binder } + override fun onDestroy() { + super.onDestroy() + MatrixLog.e(TAG, "SupervisorService destroyed!!!") + instance = null + } + internal fun backgroundLruKill( killedCallback: (result: Int, process: String?, pid: Int) -> Unit ) { diff --git a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MatrixHandlerThread.java b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MatrixHandlerThread.java index 743ec9dbf..9e035eb94 100644 --- a/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MatrixHandlerThread.java +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MatrixHandlerThread.java @@ -75,7 +75,7 @@ public static Handler getDefaultHandler() { return defaultHandler; } - public static HandlerThread getNewHandlerThread(String name, int priority) { + public static synchronized HandlerThread getNewHandlerThread(String name, int priority) { for (Iterator i = handlerThreads.iterator(); i.hasNext(); ) { HandlerThread element = i.next(); if (!element.isAlive()) { diff --git a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemInfoFactory.kt b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MemInfoFactory.kt similarity index 70% rename from matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemInfoFactory.kt rename to matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MemInfoFactory.kt index 1d747fa43..5cff2a955 100644 --- a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemInfoFactory.kt +++ b/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/util/MemInfoFactory.kt @@ -1,28 +1,18 @@ -package com.tencent.matrix.memory.canary +package com.tencent.matrix.util import android.app.ActivityManager import android.app.Application import android.content.Context -import android.os.Build -import android.os.Debug -import android.os.Process +import android.os.* import android.text.TextUtils import com.tencent.matrix.Matrix import com.tencent.matrix.lifecycle.owners.ProcessUILifecycleOwner import com.tencent.matrix.lifecycle.owners.ProcessUIStartedStateOwner import com.tencent.matrix.lifecycle.supervisor.ProcessSupervisor -import com.tencent.matrix.util.MatrixLog -import com.tencent.matrix.util.MatrixUtil -import com.tencent.matrix.util.safeApply -import com.tencent.matrix.util.safeLet - import org.json.JSONObject import java.io.File -import java.lang.StringBuilder -import java.util.* import java.util.regex.Matcher import java.util.regex.Pattern -import kotlin.collections.ArrayList private const val TAG = "Matrix.MemoryInfoFactory" @@ -63,14 +53,23 @@ data class ProcessInfo( val activity: String = ProcessUILifecycleOwner.recentScene.substringAfterLast('.'), val isProcessFg: Boolean = ProcessUIStartedStateOwner.active(), val isAppFg: Boolean = ProcessSupervisor.isAppUIForeground -) { +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString() ?: "default", + parcel.readString() ?: "default", + parcel.readByte() != 0.toByte(), + parcel.readByte() != 0.toByte() + ) + override fun toString(): String { return String.format( - "%-21s\t%-21s %-21s %-21s", + "%-21s\t%-21s %-21s %-21s %-21s", name, "Activity=$activity", "AppForeground=$isAppFg", - "ProcessForeground=$isProcessFg" + "ProcessForeground=$isProcessFg", + "Pid=$pid" ) } @@ -83,6 +82,28 @@ data class ProcessInfo( put("isAppFg", isAppFg) } } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(pid) + parcel.writeString(name) + parcel.writeString(activity) + parcel.writeByte(if (isProcessFg) 1 else 0) + parcel.writeByte(if (isAppFg) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ProcessInfo { + return ProcessInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } data class PssInfo( @@ -95,7 +116,19 @@ data class PssInfo( var pssCodeK: Int = -1, var pssStackK: Int = -1, var pssPrivateOtherK: Int = -1 -) { +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt() + ) + override fun toString(): String { return String.format( "%-21s %-21s %-21s %-21s %-21s %-21s %-21s %-21s %-21s", @@ -173,19 +206,57 @@ data class PssInfo( } } } + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): PssInfo { + return PssInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(totalPssK) + parcel.writeInt(pssJavaK) + parcel.writeInt(pssNativeK) + parcel.writeInt(pssGraphicK) + parcel.writeInt(pssSystemK) + parcel.writeInt(pssSwapK) + parcel.writeInt(pssCodeK) + parcel.writeInt(pssStackK) + parcel.writeInt(pssPrivateOtherK) + } + + override fun describeContents(): Int { + return 0 } } data class StatusInfo( val state: String = "default", - val fdSize: Int = -1, - val vmSizeK: Int = -1, - val vmRssK: Int = -1, - val vmSwapK: Int = -1, - val threads: Int = -1, + val fdSize: Long = -1, + val vmSizeK: Long = -1, + val vmRssK: Long = -1, + val vmSwapK: Long = -1, + val threads: Long = -1, val oomAdj: Int = -1, val oomScoreAdj: Int = -1 -) { +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString() ?: "default", + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readInt(), + parcel.readInt() + ) + override fun toString(): String { return String.format( "%-21s %-21s %-21s %-21s %-21s %-21s %-21s %-21s", @@ -216,39 +287,39 @@ data class StatusInfo( companion object { @JvmStatic fun get(pid: Int = Process.myPid()): StatusInfo { - return convertProcStatus(pid).run { + return convertProcStatus(pid).safeLet(TAG, defVal = StatusInfo()) { fun Map.getString(key: String) = get(key) ?: "unknown" - fun Map.getInt(key: String): Int { + fun Map.getInt(key: String): Long { getString(key).let { val matcher = Pattern.compile("\\d+").matcher(it) while (matcher.find()) { - return matcher.group().toInt() + return matcher.group().toLong() } } return -2 } StatusInfo( - state = getString("State").trimIndent(), - fdSize = getInt("FDSize"), - vmSizeK = getInt("VmSize"), - vmRssK = getInt("VmRSS"), - vmSwapK = getInt("VmSwap"), - threads = getInt("Threads"), + state = it.getString("State").trimIndent(), + fdSize = it.getInt("FDSize"), + vmSizeK = it.getInt("VmSize"), + vmRssK = it.getInt("VmRSS"), + vmSwapK = it.getInt("VmSwap"), + threads = it.getInt("Threads"), oomAdj = getOomAdj(pid), oomScoreAdj = getOomScoreAdj(pid) ) } } - private fun getOomAdj(pid: Int): Int = safeLet(TAG, defVal = Int.MAX_VALUE) { + private fun getOomAdj(pid: Int): Int = safeLet(TAG, defVal = Int.MAX_VALUE, log = false) { File("/proc/$pid/oom_adj").useLines { it.first().toInt() } } - private fun getOomScoreAdj(pid: Int): Int = safeLet(TAG, defVal = Int.MAX_VALUE) { + private fun getOomScoreAdj(pid: Int): Int = safeLet(TAG, defVal = Int.MAX_VALUE, log = false) { File("/proc/$pid/oom_score_adj").useLines { it.first().toInt() } @@ -271,6 +342,32 @@ data class StatusInfo( return emptyMap() } + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): StatusInfo { + return StatusInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(state) + parcel.writeLong(fdSize) + parcel.writeLong(vmSizeK) + parcel.writeLong(vmRssK) + parcel.writeLong(vmSwapK) + parcel.writeLong(threads) + parcel.writeInt(oomAdj) + parcel.writeInt(oomScoreAdj) + } + + override fun describeContents(): Int { + return 0 } } @@ -281,7 +378,16 @@ data class JavaMemInfo( val maxByte: Long = Runtime.getRuntime().maxMemory(), val memClass: Int = MemInfoFactory.memClass, val largeMemClass: Int = MemInfoFactory.largeMemClass -) { +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readInt(), + parcel.readInt() + ) + override fun toString(): String { return String.format( "%-21s %-21s %-21s %-21s %-21s %-21s", @@ -304,13 +410,42 @@ data class JavaMemInfo( put("largeMemClass", largeMemClass) } } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeLong(heapSizeByte) + parcel.writeLong(recycledByte) + parcel.writeLong(usedByte) + parcel.writeLong(maxByte) + parcel.writeInt(memClass) + parcel.writeInt(largeMemClass) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): JavaMemInfo { + return JavaMemInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } data class NativeMemInfo( val heapSizeByte: Long = Debug.getNativeHeapSize(), val recycledByte: Long = Debug.getNativeHeapFreeSize(), val usedByte: Long = Debug.getNativeHeapAllocatedSize() -) { +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readLong(), + parcel.readLong(), + parcel.readLong() + ) + override fun toString(): String { return String.format( "%-21s %-21s %-21s", @@ -327,6 +462,26 @@ data class NativeMemInfo( put("heapSize", heapSizeByte) } } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeLong(heapSizeByte) + parcel.writeLong(recycledByte) + parcel.writeLong(usedByte) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): NativeMemInfo { + return NativeMemInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } data class SystemInfo( @@ -334,7 +489,7 @@ data class SystemInfo( val availMemByte: Long = -1, val lowMemory: Boolean = false, val thresholdByte: Long = -1 -) { +) : Parcelable { companion object { fun get(): SystemInfo { val info = ActivityManager.MemoryInfo() @@ -346,8 +501,26 @@ data class SystemInfo( thresholdByte = info.threshold ) } + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SystemInfo { + return SystemInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } + constructor(parcel: Parcel) : this( + parcel.readLong(), + parcel.readLong(), + parcel.readByte() != 0.toByte(), + parcel.readLong() + ) + override fun toString(): String { return String.format( "%-21s %-21s %-21s %-21s", @@ -366,6 +539,17 @@ data class SystemInfo( put("threshold", thresholdByte) } } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeLong(totalMemByte) + parcel.writeLong(availMemByte) + parcel.writeByte(if (lowMemory) 1 else 0) + parcel.writeLong(thresholdByte) + } + + override fun describeContents(): Int { + return 0 + } } data class FgServiceInfo(val fgServices: List = getRunningForegroundServices()) { @@ -374,7 +558,14 @@ data class FgServiceInfo(val fgServices: List = getRunningForegroundServ } companion object { - private fun getRunningForegroundServices(): List { + + @JvmStatic + fun getCurrentProcessFgServices() = FgServiceInfo(getRunningForegroundServices(false)) + + @JvmStatic + fun getAllProcessFgServices() = FgServiceInfo(getRunningForegroundServices(true)) + + private fun getRunningForegroundServices(allProcess: Boolean = false): List { val fgServices = ArrayList() val runningServiceInfoList: List = safeLet(TAG, true, defVal = emptyList()) { @@ -384,7 +575,7 @@ data class FgServiceInfo(val fgServices: List = getRunningForegroundServ if (serviceInfo.uid != Process.myUid()) { continue } - if (serviceInfo.pid != Process.myPid()) { + if (!allProcess && serviceInfo.pid != Process.myPid()) { continue } if (serviceInfo.foreground) { @@ -405,8 +596,22 @@ data class MemInfo( var amsPssInfo: PssInfo? = null, var debugPssInfo: PssInfo? = null, var fgServiceInfo: FgServiceInfo? = FgServiceInfo() -) { +) : Parcelable { var cost = 0L + + constructor(parcel: Parcel) : this( + parcel.readParcelable(ProcessInfo::class.java.classLoader), + parcel.readParcelable(StatusInfo::class.java.classLoader), + parcel.readParcelable(JavaMemInfo::class.java.classLoader), + parcel.readParcelable(NativeMemInfo::class.java.classLoader), + parcel.readParcelable(SystemInfo::class.java.classLoader), + parcel.readParcelable(PssInfo::class.java.classLoader), + parcel.readParcelable(PssInfo::class.java.classLoader), + null + ) { + cost = parcel.readLong() + } + override fun toString(): String { return "\n" + """ |>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> MemInfo <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -530,11 +735,39 @@ data class MemInfo( javaMemInfo = null, nativeMemInfo = null, systemInfo = systemInfo, + debugPssInfo = PssInfo(), + amsPssInfo = PssInfo() ) ) } return memoryInfoList.toTypedArray() } + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MemInfo { + return MemInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(processInfo, flags) + parcel.writeParcelable(statusInfo, flags) + parcel.writeParcelable(javaMemInfo, flags) + parcel.writeParcelable(nativeMemInfo, flags) + parcel.writeParcelable(systemInfo, flags) + parcel.writeParcelable(amsPssInfo, flags) + parcel.writeParcelable(debugPssInfo, flags) + parcel.writeLong(cost) + } + + override fun describeContents(): Int { + return 0 } } @@ -555,6 +788,83 @@ data class SmapsItem( data class MergedSmapsInfo( val list: List? = null ) { + fun toBriefString(): String { + val sb = StringBuilder() + sb.append("\n") + sb.append( + String.format( + FORMAT, + "PSS", + "RSS", + "SIZE", + "SWAP_PSS", + "SH_C", + "SH_D", + "PRI_C", + "PRI_D", + "COUNT", + "PERM", + "NAME" + ) + ).append("\n") + sb.append( + String.format( + FORMAT, + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----" + ) + ).append("\n") + for ((name, permission, count, vmSize, rss, pss, sharedClean, sharedDirty, privateClean, privateDirty, swapPss) in list!!) { + if (pss < 1024 /* K */) { + break + } + sb.append( + String.format( + FORMAT, + pss, + rss, + vmSize, + swapPss, + sharedClean, + sharedDirty, + privateClean, + privateDirty, + count, + permission, + name + ) + ).append("\n") + } + sb.append( + String.format( + FORMAT, + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----", + "----" + ) + ) + sb.append("\n") + + return sb.toString() + } + override fun toString(): String { val sb = StringBuilder() sb.append("\n") @@ -641,7 +951,7 @@ data class MergedSmapsInfo( private fun mergeSmaps(pid: Int): ArrayList { val pattern = - Pattern.compile("^[0-9a-f]+-[0-9a-f]+\\s+([rwxps-]{4})\\s+\\d+\\s+\\d+:\\d+\\s+\\d+\\s+(.*)$") + Pattern.compile("^[0-9a-f]+-[0-9a-f]+\\s+([rwxps-]{4})\\s+[0-9a-f]+\\s+[0-9a-f]+:[0-9a-f]+\\s+\\d+\\s*(.*)$") val merged: HashMap = HashMap() var currentInfo: SmapsItem? = null @@ -707,7 +1017,10 @@ data class MergedSmapsInfo( val matcher: Matcher = pattern.matcher(line) if (matcher.find()) { val permission = matcher.group(1) - val name = matcher.group(2) + var name = matcher.group(2) + if (name.isNullOrBlank()) { + name = "[no-name]" + } currentInfo = merged["$permission|$name"] if (currentInfo == null) { currentInfo = SmapsItem() diff --git a/matrix/matrix-android/matrix-apk-canary/build.gradle b/matrix/matrix-android/matrix-apk-canary/build.gradle index a47def1bd..d0d57ffc1 100644 --- a/matrix/matrix-android/matrix-apk-canary/build.gradle +++ b/matrix/matrix-android/matrix-apk-canary/build.gradle @@ -10,7 +10,7 @@ group rootProject.ext.GROUP dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.google.code.gson:gson:2.7' + implementation 'com.google.code.gson:gson:2.8.9' implementation project(':matrix-commons') implementation 'com.android.tools:common:25.1.0' } diff --git a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnusedResourcesTask.java b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnusedResourcesTask.java index 8962d9c3e..1e738d216 100644 --- a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnusedResourcesTask.java +++ b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnusedResourcesTask.java @@ -29,6 +29,7 @@ import com.tencent.matrix.apk.model.result.TaskResultFactory; import com.tencent.matrix.apk.model.task.util.ApkResourceDecoder; import com.tencent.matrix.apk.model.task.util.ApkUtil; +import com.tencent.matrix.apk.model.task.util.ResguardUtil; import com.tencent.matrix.javalib.util.FileUtil; import com.tencent.matrix.javalib.util.Log; import com.tencent.matrix.javalib.util.Util; @@ -51,6 +52,8 @@ import java.util.Map; import java.util.Set; import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import brut.androlib.AndrolibException; @@ -69,6 +72,7 @@ public class UnusedResourcesTask extends ApkTask { private File resMappingTxt; private final List dexFileNameList; private final Map rclassProguardMap; + private final Map resguardMap; private final Map resourceDefMap; private final Map> styleableMap; private final Set resourceRefSet; @@ -83,6 +87,7 @@ public UnusedResourcesTask(JobConfig config, Map params) { dexFileNameList = new ArrayList<>(); ignoreSet = new HashSet<>(); rclassProguardMap = new HashMap<>(); + resguardMap = new HashMap<>(); resourceDefMap = new HashMap<>(); styleableMap = new HashMap<>(); resourceRefSet = new HashSet<>(); @@ -157,6 +162,8 @@ private String parseResourceId(String resId) { return ""; } + private static final Pattern sRClassPattern = Pattern.compile("(([a-zA-Z0-9_]*\\.)*)R\\$([a-z]+)"); + private String parseResourceNameFromProguard(String entry) { if (!Util.isNullOrNil(entry)) { String[] columns = entry.split("->"); @@ -170,7 +177,17 @@ private String parseResourceNameFromProguard(String entry) { if (rclassProguardMap.containsKey(resource)) { return rclassProguardMap.get(resource); } else { - return ""; + final Matcher matcher = sRClassPattern.matcher(className); + if (matcher.find()) { + final StringBuilder resultBuilder = new StringBuilder(); + resultBuilder.append("R."); + resultBuilder.append(matcher.group(3)); + resultBuilder.append("."); + resultBuilder.append(fieldName); + return resultBuilder.toString(); + } else { + return ""; + } } } else { if (ApkUtil.isRClassName(ApkUtil.getPureClassName(className))) { @@ -363,6 +380,13 @@ private void readSmaliLines(String[] lines) { resourceRefSet.add(resourceDefMap.get(resId)); } } + if (line.trim().startsWith("0x")) { + final String resId = parseResourceId(line.trim()); + if (!Util.isNullOrNil(resId) && resourceDefMap.containsKey(resId)) { + Log.d(TAG, "array field resource, %s", resId); + resourceRefSet.add(resourceDefMap.get(resId)); + } + } } } } @@ -383,8 +407,6 @@ private void decodeResources() throws IOException, InterruptedException, Androli ApkResourceDecoder.decodeResourcesRef(manifestFile, arscFile, resDir, fileResMap, valuesReferences); - Map resguardMap = config.getResguardMap(); - for (String resource : fileResMap.keySet()) { Set result = new HashSet<>(); for (String resName : fileResMap.get(resource)) { @@ -454,6 +476,7 @@ public TaskResult call() throws TaskExecuteException { long startTime = System.currentTimeMillis(); readMappingTxtFile(); readResourceTxtFile(); + ResguardUtil.readResMappingTxtFile(resMappingTxt, null, resguardMap); unusedResSet.addAll(resourceDefMap.values()); Log.i(TAG, "find resource declarations %d items.", unusedResSet.size()); decodeCode(); diff --git a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnzipTask.java b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnzipTask.java index 409f36dcd..f7156e793 100644 --- a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnzipTask.java +++ b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/UnzipTask.java @@ -25,6 +25,7 @@ import com.tencent.matrix.apk.model.result.TaskResult; import com.tencent.matrix.apk.model.result.TaskResultFactory; import com.android.utils.Pair; +import com.tencent.matrix.apk.model.task.util.ResguardUtil; import com.tencent.matrix.javalib.util.FileUtil; import com.tencent.matrix.javalib.util.Log; import com.tencent.matrix.javalib.util.Util; @@ -134,59 +135,6 @@ private void readMappingTxtFile() throws IOException { } } - private String parseResourceNameFromResguard(String resName) { - if (!Util.isNullOrNil(resName)) { - int index = resName.indexOf('R'); - if (index >= 0) { - return resName.substring(index); - } - } - return ""; - } - - - private void readResMappingTxtFile() throws IOException { - if (resMappingTxt != null) { - BufferedReader bufferedReader = new BufferedReader(new FileReader(resMappingTxt)); - try { - String line = bufferedReader.readLine(); - boolean readResStart = false; - boolean readPathStart = false; - while (line != null) { - if (line.trim().equals("res path mapping:")) { - readPathStart = true; - } else if (line.trim().equals("res id mapping:")) { - readResStart = true; - readPathStart = false; - } else if (readPathStart) { - String[] columns = line.split("->"); - if (columns.length == 2) { - String before = columns[0].trim(); - String after = columns[1].trim(); - if (!Util.isNullOrNil(before) && !Util.isNullOrNil(after)) { - Log.d(TAG, "%s->%s", before, after); - resDirMap.put(after, before); - } - } - } else if (readResStart) { - String[] columns = line.split("->"); - if (columns.length == 2) { - String before = parseResourceNameFromResguard(columns[0].trim()); - String after = parseResourceNameFromResguard(columns[1].trim()); - if (!Util.isNullOrNil(before) && !Util.isNullOrNil(after)) { - Log.d(TAG, "%s->%s", before, after); - resguardMap.put(after, before); - } - } - } - line = bufferedReader.readLine(); - } - } finally { - bufferedReader.close(); - } - } - } - private String parseResourceNameFromPath(String dir, String filename) { if (Util.isNullOrNil(dir) || Util.isNullOrNil(filename)) { return ""; @@ -310,7 +258,7 @@ public TaskResult call() throws TaskExecuteException { readMappingTxtFile(); config.setProguardClassMap(proguardClassMap); - readResMappingTxtFile(); + ResguardUtil.readResMappingTxtFile(resMappingTxt, resDirMap, resguardMap); config.setResguardMap(resguardMap); Enumeration entries = zipFile.entries(); diff --git a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/ResguardUtil.java b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/ResguardUtil.java new file mode 100644 index 000000000..a4ce95b5d --- /dev/null +++ b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/ResguardUtil.java @@ -0,0 +1,81 @@ +package com.tencent.matrix.apk.model.task.util; + +import com.tencent.matrix.javalib.util.Log; +import com.tencent.matrix.javalib.util.Util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ResguardUtil { + + private static final String TAG = "Matrix.ResguardUtil"; + + public static void readResMappingTxtFile(File resMappingTxt, Map resDirMap, Map resguardMap) throws IOException { + if (resMappingTxt != null) { + BufferedReader bufferedReader = new BufferedReader(new FileReader(resMappingTxt)); + try { + String line = bufferedReader.readLine(); + boolean readResStart = false; + boolean readPathStart = false; + while (line != null) { + if (line.trim().equals("res path mapping:")) { + readPathStart = true; + } else if (line.trim().equals("res id mapping:")) { + readResStart = true; + readPathStart = false; + } else if (readPathStart && resDirMap != null) { + String[] columns = line.split("->"); + if (columns.length == 2) { + String before = columns[0].trim(); + String after = columns[1].trim(); + if (!Util.isNullOrNil(before) && !Util.isNullOrNil(after)) { + Log.d(TAG, "%s->%s", before, after); + resDirMap.put(after, before); + } + } + } else if (readResStart && resguardMap != null) { + String[] columns = line.split("->"); + if (columns.length == 2) { + String before = parseResourceNameFromResguard(columns[0].trim()); + String after = parseResourceNameFromResguard(columns[1].trim()); + if (!Util.isNullOrNil(before) && !Util.isNullOrNil(after)) { + Log.d(TAG, "%s->%s", before, after); + resguardMap.put(after, before); + } + } + } + line = bufferedReader.readLine(); + } + } finally { + bufferedReader.close(); + } + } + } + + private static final Pattern RESOURCE_ID_PATTERN = Pattern.compile("^(\\S+\\.)*R\\.(\\S+?)\\.(\\S+)"); + + private static String parseResourceNameFromResguard(String resName) { + if (Util.isNullOrNil(resName)) return ""; + final Matcher matcher = RESOURCE_ID_PATTERN.matcher(resName); + if (matcher.find()) { + final StringBuilder builder = new StringBuilder(); + builder.append("R."); + builder.append(matcher.group(2)); + /* + The resource ID from resguard is read from ARSC file, which format is package-like + (for example: R.style.Theme.AppCompat.Light.DarkActionBar). We should convert it as the regular format + in code (for example: R.style.Theme_AppCompat_Light_DarkActionBar). + */ + builder.append('.'); + builder.append(matcher.group(3).replace('.', '_')); + return builder.toString(); + } else { + return ""; + } + } +} diff --git a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/XmlPullResourceRefDecoder.java b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/XmlPullResourceRefDecoder.java index c160884b2..9df230a68 100644 --- a/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/XmlPullResourceRefDecoder.java +++ b/matrix/matrix-android/matrix-apk-canary/src/main/java/com/tencent/matrix/apk/model/task/util/XmlPullResourceRefDecoder.java @@ -93,6 +93,16 @@ private void handleElement() { int index = value.indexOf('/'); if (index > 1) { resourceRefSet.add(ApkConstants.R_ATTR_PREFIX + "." + value.substring(index + 1).replace('.', '_')); + } else { + // Attribute reference may be omitted the type, for example: + // ?attr/xxx -> ?xxx + // ?android:attr/xxx -> ?android:xxx + int colonIndex = value.indexOf(':'); + if (colonIndex > 1) { + resourceRefSet.add(ApkConstants.R_ATTR_PREFIX + value.substring(colonIndex + 1).replace('.', '_')); + } else { + resourceRefSet.add(ApkConstants.R_ATTR_PREFIX + value.substring(1).replace('.', '_')); + } } } } diff --git a/matrix/matrix-android/matrix-arscutil/build.gradle b/matrix/matrix-android/matrix-arscutil/build.gradle index c85af8788..1a850c4b5 100644 --- a/matrix/matrix-android/matrix-arscutil/build.gradle +++ b/matrix/matrix-android/matrix-arscutil/build.gradle @@ -8,7 +8,7 @@ group rootProject.ext.GROUP dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'commons-io:commons-io:2.6' - compile project(':matrix-commons') + implementation project(':matrix-commons') } if("External" == rootProject.ext.PUBLISH_CHANNEL) { diff --git a/matrix/matrix-android/matrix-arscutil/src/main/java/com/tencent/mm/arscutil/ArscUtil.java b/matrix/matrix-android/matrix-arscutil/src/main/java/com/tencent/mm/arscutil/ArscUtil.java index d666e4a1f..54c358e99 100644 --- a/matrix/matrix-android/matrix-arscutil/src/main/java/com/tencent/mm/arscutil/ArscUtil.java +++ b/matrix/matrix-android/matrix-arscutil/src/main/java/com/tencent/mm/arscutil/ArscUtil.java @@ -115,7 +115,7 @@ public static void removeResource(ResTable resTable, int resourceId, String reso resType.refresh(); } if (resNameStringPoolIndex != -1) { - Log.i(TAG, "try to remove %s (%H), find resource %s", resourceName, resourceId, ResStringBlock.resolveStringPoolEntry(resPackage.getResNamePool().getStrings().get(resNameStringPoolIndex).array(), resPackage.getResNamePool().getCharSet())); + Log.d(TAG, "try to remove %s (%H), find resource %s", resourceName, resourceId, ResStringBlock.resolveStringPoolEntry(resPackage.getResNamePool().getStrings().get(resNameStringPoolIndex).array(), resPackage.getResNamePool().getCharSet())); } resPackage.shrinkResNameStringPool(); resPackage.refresh(); @@ -126,7 +126,7 @@ public static void removeResource(ResTable resTable, int resourceId, String reso public static boolean replaceFileResource(ResTable resTable, int sourceResId, String sourceFile, int targetResId, String targetFile) throws IOException { int sourcePkgId = getPackageId(sourceResId); int targetPkgId = getPackageId(targetResId); - Log.i(TAG, "try to replace %H(%s) with %H(%s)", sourceResId, sourceFile, targetResId, targetFile); + Log.d(TAG, "try to replace %H(%s) with %H(%s)", sourceResId, sourceFile, targetResId, targetFile); if (sourcePkgId == targetPkgId) { ResPackage resPackage = findResPackage(resTable, sourcePkgId); if (resPackage != null) { @@ -252,7 +252,7 @@ public static void replaceResEntryName(ResTable resTable, Map r } public static boolean replaceResFileName(ResTable resTable, int resId, String srcFileName, String targetFileName) { - Log.i(TAG, "try to replace resource (%H) file %s with %s", resId, srcFileName, targetFileName); + Log.d(TAG, "try to replace resource (%H) file %s with %s", resId, srcFileName, targetFileName); ResPackage resPackage = findResPackage(resTable, getPackageId(resId)); boolean result = false; if (resPackage != null) { diff --git a/matrix/matrix-android/matrix-backtrace/src/main/java/com/tencent/matrix/backtrace/ProcessUtil.java b/matrix/matrix-android/matrix-backtrace/src/main/java/com/tencent/matrix/backtrace/ProcessUtil.java index db8544186..3192b6d07 100644 --- a/matrix/matrix-android/matrix-backtrace/src/main/java/com/tencent/matrix/backtrace/ProcessUtil.java +++ b/matrix/matrix-android/matrix-backtrace/src/main/java/com/tencent/matrix/backtrace/ProcessUtil.java @@ -22,6 +22,7 @@ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; +import java.util.List; public class ProcessUtil { @@ -41,9 +42,12 @@ private static String getProcessNameByPidImpl(final Context context, final int p } try { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - for (final ActivityManager.RunningAppProcessInfo i : am.getRunningAppProcesses()) { - if (i.pid == pid && i.processName != null && !i.processName.equals("")) { - return i.processName; + List processes = am.getRunningAppProcesses(); + if (processes != null) { + for (final ActivityManager.RunningAppProcessInfo i : processes) { + if (i.pid == pid && i.processName != null && !i.processName.equals("")) { + return i.processName; + } } } } catch (Exception ignore) { diff --git a/matrix/matrix-android/matrix-battery-canary/build.gradle b/matrix/matrix-android/matrix-battery-canary/build.gradle index f888d207e..721d0a13d 100644 --- a/matrix/matrix-android/matrix-battery-canary/build.gradle +++ b/matrix/matrix-android/matrix-battery-canary/build.gradle @@ -10,7 +10,9 @@ android { versionCode 1 versionName rootProject.ext.VERSION_NAME + multiDexEnabled true testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + // testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner" consumerProguardFiles 'consumer-rules.pro' } @@ -39,7 +41,8 @@ dependencies { androidTestImplementation 'commons-io:commons-io:2.6' // androidTestImplementation 'androidx.core:core:1.3.2' androidTestImplementation 'androidx.annotation:annotation:1.0.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation('androidx.multidex:multidex-instrumentation:2.0.0') androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' androidTestImplementation "org.mockito:mockito-core:2.8.9" androidTestImplementation "org.mockito:mockito-android:2.8.9" diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/AndroidManifest.xml b/matrix/matrix-android/matrix-battery-canary/src/androidTest/AndroidManifest.xml index 34c4adfb8..0d8e7adc6 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/AndroidManifest.xml +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/AndroidManifest.xml @@ -1,4 +1,5 @@ @@ -8,7 +9,14 @@ - + + + + + 0); + Assert.assertTrue("cpuLoad: " + cpuLoad, cpuLoad >= 0); } } } @@ -98,8 +110,16 @@ public void exampleForCpuFreqSampling() { if (Matrix.isInstalled()) { BatteryMonitorPlugin monitor = Matrix.with().getPluginByClass(BatteryMonitorPlugin.class); if (monitor != null) { + /* + * 注意: + * 1. CompositeMonitors 设计为非线程安全, 需要做好并发控制 + * 2. #start() 和 #finish() 必须成对调用, 以下代码监控了 doSomething() 这段时间内的功耗数据 + * 3. 监控结束后可通过 CompositeMonitors 实例访问功耗数据 + * 4. samplingIntervalMs 为采样周期, 建议取值 1minute, 最低值不应低于 5s (此处 10ms 仅为单元测试) + */ CompositeMonitors compositor = new CompositeMonitors(monitor.core()); - compositor.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, 10L); + long samplingIntervalMs = 10L; + compositor.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, samplingIntervalMs); compositor.start(); doSomething(); @@ -112,6 +132,67 @@ public void exampleForCpuFreqSampling() { } } + /** + * 计算 Cpu Load(叠加 CpuFreq 采样权重): + * 1. CpuLoad 计算口径与 adb shell top 里的 CPU 负载计算一致 + * 2. CpuLoad = [0, 100 * cpu 核心数] + * 3. CpuLoad 只能反馈 CPU 资源的使用率, 如果需要考虑 CPU 整体负载还要考虑 CPU 大小核的工作频率, 因此需要额外加多 CpuFreq 的数据 + * 4. CpuLoadNormalized = cpuLoad * avgCpuFreq / maxCpuFreq + */ + @Test + public void exampleForCpuLoadNormalize() { + if (TestUtils.isAssembleTest()) { + return; + } else { + mockSetup(); + } + + if (Matrix.isInstalled()) { + BatteryMonitorPlugin monitor = Matrix.with().getPluginByClass(BatteryMonitorPlugin.class); + if (monitor != null) { + /* + * 注意: + * 1. CompositeMonitors 设计为非线程安全, 需要做好并发控制 + * 2. #start() 和 #finish() 必须成对调用, 以下代码监控了 doSomething() 这段时间内的功耗数据 + * 3. 监控结束后可通过 CompositeMonitors 实例访问功耗数据 + * 4. samplingIntervalMs 为采样周期, 建议取值 1minute, 最低值不应低于 5s (此处 10ms 仅为单元测试) + */ + CompositeMonitors compositor = new CompositeMonitors(monitor.core()); + compositor.metric(JiffiesMonitorFeature.JiffiesSnapshot.class); + long samplingIntervalMs = 10L; + compositor.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, samplingIntervalMs); + compositor.start(); + + doSomething(); + + compositor.finish(); + int cpuLoad = compositor.getCpuLoad(); + Assert.assertTrue("cpuLoad: " + cpuLoad, cpuLoad >= 0); + + MonitorFeature.Snapshot.Sampler.Result result = compositor.getSamplingResult(DeviceStatMonitorFeature.CpuFreqSnapshot.class); + Assert.assertNotNull(result); + List cpuFreqSteps = BatteryCanaryUtil.getCpuFreqSteps(); + Assert.assertEquals(BatteryCanaryUtil.getCpuCoreNum(), cpuFreqSteps.size()); + + long sumMax = 0; + for (int[] steps : cpuFreqSteps) { + int max = 0; + for (int item : steps) { + if (item > max) { + max = item; + } + } + sumMax += max; + } + Assert.assertTrue("cpuFreqSumAvg: " + result.sampleAvg + "vs cpuFreqSumMax: " + sumMax, sumMax >= result.sampleAvg); + int cpuLoadNormalized = (int) (cpuLoad * result.sampleAvg / sumMax); + Assert.assertTrue("cpuLoadNormalized: " + cpuLoadNormalized + "vs cpuLoad: " + sumMax, cpuLoad >= cpuLoadNormalized); + + Assert.assertEquals(cpuLoadNormalized, compositor.getNorCpuLoad()); + } + } + } + /** * 电量温度采样 */ @@ -126,8 +207,16 @@ public void exampleForTemperatureSampling() { if (Matrix.isInstalled()) { BatteryMonitorPlugin monitor = Matrix.with().getPluginByClass(BatteryMonitorPlugin.class); if (monitor != null) { + /* + * 注意: + * 1. CompositeMonitors 设计为非线程安全, 需要做好并发控制 + * 2. #start() 和 #finish() 必须成对调用, 以下代码监控了 doSomething() 这段时间内的功耗数据 + * 3. 监控结束后可通过 CompositeMonitors 实例访问功耗数据 + * 4. samplingIntervalMs 为采样周期, 建议取值 1minute, 最低值不应低于 5s (此处 10ms 仅为单元测试) + */ CompositeMonitors compositor = new CompositeMonitors(monitor.core()); - compositor.sample(DeviceStatMonitorFeature.BatteryTmpSnapshot.class, 10L); + long samplingIntervalMs = 10L; + compositor.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, samplingIntervalMs); compositor.start(); doSomething(); @@ -256,8 +345,14 @@ public void accept(MonitorFeature.Snapshot.Delta= 0 && cpuLoad <= BatteryCanaryUtil.getCpuCoreNum() * 100); + int devCpuLoad = compositeMonitor.getDevCpuLoad(); + Assert.assertTrue("devCpuLoad: " + devCpuLoad, devCpuLoad >= 0 && devCpuLoad <= BatteryCanaryUtil.getCpuCoreNum() * 100); - Assert.assertEquals(cpuLoad, cpuLoadR, 10); + Assert.assertEquals(devCpuLoad, cpuLoadR, 10); compositeMonitor = new CompositeMonitors(monitor); Assert.assertFalse(compositeMonitor.mMetrics.contains(JiffiesSnapshot.class)); @@ -214,8 +214,8 @@ public void run() { compositeMonitor.finish(); long wallTimeEnd = System.currentTimeMillis(); long upTimeEnd = SystemClock.uptimeMillis(); - cpuLoad = compositeMonitor.getCpuLoad(); - Assert.assertTrue(cpuLoad >= 0 && cpuLoad <= BatteryCanaryUtil.getCpuCoreNum() * 100); + devCpuLoad = compositeMonitor.getDevCpuLoad(); + Assert.assertTrue("devCpuLoad: " + devCpuLoad,devCpuLoad >= 0 && devCpuLoad <= BatteryCanaryUtil.getCpuCoreNum() * 100); long wallTimeDelta = wallTimeEnd - wallTimeBgn; long uptimeDelta = upTimeEnd - upTimeBgn; @@ -229,7 +229,7 @@ public void run() { cpuLoadR = (int) ((appJiffies.dlt.totalJiffies.get() / (uptimeDelta / 10f)) * 100); Assert.assertTrue("cpuLoadR: " + cpuLoadR, cpuLoadR >= 0 && cpuLoadR <= BatteryCanaryUtil.getCpuCoreNum() * 100); - Assert.assertEquals(cpuLoad, cpuLoadR, 10); + Assert.assertEquals(devCpuLoad, cpuLoadR, 10); } @Test @@ -260,4 +260,33 @@ public void testSampling() throws InterruptedException { Assert.assertTrue(compositeMonitor.getSamplingResult(DeviceStatMonitorFeature.BatteryTmpSnapshot.class).count > compositeMonitor.getSamplingResult(DeviceStatMonitorFeature.CpuFreqSnapshot.class).count); } + + @Test + public void testSamplingStop() throws InterruptedException { + final BatteryMonitorCore monitor = mockMonitor(); + BatteryMonitorPlugin plugin = new BatteryMonitorPlugin(monitor.getConfig()); + Matrix.with().getPlugins().add(plugin); + monitor.enableForegroundLoopCheck(true); + monitor.start(); + + long interval = 100L; + long samplingTime = 1000L; + CompositeMonitors compositeMonitor = new CompositeMonitors(monitor); + compositeMonitor.sample(DeviceStatMonitorFeature.BatteryTmpSnapshot.class, interval); + compositeMonitor.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, interval); + compositeMonitor.sample(DeviceStatMonitorFeature.ChargeWattageSnapshot.class, interval); + compositeMonitor.sample(CpuStatFeature.CpuStateSnapshot.class, interval); + compositeMonitor.sample(JiffiesMonitorFeature.UidJiffiesSnapshot.class, interval); + + compositeMonitor.start(); + Thread.sleep(samplingTime); + compositeMonitor.finish(); + + Thread.sleep(4000L); + Assert.assertEquals(samplingTime/interval, compositeMonitor.getSamplingResult(DeviceStatMonitorFeature.BatteryTmpSnapshot.class).count, 2); + Assert.assertEquals(samplingTime/interval, compositeMonitor.getSamplingResult(DeviceStatMonitorFeature.CpuFreqSnapshot.class).count, 2); + Assert.assertEquals(samplingTime/interval, compositeMonitor.getSamplingResult(DeviceStatMonitorFeature.ChargeWattageSnapshot.class).count, 2); + Assert.assertEquals(samplingTime/interval, compositeMonitor.getSamplingResult(CpuStatFeature.CpuStateSnapshot.class).count, 3); + Assert.assertEquals(samplingTime/interval, compositeMonitor.getSamplingResult(JiffiesMonitorFeature.UidJiffiesSnapshot.class).count, 2); + } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureLooperTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureLooperTest.java index a934a231a..1e4a8536b 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureLooperTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureLooperTest.java @@ -35,6 +35,7 @@ import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; import com.tencent.matrix.batterycanary.utils.TimeBreaker; import com.tencent.matrix.trace.core.LooperMonitor; +import com.tencent.matrix.trace.listeners.ILooperListener; import org.junit.After; import org.junit.Assert; @@ -47,7 +48,6 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -77,24 +77,23 @@ public void testLooperMonitor() throws InterruptedException { HandlerThread handlerThread = new HandlerThread("looper-test"); handlerThread.start(); LooperMonitor looperMonitor = LooperMonitor.of(handlerThread.getLooper()); - looperMonitor.addListener(new LooperMonitor.LooperDispatchListener() { + looperMonitor.addListener(new ILooperListener() { @Override public boolean isValid() { return true; } @Override - public void dispatchStart() { + public void onDispatchBegin(String log) { Assert.assertEquals("looper-test", Thread.currentThread().getName()); if (!hasStart.get()) { Assert.assertFalse(hasFinish.get()); } hasStart.set(true); - } @Override - public void dispatchEnd() { + public void onDispatchEnd(String log, long beginNs, long endNs) { Assert.assertEquals("looper-test", Thread.currentThread().getName()); hasFinish.set(true); Assert.assertTrue(hasStart.get()); @@ -136,7 +135,7 @@ public void testLooperMonitorV2() throws InterruptedException { HandlerThread handlerThread = new HandlerThread("looper-test"); handlerThread.start(); LooperMonitor looperMonitor = LooperMonitor.of(handlerThread.getLooper()); - looperMonitor.addListener(new LooperMonitor.LooperDispatchListener() { + looperMonitor.addListener(new ILooperListener() { @Override public boolean isValid() { return true; @@ -146,8 +145,7 @@ public boolean isValid() { * >>>>> Dispatching to Handler (android.os.Handler) {b54e421} com.tencent.matrix.batterycanary.utils.LooperMonitorTest$TestTask@32d6046: 0 */ @Override - public void onDispatchStart(String x) { - super.onDispatchStart(x); + public void onDispatchBegin(String x) { if (!hasStart.get()) { Assert.assertFalse(hasFinish.get()); } @@ -188,8 +186,7 @@ public void onDispatchStart(String x) { * <<<<< Finished to Handler (android.os.Handler) {b54e421} com.tencent.matrix.batterycanary.utils.LooperMonitorTest$TestTask@32d6046 */ @Override - public void onDispatchEnd(String x) { - super.onDispatchEnd(x); + public void onDispatchEnd(String x, long beginNs, long endNs) { hasFinish.set(true); Assert.assertTrue(hasStart.get()); @@ -341,7 +338,7 @@ public void run() { Assert.assertEquals(1, feature.mDeltaList.size()); Assert.assertTrue(feature.mDeltaList.get(0).dlt.name.contains(MonitorFeatureLooperTest.class.getName() + "$")); - List> deltas = feature.currentJiffies(); + List> deltas = feature.currentJiffies(0); Assert.assertEquals(1, deltas.size()); Assert.assertEquals(feature.mDeltaList, deltas); @@ -416,7 +413,7 @@ public void run() { Assert.assertEquals(1, feature.mDeltaList.size()); Assert.assertTrue(feature.mDeltaList.get(0).dlt.name.contains(MonitorFeatureLooperTest.class.getName() + "$")); - List> deltas = feature.currentJiffies(); + List> deltas = feature.currentJiffies(0); Assert.assertEquals(1, deltas.size()); Assert.assertEquals(feature.mDeltaList, deltas); @@ -545,7 +542,7 @@ public void run() { Assert.assertEquals(1, feature.mDeltaList.size()); Assert.assertTrue(feature.mDeltaList.get(0).dlt.name.contains(MonitorFeatureLooperTest.class.getName() + "$")); - List> deltas = feature.currentJiffies(); + List> deltas = feature.currentJiffies(0); Assert.assertEquals(1, deltas.size()); Assert.assertEquals(feature.mDeltaList, deltas); diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureOverAllTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureOverAllTest.java index 4fc36c443..80da2307f 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureOverAllTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeatureOverAllTest.java @@ -81,10 +81,16 @@ public BatteryPrinter attach(BatteryMonitorCore monitorCore) { BatteryPrinter core = super.attach(monitorCore); mCompositeMonitors.sample(DeviceStatMonitorFeature.BatteryTmpSnapshot.class, 100L); mCompositeMonitors.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, 100L); + mCompositeMonitors.sample(DeviceStatMonitorFeature.ChargeWattageSnapshot.class, 100L); + mCompositeMonitors.sample(CpuStatFeature.CpuStateSnapshot.class, 100L); + mCompositeMonitors.sample(JiffiesMonitorFeature.UidJiffiesSnapshot.class, 100L); + mCompositeMonitors.sample(TrafficMonitorFeature.RadioStatSnapshot.class, 100L); return core; } }) .build(); + + return new BatteryMonitorCore(config); } diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/SamplerTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/SamplerTest.java index dd6ffd77d..3a2b9f199 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/SamplerTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/monitor/feature/SamplerTest.java @@ -84,6 +84,13 @@ private BatteryMonitorCore mockMonitor() { return new BatteryMonitorCore(config); } + @Test + public void testInvalidSamplingResult() { + Number result = Integer.MIN_VALUE; + Assert.assertTrue(result.equals(Integer.MIN_VALUE)); + Assert.assertEquals(Integer.MIN_VALUE, result); + } + @Test public void testSampling() throws InterruptedException { final BatteryMonitorCore monitor = mockMonitor(); @@ -95,7 +102,7 @@ public void testSampling() throws InterruptedException { final int samplingCount = 10; final AtomicInteger counter = new AtomicInteger(0); - final MonitorFeature.Snapshot.Sampler sampler = new MonitorFeature.Snapshot.Sampler(monitor.getHandler(), new Callable() { + final MonitorFeature.Snapshot.Sampler sampler = new MonitorFeature.Snapshot.Sampler("test-1", monitor.getHandler(), new Callable() { @Override public Integer call() throws InterruptedException { int count = counter.incrementAndGet(); @@ -128,7 +135,7 @@ public Integer call() throws InterruptedException { Assert.assertEquals(55d / samplingCount, result.sampleAvg, 0.1); final AtomicInteger counterDec = new AtomicInteger(0); - final MonitorFeature.Snapshot.Sampler samplerDec = new MonitorFeature.Snapshot.Sampler(monitor.getHandler(), new Callable() { + final MonitorFeature.Snapshot.Sampler samplerDec = new MonitorFeature.Snapshot.Sampler("test-2", monitor.getHandler(), new Callable() { @Override public Integer call() throws InterruptedException { int count = counterDec.incrementAndGet(); diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/stats/HealthStatsTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/stats/HealthStatsTest.java new file mode 100644 index 000000000..f03c1b7c3 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/stats/HealthStatsTest.java @@ -0,0 +1,792 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.batterycanary.stats; + +import android.app.Application; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.health.HealthStats; +import android.os.health.SystemHealthManager; +import android.os.health.TimerStat; +import android.os.health.UidHealthStats; + +import com.tencent.matrix.Matrix; +import com.tencent.matrix.batterycanary.monitor.BatteryMonitorConfig; +import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature; +import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; +import com.tencent.matrix.batterycanary.stats.HealthStatsFeature.HealthStatsSnapshot; +import com.tencent.matrix.batterycanary.utils.PowerProfile; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + + +@RunWith(AndroidJUnit4.class) +public class HealthStatsTest { + static final String TAG = "Matrix.test.HealthStatsTest"; + + Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + if (!Matrix.isInstalled()) { + Matrix.init(new Matrix.Builder(((Application) mContext.getApplicationContext())).build()); + } + } + + @After + public void shutDown() { + } + + private BatteryMonitorCore mockMonitor() { + BatteryMonitorConfig config = new BatteryMonitorConfig.Builder() + .enable(CpuStatFeature.class) + .enable(HealthStatsFeature.class) + .enableBuiltinForegroundNotify(false) + .enableForegroundMode(false) + .wakelockTimeout(1000) + .greyJiffiesTime(100) + .foregroundLoopCheckTime(1000) + .build(); + return new BatteryMonitorCore(config); + } + + @Test + public void testRoundDecimalPlace() { + Assert.assertEquals(1.1d, HealthStatsHelper.round(1.12345d, 1), 0.00001d); + Assert.assertEquals(1.2d, HealthStatsHelper.round(1.19945d, 1), 0.00001d); + Assert.assertEquals(1.12d, HealthStatsHelper.round(1.12345d, 2), 0.00001d); + Assert.assertEquals(1.13d, HealthStatsHelper.round(1.12945d, 2), 0.00001d); + Assert.assertEquals(1.1234d, HealthStatsHelper.round(1.12341d, 4), 0.00001d); + Assert.assertEquals(1.1235d, HealthStatsHelper.round(1.12345d, 4), 0.00001d); + Assert.assertEquals(1.1200d, HealthStatsHelper.round(1.12d, 2), 0.00001d); + Assert.assertEquals(1.1200d, HealthStatsHelper.round(1.1245d, 2), 0.00001d); + } + + @Test + public void testGetSenorsHandle() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + SensorManager sm = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + Assert.assertNotNull(sm); + List sensorList = sm.getSensorList(Sensor.TYPE_ALL); + Assert.assertFalse(sensorList.isEmpty()); + + for (Sensor item : sensorList) { + String name = item.getName(); + int id = item.getId(); + int type = item.getType(); + float power = item.getPower(); + Method method = item.getClass().getDeclaredMethod("getHandle"); + int handle = (int) method.invoke(item); + Assert.assertTrue(handle > 0); + } + } + + @Test + public void testLiterateStats() { + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + Assert.assertEquals("UidHealthStats", healthStats.getDataType()); + + List statsList = new ArrayList<>(); + statsList.add(healthStats); + + int statsKeyCount = healthStats.getStatsKeyCount(); + Assert.assertTrue(statsKeyCount > 0); + + for (int i = 0; i < statsKeyCount; i++) { + int key = healthStats.getStatsKeyAt(i); + if (healthStats.hasStats(key)) { + Map stats = healthStats.getStats(key); + for (HealthStats item : stats.values()) { + Assert.assertTrue(Arrays.asList( + "PidHealthStats", + "ProcessHealthStats", + "PackageHealthStats", + "ServiceHealthStats" + ).contains(item.getDataType())); + + statsList.add(item); + + int subKeyCount = item.getStatsKeyCount(); + if (subKeyCount > 0) { + for (int j = 0; j < subKeyCount; j++) { + int subKey = item.getStatsKeyAt(j); + if (item.hasStats(subKey)) { + statsList.addAll(item.getStats(subKey).values()); + } + } + } + } + } + } + + for (HealthStats item : statsList) { + int count = item.getMeasurementKeyCount(); + for (int i = 0; i < count; i++) { + int key = item.getMeasurementKeyAt(i); + long value = item.getMeasurement(key); + Assert.assertTrue(item.getDataType() + ": " + key + "=" + value, value >= 0); + } + } + for (HealthStats item : statsList) { + int count = item.getMeasurementsKeyCount(); + for (int i = 0; i < count; i++) { + int key = item.getMeasurementsKeyAt(i); + Map values = item.getMeasurements(key); + for (Map.Entry entry : values.entrySet()) { + Assert.assertTrue(item.getDataType() + ": " + entry.getKey() + "=" + entry.getValue(), entry.getValue() >= 0); + } + } + } + + for (HealthStats item : statsList) { + int count = item.getTimerKeyCount(); + for (int i = 0; i < count; i++) { + int key = item.getTimerKeyAt(i); + TimerStat timerStat = item.getTimer(key); + Assert.assertTrue(item.getDataType() + ": " + key + "=" + timerStat.getCount() + "," + timerStat.getTime(), timerStat.getCount() >= 0); + Assert.assertTrue(item.getDataType() + ": " + key + "=" + timerStat.getCount() + "," + timerStat.getTime(), timerStat.getTime() >= 0); + } + } + for (HealthStats item : statsList) { + int count = item.getTimersKeyCount(); + for (int i = 0; i < count; i++) { + int key = item.getTimersKeyAt(i); + Map timerStatMap = item.getTimers(key); + for (Map.Entry entry : timerStatMap.entrySet()) { + Assert.assertTrue(item.getDataType() + ": " + entry.getKey() + "=" + entry.getValue().getCount() + "," + entry.getValue().getTime(), entry.getValue().getCount() >= 0); + Assert.assertTrue(item.getDataType() + ": " + entry.getKey() + "=" + entry.getValue().getCount() + "," + entry.getValue().getTime(), entry.getValue().getTime() >= 0); + } + } + } + } + + @Test + public void getGetArrayItemAvgPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + for (String key : PowerProfile.getPowerItemMap().keySet()) { + Assert.assertEquals(powerProfile.getAveragePower(key), powerProfile.getAveragePowerUni(key), 0d); + } + + for (String key : PowerProfile.getPowerArrayMap().keySet()) { + Assert.assertEquals(powerProfile.getAveragePower(key, 0), powerProfile.getAveragePower(key), 0d); + + double sum = 0; + int num = powerProfile.getNumElements(key); + for (int i = 0; i < num; i++) { + sum += powerProfile.getAveragePower(key, i); + } + Assert.assertEquals(sum / num, powerProfile.getAveragePowerUni(key), 0d); + } + } + + @Test + public void testEstimateCpuPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + double cpuActivePower = powerProfile.getAveragePower("cpu.active"); + Assert.assertTrue(cpuActivePower > 0); + UsageBasedPowerEstimator etmCpuActivePower = new UsageBasedPowerEstimator(cpuActivePower); + } + + @Test + public void testEstimateCpuPowerByHealthStats() throws IOException { + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + if (healthStats.hasStats(UidHealthStats.MEASUREMENT_CPU_POWER_MAMS)) { + double powerMah = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_CPU_POWER_MAMS) / (1000.0 * 60 * 60); + Assert.assertTrue(powerMah >= 0); + } + } + + @Test + public void testEstimateCpuPowerByCpuStats() throws IOException { + CpuStatFeature feature = new CpuStatFeature(); + feature.configure(mockMonitor()); + feature.onTurnOn(); + + Assert.assertTrue(feature.isSupported()); + CpuStatFeature.CpuStateSnapshot cpuStateSnapshot = feature.currentCpuStateSnapshot(); + Assert.assertTrue(cpuStateSnapshot.procCpuCoreStates.size() > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_USER_CPU_TIME_MS); + healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_SYSTEM_CPU_TIME_MS); + long cpuTimeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_USER_CPU_TIME_MS) + healthStats.getMeasurement(UidHealthStats.MEASUREMENT_SYSTEM_CPU_TIME_MS); + + double powerMahByUid = 0, powerMahByDev = 0; + double activePower = HealthStatsHelper.estimateCpuActivePower(feature.getPowerProfile(), cpuTimeMs); + Assert.assertTrue(activePower >= 0); + powerMahByUid += activePower; + powerMahByDev += activePower; + + + powerMahByUid += HealthStatsHelper.estimateCpuClustersPowerByUidStats(feature.getPowerProfile(), cpuStateSnapshot, cpuTimeMs, false); + Assert.assertTrue(powerMahByUid >= 0); + powerMahByUid += HealthStatsHelper.estimateCpuCoresPowerByUidStats(feature.getPowerProfile(), cpuStateSnapshot, cpuTimeMs, false); + Assert.assertTrue(powerMahByUid >= 0); + + powerMahByDev += HealthStatsHelper.estimateCpuClustersPowerByDevStats(feature.getPowerProfile(), cpuStateSnapshot, cpuTimeMs); + Assert.assertTrue(powerMahByDev >= 0); + powerMahByDev += HealthStatsHelper.estimateCpuCoresPowerByDevStats(feature.getPowerProfile(), cpuStateSnapshot, cpuTimeMs); + Assert.assertTrue(powerMahByDev >= 0); + + double calcCpuPower = HealthStatsHelper.calcCpuPower(feature.getPowerProfile(), healthStats); + Assert.assertEquals(powerMahByDev, calcCpuPower, 1d); + } + + @Test + public void testEstimateMemoryPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + int num = powerProfile.getNumElements(PowerProfile.POWER_MEMORY); + for (int i = 0; i < num; i++) { + Assert.assertTrue(powerProfile.getAveragePower(PowerProfile.POWER_MEMORY, num) > 0); + } + + double calcPower = HealthStatsHelper.calcMemoryPower(powerProfile); + Assert.assertTrue(calcPower >= 0); + } + + @Test + public void testEstimateWakelockPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + Assert.assertTrue(powerProfile.getAveragePower("cpu.idle") > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double powerMah = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL); + long timeMs = 0; + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + double powerMa = powerProfile.getAveragePower("cpu.idle"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + Assert.assertTrue(powerMah >= 0); + + double calcPower = HealthStatsHelper.calcWakelocksPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + } + + @Test + public void testEstimateMobileRadioPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_MOBILE_POWER_MAMS)) { + double powerMahByHealthStats = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_MOBILE_POWER_MAMS) / (1000.0 * 60 * 60); + Assert.assertTrue(powerMahByHealthStats >= 0); + } + + double powerMahByRadio = 0; + if (powerProfile.getAveragePower("radio.active") > 0) { + if (healthStats.hasTimer(UidHealthStats.TIMER_MOBILE_RADIO_ACTIVE)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_MOBILE_RADIO_ACTIVE); + double powerMa = powerProfile.getAveragePower("radio.active"); + powerMahByRadio += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + } + Assert.assertTrue(powerMahByRadio >= 0); + + double powerMahByTime = 0; + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_MOBILE_IDLE_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_MOBILE_IDLE_MS); + double powerMa = powerProfile.getAveragePower("modem.controller.idle"); + powerMahByTime += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_MOBILE_RX_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_MOBILE_RX_MS); + double powerMa = powerProfile.getAveragePower("modem.controller.rx"); + powerMahByTime += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_MOBILE_TX_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_MOBILE_TX_MS); + double powerMa = powerProfile.getAveragePower("modem.controller.tx"); + powerMahByTime += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + Assert.assertTrue(powerMahByTime >= 0); + + double calcPower = HealthStatsHelper.calcMobilePower(powerProfile, healthStats); + Assert.assertEquals(powerMahByTime, calcPower, 1d); + } + + @Test + public void testEstimateWifiPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + + double wifiIdlePower = powerProfile.getAveragePower("wifi.controller.idle"); + Assert.assertTrue(wifiIdlePower >= 0); + UsageBasedPowerEstimator etmWifiIdlePower = new UsageBasedPowerEstimator(wifiIdlePower); + double wifiRxPower = powerProfile.getAveragePower("wifi.controller.rx"); + Assert.assertTrue(wifiRxPower >= 0); + UsageBasedPowerEstimator etmWifiRxPower = new UsageBasedPowerEstimator(wifiIdlePower); + double wifiTxPower = powerProfile.getAveragePower("wifi.controller.tx"); + Assert.assertTrue(wifiTxPower >= 0); + UsageBasedPowerEstimator etmWifiTxPower = new UsageBasedPowerEstimator(wifiIdlePower); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + // calc from packets + // double power = 0; + // double powerMaPerPacket = 0; + // MatrixLog.i(TAG, "estimate WIFI by packets"); + // { + // final long wifiBps = 1000000; + // final double averageWifiActivePower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + // powerMaPerPacket = averageWifiActivePower / (((double) wifiBps) / 8 / 2048); + // long packets = 1213737 + 244433; + // power += powerMaPerPacket * packets; + // } + // { + // double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_ON); + // long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RUNNING_MS); + // power += new HealthStatsHelper.UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + // } + // { + // double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_SCAN); + // long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_WIFI_SCAN); + // power += new HealthStatsHelper.UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + // } + // Assert.fail("power: " + power + ", watt: " + powerMaPerPacket); + + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_WIFI_POWER_MAMS)) { + double powerMahByHealthStats = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_WIFI_POWER_MAMS) / (1000.0 * 60 * 60); + Assert.assertTrue(powerMahByHealthStats >= 0); + } + + double powerMah = 0; + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_WIFI_IDLE_MS)) { + long idleMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_WIFI_IDLE_MS); + powerMah += etmWifiIdlePower.calculatePower(idleMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_WIFI_RX_MS)) { + long rxMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_WIFI_RX_MS); + powerMah += etmWifiRxPower.calculatePower(rxMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_WIFI_TX_MS)) { + long txMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_WIFI_TX_MS); + powerMah += etmWifiTxPower.calculatePower(txMs); + } + Assert.assertTrue(powerMah >= 0); + + double calcPower = HealthStatsHelper.calcWifiPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + } + + @Test + public void testEstimateBlueToothPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + Assert.assertTrue(powerProfile.getAveragePower("bluetooth.controller.idle") > 0); + Assert.assertTrue(powerProfile.getAveragePower("bluetooth.controller.rx") > 0); + Assert.assertTrue(powerProfile.getAveragePower("bluetooth.controller.tx") > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_POWER_MAMS)) { + double powerMahByHealthStats = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_POWER_MAMS) / (1000.0 * 60 * 60); + Assert.assertTrue(powerMahByHealthStats >= 0); + } + + double powerMah = 0; + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_IDLE_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_IDLE_MS); + double powerMa = powerProfile.getAveragePower("bluetooth.controller.idle"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_RX_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_RX_MS); + double powerMa = powerProfile.getAveragePower("bluetooth.controller.rx"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + if (healthStats.hasMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_TX_MS)) { + long timeMs = healthStats.getMeasurement(UidHealthStats.MEASUREMENT_BLUETOOTH_TX_MS); + double powerMa = powerProfile.getAveragePower("bluetooth.controller.tx"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + Assert.assertTrue(powerMah >= 0); + + double calcPower = HealthStatsHelper.calcBlueToothPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + } + + @Test + public void testEstimateGpsPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + double powerMa = 0; + powerMa = powerProfile.getAveragePower("gps.on"); + if (powerMa <= 0) { + int num = powerProfile.getNumElements("gps.signalqualitybased"); + double sumMa = 0; + for (int i = 0; i < num; i++) { + sumMa += powerProfile.getAveragePower("gps.signalqualitybased", i); + } + powerMa = sumMa / num; + } + + Assert.assertTrue(powerMa > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double powerMah = 0; + if (healthStats.hasTimer(UidHealthStats.TIMER_GPS_SENSOR)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_GPS_SENSOR); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + Assert.assertTrue(powerMah >= 0); + + double calcPower = HealthStatsHelper.calcGpsPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + } + + @Test + public void testEstimateSensorsPower() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double powerMah = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_SENSORS)) { + SensorManager sm = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + Assert.assertNotNull(sm); + List sensorList = sm.getSensorList(Sensor.TYPE_ALL); + Assert.assertFalse(sensorList.isEmpty()); + + Map sensorMap = new HashMap<>(); + for (Sensor item : sensorList) { + Method method = item.getClass().getDeclaredMethod("getHandle"); + int handle = (int) method.invoke(item); + sensorMap.put(String.valueOf(handle), item); + } + + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SENSORS); + for (Map.Entry item : timers.entrySet()) { + String handle = item.getKey(); + long timeMs = item.getValue().getTime(); + if (handle.equals("-10000")) { + continue; // skip GPS Sensors + } + Sensor sensor = sensorMap.get(handle); + if (sensor != null) { + powerMah += new UsageBasedPowerEstimator(sensor.getPower()).calculatePower(timeMs); + } + } + } + Assert.assertTrue(powerMah >= 0); + } + + @Test + public void testEstimateMediaAndHwPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double powerMah = 0; + + // Camera + Assert.assertTrue(powerProfile.getAveragePower("camera.avg") > 0); + if (healthStats.hasTimer(UidHealthStats.TIMER_CAMERA)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_CAMERA); + double powerMa = powerProfile.getAveragePower("camera.avg"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + + double calcPower = HealthStatsHelper.calcCameraPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + + // Flash Light + Assert.assertTrue(powerProfile.getAveragePower("camera.flashlight") > 0); + if (healthStats.hasTimer(UidHealthStats.TIMER_FLASHLIGHT)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_FLASHLIGHT); + double powerMa = powerProfile.getAveragePower("camera.flashlight"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + + calcPower = HealthStatsHelper.calcFlashLightPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + + // Media + Assert.assertTrue(powerProfile.getAveragePower("audio") > 0); + Assert.assertTrue(powerProfile.getAveragePower("video") > 0); + if (healthStats.hasTimer(UidHealthStats.TIMER_AUDIO)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_AUDIO); + double powerMa = powerProfile.getAveragePower("audio"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + + calcPower = HealthStatsHelper.calcAudioPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + + if (healthStats.hasTimer(UidHealthStats.TIMER_VIDEO)) { + long timeMs = healthStats.getTimerTime(UidHealthStats.TIMER_VIDEO); + double powerMa = powerProfile.getAveragePower("video"); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + Assert.assertTrue(powerMah >= 0); + + calcPower = HealthStatsHelper.calcVideoPower(powerProfile, healthStats); + Assert.assertEquals(powerMah, calcPower, 1d); + } + + @Test + public void testEstimateScreenPower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + Assert.assertTrue(powerProfile.getAveragePower("screen.on") > 0); + Assert.assertTrue(powerProfile.getAveragePower("screen.full") > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double calcPower = HealthStatsHelper.calcScreenPower(powerProfile, healthStats); + Assert.assertTrue(calcPower >= 0); + } + + @Test + public void testEstimateSystemServicePower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + CpuStatFeature feature = new CpuStatFeature(); + feature.configure(mockMonitor()); + feature.onTurnOn(); + + Assert.assertTrue(feature.isSupported()); + CpuStatFeature.CpuStateSnapshot cpuStateSnapshot = feature.currentCpuStateSnapshot(); + Assert.assertTrue(cpuStateSnapshot.procCpuCoreStates.size() > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + long timeMs = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_JOBS)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_JOBS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_SYNCS)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SYNCS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + } + + double calcPower = HealthStatsHelper.calcSystemServicePower(powerProfile, healthStats); + Assert.assertTrue(calcPower >= 0); + } + + @Test + public void testEstimateIdlePower() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + Assert.assertTrue(powerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND) > 0); + Assert.assertTrue(powerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE) > 0); + + SystemHealthManager manager = (SystemHealthManager) mContext.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + HealthStats healthStats = manager.takeMyUidSnapshot(); + Assert.assertNotNull(healthStats); + + double calcPower = HealthStatsHelper.calcIdlePower(powerProfile, healthStats); + Assert.assertTrue(calcPower >= 0); + } + + @Test + public void testGetCurrSnapshot() { + HealthStatsFeature feature = new HealthStatsFeature(); + feature.configure(mockMonitor()); + feature.onTurnOn(); + + Assert.assertNotNull(feature.currHealthStats()); + + HealthStatsSnapshot bgn = feature.currHealthStatsSnapshot(); + Assert.assertNotNull(bgn); + Assert.assertNotNull(bgn.healthStats); + + HealthStatsSnapshot end = feature.currHealthStatsSnapshot(); + Assert.assertNotNull(end); + Assert.assertNotNull(end.healthStats); + + Delta delta = end.diff(bgn); + Assert.assertNotNull(delta); + Assert.assertNull(delta.dlt.healthStats); + + Assert.assertEquals(delta.dlt.getTotalPower(), end.getTotalPower() - bgn.getTotalPower(), 0.001d); + } + + @Test + public void testHealthStatsAccCollecting() throws InterruptedException { + HealthStatsFeature feature = new HealthStatsFeature(); + feature.configure(mockMonitor()); + feature.onTurnOn(); + + Assert.assertNotNull(feature.currHealthStats()); + + HealthStatsSnapshot bgn = feature.currHealthStatsSnapshot(); + Assert.assertNotNull(bgn); + + Assert.assertNull(bgn.accCollector); + HealthStatsSnapshot.AccCollector accCollector = bgn.startAccCollecting(); + Assert.assertNotNull(accCollector); + Assert.assertSame(bgn.accCollector, accCollector); + Assert.assertSame(bgn, accCollector.last); + + int accCount = 2; + long intervalMs = 2000L; + long accDuringMs = 0L; + for (int i = 0; i < accCount; i++) { + Thread.sleep(intervalMs); + HealthStatsSnapshot curr = feature.currHealthStatsSnapshot(); + Delta delta = bgn.accCollect(curr); + Assert.assertNotNull(delta); + Assert.assertEquals(i + 1, accCollector.count); + Assert.assertEquals(accDuringMs += delta.during, accCollector.duringMs); + } + + Thread.sleep(intervalMs); + HealthStatsSnapshot end = feature.currHealthStatsSnapshot(); + Assert.assertNotNull(end); + + Assert.assertEquals(accCount, accCollector.count); + Delta deltaByAcc = end.diffByAccCollector(bgn); + Assert.assertNotNull(deltaByAcc); + Assert.assertTrue(deltaByAcc instanceof Delta.SimpleDelta); + Assert.assertEquals(accCount + 1, accCollector.count); + + Delta delta = end.diff(bgn); + Assert.assertNotNull(delta); + Assert.assertFalse(delta instanceof Delta.SimpleDelta); + + Assert.assertSame(delta.bgn, deltaByAcc.bgn); + Assert.assertSame(delta.end, deltaByAcc.end); + Assert.assertEquals(delta.during, deltaByAcc.during); + Assert.assertEquals(delta.dlt.isDelta, deltaByAcc.dlt.isDelta); + Assert.assertEquals(delta.dlt.getTotalPower(), deltaByAcc.dlt.getTotalPower(), 0.0001d); + + Assert.assertSame(delta.end, accCollector.last); + Assert.assertEquals(delta.during, accCollector.duringMs); + } + + @Test + public void testCalcAvgPowerPerPacket() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + final long MOBILE_BPS = 200000; + final double MOBILE_POWER = powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, 120) / 3600; + final double mobilePps = (((double) MOBILE_BPS) / 8 / 2048); + double mobilePowerPerPacket = (MOBILE_POWER / mobilePps) / (60 * 60); + Assert.assertTrue(mobilePowerPerPacket > 0); + + final long wifiBps = 1000000; + final double averageWifiActivePower = powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_WIFI_ACTIVE, 120) / 3600; + double wifiPowerPerPacket = averageWifiActivePower / (((double) wifiBps) / 8 / 2048); + Assert.assertTrue(wifiPowerPerPacket > 0); + + Assert.assertTrue(mobilePowerPerPacket < wifiPowerPerPacket); + } + + + public static class UsageBasedPowerEstimator { + private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60; + private final double mAveragePowerMahPerMs; + + public UsageBasedPowerEstimator(double averagePowerMilliAmp) { + mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR; + } + + public boolean isSupported() { + return mAveragePowerMahPerMs != 0; + } + + public double calculatePower(long durationMs) { + return mAveragePowerMahPerMs * durationMs; + } + } + +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspectorTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspectorTest.java new file mode 100644 index 000000000..3a13fb736 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspectorTest.java @@ -0,0 +1,53 @@ +package com.tencent.matrix.batterycanary.utils; + +import android.app.Application; +import android.content.Context; + +import com.tencent.matrix.Matrix; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +/** + * @author Kaede + * @since 9/9/2022 + */ +@RunWith(AndroidJUnit4.class) +public class BatteryCurrencyInspectorTest { + static final String TAG = "Matrix.test.BatteryCurrencyInspectorTest"; + + Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + if (!Matrix.isInstalled()) { + Matrix.init(new Matrix.Builder(((Application) mContext.getApplicationContext())).build()); + } + } + + @After + public void shutDown() { + } + + @Test + @SuppressWarnings("ConstantConditions") + public void testInspection() { + boolean charging = BatteryCanaryUtil.isDeviceCharging(mContext); + if (charging) { + Assert.assertNull(BatteryCurrencyInspector.isMicroAmpCurr(mContext)); + Assert.assertTrue(BatteryCurrencyInspector.isPositiveInChargeCurr(mContext)); + Assert.assertNull(BatteryCurrencyInspector.isPositiveOutOfChargeCurr(mContext)); + } else { + Assert.assertTrue(BatteryCurrencyInspector.isMicroAmpCurr(mContext)); + Assert.assertNull(BatteryCurrencyInspector.isPositiveInChargeCurr(mContext)); + Assert.assertFalse(BatteryCurrencyInspector.isPositiveOutOfChargeCurr(mContext)); + } + } +} diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryMetricsTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryMetricsTest.java index 7c9fa89cb..c8e7520a0 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryMetricsTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/BatteryMetricsTest.java @@ -23,10 +23,12 @@ import android.os.Looper; import android.os.Process; import android.os.SystemClock; +import android.system.Os; import android.text.TextUtils; import android.util.Log; import com.tencent.matrix.batterycanary.TestUtils; +import com.tencent.matrix.batterycanary.monitor.feature.CompositeMonitors; import org.junit.After; import org.junit.Assert; @@ -44,6 +46,7 @@ import java.util.Arrays; import java.util.List; import java.util.Stack; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -53,7 +56,6 @@ import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.JIFFY_MILLIS; import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.ONE_HOR; -import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.ONE_MIN; @RunWith(AndroidJUnit4.class) @@ -103,6 +105,88 @@ public void testReadPowerProfile() throws Exception { Assert.assertTrue(PowerProfile.getInstance().isSupported()); } + @Test + public void testReadPowerProfileTest() throws Exception { + Class clazz = Class.forName("com.android.internal.os.PowerProfile"); + Constructor constructor = clazz.getConstructor(Context.class); + Object obj = constructor.newInstance(mContext); + Assert.assertNotNull(obj); + + int id = mContext.getResources().getIdentifier("power_profile_test", "xml", "android"); + Assert.assertTrue(id > 0); + } + + @Test + public void testFindPowerProfileFile() throws Exception { + Callable findBlock = new Callable() { + @Override + public File call() { + String customDirs = Os.getenv("CUST_POLICY_DIRS"); + Assert.assertFalse(TextUtils.isEmpty(customDirs)); + for (String dir : customDirs.split(":")) { + // example: /hw_product/etc/xml/power_profile.xml + File file = new File(dir, "/xml/power_profile.xml"); + if (file.exists() && file.canRead()) { + return file; + } + } + return null; + } + }; + + File file = findBlock.call(); + if (file != null) { + PowerProfile powerProfile = new PowerProfile(mContext); + Assert.fail(PowerProfile.getResType()); + powerProfile.readPowerValuesFromFilePath(mContext, file); + powerProfile.initCpuClusters(); + powerProfile.smoke(); + } + } + + @Test + public void KernelCpuUidFreqTimeReaderCompat() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + int[] clusterSteps = new int[powerProfile.getNumCpuClusters()]; + for (int i = 0; i < clusterSteps.length; i++) { + clusterSteps[i] = powerProfile.getNumSpeedStepsInCpuCluster(i); + } + KernelCpuUidFreqTimeReader reader = new KernelCpuUidFreqTimeReader(Process.myPid(), clusterSteps); + reader.smoke(); + } + + @Test + public void testCpuCoreStepSpeedsSampling() throws IOException, InterruptedException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + List cpuFreqSteps = BatteryCanaryUtil.getCpuFreqSteps(); + int[] cpuCurrentFreq = BatteryCanaryUtil.getCpuCurrentFreq(); + Assert.assertNotNull(cpuFreqSteps); + Assert.assertNotNull(cpuCurrentFreq); + + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + } + } + }).start(); + + CompositeMonitors.CpuFreqSampler sampler = new CompositeMonitors.CpuFreqSampler(BatteryCanaryUtil.getCpuFreqSteps()); + Assert.assertTrue(sampler.isCompat(powerProfile)); + if (!TestUtils.isAssembleTest()) { + while (true) { + sampler.count(BatteryCanaryUtil.getCpuCurrentFreq()); + Thread.sleep(5000L); + } + } + } + @Test public void testGetAppCpuCoreNumByTid() { StringBuilder tips = new StringBuilder("\n"); @@ -259,7 +343,7 @@ public void testReadKernelCpuSpeedState() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - Assert.assertTrue(kernelCpuSpeedReader.readTotoal() > 0); + Assert.assertTrue(kernelCpuSpeedReader.readTotal() > 0); long[] cpuCoreJiffies = kernelCpuSpeedReader.readAbsolute(); Assert.assertEquals(powerProfile.getNumSpeedStepsInCpuCluster(i), cpuCoreJiffies.length); for (int j = 0; j < cpuCoreJiffies.length; j++) { @@ -291,7 +375,7 @@ public void testConfigureCpuLoad() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue("idx = " + i + ", read = " + Arrays.toString(kernelCpuSpeedReader.readAbsolute()), cpuCoreJiffies > 0); cpuJiffiesBgn += cpuCoreJiffies; } @@ -303,7 +387,7 @@ public void testConfigureCpuLoad() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue(cpuCoreJiffies > 0); cpuJiffiesEnd += cpuCoreJiffies; } @@ -346,7 +430,7 @@ public void testConfigureCpuLoadWithMultiThread() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue("idx = " + i + ", read = " + Arrays.toString(kernelCpuSpeedReader.readAbsolute()), cpuCoreJiffies > 0); cpuJiffiesBgn += cpuCoreJiffies; } @@ -366,7 +450,7 @@ public void run() { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue(cpuCoreJiffies > 0); cpuJiffiesEnd += cpuCoreJiffies; } @@ -513,7 +597,7 @@ public void testConfigureCpuBatterySippingDelta() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue("idx = " + i + ", read = " + Arrays.toString(kernelCpuSpeedReader.readAbsolute()), cpuCoreJiffies > 0); cpuJiffiesBgn += cpuCoreJiffies; } @@ -532,7 +616,7 @@ public void testConfigureCpuBatterySippingDelta() throws IOException { for (int i = 0; i < readers.length; i++) { KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; Assert.assertNotNull(kernelCpuSpeedReader); - long cpuCoreJiffies = kernelCpuSpeedReader.readTotoal(); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); Assert.assertTrue(cpuCoreJiffies > 0); cpuJiffiesEnd += cpuCoreJiffies; } @@ -809,6 +893,64 @@ public void testJiffiesConsumption2() { } } + @Test + public void testRead() throws IOException { + PowerProfile powerProfile = PowerProfile.init(mContext); + Assert.assertNotNull(powerProfile); + Assert.assertTrue(powerProfile.isSupported()); + + List read = KernelCpuFreqPolicyReader.read(); + long uptimeBgn = SystemClock.uptimeMillis(); + long policyBgn = 0; + long kernelBgn = 0; + long procBgn = ProcStatUtil.of(Process.myPid()).getJiffies(); + + for (KernelCpuFreqPolicyReader item : read) { + policyBgn += item.readTotal(); + } + KernelCpuSpeedReader[] readers = new KernelCpuSpeedReader[BatteryCanaryUtil.getCpuCoreNum()]; + for (int i = 0; i < BatteryCanaryUtil.getCpuCoreNum(); i++) { + final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(powerProfile.getClusterByCpuNum(i)); + readers[i] = new KernelCpuSpeedReader(i, numSpeedSteps); + } + for (int i = 0; i < readers.length; i++) { + KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; + Assert.assertNotNull(kernelCpuSpeedReader); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); + Assert.assertTrue("idx = " + i + ", read = " + Arrays.toString(kernelCpuSpeedReader.readAbsolute()), cpuCoreJiffies > 0); + kernelBgn += cpuCoreJiffies; + } + + // No matter full-running or sleep, the delta of cpufreq policy & cpu speed time is always the same? + CpuConsumption.hanoi(20); + // try { + // Thread.sleep(10000L); + // } catch (InterruptedException e) { + // e.printStackTrace(); + // } + + long uptimeEnd = SystemClock.uptimeMillis(); + long policyEnd = 0; + long kernelEnd = 0; + long procEnd = ProcStatUtil.of(Process.myPid()).getJiffies(); + + for (KernelCpuFreqPolicyReader item : read) { + policyEnd += item.readTotal(); + } + for (int i = 0; i < readers.length; i++) { + KernelCpuSpeedReader kernelCpuSpeedReader = readers[i]; + Assert.assertNotNull(kernelCpuSpeedReader); + long cpuCoreJiffies = kernelCpuSpeedReader.readTotal(); + Assert.assertTrue(cpuCoreJiffies > 0); + kernelEnd += cpuCoreJiffies; + } + + Assert.fail("policy: " + (policyEnd - policyBgn) * 10f / (uptimeEnd - uptimeBgn) + + ", kernel: " + (kernelEnd - kernelBgn) * 10f / (uptimeEnd - uptimeBgn) + + ", procStat: " + (procEnd - procBgn) * 10f / (uptimeEnd - uptimeBgn) + ); + } + public static class CpuConsumption { public static void inc(int num) { for (int i = 0; i < num; i++) { diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/CanaryUtilsTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/CanaryUtilsTest.java index 704399d8e..a8539be08 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/CanaryUtilsTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/CanaryUtilsTest.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; +import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -46,6 +47,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; @@ -133,6 +135,29 @@ public void testGetPkgName() { Assert.assertEquals(mContext.getPackageName(), pkg); } + @Test + public void testGetCpuCoreNum() throws IOException { + long nanos1 = SystemClock.elapsedRealtimeNanos(); + + int coreNum1 = BatteryCanaryUtil.getCpuCoreNumImmediately(); + long nanos2 = SystemClock.elapsedRealtimeNanos(); + + int coreNum2 = Runtime.getRuntime().availableProcessors(); + long nanos3 = SystemClock.elapsedRealtimeNanos(); + + PowerProfile powerProfile = PowerProfile.init(mContext); + int coreNum3 = powerProfile.getCpuCoreNum(); + long nanos4 = SystemClock.elapsedRealtimeNanos(); + + + Assert.assertEquals(coreNum1, coreNum2); + Assert.assertEquals(coreNum2, coreNum3); + + if (!TestUtils.isAssembleTest()) { + Assert.fail((nanos2 - nanos1) + " vs " + (nanos3 - nanos2) + " vs " + (nanos4 - nanos3)); + } + } + @Test public void testGetThrowableStack() { if (TestUtils.isAssembleTest()) return; @@ -169,6 +194,22 @@ public void testGetCpuFreq() throws InterruptedException { } } + @Test + public void testGetCpuFreqSteps() throws InterruptedException { + List last = null; + for (int i = 0; i < 5; i++) { + List freqSteps = BatteryCanaryUtil.getCpuFreqSteps(); + Assert.assertNotNull(freqSteps); + if (last != null) { + for (int j = 0, freqStepsSize = freqSteps.size(); j < freqStepsSize; j++) { + Assert.assertArrayEquals(last.get(j), freqSteps.get(j)); + } + } + last = freqSteps; + Thread.sleep(100L); + } + } + @Test public void testGetBatteryTemps() throws InterruptedException { for (int i = 0; i < 5; i++) { @@ -219,10 +260,10 @@ public void testGetBatteryPercentage() { @Test public void testGetBatteryCapacity() throws Exception { + PowerProfile powerProfile = PowerProfile.init(mContext); int capacity0 = BatteryCanaryUtil.getBatteryCapacityImmediately(mContext); Assert.assertTrue(capacity0 > 0); - PowerProfile powerProfile = PowerProfile.init(mContext); Assert.assertNotNull(powerProfile); Assert.assertTrue(powerProfile.isSupported()); double capacity1 = powerProfile.getBatteryCapacity(); @@ -246,6 +287,21 @@ public void testGetBatteryCapacity() throws Exception { Assert.assertEquals(capacity3, capacity4, 1000d); } + @Test + public void testGetBatteryCurrent() { + BatteryManager batteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE); + long avgCurrent = 0, currentNow = 0; + avgCurrent = batteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE); + currentNow = batteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW); + Log.d(TAG, "BATTERY_PROPERTY_CURRENT_AVERAGE = " + avgCurrent + "microA"); + Log.d(TAG, "BATTERY_PROPERTY_CURRENT_NOW = " + currentNow + "microA"); + Assert.assertNotEquals(0, avgCurrent); + Assert.assertNotEquals(0, currentNow); + + long value = BatteryCanaryUtil.getBatteryCurrencyImmediately(mContext); + Assert.assertNotEquals(0, value); + } + @Test public void testCheckIfLowBattery() { Intent intent = BatteryCanaryUtil.getBatteryStickyIntent(mContext); diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ConnectivityManagerHookerTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ConnectivityManagerHookerTest.java new file mode 100644 index 000000000..fba7caac2 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ConnectivityManagerHookerTest.java @@ -0,0 +1,181 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.batterycanary.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.SystemClock; + +import com.tencent.matrix.batterycanary.TestUtils; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.Pair; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.ONE_HOR; + + +@RunWith(AndroidJUnit4.class) +public class ConnectivityManagerHookerTest { + static final String TAG = "Matrix.test.ConnectivityManagerHookerTest"; + + Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + } + + @After + public void shutDown() { + } + + @Test + public void testScanning() throws Exception { + if (TestUtils.isAssembleTest()) return; + + final AtomicInteger scanInc = new AtomicInteger(); + final AtomicInteger getScanInc = new AtomicInteger(); + SystemServiceBinderHooker hooker = new SystemServiceBinderHooker(Context.CONNECTIVITY_SERVICE, "android.net.IConnectivityManager", new SystemServiceBinderHooker.HookCallback() { + @Override + public void onServiceMethodInvoke(Method method, Object[] args) { + Assert.assertNotNull(method); + } + + @Nullable + @Override + public Object onServiceMethodIntercept(Object receiver, Method method, Object[] args) throws Throwable { + return null; + } + }); + + hooker.doHook(); + + ConnectivityManager manager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + Assert.assertNotNull(manager); + + List>> pairs = new ArrayList<>(); + for (Network item : manager.getAllNetworks()) { + NetworkInfo networkInfo = manager.getNetworkInfo(item); + if (networkInfo != null && (networkInfo.isConnected() || networkInfo.isConnectedOrConnecting())) { + pairs.add(Arrays.asList( + new Pair<>(networkInfo.getType(), String.valueOf(networkInfo.getTypeName())), + new Pair<>(networkInfo.getSubtype(), String.valueOf(networkInfo.getSubtypeName())) + )); + int txBwKBps = 0, rxBwKBps = 0; + NetworkCapabilities capabilities = manager.getNetworkCapabilities(item); + if (capabilities != null) { + txBwKBps = capabilities.getLinkUpstreamBandwidthKbps() / 8; + rxBwKBps = capabilities.getLinkDownstreamBandwidthKbps() / 8; + } + } + } + + Assert.assertFalse(pairs.isEmpty()); + + ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() { + long mLastMs = 0; + + @Override + public void onAvailable(@NonNull Network network) { + super.onAvailable(network); + } + + @Override + public void onLosing(@NonNull Network network, int maxMsToLive) { + super.onLosing(network, maxMsToLive); + } + + @Override + public void onLost(@NonNull Network network) { + super.onLost(network); + } + + @Override + public void onUnavailable() { + super.onUnavailable(); + } + + @SuppressLint({"WrongConstant", "RestrictedApi"}) + @Override + public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { + super.onCapabilitiesChanged(network, networkCapabilities); + try { + int strength = 0; + int[] capabilities = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + strength = networkCapabilities.getSignalStrength(); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + capabilities = networkCapabilities.getCapabilities(); + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + } + } catch (Throwable ignored) { + } + + // Dual-link check: both wifi & cellar transported networks + { + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + } + } + } catch (Throwable e) { + } + } + + @Override + public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties linkProperties) { + super.onLinkPropertiesChanged(network, linkProperties); + } + + @Override + public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) { + super.onBlockedStatusChanged(network, blocked); + } + }; + + manager.registerDefaultNetworkCallback(callback); + hooker.doUnHook(); + } +} + diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/KernelCpuFreqPolicyReader.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/KernelCpuFreqPolicyReader.java new file mode 100644 index 000000000..20b3ec3d5 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/KernelCpuFreqPolicyReader.java @@ -0,0 +1,62 @@ +package com.tencent.matrix.batterycanary.utils; + + +import android.text.TextUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +public class KernelCpuFreqPolicyReader { + private static final String TAG = "KernelCpuSpeedReader"; + + public static List read() { + List list = new ArrayList<>(); + File file = new File("/sys/devices/system/cpu/cpufreq/"); + File[] files = file.listFiles(); + if (files != null) { + for (File item : files) { + if (item.isDirectory() && item.getName().startsWith("policy") && item.canRead()) { + list.add(new KernelCpuFreqPolicyReader(item.getName())); + } + } + } + return list; + } + + private final String mPolicy; + private final String mProcFile; + + public KernelCpuFreqPolicyReader(String policy) { + mPolicy = policy; + mProcFile = "/sys/devices/system/cpu/cpufreq/" + policy + "/stats/time_in_state"; + } + + public long readTotal() throws IOException { + long sum = 0; + for (long item : readAbsolute()) { + sum += item; + } + return sum; + } + + public List readAbsolute() throws IOException { + List speedTimeJiffies = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) { + TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); + String line; + while ((line = reader.readLine()) != null) { + splitter.setString(line); + splitter.next(); + speedTimeJiffies.add(Long.parseLong(splitter.next())); + } + } catch (Throwable e) { + throw new IOException("Failed to read cpu-freq time: " + e.getMessage(), e); + } + return speedTimeJiffies; + } +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/PowerManagerHookerTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/PowerManagerHookerTest.java index 9f7efb31e..a4f73c639 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/PowerManagerHookerTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/PowerManagerHookerTest.java @@ -16,10 +16,14 @@ package com.tencent.matrix.batterycanary.utils; +import android.annotation.SuppressLint; import android.content.Context; import android.os.IBinder; import android.os.PowerManager; import android.os.WorkSource; +import android.text.TextUtils; +import android.util.Log; + import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -31,6 +35,7 @@ import org.junit.runner.RunWith; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -89,6 +94,8 @@ public void onReleaseWakeLock(IBinder token, int flags) { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wakLockRef.set(wakeLock); + String wakeLockTag = getWakeLockTag(wakeLock); + Assert.assertEquals(TAG, wakeLockTag); Assert.assertNotNull(wakeLock); wakeLock.acquire(); @@ -101,6 +108,51 @@ public void onReleaseWakeLock(IBinder token, int flags) { Assert.assertTrue(hasRelease.get()); } + private static String getWakeLockTag(PowerManager.WakeLock wakeLock) { + String tag = null; + try { + @SuppressWarnings("JavaReflectionMemberAccess") + @SuppressLint({"SoonBlockedPrivateApi"}) + Method method = wakeLock.getClass().getDeclaredMethod("getTag"); + method.setAccessible(true); + tag = (String) method.invoke(wakeLock); + } catch (Throwable e) { + Log.e(TAG, "getTag err: " + e); + } + if (TextUtils.isEmpty(tag)) { + try { + @SuppressWarnings("JavaReflectionMemberAccess") + @SuppressLint("DiscouragedPrivateApi") + Field field = wakeLock.getClass().getDeclaredField("mTag"); + field.setAccessible(true); + tag = (String) field.get(wakeLock); + } catch (Throwable e) { + Log.e(TAG, "mTag err: " + e); + } + } + if (TextUtils.isEmpty(tag)) { + try { + @SuppressWarnings("JavaReflectionMemberAccess") + @SuppressLint({"SoonBlockedPrivateApi"}) + Field field = wakeLock.getClass().getDeclaredField("mTraceName"); + field.setAccessible(true); + tag = (String) field.get(wakeLock); + if (tag != null && tag.startsWith("WakeLock (") && tag.endsWith(")")) { + int idxBgn = tag.indexOf("WakeLock (") + "WakeLock (".length(); + int idxEnd = tag.lastIndexOf(")"); + if (idxBgn < idxEnd) { + tag = tag.substring(idxBgn, idxEnd); + } + } + } catch (Throwable e) { + Log.e(TAG, "mTraceName err: " + e); + } + } + + Log.i(TAG, "getWakeLockTag: " + tag); + return tag; + } + @Ignore @Test public void testAcquireWakeupTimeout() throws Exception { diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ProcStatUtilsTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ProcStatUtilsTest.java index 47047667b..0f5deae15 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ProcStatUtilsTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/ProcStatUtilsTest.java @@ -24,12 +24,9 @@ import android.os.Process; import android.os.SystemClock; import android.text.TextUtils; -import android.util.Pair; import android.util.SparseArray; import com.tencent.matrix.batterycanary.TestUtils; -import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature; -import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature; import com.tencent.matrix.util.MatrixLog; import org.junit.After; @@ -41,6 +38,7 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -51,10 +49,15 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import androidx.arch.core.util.Function; +import androidx.core.util.Consumer; +import androidx.core.util.Pair; +import androidx.core.util.Supplier; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; @@ -94,6 +97,165 @@ public void testGetCurrentMethodName() { Assert.assertEquals("testGetCurrentMethodName", ste[2].getMethodName()); } + @Test + public void testGetRootProcStat() { + List names = Arrays.asList( + "asound", + "ath_pktlog", + "bldrlog", + "buddyinfo", + "bus", + "cgroups", + "cld", + "cmdline", + "config.gz", + "consoles", + "crypto", + "debugdriver", + "driver", + "dynamic_debug", + "device-tree", + "devices", + "diskstats", + "execdomains", + "fs", + "fts", + "fb", + "filesystems", + "interrupts", + "iomem", + "ioports", + "irq", + "kallsyms", + "key-users", + "keys", + "kmsg", + "kpagecount", + "kpageflags", + "loadavg", + "locks", + "misc", + "modules", + "net", + "pressure", + "pagetypeinfo", + "partitions", + "sched_debug", + "slabinfo", + "sysrq-trigger", + "schedstat", + "softirqs", + "stat", + "swaps", + "sysrq-trigger", + "scsi", + "sys", + "tty", + "timer_list", + "uid", + "uid_concurrent_active_time", + "uid_concurrent_policy_time", + "uid_cputime", + "uid_io", + "uid_procstat", + "uid_time_in_state", + "uptime", + "version", + "vmallocinfo", + "vmstat", + "zoneinfo" + ); + + final Function inWhiteList = new Function() { + @Override + public Boolean apply(File input) { + for (String item : Arrays.asList( + "/proc/sys/kernel/perf_", + "/proc/sys/kernel/random/" + )) { + if (input.getAbsolutePath().startsWith(item)) { + return true; + } + } + return false; + } + }; + + final Consumer fileConsumer = new Consumer() { + @Override + public void accept(File file) { + if (file.canRead()) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null && files.length > 0) { + for (File item : files) { + this.accept(item); + } + } + } else { + try (InputStream is = new FileInputStream(file)) { + Collection cat = IOUtil.readLines(is); + if (!inWhiteList.apply(file)) { + Assert.assertNull("Should be null-content: " + file.getAbsolutePath(), cat); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + }; + for (String item : names) { + File file = new File("proc/" + item); + Assert.assertTrue("shoud exits: " + item, file.exists()); + fileConsumer.accept(file); + } + } + + @Test + public void testGetReadableRootProcStat() { + List names = Arrays.asList( + "cpuinfo", + "meminfo", + "mounts", + "sys/kernel" + ); + + final Function>, Pair>> func = new Function>, Pair>>() { + @SuppressWarnings("ConstantConditions") + @Override + public Pair> apply(Pair> input) { + if (input.first.canRead()) { + if (input.first.isDirectory()) { + File[] files = input.first.listFiles(); + if (files != null && files.length > 0) { + for (File item : files) { + this.apply(new Pair<>(item, input.second)); + } + } + } else { + try (InputStream is = new FileInputStream(input.first)) { + Collection cat = IOUtil.readLines(is); + Assert.assertNotNull("Should not be empty: " + input.first.getAbsolutePath(), cat); + input.second.add(cat.toArray(new String[0])); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return input; + } + }; + + for (String item : names) { + File file = new File("proc/" + item); + Assert.assertTrue("shoud exits: " + item, file.exists()); + Assert.assertTrue("shoud readable: " + item, file.canRead()); + Pair> ret = func.apply(new Pair>(file, new ArrayList())); + Assert.assertNotEquals("shoud not empty: " + item, 0, ret.second.size()); + } + } + @Test public void testGetThreadJiffies() { ProcStatUtil.ProcStat stat = ProcStatUtil.current(); @@ -188,6 +350,18 @@ public void testGetCpuLoad() { public void testGetProcStat() { String cat = BatteryCanaryUtil.cat("/proc/stat"); Assert.assertTrue(TextUtils.isEmpty(cat)); + + File file = new File("/proc"); + File[] files = file.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile(); + } + }); + for (File item : files) { + cat = BatteryCanaryUtil.cat("/proc/stat"); + Assert.assertTrue(TextUtils.isEmpty(cat)); + } } /** diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TemperatureUtilsTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TemperatureUtilsTest.java index f0770112b..2945dc677 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TemperatureUtilsTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TemperatureUtilsTest.java @@ -69,6 +69,22 @@ public void setUp() { public void shutDown() { } + @Test + public void getHardwareManager() { + HardwarePropertiesManager manager = (HardwarePropertiesManager) mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); + Assert.assertNotNull(manager); + try { + manager.getCpuUsages(); + Assert.fail("Should be permission denied"); + } catch (SecurityException ignored) { + } + try { + manager.getFanSpeeds(); + Assert.fail("Should be permission denied"); + } catch (SecurityException ignored) { + } + } + @Test public void testGetCpuTemp() { HardwarePropertiesManager manager = (HardwarePropertiesManager) mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TimeBreakerTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TimeBreakerTest.java index 7b15e76a7..14798ba73 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TimeBreakerTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/TimeBreakerTest.java @@ -20,15 +20,21 @@ import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Objects; @@ -278,6 +284,54 @@ public TimeBreaker.Stamp stamp(String key) { } } + @Test + public void testPortionFromJson() throws JSONException { + final List stampList = new ArrayList<>(); + JSONArray json = new JSONArray("[\n" + + " {\n" + + " \"key\": \"1\",\n" + + " \"upTime\": 13269846,\n" + + " \"statMillis\": \"05:25:05\"\n" + + " },\n" + + " {\n" + + " \"key\": \"1\",\n" + + " \"upTime\": 13269845,\n" + + " \"statMillis\": \"05:25:05\"\n" + + " }\n" + + "]"); + + for (int i = 0; i < json.length(); i++) { + JSONObject jsonObject = json.getJSONObject(i); + long upTime = jsonObject.getLong("upTime"); + Timestamp timestamp = Timestamp.valueOf(new SimpleDateFormat("yyyy-MM-dd ") + .format(new Date()) + .concat(jsonObject.getString("statMillis"))); + long statMillis = timestamp.getTime(); + TimeBreaker.Stamp stamp = new TimeBreaker.Stamp(jsonObject.getString("key"), upTime, statMillis); + stampList.add(stamp); + } + + Assert.assertFalse(stampList.isEmpty()); + + long windowMs = BatteryCanaryUtil.ONE_HOR; + final String currStat = "1"; + final long currUpTimeMs = 16978289; + final long currStatMs = Timestamp.valueOf(new SimpleDateFormat("yyyy-MM-dd ") + .format(new Date()) + .concat("06:26:54")).getTime(); + TimeBreaker.TimePortions portions = TimeBreaker.configurePortions( + stampList, + windowMs, + 10L, + new TimeBreaker.Stamp.Stamper() { + @Override + public TimeBreaker.Stamp stamp(String key) { + return new TimeBreaker.Stamp(currStat, currUpTimeMs, currStatMs); + } + }); + Assert.assertTrue(portions.isValid()); + } + @Test public void testConcurrentBenchmark() throws InterruptedException { final List stampList = new ArrayList<>(); diff --git a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/WifiManagerHookerTest.java b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/WifiManagerHookerTest.java index c1ecc28d1..02a36cadf 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/WifiManagerHookerTest.java +++ b/matrix/matrix-android/matrix-battery-canary/src/androidTest/java/com/tencent/matrix/batterycanary/utils/WifiManagerHookerTest.java @@ -36,7 +36,7 @@ @RunWith(AndroidJUnit4.class) public class WifiManagerHookerTest { - static final String TAG = "Matrix.test.BleManagerHookerTest"; + static final String TAG = "Matrix.test.WifiManagerHookerTest"; Context mContext; diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/BatteryEventDelegate.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/BatteryEventDelegate.java index 810e8c6b0..e55051a26 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/BatteryEventDelegate.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/BatteryEventDelegate.java @@ -165,6 +165,13 @@ public void onReceive(Context context, final Intent intent) { mCore.getHandler().post(new Runnable() { @Override public void run() { + // Full charged + int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + if (status == BatteryManager.BATTERY_STATUS_FULL) { + onBatteryFullCharged(); + return; + } + // Power percentage int currPct = BatteryCanaryUtil.getBatteryPercentage(mContext); if (currPct >= 0 && currPct <= 1000) { @@ -179,6 +186,7 @@ public void run() { onBatteryPowerChanged(currPct); } } + // Battery temperature try { int currTemp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1); @@ -343,6 +351,19 @@ public void run() { } } + private void onBatteryFullCharged() { + if (Looper.myLooper() == Looper.getMainLooper()) { + dispatchBatteryFullCharged(); + } else { + mUiHandler.post(new Runnable() { + @Override + public void run() { + dispatchBatteryFullCharged(); + } + }); + } + } + private void onBatteryTemperatureChanged(final int temp) { if (Looper.myLooper() == Looper.getMainLooper()) { dispatchBatteryTemperatureChanged(temp); @@ -423,6 +444,21 @@ void dispatchBatteryPowerChanged(int pct) { } } + @VisibleForTesting + void dispatchBatteryFullCharged() { + MatrixLog.i(TAG, "dispatchBatteryFullCharged"); + synchronized (mListenerList) { + BatteryState batteryState = currentState(); + for (Listener item : mListenerList) { + if (item instanceof Listener.ExListener) { + if (((Listener.ExListener) item).onBatteryFullCharged(batteryState)) { + removeListener(item); + } + } + } + } + } + @VisibleForTesting void dispatchBatteryTemperatureChanged(int temperature) { MatrixLog.i(TAG, "onBatteryTemperatureChanged >> " + (temperature / 10f) + "°C"); @@ -599,6 +635,15 @@ interface ExListener extends Listener { @UiThread boolean onBatteryPowerChanged(BatteryState batteryState, int levelPct); + /** + * On battery power full charged. + * + * @param batteryState {@link BatteryState} + * @return return true if your listening is done, thus we remove your listener + */ + @UiThread + boolean onBatteryFullCharged(BatteryState batteryState); + /** * On battery power low or ok. * @@ -621,7 +666,7 @@ public DefaultListenerImpl(boolean keepAlive) { @Override public boolean onStateChanged(String event) { - return !mKeepAlive; + throw new RuntimeException("Use #onStateChanged(BatteryState, String) instead"); } @Override @@ -644,6 +689,11 @@ public boolean onBatteryPowerChanged(BatteryState batteryState, int levelPct) { return !mKeepAlive; } + @Override + public boolean onBatteryFullCharged(BatteryState batteryState) { + return !mKeepAlive; + } + @Override public boolean onBatteryStateChanged(BatteryState batteryState, boolean isLowBattery) { return !mKeepAlive; diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCallback.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCallback.java index fbcfe9502..4eb351a0d 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCallback.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCallback.java @@ -1,7 +1,6 @@ package com.tencent.matrix.batterycanary.monitor; import android.content.ComponentName; -import android.os.Process; import android.os.SystemClock; import android.text.TextUtils; @@ -408,7 +407,7 @@ public void accept(Delta delta) { } } - protected boolean onWritingSectionContent(@NonNull Delta sessionDelta, CompositeMonitors monitors, final Printer printer) { + protected boolean onWritingSectionContent(@NonNull Delta sessionDelta, final CompositeMonitors monitors, final Printer printer) { if (monitors.getMonitor() == null || monitors.getAppStats() == null) { return false; } @@ -419,8 +418,8 @@ protected boolean onWritingSectionContent(@NonNull Delta sessionDelta, Compos Delta delta = (Delta) sessionDelta; // header long minute = Math.max(1, delta.during / ONE_MIN); - long avgJiffies = delta.dlt.totalJiffies.get() / minute; - printer.append("| ").append("pid=").append(Process.myPid()) + long avgJiffies = monitors.computeAvgJiffies(delta.dlt.totalJiffies.get()); + printer.append("| ").append("cpu=").append(monitors.getCpuLoad()).append("/").append(monitors.getNorCpuLoad()) .tab().tab().append("fg=").append(BatteryCanaryUtil.convertAppStat(appStats.getAppStat())) .tab().tab().append("during(min)=").append(minute) .tab().tab().append("diff(jiffies)=").append(delta.dlt.totalJiffies.get()) @@ -432,22 +431,31 @@ protected boolean onWritingSectionContent(@NonNull Delta sessionDelta, Compos printer.writeLine("desc", "(status)name(tid)\tavg/total"); printer.writeLine("inc_thread_num", String.valueOf(delta.dlt.threadNum.get())); printer.writeLine("cur_thread_num", String.valueOf(delta.end.threadNum.get())); - for (ThreadJiffiesEntry threadJiffies : delta.dlt.threadEntries.getList().subList(0, Math.min(delta.dlt.threadEntries.getList().size(), 8))) { + int toppingCount = 8; + long remainJffies = 0; + for (int i = 0; i < delta.dlt.threadEntries.getList().size(); i++) { + ThreadJiffiesEntry threadJiffies = delta.dlt.threadEntries.getList().get(i); long entryJffies = threadJiffies.get(); - printer.append("| -> (").append(threadJiffies.isNewAdded ? "+" : "~").append("/").append(threadJiffies.stat).append(")") - .append(threadJiffies.name).append("(").append(threadJiffies.tid).append(")\t") - .append(entryJffies / minute).append("/").append(entryJffies).append("\tjiffies") - .append("\n"); - - // List threadTasks = tasks.get(threadJiffies.tid); - // if (null != threadTasks && !threadTasks.isEmpty()) { - // for (LooperTaskMonitorFeature.TaskTraceInfo task : threadTasks.subList(0, Math.min(3, threadTasks.size()))) { - // printer.append("|\t\t").append(task).append("\n"); - // } - // } + if (i < toppingCount) { + printer.append("| -> (").append(threadJiffies.isNewAdded ? "+" : "~").append("/").append(threadJiffies.stat).append(")") + .append(threadJiffies.name).append("(").append(threadJiffies.tid).append(")\t") + .append(monitors.computeAvgJiffies(entryJffies)).append("/").append(entryJffies).append("\tjiffies") + .append("\n"); + } else { + remainJffies += entryJffies; + } } printer.append("|\t\t......\n"); + if (remainJffies > 0) { + printer.append("| -> R/R)") + .append("REMAINS").append("(").append(delta.dlt.threadEntries.getList().size() - toppingCount).append(")\t") + .append(monitors.computeAvgJiffies(remainJffies) / minute).append("/").append(remainJffies).append("\tjiffies") + .append("\n"); + } if (avgJiffies > 1000L || !delta.isValid()) { + // Proc CPU Load = avgJiffies / 6000L + // 1000L -> 16.6% + // 1200L -> 20.0% printer.append("| ").append(avgJiffies > 1000L ? " #overHeat" : "").append(!delta.isValid() ? " #invalid" : "").append("\n"); } return true; @@ -546,45 +554,33 @@ public void accept(Sampler.Result result) { if (sessionDelta.dlt instanceof CpuStateSnapshot) { //noinspection unchecked final Delta delta = (Delta) sessionDelta; + final long minute = Math.max(1, delta.during / ONE_MIN); // Cpu Usage - printer.createSubSection("cpu_load"); + printer.createSubSection("dev_cpu_load"); printer.writeLine(delta.during + "(mls)\t" + (delta.during / ONE_MIN) + "(min)"); final CpuStatFeature cpuStatFeature = monitors.getFeature(CpuStatFeature.class); if (cpuStatFeature != null) { - monitors.getDelta(JiffiesSnapshot.class, new Consumer>() { - @Override - public void accept(Delta jiffiesDelta) { - long appJiffiesDelta = jiffiesDelta.dlt.totalJiffies.get(); - long cpuJiffiesDelta = delta.dlt.totalCpuJiffies(); - float cpuLoad = (float) appJiffiesDelta / cpuJiffiesDelta; - float cpuLoadAvg = cpuLoad * cpuStatFeature.getPowerProfile().getCpuCoreNum(); - printer.writeLine("usage", (int) (cpuLoadAvg * 100) + "%"); - } - }); + printer.writeLine("usage", monitors.getDevCpuLoad() + "%"); } for (int i = 0; i < delta.dlt.cpuCoreStates.size(); i++) { ListEntry> listEntry = delta.dlt.cpuCoreStates.get(i); printer.writeLine("cpu" + i, Arrays.toString(listEntry.getList().toArray())); } - // BatterySip - if (cpuStatFeature != null) { + // Cpu BatterySip + if (cpuStatFeature != null && cpuStatFeature.isSupported()) { printer.createSubSection("cpu_sip"); // Cpu battery sip - CPU State final PowerProfile powerProfile = cpuStatFeature.getPowerProfile(); - printer.writeLine("inc_cpu_sip", String.format(Locale.US, "%.2f(mAh)", delta.dlt.configureCpuSip(powerProfile))); + printer.writeLine("inc_cpu_sip", String.format(Locale.US, "%.2f(mAh)/min", delta.dlt.configureCpuSip(powerProfile) / minute)); printer.writeLine("cur_cpu_sip", String.format(Locale.US, "%.2f(mAh)", delta.end.configureCpuSip(powerProfile))); // Cpu battery sip - Proc State monitors.getDelta(JiffiesSnapshot.class, new Consumer>() { @Override public void accept(Delta jiffiesDelta) { - double procSipDelta = delta.dlt.configureProcSip(powerProfile, jiffiesDelta.dlt.totalJiffies.get()); + double procSipBgn = delta.bgn.configureProcSip(powerProfile, jiffiesDelta.bgn.totalJiffies.get()); double procSipEnd = delta.end.configureProcSip(powerProfile, jiffiesDelta.end.totalJiffies.get()); - printer.writeLine("inc_prc_sip", String.format(Locale.US, "%.2f(mAh)", procSipDelta)); + printer.writeLine("inc_prc_sip", String.format(Locale.US, "%.2f(mAh)/min", (procSipEnd - procSipBgn) / minute)); printer.writeLine("cur_prc_sip", String.format(Locale.US, "%.2f(mAh)", procSipEnd)); - if (Double.isNaN(procSipDelta)) { - double procSipBgn = delta.bgn.configureProcSip(powerProfile, jiffiesDelta.bgn.totalJiffies.get()); - printer.writeLine("inc_prc_sipr", String.format(Locale.US, "%.2f(mAh)", procSipEnd - procSipBgn)); - } } }); } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorConfig.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorConfig.java index 4dcebab48..46fecb191 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorConfig.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorConfig.java @@ -1,12 +1,16 @@ package com.tencent.matrix.batterycanary.monitor; import android.app.ActivityManager; +import android.os.HandlerThread; import com.tencent.matrix.batterycanary.BuildConfig; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature.UidCpuStateSnapshot.IpcCpuStat.RemoteStat; +import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature.UidJiffiesSnapshot.IpcJiffies.IpcProcessJiffies; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature; import com.tencent.matrix.batterycanary.stats.BatteryRecorder; import com.tencent.matrix.batterycanary.stats.BatteryStats; import com.tencent.matrix.batterycanary.utils.CallStackCollector; +import com.tencent.matrix.batterycanary.utils.Function; import java.util.ArrayList; import java.util.Collections; @@ -16,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.util.Pair; /** * @author Kaede @@ -32,6 +37,8 @@ public class BatteryMonitorConfig { public static final int AMS_HOOK_FLAG_BT = 0b00000001; + @Nullable + public HandlerThread canaryThread = null; @NonNull public BatteryMonitorCallback callback = new BatteryMonitorCallback.BatteryPrinter(); @Nullable @@ -53,6 +60,7 @@ public class BatteryMonitorConfig { public boolean isStatPidProc = BuildConfig.DEBUG; public boolean isInspectiffiesError = BuildConfig.DEBUG; public boolean isAmsHookEnabled = BuildConfig.DEBUG; + public boolean isSkipNewAddedPidTid = false; public int amsHookEnableFlag = 0; public boolean isAggressiveMode = BuildConfig.DEBUG; public boolean isUseThreadClock = BuildConfig.DEBUG; @@ -64,6 +72,9 @@ public class BatteryMonitorConfig { public BatteryRecorder batteryRecorder; public BatteryStats batteryStats; public CallStackCollector callStackCollector; + public Function, IpcProcessJiffies> ipcJiffiesCollector; + public Function, RemoteStat> ipcCpuStatCollector; + public boolean isTuningPowers = BuildConfig.DEBUG; private BatteryMonitorConfig() { } @@ -72,32 +83,7 @@ private BatteryMonitorConfig() { @Override public String toString() { return "BatteryMonitorConfig{" - + "wakelockTimeout=" + wakelockTimeout - + ", wakelockWarnCount=" + wakelockWarnCount - + ", greyTime=" + greyTime - + ", foregroundLoopCheckTime=" + foregroundLoopCheckTime - + ", backgroundLoopCheckTime=" + backgroundLoopCheckTime - + ", overHeatCount=" + overHeatCount - + ", foregroundServiceLeakLimit=" + foregroundServiceLeakLimit - + ", fgThreadWatchingLimit=" + fgThreadWatchingLimit - + ", bgThreadWatchingLimit=" + bgThreadWatchingLimit - + ", isForegroundModeEnabled=" + isForegroundModeEnabled - + ", isBackgroundModeEnabled=" + isBackgroundModeEnabled - + ", isBuiltinForegroundNotifyEnabled=" + isBuiltinForegroundNotifyEnabled - + ", isStatAsSample=" + isStatAsSample - + ", isStatPidProc=" + isStatPidProc - + ", isInspectiffiesError=" + isInspectiffiesError - + ", isAmsHookEnabled=" + isAmsHookEnabled - + ", isAggressiveMode=" + isAggressiveMode - + ", isUseThreadClock=" + isUseThreadClock - + ", tagWhiteList=" + tagWhiteList - + ", tagBlackList=" + tagBlackList - + ", looperWatchList=" + looperWatchList - + ", threadWatchList=" + threadWatchList - + ", features=" + features - + ", batteryRecorder=" + batteryRecorder - + ", batteryStats=" + batteryStats - + ", callStackCollector=" + callStackCollector + + "features=" + features + '}'; } @@ -107,6 +93,11 @@ public String toString() { public static class Builder { private final BatteryMonitorConfig config = new BatteryMonitorConfig(); + public Builder setCanaryThread(HandlerThread thread) { + config.canaryThread = thread; + return this; + } + public Builder setCallback(BatteryMonitorCallback callback) { config.callback = callback; return this; @@ -285,6 +276,21 @@ public Builder setCollector(CallStackCollector collector) { return this; } + public Builder setCollector(Function, IpcProcessJiffies> collector) { + config.ipcJiffiesCollector = collector; + return this; + } + + public Builder enableTuningPowers(boolean enable) { + config.isTuningPowers = enable; + return this; + } + + public Builder skipNewAddedPidTid(boolean skip) { + config.isSkipNewAddedPidTid = skip; + return this; + } + public BatteryMonitorConfig build() { Collections.sort(config.features, new Comparator() { @Override diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCore.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCore.java index 8bc47bf45..9e6506b37 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCore.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/BatteryMonitorCore.java @@ -4,6 +4,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.Handler; +import android.os.HandlerThread; import android.os.Message; import com.tencent.matrix.Matrix; @@ -93,9 +94,9 @@ public void run() { private final BatteryMonitorConfig mConfig; @NonNull private final Handler mHandler; + @NonNull private final Handler mCanaryHandler; @Nullable private ForegroundLoopCheckTask mFgLooperTask; @Nullable private BackgroundLoopCheckTask mBgLooperTask; - @Nullable private TaskJiffiesSnapshot mLastInternalSnapshot; @NonNull Callable mSupplier = new Callable() { @@ -121,7 +122,16 @@ public BatteryMonitorCore(BatteryMonitorConfig config) { mSupplier = config.onSceneSupplier; } - mHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper(), this); + if (config.canaryThread != null) { + HandlerThread thread = config.canaryThread; + mHandler = new Handler(thread.getLooper(), this); // For BatteryMonitorCore only + mCanaryHandler = new Handler(thread.getLooper(), this); // For BatteryCanary + } else { + HandlerThread thread = MatrixHandlerThread.getDefaultHandlerThread(); + mHandler = new Handler(thread.getLooper(), this); // For BatteryMonitorCore only + mCanaryHandler = mHandler; // For BatteryCanary as legacy logic + } + enableForegroundLoopCheck(config.isForegroundModeEnabled); enableBackgroundLoopCheck(config.isBackgroundModeEnabled); mMonitorDelayMillis = config.greyTime; @@ -265,7 +275,7 @@ public void onForeground(boolean isForeground) { @NonNull public Handler getHandler() { - return mHandler; + return mCanaryHandler; } public Context getContext() { @@ -292,7 +302,7 @@ public int getCurrentBatteryTemperature(Context context) { return tmp; } catch (Throwable e) { MatrixLog.printErrStackTrace(TAG, e, "#currentBatteryTemperature error"); - return 0; + return -1; } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AbsTaskMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AbsTaskMonitorFeature.java index 5536cada5..91bb65579 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AbsTaskMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AbsTaskMonitorFeature.java @@ -104,7 +104,7 @@ public void onTurnOff() { } } - public List> currentJiffies() { + public List> currentJiffies(long windowMsFromNow) { // Report unfinished task: // Maybe cause duplicated reporting // for (TaskJiffiesSnapshot bgn : mTaskJiffiesTrace.values()) { @@ -116,7 +116,17 @@ public List> currentJiffies() { ArrayList> list; synchronized (mDeltaList) { - list = new ArrayList<>(mDeltaList); + if (windowMsFromNow <= 0) { + list = new ArrayList<>(mDeltaList); + } else { + list = new ArrayList<>(); + long bgnMillis = SystemClock.uptimeMillis() - windowMsFromNow; + for (Delta item : mDeltaList) { + if (item.bgn.time >= bgnMillis) { + list.add(item); + } + } + } } // Sorting by jiffies dec @@ -408,7 +418,7 @@ protected void onCoolingDown() { // task jiffies list overheat if (mDeltaList.size() > mOverHeatCount) { MatrixLog.w(TAG, "cooling task jiffies list, before = " + mDeltaList.size()); - List> deltas = currentJiffies(); + List> deltas = currentJiffies(0); clearFinishedJiffies(); MatrixLog.w(TAG, "cooling task jiffies list, after = " + mDeltaList.size()); diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AppStatMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AppStatMonitorFeature.java index e986b345e..93012fa30 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AppStatMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/AppStatMonitorFeature.java @@ -13,6 +13,7 @@ import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; import com.tencent.matrix.batterycanary.utils.TimeBreaker; import com.tencent.matrix.lifecycle.IStateObserver; +import com.tencent.matrix.lifecycle.owners.ForegroundServiceLifecycleOwner; import com.tencent.matrix.lifecycle.owners.OverlayWindowLifecycleOwner; import com.tencent.matrix.util.MatrixLog; @@ -71,22 +72,58 @@ public void run() { } }; + private final IStateObserver mFgSrvObserver = new IStateObserver() { + @Override + public void on() { + MatrixLog.i(TAG, "fgSrv >> on"); + boolean foreground = mCore.isForeground(); + int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), foreground); + if (appStat != APP_STAT_FOREGROUND) { + MatrixLog.i(TAG, "statAppStat: " + APP_STAT_FOREGROUND_SERVICE); + onStatAppStat(APP_STAT_FOREGROUND_SERVICE); + } else { + MatrixLog.i(TAG, "skip statAppStat, fg = " + foreground + ", currAppStat = " + appStat); + } + } + + @Override + public void off() { + MatrixLog.i(TAG, "fgSrv >> off"); + boolean foreground = mCore.isForeground(); + int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), foreground); + if (appStat != APP_STAT_FOREGROUND && appStat != APP_STAT_FOREGROUND_SERVICE && appStat != APP_STAT_FLOAT_WINDOW) { + MatrixLog.i(TAG, "statAppStat: " + APP_STAT_BACKGROUND); + onStatAppStat(APP_STAT_BACKGROUND); + } else { + MatrixLog.i(TAG, "skip statAppStat, fg = " + foreground + ", currAppStat = " + appStat); + } + } + }; + private final IStateObserver mFloatViewObserver = new IStateObserver() { @Override public void on() { MatrixLog.i(TAG, "floatView >> on"); - int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), mCore.isForeground()); + boolean foreground = mCore.isForeground(); + int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), foreground); if (appStat != APP_STAT_FOREGROUND && appStat != APP_STAT_FOREGROUND_SERVICE) { + MatrixLog.i(TAG, "statAppStat: " + APP_STAT_FLOAT_WINDOW); onStatAppStat(APP_STAT_FLOAT_WINDOW); + } else { + MatrixLog.i(TAG, "skip statAppStat, fg = " + foreground + ", currAppStat = " + appStat); } } @Override public void off() { MatrixLog.i(TAG, "floatView >> off"); - int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), mCore.isForeground()); - if (appStat != APP_STAT_FOREGROUND && appStat != APP_STAT_FOREGROUND_SERVICE) { + boolean foreground = mCore.isForeground(); + int appStat = BatteryCanaryUtil.getAppStatImmediately(mCore.getContext(), foreground); + if (appStat != APP_STAT_FOREGROUND && appStat != APP_STAT_FOREGROUND_SERVICE && appStat != APP_STAT_FLOAT_WINDOW) { + MatrixLog.i(TAG, "statAppStat: " + APP_STAT_BACKGROUND); onStatAppStat(APP_STAT_BACKGROUND); + } else { + MatrixLog.i(TAG, "skip statAppStat, fg = " + foreground + ", currAppStat = " + appStat); } } }; @@ -114,12 +151,14 @@ public void onTurnOn() { mSceneStampList.add(0, firstSceneStamp); } + ForegroundServiceLifecycleOwner.INSTANCE.observeForever(mFgSrvObserver); OverlayWindowLifecycleOwner.INSTANCE.observeForever(mFloatViewObserver); } @Override public void onTurnOff() { super.onTurnOff(); + ForegroundServiceLifecycleOwner.INSTANCE.removeObserver(mFgSrvObserver); OverlayWindowLifecycleOwner.INSTANCE.removeObserver(mFloatViewObserver); synchronized (TAG) { mStampList.clear(); diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CompositeMonitors.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CompositeMonitors.java index 2bcf1a699..6b31f12d6 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CompositeMonitors.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CompositeMonitors.java @@ -1,5 +1,6 @@ package com.tencent.matrix.batterycanary.monitor.feature; +import android.annotation.SuppressLint; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; @@ -7,26 +8,35 @@ import com.tencent.matrix.batterycanary.monitor.AppStats; import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore; +import com.tencent.matrix.batterycanary.monitor.feature.AbsTaskMonitorFeature.TaskJiffiesSnapshot; import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature.JiffiesSnapshot; import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature.JiffiesSnapshot.ThreadJiffiesEntry; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.DigitEntry; +import com.tencent.matrix.batterycanary.stats.HealthStatsFeature; +import com.tencent.matrix.batterycanary.stats.HealthStatsFeature.HealthStatsSnapshot; +import com.tencent.matrix.batterycanary.stats.HealthStatsHelper; import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; import com.tencent.matrix.batterycanary.utils.Consumer; +import com.tencent.matrix.batterycanary.utils.Function; +import com.tencent.matrix.batterycanary.utils.PowerProfile; +import com.tencent.matrix.batterycanary.utils.RadioStatUtil; import com.tencent.matrix.util.MatrixLog; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.util.Pair; /** * @author Kaede @@ -39,6 +49,8 @@ public class CompositeMonitors { public static final String SCOPE_CANARY = "canary"; public static final String SCOPE_INTERNAL = "internal"; public static final String SCOPE_OVERHEAT = "overheat"; + public static final String SCOPE_TOP_SHELL = "topShell"; + public static final String SCOPE_TOP_INDICATOR = "topIndicator"; // Differing protected final List>> mMetrics = new ArrayList<>(); @@ -51,7 +63,8 @@ public class CompositeMonitors { protected final Map>, Snapshot.Sampler.Result> mSampleResults = new HashMap<>(); // Task Tracing - protected final Map, List>> mTaskDeltas = new HashMap<>(); + protected final Map, List>> mTaskDeltas = new HashMap<>(); + protected final Map, Delta>>> mTaskDeltasCollect = new HashMap<>(); // Extra Info protected final Bundle mExtras = new Bundle(); @@ -63,6 +76,11 @@ public class CompositeMonitors { protected BatteryMonitorCore mMonitor; @Nullable protected AppStats mAppStats; + @Nullable + protected CpuFreqSampler mCpuFreqSampler; + @Nullable + protected BpsSampler mBpsSampler; + protected long mBgnMillis = SystemClock.uptimeMillis(); protected String mScope; @@ -82,16 +100,26 @@ public String getScope() { @CallSuper public void clear() { + MatrixLog.i(TAG, hashCode() + " #clear: " + mScope); mBgnSnapshots.clear(); mDeltas.clear(); mSamplers.clear(); mSampleResults.clear(); mTaskDeltas.clear(); + mTaskDeltasCollect.clear(); + mExtras.clear(); + mStacks.clear(); + mCpuFreqSampler = null; } - @CallSuper public CompositeMonitors fork() { - CompositeMonitors that = new CompositeMonitors(mMonitor, mScope); + return fork(new CompositeMonitors(mMonitor, mScope)); + } + + @CallSuper + protected CompositeMonitors fork(CompositeMonitors that) { + MatrixLog.i(TAG, hashCode() + " #fork: " + mScope); + that.clear(); that.mBgnMillis = this.mBgnMillis; that.mAppStats = this.mAppStats; @@ -99,12 +127,16 @@ public CompositeMonitors fork() { that.mBgnSnapshots.putAll(mBgnSnapshots); that.mDeltas.putAll(mDeltas); - that.mSampleRegs.putAll(mSampleRegs); - that.mSamplers.putAll(mSamplers); - that.mSampleResults.putAll(mSampleResults); + // Sampler can not be cloned. + // that.mSampleRegs.putAll(mSampleRegs); + // that.mSamplers.putAll(mSamplers); + // that.mSampleResults.putAll(mSampleResults); that.mTaskDeltas.putAll(this.mTaskDeltas); + that.mTaskDeltasCollect.putAll(this.mTaskDeltasCollect); that.mExtras.putAll(this.mExtras); + that.mStacks.putAll(this.mStacks); + that.mCpuFreqSampler = this.mCpuFreqSampler; return that; } @@ -156,26 +188,96 @@ public int getCpuLoad() { MatrixLog.w(TAG, "AppStats should not be null to get CpuLoad"); return -1; } + long appJiffiesDelta; + Delta uidJiffies = getDelta(JiffiesMonitorFeature.UidJiffiesSnapshot.class); + if (uidJiffies != null) { + appJiffiesDelta = uidJiffies.dlt.totalUidJiffies.get(); + } else { + Delta pidJiffies = getDelta(JiffiesSnapshot.class); + if (pidJiffies == null) { + MatrixLog.w(TAG, JiffiesSnapshot.class + " should be metrics to get CpuLoad"); + return -1; + } + appJiffiesDelta = pidJiffies.dlt.totalJiffies.get(); + } + + long cpuUptimeDelta = mAppStats.duringMillis; + float cpuLoad = cpuUptimeDelta > 0 ? (float) (appJiffiesDelta * 10) / cpuUptimeDelta : 0; + return (int) (cpuLoad * 100); + } - Delta appJiffies = getDelta(JiffiesSnapshot.class); - if (appJiffies == null) { - MatrixLog.w(TAG, JiffiesSnapshot.class + " should be metrics to get CpuLoad"); + public int getNorCpuLoad() { + int cpuLoad = getCpuLoad(); + if (cpuLoad == -1) { + MatrixLog.w(TAG, "cpu is invalid"); + return -1; + } + MonitorFeature.Snapshot.Sampler.Result result = getSamplingResult(DeviceStatMonitorFeature.CpuFreqSnapshot.class); + if (result == null) { + MatrixLog.w(TAG, "cpufreq is null"); + return -1; + } + List cpuFreqSteps = BatteryCanaryUtil.getCpuFreqSteps(); + if (cpuFreqSteps.size() != BatteryCanaryUtil.getCpuCoreNum()) { + MatrixLog.w(TAG, "cpuCore is invalid: " + cpuFreqSteps.size() + " vs " + BatteryCanaryUtil.getCpuCoreNum()); + } + long sumMax = 0; + for (int[] steps : cpuFreqSteps) { + int max = 0; + for (int item : steps) { + if (item > max) { + max = item; + } + } + sumMax += max; + } + if (sumMax <= 0) { + MatrixLog.w(TAG, "cpufreq sum is invalid: " + sumMax); return -1; } + if (result.sampleAvg >= sumMax) { + // avgFreq should not greater than maxFreq + MatrixLog.w(TAG, "NorCpuLoad err: sampling = " + result); + for (int[] item : cpuFreqSteps) { + MatrixLog.w(TAG, "NorCpuLoad err: freqs = " + Arrays.toString(item)); + } + } + return (int) (cpuLoad * result.sampleAvg / sumMax); + } + /** + * Work in progress + */ + public int getDevCpuLoad() { + if (mAppStats == null) { + MatrixLog.w(TAG, "AppStats should not be null to get CpuLoad"); + return -1; + } Delta cpuJiffies = getDelta(CpuStatFeature.CpuStateSnapshot.class); if (cpuJiffies == null) { MatrixLog.w(TAG, "Configure CpuLoad by uptime"); - long appJiffiesDelta = appJiffies.dlt.totalJiffies.get(); - long cpuUptimeDelta = mAppStats.duringMillis; - float cpuLoad = cpuUptimeDelta > 0 ? (float) (appJiffiesDelta * 10) / cpuUptimeDelta : 0; - return (int) (cpuLoad * 100); + return -1; } - long appJiffiesDelta = appJiffies.dlt.totalJiffies.get(); long cpuJiffiesDelta = cpuJiffies.dlt.totalCpuJiffies(); - float cpuLoad = cpuJiffiesDelta > 0 ? (float) appJiffiesDelta / cpuJiffiesDelta : 0; - return (int) (cpuLoad * BatteryCanaryUtil.getCpuCoreNum() * 100); + long devJiffiesDelta = mAppStats.duringMillis; + float cpuLoad = devJiffiesDelta > 0 ? (float) (cpuJiffiesDelta * 10) / devJiffiesDelta : 0; + return (int) (cpuLoad * 100); + } + + public long computeAvgJiffies(long jiffies) { + if (mAppStats == null) { + MatrixLog.w(TAG, "AppStats should not be null to computeAvgJiffies"); + return -1; + } + return computeAvgJiffies(jiffies, mAppStats.duringMillis); + } + + public static long computeAvgJiffies(long jiffies, long millis) { + if (millis <= 0) { + throw new IllegalArgumentException("Illegal millis: " + millis); + } + return (long) (jiffies / (millis / 60000f)); } public > boolean isOverHeat(Class snapshotClass) { @@ -280,6 +382,7 @@ public CompositeMonitors sample(Class> snapshotClass, long } public void start() { + MatrixLog.i(TAG, hashCode() + " #start: " + mScope); mAppStats = null; mBgnMillis = SystemClock.uptimeMillis(); configureBgnSnapshots(); @@ -287,15 +390,29 @@ public void start() { } public void finish() { + MatrixLog.i(TAG, hashCode() + " #finish: " + mScope); configureEndDeltas(); collectStacks(); configureSampleResults(); mAppStats = AppStats.current(SystemClock.uptimeMillis() - mBgnMillis); + + // For further procedures + polishEstimatedPower(); } protected void configureBgnSnapshots() { for (Class> item : mMetrics) { - statCurrSnapshot(item); + Snapshot currSnapshot = statCurrSnapshot(item); + if (currSnapshot != null) { + mBgnSnapshots.put(item, currSnapshot); + + // Start acc collecting for HealthStatsSnapshot + if (currSnapshot instanceof HealthStatsSnapshot) { + if (mSampleRegs.containsKey(HealthStatsSnapshot.class)) { + ((HealthStatsSnapshot) currSnapshot).startAccCollecting(); + } + } + } } } @@ -307,7 +424,13 @@ protected void configureEndDeltas() { Class> snapshotClass = item.getKey(); Snapshot currSnapshot = statCurrSnapshot(snapshotClass); if (currSnapshot != null && currSnapshot.getClass() == lastSnapshot.getClass()) { - putDelta(snapshotClass, currSnapshot.diff(lastSnapshot)); + Delta delta; + if (lastSnapshot instanceof HealthStatsSnapshot && ((HealthStatsSnapshot) lastSnapshot).accCollector != null) { + delta = ((HealthStatsSnapshot) currSnapshot).diffByAccCollector((HealthStatsSnapshot) lastSnapshot); + } else { + delta = currSnapshot.diff(lastSnapshot); + } + putDelta(snapshotClass, delta); } } } @@ -352,7 +475,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas AlarmMonitorFeature feature = getFeature(AlarmMonitorFeature.class); if (feature != null) { snapshot = feature.currentAlarms(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -360,7 +482,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas BlueToothMonitorFeature feature = getFeature(BlueToothMonitorFeature.class); if (feature != null) { snapshot = feature.currentSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -368,7 +489,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (feature != null) { snapshot = feature.currentCpuFreq(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -376,7 +496,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (feature != null && mMonitor != null) { snapshot = feature.currentBatteryTemperature(mMonitor.getContext()); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -384,15 +503,19 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas JiffiesMonitorFeature feature = getFeature(JiffiesMonitorFeature.class); if (feature != null) { snapshot = feature.currentJiffiesSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } + if (snapshotClass == JiffiesMonitorFeature.UidJiffiesSnapshot.class) { + JiffiesMonitorFeature feat = getFeature(JiffiesMonitorFeature.class); + if (feat != null) { + return feat.currentUidJiffiesSnapshot(); + } + } if (snapshotClass == LocationMonitorFeature.LocationSnapshot.class) { LocationMonitorFeature feature = getFeature(LocationMonitorFeature.class); if (feature != null) { snapshot = feature.currentSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -400,7 +523,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas TrafficMonitorFeature feature = getFeature(TrafficMonitorFeature.class); if (feature != null && mMonitor != null) { snapshot = feature.currentRadioSnapshot(mMonitor.getContext()); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -408,7 +530,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas WakeLockMonitorFeature feature = getFeature(WakeLockMonitorFeature.class); if (feature != null) { snapshot = feature.currentWakeLocks(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -416,7 +537,6 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas WifiMonitorFeature feature = getFeature(WifiMonitorFeature.class); if (feature != null) { snapshot = feature.currentSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); } return snapshot; } @@ -424,7 +544,13 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas CpuStatFeature feature = getFeature(CpuStatFeature.class); if (feature != null && feature.isSupported()) { snapshot = feature.currentCpuStateSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); + } + return snapshot; + } + if (snapshotClass == CpuStatFeature.UidCpuStateSnapshot.class) { + CpuStatFeature feature = getFeature(CpuStatFeature.class); + if (feature != null && feature.isSupported()) { + snapshot = feature.currentUidCpuStateSnapshot(); } return snapshot; } @@ -432,7 +558,13 @@ protected Snapshot statCurrSnapshot(Class> snapshotClas AppStatMonitorFeature feature = getFeature(AppStatMonitorFeature.class); if (feature != null) { snapshot = feature.currentAppStatSnapshot(); - mBgnSnapshots.put(snapshotClass, snapshot); + } + return snapshot; + } + if (snapshotClass == HealthStatsSnapshot.class) { + HealthStatsFeature feature = getFeature(HealthStatsFeature.class); + if (feature != null) { + snapshot = feature.currHealthStatsSnapshot(); } return snapshot; } @@ -451,6 +583,7 @@ protected void configureSamplers() { protected void configureSampleResults() { for (Map.Entry>, Snapshot.Sampler> item : mSamplers.entrySet()) { + MatrixLog.i(TAG, hashCode() + " " + item.getValue().getTag() + " #pause: " + mScope); item.getValue().pause(); Snapshot.Sampler.Result result = item.getValue().getResult(); if (result != null) { @@ -465,18 +598,41 @@ protected Snapshot.Sampler statSampler(Class> snapshotClas if (snapshotClass == DeviceStatMonitorFeature.CpuFreqSnapshot.class) { final DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (feature != null && mMonitor != null) { - sampler = new Snapshot.Sampler(mMonitor.getHandler(), new Callable() { + final CpuStatFeature cpuStatsFeat = getFeature(CpuStatFeature.class); + if (cpuStatsFeat != null) { + if (cpuStatsFeat.isSupported()) { + mCpuFreqSampler = new CpuFreqSampler(BatteryCanaryUtil.getCpuFreqSteps()); + } + } + sampler = new Snapshot.Sampler("cpufreq", mMonitor.getHandler(), new Function() { @Override - public Number call() { - DeviceStatMonitorFeature.CpuFreqSnapshot snapshot = feature.currentCpuFreq(); - List> list = snapshot.cpuFreqs.getList(); - Collections.sort(list, new Comparator>() { - @Override - public int compare(DigitEntry o1, DigitEntry o2) { - return o1.get().compareTo(o2.get()); + public Number apply(Snapshot.Sampler sampler) { + int[] cpuFreqs = BatteryCanaryUtil.getCpuCurrentFreq(); + if (cpuStatsFeat != null && cpuStatsFeat.isSupported()) { + if (mCpuFreqSampler != null && mCpuFreqSampler.isCompat(cpuStatsFeat.getPowerProfile())) { + mCpuFreqSampler.count(cpuFreqs); } - }); - return list.isEmpty() ? 0 : list.get(list.size() - 1).get(); + } + DeviceStatMonitorFeature.CpuFreqSnapshot snapshot = feature.currentCpuFreq(cpuFreqs); + List> list = snapshot.cpuFreqs.getList(); + MatrixLog.i(TAG, CompositeMonitors.this.hashCode() + " #onSampling: " + mScope); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + list); + if (list.isEmpty()) { + return Snapshot.Sampler.INVALID; + } + // Better to use sum of all cpufreqs, rather than just use the max value? + // Collections.sort(list, new Comparator>() { + // @Override + // public int compare(DigitEntry o1, DigitEntry o2) { + // return o1.get().compareTo(o2.get()); + // } + // }); + // return list.isEmpty() ? 0 : list.get(list.size() - 1).get(); + long sum = 0; + for (DigitEntry item : list) { + sum += item.get(); + } + return sum; } }); mSamplers.put(snapshotClass, sampler); @@ -486,11 +642,16 @@ public int compare(DigitEntry o1, DigitEntry o2) { if (snapshotClass == DeviceStatMonitorFeature.BatteryTmpSnapshot.class) { final DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (feature != null && mMonitor != null) { - sampler = new Snapshot.Sampler(mMonitor.getHandler(), new Callable() { + sampler = new Snapshot.Sampler("batt-temp", mMonitor.getHandler(), new Function() { @Override - public Number call() { + public Number apply(Snapshot.Sampler sampler) { DeviceStatMonitorFeature.BatteryTmpSnapshot snapshot = feature.currentBatteryTemperature(mMonitor.getContext()); - return snapshot.temp.get(); + Integer value = snapshot.temp.get(); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + value); + if (value == -1) { + return Snapshot.Sampler.INVALID; + } + return value; } }); mSamplers.put(snapshotClass, sampler); @@ -500,10 +661,15 @@ public Number call() { if (snapshotClass == DeviceStatMonitorFeature.ThermalStatSnapshot.class) { final DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && feature != null && mMonitor != null) { - sampler = new Snapshot.Sampler(mMonitor.getHandler(), new Callable() { + sampler = new Snapshot.Sampler("thermal-stat", mMonitor.getHandler(), new Function() { @Override - public Number call() { - return BatteryCanaryUtil.getThermalStat(mMonitor.getContext()); + public Number apply(Snapshot.Sampler sampler) { + int value = BatteryCanaryUtil.getThermalStat(mMonitor.getContext()); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + value); + if (value == -1) { + return Snapshot.Sampler.INVALID; + } + return value; } }); mSamplers.put(snapshotClass, sampler); @@ -515,10 +681,15 @@ public Number call() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && feature != null && mMonitor != null) { final Long interval = mSampleRegs.get(snapshotClass); if (interval != null && interval >= 1000L) { - sampler = new Snapshot.Sampler(mMonitor.getHandler(), new Callable() { + sampler = new Snapshot.Sampler("thermal-headroom", mMonitor.getHandler(), new Function() { @Override - public Number call() { - return BatteryCanaryUtil.getThermalHeadroom(mMonitor.getContext(), (int) (interval / 1000L)); + public Number apply(Snapshot.Sampler sampler) { + float value = BatteryCanaryUtil.getThermalHeadroom(mMonitor.getContext(), (int) (interval / 1000L)); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + value); + if (value == -1f) { + return Snapshot.Sampler.INVALID; + } + return value; } }); mSamplers.put(snapshotClass, sampler); @@ -529,10 +700,144 @@ public Number call() { if (snapshotClass == DeviceStatMonitorFeature.ChargeWattageSnapshot.class) { final DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); if (feature != null && mMonitor != null) { - sampler = new Snapshot.Sampler(mMonitor.getHandler(), new Callable() { + sampler = new Snapshot.Sampler("batt-watt", mMonitor.getHandler(), new Function() { @Override - public Number call() { - return BatteryCanaryUtil.getChargingWatt(mMonitor.getContext()); + public Number apply(Snapshot.Sampler sampler) { + int value = BatteryCanaryUtil.getChargingWatt(mMonitor.getContext()); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + value); + if (value == -1) { + return Snapshot.Sampler.INVALID; + } + return value; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == CpuStatFeature.CpuStateSnapshot.class) { + final CpuStatFeature feature = getFeature(CpuStatFeature.class); + if (feature != null && feature.isSupported() && mMonitor != null) { + sampler = new Snapshot.Sampler("cpu-stat", mMonitor.getHandler(), new Function() { + @Override + public Number apply(Snapshot.Sampler sampler) { + CpuStatFeature.CpuStateSnapshot snapshot = feature.currentCpuStateSnapshot(); + for (int i = 0; i < snapshot.cpuCoreStates.size(); i++) { + Snapshot.Entry.ListEntry> item = snapshot.cpuCoreStates.get(i); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " cpuCore" + i + ", val = " + item.getList()); + } + for (int i = 0; i < snapshot.procCpuCoreStates.size(); i++) { + Snapshot.Entry.ListEntry> item = snapshot.procCpuCoreStates.get(i); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " procCpuCluster" + i + ", val = " + item.getList()); + } + return 0; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == JiffiesMonitorFeature.UidJiffiesSnapshot.class) { + final JiffiesMonitorFeature feature = getFeature(JiffiesMonitorFeature.class); + if (feature != null && mMonitor != null) { + sampler = new Snapshot.Sampler("uid-jiffies", mMonitor.getHandler(), new Function() { + JiffiesMonitorFeature.UidJiffiesSnapshot mLastSnapshot; + @Override + public Number apply(Snapshot.Sampler sampler) { + JiffiesMonitorFeature.UidJiffiesSnapshot curr = feature.currentUidJiffiesSnapshot(); + if (mLastSnapshot != null) { + Delta delta = curr.diff(mLastSnapshot); + long minute = Math.max(1, delta.during / BatteryCanaryUtil.ONE_MIN); + long avgUidJiffies = computeAvgJiffies(delta.dlt.totalUidJiffies.get(), delta.during); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " avgUidJiffies, val = " + avgUidJiffies + ", minute = " + minute); + for (Delta item : delta.dlt.pidDeltaJiffiesList) { + long avgPidJiffies = computeAvgJiffies(item.dlt.totalJiffies.get(), delta.during); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " avgPidJiffies, val = " + avgPidJiffies + ", minute = " + minute + ", name = " + item.dlt.name); + } + mLastSnapshot = curr; + return avgUidJiffies; + } else { + mLastSnapshot = curr; + } + return 0; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == TrafficMonitorFeature.RadioStatSnapshot.class) { + final TrafficMonitorFeature feature = getFeature(TrafficMonitorFeature.class); + if (feature != null && mMonitor != null) { + sampler = new MonitorFeature.Snapshot.Sampler("traffic", mMonitor.getHandler(), new Function() { + @Override + public Number apply(Snapshot.Sampler sampler) { + TrafficMonitorFeature.RadioStatSnapshot snapshot = feature.currentRadioSnapshot(mMonitor.getContext()); + if (snapshot != null) { + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " wifiRx, val = " + snapshot.wifiRxBytes); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " wifiTx, val = " + snapshot.wifiTxBytes); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " mobileRx, val = " + snapshot.mobileRxBytes); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + " mobileTx, val = " + snapshot.mobileTxBytes); + } + return 0; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == TrafficMonitorFeature.RadioBpsSnapshot.class) { + final TrafficMonitorFeature feature = getFeature(TrafficMonitorFeature.class); + if (feature != null && mMonitor != null) { + mBpsSampler = new BpsSampler(); + sampler = new MonitorFeature.Snapshot.Sampler("trafficBps", mMonitor.getHandler(), new Function() { + @Override + public Number apply(Snapshot.Sampler sampler) { + TrafficMonitorFeature.RadioBpsSnapshot snapshot = feature.currentRadioBpsSnapshot(mMonitor.getContext()); + if (snapshot != null) { + mBpsSampler.count(snapshot); + } + return 0; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == DeviceStatMonitorFeature.BatteryCurrentSnapshot.class) { + final DeviceStatMonitorFeature feature = getFeature(DeviceStatMonitorFeature.class); + if (feature != null && mMonitor != null) { + sampler = new MonitorFeature.Snapshot.Sampler("batt-curr", mMonitor.getHandler(), new Function() { + @Override + public Number apply(Snapshot.Sampler sampler) { + if (BatteryCanaryUtil.isDeviceCharging(mMonitor.getContext())) { + // battery currency is meaningless when charging + return Snapshot.Sampler.INVALID; + } + long value = BatteryCanaryUtil.getBatteryCurrencyImmediately(mMonitor.getContext()); + MatrixLog.i(TAG, "onSampling " + sampler.mCount + " " + sampler.mTag + ", val = " + value); + if (value == -1L) { + return Snapshot.Sampler.INVALID; + } + return value; + } + }); + mSamplers.put(snapshotClass, sampler); + } + return sampler; + } + if (snapshotClass == HealthStatsSnapshot.class) { + final HealthStatsFeature feature = getFeature(HealthStatsFeature.class); + if (feature != null && mMonitor != null) { + sampler = new MonitorFeature.Snapshot.Sampler("health-stats", mMonitor.getHandler(), new Function() { + @Override + public Number apply(Snapshot.Sampler sampler) { + Snapshot snapshot = mBgnSnapshots.get(HealthStatsSnapshot.class); + if (snapshot instanceof HealthStatsSnapshot) { + MatrixLog.i(TAG, "onAcc " + sampler.mCount + " " + sampler.mTag); + ((HealthStatsSnapshot) snapshot).accCollect(feature.currHealthStatsSnapshot()); + } + return Snapshot.Sampler.INVALID; } }); mSamplers.put(snapshotClass, sampler); @@ -542,34 +847,104 @@ public Number call() { return null; } - public void configureTaskDeltas(final Class featClass) { - AbsTaskMonitorFeature taskFeat = getFeature(featClass); - if (taskFeat != null) { - List> deltas = taskFeat.currentJiffies(); - taskFeat.clearFinishedJiffies(); - putTaskDeltas(featClass, deltas); + protected void configureTaskDeltas(final Class featClass) { + if (mAppStats != null) { + AbsTaskMonitorFeature taskFeat = getFeature(featClass); + if (taskFeat != null) { + List> deltas = taskFeat.currentJiffies(mAppStats.duringMillis); + // No longer clear here + // Clear at BG Scope or OverHeat + // taskFeat.clearFinishedJiffies(); + putTaskDeltas(featClass, deltas); + } + } + } + + protected void collectTaskDeltas() { + if (!mTaskDeltas.isEmpty()) { + for (Map.Entry, List>> entry : mTaskDeltas.entrySet()) { + Class key = entry.getKey(); + for (Delta taskDelta : entry.getValue()) { + // FIXME: better windowMillis cfg of Task and AppStats + if (taskDelta.bgn.time >= mBgnMillis) { + List, Delta>> pairList = mTaskDeltasCollect.get(taskDelta.dlt.name); + if (pairList == null) { + pairList = new ArrayList<>(); + mTaskDeltasCollect.put(taskDelta.dlt.name, pairList); + } + pairList.add(new Pair, Delta>(key, taskDelta)); + } + } + } } } - public void putTaskDeltas(Class key, List> deltas) { + public void putTaskDeltas(Class key, List> deltas) { mTaskDeltas.put(key, deltas); } - public List> getTaskDeltas(Class key) { - List> deltas = mTaskDeltas.get(key); + public List> getTaskDeltas(Class key) { + List> deltas = mTaskDeltas.get(key); if (deltas == null) { return Collections.emptyList(); } return deltas; } - public void getTaskDeltas(Class key, Consumer>> block) { - List> deltas = mTaskDeltas.get(key); + public void getTaskDeltas(Class key, Consumer>> block) { + List> deltas = mTaskDeltas.get(key); if (deltas != null) { block.accept(deltas); } } + public Map, Delta>>> getCollectedTaskDeltas() { + if (mTaskDeltasCollect.size() <= 1) { + return mTaskDeltasCollect; + } + // Sorting by jiffies sum + return BatteryCanaryUtil.sortMapByValue(mTaskDeltasCollect, new Comparator, Delta>>>>() { + @SuppressWarnings("ConstantConditions") + @Override + public int compare(Map.Entry, Delta>>> o1, Map.Entry, Delta>>> o2) { + long sumLeft = 0, sumRight = 0; + for (Pair, Delta> item : o1.getValue()) { + sumLeft += item.second.dlt.jiffies.get(); + } + for (Pair, Delta> item : o2.getValue()) { + sumRight += item.second.dlt.jiffies.get(); + } + long minus = sumLeft - sumRight; + if (minus == 0) return 0; + if (minus > 0) return -1; + return 1; + } + }); + } + + public void getCollectedTaskDeltas(Consumer, Delta>>>> block) { + block.accept(getCollectedTaskDeltas()); + } + + public void getAllPidDeltaList(Consumer>> block) { + List> deltaList = getAllPidDeltaList(); + if (deltaList != null) { + block.accept(deltaList); + } + } + + public List> getAllPidDeltaList() { + Delta delta = getDelta(JiffiesMonitorFeature.UidJiffiesSnapshot.class); + if (delta == null) { + Delta pidDelta = getDelta(JiffiesSnapshot.class); + if (pidDelta != null) { + return Collections.singletonList(pidDelta); + } + return Collections.emptyList(); + } + return delta.dlt.pidDeltaJiffiesList; + } + public Map getStacks() { return mStacks; } @@ -578,6 +953,16 @@ public Bundle getExtras() { return mExtras; } + @Nullable + public CpuFreqSampler getCpuFreqSampler() { + return mCpuFreqSampler; + } + + @Nullable + public BpsSampler getBpsSampler() { + return mBpsSampler; + } + @Override @NonNull public String toString() { @@ -594,4 +979,477 @@ public String toString() { ", Extras =" + mExtras + "\n" + '}'; } + + public static class CpuFreqSampler { + public int[] cpuCurrentFreq; + public final List cpuFreqSteps; + public final List cpuFreqCounters; + + public CpuFreqSampler(List cpuFreqSteps) { + this.cpuFreqSteps = cpuFreqSteps; + this.cpuFreqCounters = new ArrayList<>(cpuFreqSteps.size()); + for (int[] item : cpuFreqSteps) { + this.cpuFreqCounters.add(new int[item.length]); + } + } + + public boolean isCompat(PowerProfile powerProfile) { + if (cpuFreqSteps.size() == powerProfile.getCpuCoreNum()) { + for (int i = 0; i < cpuFreqSteps.size(); i++) { + int clusterByCpuNum = powerProfile.getClusterByCpuNum(i); + int steps = powerProfile.getNumSpeedStepsInCpuCluster(clusterByCpuNum); + if (cpuFreqSteps.get(i).length != steps) { + return false; + } + } + return true; + } + return false; + } + + public void count(int[] cpuCurrentFreq) { + this.cpuCurrentFreq = cpuCurrentFreq; + for (int i = 0; i < cpuCurrentFreq.length; i++) { + int speed = cpuCurrentFreq[i]; + int[] steps = cpuFreqSteps.get(i); + if (speed < steps[0]) { + cpuFreqCounters.get(i)[0]++; + continue; + } + boolean found = false; + for (int j = 0; j < steps.length; j++) { + if (speed <= steps[j]) { + cpuFreqCounters.get(i)[j]++; + found = true; + break; + } + } + if (!found) { + if (speed > steps[steps.length - 1]) { + cpuFreqCounters.get(i)[steps.length - 1]++; + } + } + } + } + } + + public static class BpsSampler { + public int count; + public long wifiRxBps; + public long wifiTxBps; + public long mobileRxBps; + public long mobileTxBps; + + public void count(TrafficMonitorFeature.RadioBpsSnapshot snapshot) { + count++; + wifiRxBps += snapshot.wifiRxBps.get(); + wifiTxBps += snapshot.wifiTxBps.get(); + mobileRxBps += snapshot.mobileRxBps.get(); + mobileTxBps += snapshot.mobileTxBps.get(); + } + + public double getAverage(long input) { + if (count != 0) { + return input * 1f / count; + } + return 0; + } + } + + + protected void polishEstimatedPower() { + getDelta(HealthStatsFeature.HealthStatsSnapshot.class, new Consumer>() { + @Override + public void accept(Delta healthStatsDelta) { + tuningPowers(healthStatsDelta.dlt); + { + // Reset cpuPower + double power = 0; + Object powers = healthStatsDelta.dlt.extras.get("JiffyUid"); + if (powers instanceof Map) { + // Take power-cpu-uidDiff or 0 as default + Object val = ((Map) powers).get("power-cpu-uidDiff"); + if (val instanceof Double) { + power = (double) val; + } + } + healthStatsDelta.dlt.cpuPower = DigitEntry.of(power); + } + { + // Reset mobilePower if exists + if (healthStatsDelta.dlt.mobilePower.get() <= 0) { + double power = 0; + // Take power-mobile-statByte + Object val = healthStatsDelta.dlt.extras.get("power-mobile-statByte"); + if (val instanceof Double) { + power = (double) val; + } + healthStatsDelta.dlt.mobilePower = DigitEntry.of(power); + } + } + { + // Reset wifiPower if exists + if (healthStatsDelta.dlt.wifiPower.get() <= 0) { + double power = 0; + // Take power-wifi-statByte + Object val = healthStatsDelta.dlt.extras.get("power-wifi-statByte"); + if (val instanceof Double) { + power = (double) val; + } + healthStatsDelta.dlt.wifiPower = DigitEntry.of(power); + } + } + } + }); + } + + protected void tuningPowers(final HealthStatsFeature.HealthStatsSnapshot snapshot) { + if (!snapshot.isDelta) { + throw new IllegalStateException("Only support delta snapshot"); + } + BatteryMonitorCore monitor = getMonitor(); + if (monitor == null) { + return; + } + snapshot.extras = new HashMap<>(); + final boolean tunning = monitor.getConfig().isTuningPowers; + final Tuner tuner = new Tuner(); + + getFeature(CpuStatFeature.class, new Consumer() { + @Override + public void accept(CpuStatFeature feat) { + if (feat.isSupported()) { + final PowerProfile powerProfile = feat.getPowerProfile(); + + // 1. Tune CPU + getDelta(CpuStatFeature.CpuStateSnapshot.class, new Consumer>() { + @Override + public void accept(final Delta cpuStatDelta) { + // 1.1 CpuTimeMs + if (tunning) { + getDelta(HealthStatsSnapshot.class, new Consumer>() { + @Override + public void accept(final Delta healthStats) { + healthStats.dlt.extras.put("TimeUid", tuner.tuningCpuPowers(powerProfile, CompositeMonitors.this, new Tuner.CpuTime() { + @Override + public long getBgnMs(String procSuffix) { + return getCpuTimeMs(procSuffix, healthStats.bgn); + } + + @Override + public long getEndMs(String procSuffix) { + return getCpuTimeMs(procSuffix, healthStats.end); + } + + @Override + public long getDltMs(String procSuffix) { + return getCpuTimeMs(procSuffix, healthStats.dlt); + } + + private long getCpuTimeMs(String procSuffix, HealthStatsSnapshot healthStatsSnapshot) { + if (procSuffix == null) { + return healthStatsSnapshot.cpuUsrTimeMs.get() + + healthStatsSnapshot.cpuSysTimeMs.get(); + } else { + if (mMonitor != null) { + String procName = mMonitor.getContext().getPackageName(); + if ("main".equals(procSuffix)) { + procName = mMonitor.getContext().getPackageName() + ":" + procSuffix; + } + DigitEntry usrTime = healthStatsSnapshot.procStatsCpuUsrTimeMs.get(procName); + DigitEntry sysTime = healthStatsSnapshot.procStatsCpuSysTimeMs.get(procName); + return (usrTime == null ? 0 : usrTime.get()) + (sysTime == null ? 0 : sysTime.get()); + } + } + return 0L; + } + })); + } + }); + } + + // 1.2 CpuTimeJiffies + getDelta(JiffiesMonitorFeature.UidJiffiesSnapshot.class, new Consumer>() { + @Override + public void accept(final Delta delta) { + snapshot.extras.put("JiffyUid", tuner.tuningCpuPowers(powerProfile, CompositeMonitors.this, new Tuner.CpuTime() { + @Override + public long getBgnMs(String procSuffix) { + if (procSuffix == null) { + // All + return delta.bgn.totalUidJiffies.get() * 10L; + } else { + for (JiffiesSnapshot item : delta.bgn.pidCurrJiffiesList) { + if (item.name.equals(procSuffix)) { + return item.totalJiffies.get() * 10L; + } + } + } + return 0L; + } + @Override + public long getEndMs(String procSuffix) { + if (procSuffix == null) { + // All + return delta.end.totalUidJiffies.get() * 10L; + } else { + for (JiffiesSnapshot item : delta.end.pidCurrJiffiesList) { + if (item.name.equals(procSuffix)) { + return item.totalJiffies.get() * 10L; + } + } + } + return 0L; + } + @Override + public long getDltMs(String procSuffix) { + if (procSuffix == null) { + // All + return delta.dlt.totalUidJiffies.get() * 10L; + } else { + for (Delta item : delta.dlt.pidDeltaJiffiesList) { + if (item.dlt.name.equals(procSuffix)) { + return item.dlt.totalJiffies.get() * 10L; + } + } + } + return 0L; + } + })); + } + }); + } + }); + + // 2. Tune Network + { + double mobileRxBps = 0, mobileTxBps = 0; + double wifiRxBps = 0, wifiTxBps = 0; + BpsSampler bpsSampler = getBpsSampler(); + if (bpsSampler != null) { + mobileRxBps = bpsSampler.getAverage(bpsSampler.mobileRxBps); + mobileTxBps = bpsSampler.getAverage(bpsSampler.mobileTxBps); + wifiRxBps = bpsSampler.getAverage(bpsSampler.wifiRxBps); + wifiTxBps = bpsSampler.getAverage(bpsSampler.wifiTxBps); + } else { + if (mMonitor != null) { + RadioStatUtil.RadioBps bpsStat = RadioStatUtil.getCurrentBps(mMonitor.getContext()); + if (bpsStat != null) { + mobileRxBps = bpsStat.mobileRxBps; + mobileTxBps = bpsStat.mobileTxBps; + wifiRxBps = bpsStat.wifiRxBps; + wifiTxBps = bpsStat.wifiTxBps; + } + } + } + + final double finalMobileRxBps = mobileRxBps; + final double finalMobileTxBps = mobileTxBps; + final double finalWifiRxBps = wifiRxBps; + final double finalWifiTxBps = wifiTxBps; + + // 2.1 HealthStats + getDelta(HealthStatsSnapshot.class, new Consumer>() { + @SuppressLint("VisibleForTests") + @Override + public void accept(Delta delta) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + // mobile + { + double power = 0; + if (delta.bgn.healthStats != null && delta.end.healthStats != null) { + double powerBgn = HealthStatsHelper.calcMobilePowerByRadioActive(powerProfile, delta.bgn.healthStats); + double powerEnd = HealthStatsHelper.calcMobilePowerByRadioActive(powerProfile, delta.end.healthStats); + power = powerEnd - powerBgn; + } + snapshot.extras.put("power-mobile-radio", power); + } + { + double power = 0; + if (delta.bgn.healthStats != null && delta.end.healthStats != null) { + double powerBgn = HealthStatsHelper.calcMobilePowerByController(powerProfile, delta.bgn.healthStats); + double powerEnd = HealthStatsHelper.calcMobilePowerByController(powerProfile, delta.end.healthStats); + power = powerEnd - powerBgn; + } + snapshot.extras.put("power-mobile-controller", power); + } + { + double power = 0; + if (delta.bgn.healthStats != null && delta.end.healthStats != null) { + double powerBgn = HealthStatsHelper.calcMobilePowerByPackets(powerProfile, delta.bgn.healthStats, finalMobileRxBps, finalMobileTxBps); + double powerEnd = HealthStatsHelper.calcMobilePowerByPackets(powerProfile, delta.end.healthStats, finalMobileRxBps, finalMobileTxBps); + power = powerEnd - powerBgn; + } + snapshot.extras.put("power-mobile-packet", power); + } + // wifi + { + double power = 0; + if (delta.bgn.healthStats != null && delta.end.healthStats != null) { + double powerBgn = HealthStatsHelper.calcWifiPowerByController(powerProfile, delta.bgn.healthStats); + double powerEnd = HealthStatsHelper.calcWifiPowerByController(powerProfile, delta.end.healthStats); + power = powerEnd - powerBgn; + } + snapshot.extras.put("power-wifi-controller", power); + } + { + double power = 0; + if (delta.bgn.healthStats != null && delta.end.healthStats != null) { + double powerBgn = HealthStatsHelper.calcWifiPowerByPackets(powerProfile, delta.bgn.healthStats, finalWifiRxBps, finalWifiTxBps); + double powerEnd = HealthStatsHelper.calcWifiPowerByPackets(powerProfile, delta.end.healthStats, finalWifiRxBps, finalWifiTxBps); + power = powerEnd - powerBgn; + } + snapshot.extras.put("power-wifi-packet", power); + } + } + } + }); + + // 2.2 RadioStat + getDelta(TrafficMonitorFeature.RadioStatSnapshot.class, new Consumer>() { + @SuppressLint("VisibleForTests") + @Override + public void accept(Delta delta) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mMonitor != null) { + BpsSampler bpsSampler = getBpsSampler(); + if (bpsSampler != null) { + // mobile + double power = HealthStatsHelper.calcMobilePowerByNetworkStatBytes(powerProfile, delta.dlt, finalMobileRxBps, finalMobileTxBps); + snapshot.extras.put("power-mobile-statByte", power); + power = HealthStatsHelper.calcMobilePowerByNetworkStatPackets(powerProfile, delta.dlt, finalMobileRxBps, finalMobileTxBps); + snapshot.extras.put("power-mobile-statPacket", power); + // wifi + power = HealthStatsHelper.calcWifiPowerByNetworkStatBytes(powerProfile, delta.dlt, finalWifiRxBps, finalWifiTxBps); + snapshot.extras.put("power-wifi-statByte", power); + power = HealthStatsHelper.calcWifiPowerByNetworkStatPackets(powerProfile, delta.dlt, finalWifiRxBps, finalWifiTxBps); + snapshot.extras.put("power-wifi-statPacket", power); + } + } + } + }); + } + } + } + }); + } + + + @SuppressLint("RestrictedApi") + protected static class Tuner { + interface CpuTime { + long getBgnMs(String procSuffix); + long getEndMs(String procSuffix); + long getDltMs(String procSuffix); + } + + public Map tuningCpuPowers(final PowerProfile powerProfile, final CompositeMonitors monitors, final CpuTime cpuTime) { + final Map dict = new LinkedHashMap<>(); + monitors.getDelta(CpuStatFeature.UidCpuStateSnapshot.class, new Consumer>() { + @SuppressLint("VisibleForTests") + @Override + public void accept(Delta uidCpuStatDelta) { + BatteryMonitorCore monitor = monitors.getMonitor(); + if (monitor == null) { + return; + } + + // Calc by DIff + { + // UID Diff + double cpuPower = 0; + boolean scaled = false; + for (Delta cpuStateDelta : uidCpuStatDelta.dlt.pidDeltaCpuSateList) { + CpuStatFeature.CpuStateSnapshot cpuStatsSnapshot = cpuStateDelta.dlt; + long cpuTimeMs = cpuTime.getDltMs(cpuStatsSnapshot.name); + cpuPower += HealthStatsHelper.estimateCpuActivePower(powerProfile, cpuTimeMs) + + HealthStatsHelper.estimateCpuClustersPowerByUidStats(powerProfile, cpuStatsSnapshot, cpuTimeMs, scaled) + + HealthStatsHelper.estimateCpuCoresPowerByUidStats(powerProfile, cpuStatsSnapshot, cpuTimeMs, scaled); + } + dict.put("power-cpu-uidDiff", cpuPower); + } + + boolean tunning = monitor.getConfig().isTuningPowers; + if (!tunning) { + return; + } + + { + // UID Diff Scaled + double cpuPower = 0; + boolean scaled = true; + for (Delta cpuStateDelta : uidCpuStatDelta.dlt.pidDeltaCpuSateList) { + CpuStatFeature.CpuStateSnapshot cpuStatsSnapshot = cpuStateDelta.dlt; + long cpuTimeMs = cpuTime.getDltMs(cpuStatsSnapshot.name); + cpuPower += HealthStatsHelper.estimateCpuActivePower(powerProfile, cpuTimeMs) + + HealthStatsHelper.estimateCpuClustersPowerByUidStats(powerProfile, cpuStatsSnapshot, cpuTimeMs, scaled) + + HealthStatsHelper.estimateCpuCoresPowerByUidStats(powerProfile, cpuStatsSnapshot, cpuTimeMs, scaled); + } + dict.put("power-cpu-uidDiffScale", cpuPower); + } + + monitors.getDelta(CpuStatFeature.CpuStateSnapshot.class, new Consumer>() { + @Override + public void accept(Delta pidCpuStatDelta) { + { + // DEV Diff + CpuStatFeature.CpuStateSnapshot cpuStatsSnapshot = pidCpuStatDelta.dlt; + long cpuTimeMs = cpuTime.getDltMs(null); + double cpuPower = HealthStatsHelper.estimateCpuActivePower(powerProfile, cpuTimeMs) + + HealthStatsHelper.estimateCpuClustersPowerByDevStats(powerProfile, cpuStatsSnapshot, cpuTimeMs) + + HealthStatsHelper.estimateCpuCoresPowerByDevStats(powerProfile, cpuStatsSnapshot, cpuTimeMs); + dict.put("power-cpu-devDiff", cpuPower); + } + { + // CpuFreq Diff + CompositeMonitors.CpuFreqSampler cpuFreqSampler = monitors.getCpuFreqSampler(); + if (cpuFreqSampler != null) { + if (cpuFreqSampler.isCompat(powerProfile)) { + long cpuTimeMs = cpuTime.getDltMs(null); + double cpuPower = HealthStatsHelper.estimateCpuActivePower(powerProfile, cpuTimeMs) + + estimateCpuPowerByCpuFreqStats(powerProfile, cpuFreqSampler, cpuTimeMs); + dict.put("power-cpu-cpuFreq", cpuPower); + } + } + } + } + }); + } + }); + + return dict; + } + + private static double estimateCpuPowerByCpuFreqStats(PowerProfile powerProfile, CompositeMonitors.CpuFreqSampler sampler, long cpuTimeMs) { + double powerMah = 0; + if (cpuTimeMs > 0) { + long totalSum = 0; + for (int i = 0; i < sampler.cpuFreqCounters.size(); i++) { + for (int j = 0; j < sampler.cpuFreqCounters.get(i).length; j++) { + totalSum += sampler.cpuFreqCounters.get(i)[j]; + } + } + if (totalSum > 0) { + for (int i = 0; i < sampler.cpuFreqCounters.size(); i++) { + int clusterNum = powerProfile.getClusterByCpuNum(i); + long coreSum = 0; + for (int j = 0; j < sampler.cpuFreqCounters.get(i).length; j++) { + int step = sampler.cpuFreqCounters.get(i)[j]; + if (step > 0) { + long figuredCoreTimeMs = (long) ((step * 1.0f / totalSum) * cpuTimeMs); + double powerMa = powerProfile.getAveragePowerForCpuCore(clusterNum, j); + powerMah += new HealthStatsHelper.UsageBasedPowerEstimator(powerMa).calculatePower(figuredCoreTimeMs); + } + coreSum += step; + } + if (coreSum > 0) { + long figuredClusterTimeMs = (long) ((coreSum * 1.0f / totalSum) * cpuTimeMs); + double powerMa = powerProfile.getAveragePowerForCpuCluster(clusterNum); + powerMah += new HealthStatsHelper.UsageBasedPowerEstimator(powerMa).calculatePower(figuredClusterTimeMs); + } + } + } + } + return powerMah; + } + } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CpuStatFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CpuStatFeature.java index d1af3617d..6e9aefe36 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CpuStatFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/CpuStatFeature.java @@ -1,12 +1,18 @@ package com.tencent.matrix.batterycanary.monitor.feature; +import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.DigitEntry; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.ListEntry; +import com.tencent.matrix.batterycanary.shell.TopThreadFeature; +import com.tencent.matrix.batterycanary.shell.ui.TopThreadIndicator; +import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; import com.tencent.matrix.batterycanary.utils.KernelCpuSpeedReader; import com.tencent.matrix.batterycanary.utils.KernelCpuUidFreqTimeReader; import com.tencent.matrix.batterycanary.utils.PowerProfile; +import com.tencent.matrix.batterycanary.utils.ProcStatUtil; import com.tencent.matrix.util.MatrixLog; import java.io.IOException; @@ -15,6 +21,7 @@ import java.util.List; import androidx.annotation.WorkerThread; +import androidx.core.util.Pair; import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.JIFFY_MILLIS; import static com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil.ONE_HOR; @@ -24,7 +31,7 @@ * @since 2021/9/10 */ @SuppressWarnings("SpellCheckingInspection") -public class CpuStatFeature extends AbsTaskMonitorFeature { +public class CpuStatFeature extends AbsTaskMonitorFeature { private static final String TAG = "Matrix.battery.CpuStatFeature"; private PowerProfile mPowerProfile; @@ -98,6 +105,10 @@ public PowerProfile getPowerProfile() { } public CpuStateSnapshot currentCpuStateSnapshot() { + return currentCpuStateSnapshot(Process.myPid()); + } + + public CpuStateSnapshot currentCpuStateSnapshot(int pid) { CpuStateSnapshot snapshot = new CpuStateSnapshot(); try { if (!isSupported()) { @@ -108,21 +119,22 @@ public CpuStateSnapshot currentCpuStateSnapshot() { throw new IOException("PowerProfile not supported"); } // Cpu core steps jiffies - snapshot.cpuCoreStates = new ArrayList<>(); - for (int i = 0; i < mPowerProfile.getCpuCoreNum(); i++) { - final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(mPowerProfile.getClusterByCpuNum(i)); - KernelCpuSpeedReader cpuStepJiffiesReader = new KernelCpuSpeedReader(i, numSpeedSteps); - long[] cpuCoreStepJiffies = cpuStepJiffiesReader.readAbsolute(); - ListEntry> cpuCoreState = ListEntry.ofDigits(cpuCoreStepJiffies); - snapshot.cpuCoreStates.add(cpuCoreState); + if (pid == Process.myPid()) { + snapshot.cpuCoreStates = new ArrayList<>(); + for (int i = 0; i < mPowerProfile.getCpuCoreNum(); i++) { + final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(mPowerProfile.getClusterByCpuNum(i)); + KernelCpuSpeedReader cpuStepJiffiesReader = new KernelCpuSpeedReader(i, numSpeedSteps); + long[] cpuCoreStepJiffies = cpuStepJiffiesReader.readAbsolute(); + ListEntry> cpuCoreState = ListEntry.ofDigits(cpuCoreStepJiffies); + snapshot.cpuCoreStates.add(cpuCoreState); + } } - // Proc cluster steps jiffies int[] clusterSteps = new int[mPowerProfile.getNumCpuClusters()]; for (int i = 0; i < clusterSteps.length; i++) { clusterSteps[i] = mPowerProfile.getNumSpeedStepsInCpuCluster(i); } - KernelCpuUidFreqTimeReader procStepJiffiesReader = new KernelCpuUidFreqTimeReader(Process.myPid(), clusterSteps); + KernelCpuUidFreqTimeReader procStepJiffiesReader = new KernelCpuUidFreqTimeReader(pid, clusterSteps); List procStepJiffies = procStepJiffiesReader.readAbsolute(); snapshot.procCpuCoreStates = new ArrayList<>(); for (long[] item : procStepJiffies) { @@ -137,6 +149,47 @@ public CpuStateSnapshot currentCpuStateSnapshot() { return snapshot; } + public UidCpuStateSnapshot currentUidCpuStateSnapshot() { + UidCpuStateSnapshot curr = new UidCpuStateSnapshot(); + try { + List> procList = TopThreadFeature.getProcList(mCore.getContext()); + curr.pidCurrCupSateList = new ArrayList<>(procList.size()); + + for (Pair item : procList) { + //noinspection ConstantConditions + int pid = item.first; + String procName = String.valueOf(item.second); + CpuStateSnapshot snapshot = null; + + if (pid == Process.myPid()) { + // from local + snapshot = currentCpuStateSnapshot(); + } else { + if (ProcStatUtil.exists(pid)) { + // from pid + snapshot = currentCpuStateSnapshot(pid); + } + if (snapshot != null && !snapshot.isValid() && mCore.getConfig().ipcCpuStatCollector != null) { + // from ipc + UidCpuStateSnapshot.IpcCpuStat.RemoteStat remote = mCore.getConfig().ipcCpuStatCollector.apply(item); + if (remote != null) { + snapshot = UidCpuStateSnapshot.IpcCpuStat.toLocal(remote); + } + } + } + if (snapshot != null) { + snapshot.pid = pid; + snapshot.name = TopThreadIndicator.getProcSuffix(procName); + curr.pidCurrCupSateList.add(snapshot); + } + } + } catch (Exception e) { + MatrixLog.w(TAG, "get curr UidCpuStatSnapshot failed: " + e.getMessage()); + curr.setValid(false); + } + return curr; + } + public static final class CpuStateSnapshot extends Snapshot { /* * cpuCoreStates @@ -155,6 +208,8 @@ public static final class CpuStateSnapshot extends Snapshot { */ public List>> cpuCoreStates = Collections.emptyList(); public List>> procCpuCoreStates = Collections.emptyList(); + public int pid = Process.myPid(); + public String name = BatteryCanaryUtil.getProcessName(); public long totalCpuJiffies() { long sum = 0; @@ -166,6 +221,16 @@ public long totalCpuJiffies() { return sum; } + public long totalProcCpuJiffies() { + long sum = 0; + for (ListEntry> cpuCoreState : procCpuCoreStates) { + for (DigitEntry item : cpuCoreState.getList()) { + sum += item.value; + } + } + return sum; + } + public double configureCpuSip(PowerProfile powerProfile) { if (!powerProfile.isSupported()) { return 0; @@ -217,6 +282,8 @@ public Delta diff(CpuStateSnapshot bgn) { @Override protected CpuStateSnapshot computeDelta() { CpuStateSnapshot delta = new CpuStateSnapshot(); + delta.pid = end.pid; + delta.name = end.name; if (bgn.cpuCoreStates.size() != end.cpuCoreStates.size()) { delta.setValid(false); } else { @@ -234,4 +301,111 @@ protected CpuStateSnapshot computeDelta() { }; } } + + public static final class UidCpuStateSnapshot extends MonitorFeature.Snapshot { + public List pidCurrCupSateList = Collections.emptyList(); + public List> pidDeltaCpuSateList = Collections.emptyList(); + + @Override + public MonitorFeature.Snapshot.Delta diff(UidCpuStateSnapshot bgn) { + return new MonitorFeature.Snapshot.Delta(bgn, this) { + @Override + protected UidCpuStateSnapshot computeDelta() { + UidCpuStateSnapshot delta = new UidCpuStateSnapshot(); + if (end.pidCurrCupSateList.size() > 0) { + delta.pidDeltaCpuSateList = new ArrayList<>(); + for (CpuStateSnapshot end : end.pidCurrCupSateList) { + CpuStateSnapshot last = null; + for (CpuStateSnapshot bgn : bgn.pidCurrCupSateList) { + if (bgn.pid == end.pid) { + last = bgn; + break; + } + } + if (last == null) { + // newAdded Pid + CpuStateSnapshot empty = new CpuStateSnapshot(); + empty.pid = end.pid; + empty.name = end.name; + empty.procCpuCoreStates = new ArrayList<>(end.procCpuCoreStates.size()); + for (ListEntry> item : end.procCpuCoreStates) { + long[] emptyStats = new long[item.getList().size()]; + empty.procCpuCoreStates.add(ListEntry.ofDigits(emptyStats)); + } + last = empty; + } + MonitorFeature.Snapshot.Delta deltaPidCpuState = end.diff(last); + delta.pidDeltaCpuSateList.add(deltaPidCpuState); + } + } + return delta; + } + }; + } + + + public static class IpcCpuStat { + public static RemoteStat toIpc(CpuStateSnapshot local) { + RemoteStat remote = new RemoteStat(); + remote.procCpuCoreStates = new ArrayList<>(local.procCpuCoreStates.size()); + for (ListEntry> item : local.procCpuCoreStates) { + long[] stats = new long[item.getList().size()]; + for (int i = 0; i < stats.length; i++) { + stats[i] = item.getList().get(i).get(); + } + remote.procCpuCoreStates.add(stats); + } + return remote; + } + + public static CpuStateSnapshot toLocal(RemoteStat remote) { + CpuStateSnapshot local = new CpuStateSnapshot(); + local.procCpuCoreStates = new ArrayList<>(remote.procCpuCoreStates.size()); + for (long[] item : remote.procCpuCoreStates) { + local.procCpuCoreStates.add(ListEntry.ofDigits(item)); + } + return local; + } + + public static class RemoteStat implements Parcelable { + public List procCpuCoreStates = Collections.emptyList(); + + public RemoteStat() { + } + + protected RemoteStat(Parcel in) { + int size = in.readInt(); + procCpuCoreStates = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + procCpuCoreStates.add(in.createLongArray()); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + int numOfArrays = procCpuCoreStates.size(); + dest.writeInt(numOfArrays); + for (int i = 0; i < numOfArrays; i++) { + dest.writeLongArray(procCpuCoreStates.get(i)); + } + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public RemoteStat createFromParcel(Parcel in) { + return new RemoteStat(in); + } + @Override + public RemoteStat[] newArray(int size) { + return new RemoteStat[size]; + } + }; + } + } + } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/DeviceStatMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/DeviceStatMonitorFeature.java index 370ce28ea..a22c3b581 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/DeviceStatMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/DeviceStatMonitorFeature.java @@ -117,13 +117,18 @@ public int weight() { } public CpuFreqSnapshot currentCpuFreq() { - CpuFreqSnapshot snapshot = new CpuFreqSnapshot(); try { - snapshot.cpuFreqs = Snapshot.Entry.ListEntry.ofDigits(BatteryCanaryUtil.getCpuCurrentFreq()); + int[] cpuFreqs = BatteryCanaryUtil.getCpuCurrentFreq(); + return currentCpuFreq(cpuFreqs); } catch (Throwable e) { MatrixLog.printErrStackTrace(TAG, e, "#currentCpuFreq error"); - snapshot.cpuFreqs = Snapshot.Entry.ListEntry.ofDigits(new int[]{}); + return currentCpuFreq(new int[]{}); } + } + + public CpuFreqSnapshot currentCpuFreq(int[] cpuFreqs) { + CpuFreqSnapshot snapshot = new CpuFreqSnapshot(); + snapshot.cpuFreqs = Snapshot.Entry.ListEntry.ofDigits(cpuFreqs); return snapshot; } @@ -186,6 +191,12 @@ public ChargeWattageSnapshot currentChargeWattage(Context context) { return snapshot; } + public BatteryCurrentSnapshot currentBatteryCurrency(Context context) { + BatteryCurrentSnapshot snapshot = new BatteryCurrentSnapshot(); + snapshot.stat = Snapshot.Entry.DigitEntry.of(BatteryCanaryUtil.getBatteryCurrencyImmediately(context)); + return snapshot; + } + static final class DevStatListener { Consumer mListener = new Consumer() { @@ -228,7 +239,7 @@ public boolean onStateChanged(String event) { break; case Intent.ACTION_SCREEN_ON: if (!mIsCharging) { - mListener.accept(AppStats.DEV_STAT_CHARGING); + mListener.accept(AppStats.DEV_STAT_SCREEN_ON); } break; case Intent.ACTION_SCREEN_OFF: @@ -346,6 +357,23 @@ protected ChargeWattageSnapshot computeDelta() { } }; } + + } + + public static class BatteryCurrentSnapshot extends Snapshot { + public Entry.DigitEntry stat; + + @Override + public Delta diff(BatteryCurrentSnapshot bgn) { + return new Delta(bgn, this) { + @Override + protected BatteryCurrentSnapshot computeDelta() { + BatteryCurrentSnapshot delta = new BatteryCurrentSnapshot(); + delta.stat = DigitDiffer.globalDiff(bgn.stat, end.stat); + return delta; + } + }; + } } public static final class DevStatSnapshot extends Snapshot { diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/JiffiesMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/JiffiesMonitorFeature.java index cafb02353..7f8cdb56b 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/JiffiesMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/JiffiesMonitorFeature.java @@ -1,16 +1,21 @@ package com.tencent.matrix.batterycanary.monitor.feature; +import android.content.Context; import android.os.Handler; import android.os.HandlerThread; +import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; import android.text.TextUtils; import com.tencent.matrix.Matrix; +import com.tencent.matrix.batterycanary.monitor.BatteryMonitorConfig; import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore.Callback; -import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.DigitEntry; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.ListEntry; +import com.tencent.matrix.batterycanary.shell.TopThreadFeature; +import com.tencent.matrix.batterycanary.shell.ui.TopThreadIndicator; import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; import com.tencent.matrix.batterycanary.utils.ProcStatUtil; import com.tencent.matrix.util.MatrixLog; @@ -28,10 +33,12 @@ import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.annotation.WorkerThread; +import androidx.core.util.Pair; @SuppressWarnings("NotNullFieldNotInitialized") public final class JiffiesMonitorFeature extends AbsMonitorFeature { private static final String TAG = "Matrix.battery.JiffiesMonitorFeature"; + private static boolean sSkipNewAdded = false; // FIXME: move to feature's configuration public interface JiffiesListener { @Deprecated @@ -48,6 +55,12 @@ protected String getTag() { return TAG; } + @Override + public void onTurnOn() { + super.onTurnOn(); + sSkipNewAdded = mCore.getConfig().isSkipNewAddedPidTid; + } + @Override public int weight() { return Integer.MAX_VALUE; @@ -83,6 +96,11 @@ public JiffiesSnapshot currentJiffiesSnapshot(int pid) { return JiffiesSnapshot.currentJiffiesSnapshot(ProcessInfo.getProcessInfo(pid), mCore.getConfig().isStatPidProc); } + @WorkerThread + public UidJiffiesSnapshot currentUidJiffiesSnapshot() { + return UidJiffiesSnapshot.of(mCore.getContext(), mCore.getConfig()); + } + @AnyThread public void currentJiffiesSnapshot(@NonNull final Callback callback) { mCore.getHandler().post(new Runnable() { @@ -263,12 +281,16 @@ public static JiffiesSnapshot currentJiffiesSnapshot(ProcessInfo processInfo, bo } public int pid; + public boolean isNewAdded; public String name; public DigitEntry totalJiffies; public ListEntry threadEntries; public DigitEntry threadNum; + public ListEntry deadThreadEntries; private JiffiesSnapshot() { + isNewAdded = false; + deadThreadEntries = ListEntry.ofEmpty(); } @Override @@ -278,11 +300,13 @@ public Delta diff(JiffiesSnapshot bgn) { protected JiffiesSnapshot computeDelta() { JiffiesSnapshot delta = new JiffiesSnapshot(); delta.pid = end.pid; + delta.isNewAdded = end.isNewAdded; delta.name = end.name; delta.totalJiffies = Differ.DigitDiffer.globalDiff(bgn.totalJiffies, end.totalJiffies); delta.threadNum = Differ.DigitDiffer.globalDiff(bgn.threadNum, end.threadNum); delta.threadEntries = ListEntry.ofEmpty(); + // for Existing threads if (end.threadEntries.getList().size() > 0) { List deltaThreadEntries = new ArrayList<>(); for (ThreadJiffiesSnapshot endRecord : end.threadEntries.getList()) { @@ -301,7 +325,10 @@ protected JiffiesSnapshot computeDelta() { deltaThreadJiffies.name = endRecord.name; deltaThreadJiffies.stat = endRecord.stat; deltaThreadJiffies.isNewAdded = isNewAdded; - deltaThreadEntries.add(deltaThreadJiffies); + if (!isNewAdded || !sSkipNewAdded) { + // Skip new added tid for now + deltaThreadEntries.add(deltaThreadJiffies); + } } } if (deltaThreadEntries.size() > 0) { @@ -317,6 +344,30 @@ public int compare(ThreadJiffiesSnapshot o1, ThreadJiffiesSnapshot o2) { delta.threadEntries = ListEntry.of(deltaThreadEntries); } } + + // for Dead threads + if (bgn.threadEntries.getList().size() > 0) { + List deadThreadEntries = Collections.emptyList(); + for (ThreadJiffiesSnapshot bgn : bgn.threadEntries.getList()) { + boolean isDead = true; + for (ThreadJiffiesSnapshot exist : delta.threadEntries.getList()) { + if (exist.tid == bgn.tid) { + isDead = false; + break; + } + } + if (isDead) { + if (deadThreadEntries.isEmpty()) { + deadThreadEntries = new ArrayList<>(); + } + deadThreadEntries.add(bgn); + } + } + if (!deadThreadEntries.isEmpty()) { + delta.deadThreadEntries = ListEntry.of(deadThreadEntries); + } + } + return delta; } }; @@ -459,4 +510,232 @@ private long setNext(long millis) { return millis; } } + + public static class UidJiffiesSnapshot extends Snapshot { + public static UidJiffiesSnapshot of(Context context, BatteryMonitorConfig config) { + UidJiffiesSnapshot curr = new UidJiffiesSnapshot(); + List> procList = TopThreadFeature.getProcList(context); + curr.pidCurrJiffiesList = new ArrayList<>(procList.size()); + long sum = 0; + MatrixLog.i(TAG, "currProcList: " + procList); + for (Pair item : procList) { + //noinspection ConstantConditions + int pid = item.first; + String procName = String.valueOf(item.second); + if (ProcStatUtil.exists(pid)) { + MatrixLog.i(TAG, "proc: " + pid); + JiffiesSnapshot snapshot = JiffiesSnapshot.currentJiffiesSnapshot(ProcessInfo.getProcessInfo(pid), config.isStatPidProc); + snapshot.name = TopThreadIndicator.getProcSuffix(procName); + sum += snapshot.totalJiffies.get(); + curr.pidCurrJiffiesList.add(snapshot); + } else { + if (config.ipcJiffiesCollector != null) { + IpcJiffies.IpcProcessJiffies ipcProcessJiffies = config.ipcJiffiesCollector.apply(item); + if (ipcProcessJiffies != null) { + MatrixLog.i(TAG, "ipc: " + pid); + JiffiesSnapshot snapshot = IpcJiffies.toLocal(ipcProcessJiffies); + snapshot.name = TopThreadIndicator.getProcSuffix(procName); + sum += snapshot.totalJiffies.get(); + curr.pidCurrJiffiesList.add(snapshot); + continue; + } + } + MatrixLog.i(TAG, "skip: " + pid); + } + } + curr.totalUidJiffies = DigitEntry.of(sum); + return curr; + } + + public DigitEntry totalUidJiffies = DigitEntry.of(0L); + public List pidCurrJiffiesList = Collections.emptyList(); + public List> pidDeltaJiffiesList = Collections.emptyList(); + + @Override + public Delta diff(UidJiffiesSnapshot bgn) { + return new Delta(bgn, this) { + @Override + protected UidJiffiesSnapshot computeDelta() { + UidJiffiesSnapshot delta = new UidJiffiesSnapshot(); + delta.totalUidJiffies = Differ.DigitDiffer.globalDiff(bgn.totalUidJiffies, end.totalUidJiffies); + if (end.pidCurrJiffiesList.size() > 0) { + delta.pidDeltaJiffiesList = new ArrayList<>(); + for (JiffiesSnapshot end : end.pidCurrJiffiesList) { + JiffiesSnapshot last = null; + for (JiffiesSnapshot bgn : bgn.pidCurrJiffiesList) { + if (bgn.pid == end.pid) { + last = bgn; + break; + } + } + if (last == null) { + // newAdded Pid + end.isNewAdded = true; + JiffiesSnapshot empty = new JiffiesSnapshot(); + empty.pid = end.pid; + empty.name = end.name; + empty.totalJiffies = DigitEntry.of(0L); + empty.threadEntries = ListEntry.ofEmpty(); + empty.threadNum = DigitEntry.of(0); + last = empty; + } + if (!end.isNewAdded || !sSkipNewAdded) { + // Skip new added pid for now + Delta deltaPidJiffies = end.diff(last); + delta.pidDeltaJiffiesList.add(deltaPidJiffies); + } + } + + Collections.sort(delta.pidDeltaJiffiesList, new Comparator>() { + @Override + public int compare(Delta o1, Delta o2) { + long minus = o1.dlt.totalJiffies.get() - o2.dlt.totalJiffies.get(); + if (minus == 0) return 0; + if (minus > 0) return -1; + return 1; + } + }); + } + return delta; + } + }; + } + + + public static final class IpcJiffies { + public static IpcProcessJiffies toIpc(JiffiesSnapshot local) { + IpcProcessJiffies ipc = new IpcProcessJiffies(); + ipc.pid = local.pid; + ipc.name = local.name; + ipc.threadNum = local.threadNum.get(); + ipc.totalJiffies = local.totalJiffies.get(); + ipc.threadJiffyList = new ArrayList<>(local.threadEntries.getList().size()); + for (JiffiesSnapshot.ThreadJiffiesSnapshot item : local.threadEntries.getList()) { + ipc.threadJiffyList.add(toIpc(item)); + } + return ipc; + } + + public static IpcProcessJiffies.IpcThreadJiffies toIpc(JiffiesSnapshot.ThreadJiffiesSnapshot local) { + IpcProcessJiffies.IpcThreadJiffies ipc = new IpcProcessJiffies.IpcThreadJiffies(); + ipc.tid = local.tid; + ipc.name = local.name; + ipc.stat = local.stat; + ipc.jiffies = local.get(); + return ipc; + } + + public static JiffiesSnapshot toLocal(IpcProcessJiffies ipc) { + JiffiesSnapshot local = new JiffiesSnapshot(); + local.pid = ipc.pid; + local.name = ipc.name; + local.totalJiffies = DigitEntry.of(ipc.totalJiffies); + List threadJiffiesList = Collections.emptyList(); + if (!ipc.threadJiffyList.isEmpty()) { + threadJiffiesList = new ArrayList<>(ipc.threadJiffyList.size()); + for (IpcProcessJiffies.IpcThreadJiffies item : ipc.threadJiffyList) { + JiffiesSnapshot.ThreadJiffiesSnapshot threadJiffies = toLocal(item); + threadJiffiesList.add(threadJiffies); + } + } + local.threadEntries = ListEntry.of(threadJiffiesList); + local.threadNum = DigitEntry.of(threadJiffiesList.size()); + return local; + } + + public static JiffiesSnapshot.ThreadJiffiesSnapshot toLocal(IpcProcessJiffies.IpcThreadJiffies ipc) { + JiffiesSnapshot.ThreadJiffiesSnapshot local = new JiffiesSnapshot.ThreadJiffiesSnapshot(ipc.jiffies); + local.name = ipc.name; + local.stat = ipc.stat; + local.tid = ipc.tid; + local.isNewAdded = true; + return local; + } + + public static class IpcProcessJiffies implements Parcelable { + public int pid; + public String name; + public long totalJiffies; + public int threadNum; + public List threadJiffyList; + + protected IpcProcessJiffies(Parcel in) { + pid = in.readInt(); + name = in.readString(); + totalJiffies = in.readLong(); + threadNum = in.readInt(); + threadJiffyList = in.createTypedArrayList(IpcThreadJiffies.CREATOR); + } + + public static final Creator CREATOR = new Creator() { + @Override + public IpcProcessJiffies createFromParcel(Parcel in) { + return new IpcProcessJiffies(in); + } + @Override + public IpcProcessJiffies[] newArray(int size) { + return new IpcProcessJiffies[size]; + } + }; + + public IpcProcessJiffies() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(pid); + dest.writeString(name); + dest.writeLong(totalJiffies); + dest.writeInt(threadNum); + dest.writeTypedList(threadJiffyList); + } + + public static class IpcThreadJiffies implements Parcelable { + public int tid; + public String name; + public String stat; + public long jiffies; + + protected IpcThreadJiffies(Parcel in) { + tid = in.readInt(); + name = in.readString(); + stat = in.readString(); + jiffies = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public IpcThreadJiffies createFromParcel(Parcel in) { + return new IpcThreadJiffies(in); + } + @Override + public IpcThreadJiffies[] newArray(int size) { + return new IpcThreadJiffies[size]; + } + }; + + public IpcThreadJiffies() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(tid); + dest.writeString(name); + dest.writeString(stat); + dest.writeLong(jiffies); + } + } + } + } + } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/LooperTaskMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/LooperTaskMonitorFeature.java index 73693817f..e71956f59 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/LooperTaskMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/LooperTaskMonitorFeature.java @@ -6,8 +6,10 @@ import android.os.Process; import android.text.TextUtils; +import com.tencent.matrix.batterycanary.BuildConfig; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; import com.tencent.matrix.trace.core.LooperMonitor; +import com.tencent.matrix.trace.listeners.ILooperListener; import com.tencent.matrix.util.MatrixLog; import java.util.ArrayList; @@ -37,7 +39,7 @@ public interface LooperTaskListener { final Map mLooperMonitorTrace = new HashMap<>(); @Nullable - LooperMonitor.LooperDispatchListener mLooperTaskListener; + ILooperListener mLooperTaskListener; @Nullable Runnable mDelayWatchingTask; @@ -53,15 +55,14 @@ LooperTaskListener getListener() { @Override public void onTurnOn() { super.onTurnOn(); - mLooperTaskListener = new LooperMonitor.LooperDispatchListener() { + mLooperTaskListener = new ILooperListener() { @Override public boolean isValid() { return mCore.isTurnOn(); } @Override - public void onDispatchStart(String x) { - super.onDispatchStart(x); + public void onDispatchBegin(String x) { if (mCore.getConfig().isAggressiveMode) { MatrixLog.i(TAG, "[" + Thread.currentThread().getName() + "]" + x); } @@ -75,8 +76,7 @@ public void onDispatchStart(String x) { } @Override - public void onDispatchEnd(String x) { - super.onDispatchEnd(x); + public void onDispatchEnd(String x, long beginNs, long endNs) { if (mCore.getConfig().isAggressiveMode) { MatrixLog.i(TAG, "[" + Thread.currentThread().getName() + "]" + x); } @@ -310,6 +310,11 @@ protected void onConcurrentOverHeat(String key, int concurrentCount, long during protected void onParseTaskJiffiesFail(String key, int pid, int tid) { } + @Override + protected boolean shouldTraceTask(Snapshot.Delta delta) { + return BuildConfig.DEBUG || delta.during > 10L; + } + @Deprecated public static class TaskTraceInfo { diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeature.java index 5a5cf93ff..176d4b8ab 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/MonitorFeature.java @@ -5,6 +5,7 @@ import android.os.SystemClock; import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore; +import com.tencent.matrix.batterycanary.utils.Function; import com.tencent.matrix.util.MatrixLog; import java.util.ArrayList; @@ -67,11 +68,29 @@ public Delta(RECORD bgn, RECORD end) { dlt.isDelta = true; } + public Delta(RECORD bgn, RECORD end, RECORD dlt) { + this.bgn = bgn; + this.end = end; + this.during = end.time - bgn.time; + this.dlt = dlt; + dlt.isDelta = true; + } + public boolean isValid() { return bgn.isValid() && end.isValid(); } protected abstract RECORD computeDelta(); + + public static class SimpleDelta extends Delta { + public SimpleDelta(RECORD bgn, RECORD end, RECORD dlt) { + super(bgn, end, dlt); + } + @Override + protected RECORD computeDelta() { + throw new RuntimeException("stub!"); + } + } } public abstract static class Entry { @@ -419,19 +438,21 @@ public Entry.ListEntry diff(@NonNull Entry.ListEntry bgn, @NonNull public static class Sampler { private static final String TAG = "Matrix.battery.Sampler"; - public static final Number INVALID = Integer.MIN_VALUE; + public static final Integer INVALID = Integer.MIN_VALUE; + final String mTag; final Handler mHandler; - final Callable mSamplingBlock; + final Function mSamplingBlock; private final Runnable mSamplingTask = new Runnable() { @Override public void run() { try { - Number currSample = mSamplingBlock.call(); - if (currSample != INVALID) { + Number currSample = mSamplingBlock.apply(Sampler.this); + if (!currSample.equals(INVALID)) { mSampleLst = currSample.doubleValue(); mCount++; + // FIXME: calc vag on finished mSampleAvg = (mSampleAvg * (mCount - 1) + mSampleLst) / mCount; if (mSampleFst == Double.MIN_VALUE) { mSampleFst = mSampleLst; @@ -468,10 +489,38 @@ public void run() { double mSampleAvg = Double.MIN_VALUE; public Sampler(Handler handler, Callable onSampling) { + this("dft", handler, onSampling); + } + + public Sampler(String tag, Handler handler, final Callable onSampling) { + mTag = tag; + mHandler = handler; + mSamplingBlock = new Function() { + @Override + public Number apply(Sampler sampler) { + try { + return onSampling.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + + public Sampler(String tag, Handler handler, Function onSampling) { + mTag = tag; mHandler = handler; mSamplingBlock = onSampling; } + public String getTag() { + return mTag; + } + + public int getCount() { + return mCount; + } + public void setInterval(long interval) { if (interval > 0) { mInterval = interval; @@ -521,6 +570,20 @@ public static final class Result { public double sampleMax; public double sampleMin; public double sampleAvg; + + @Override + public String toString() { + return "Result{" + + "interval=" + interval + + ", count=" + count + + ", duringMillis=" + duringMillis + + ", sampleFst=" + sampleFst + + ", sampleLst=" + sampleLst + + ", sampleMax=" + sampleMax + + ", sampleMin=" + sampleMin + + ", sampleAvg=" + sampleAvg + + '}'; + } } } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/TrafficMonitorFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/TrafficMonitorFeature.java index b63877e0f..209760e52 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/TrafficMonitorFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/monitor/feature/TrafficMonitorFeature.java @@ -35,19 +35,45 @@ public TrafficMonitorFeature.RadioStatSnapshot currentRadioSnapshot(Context cont if (stat == null) { return null; } + TrafficMonitorFeature.RadioStatSnapshot snapshot = new TrafficMonitorFeature.RadioStatSnapshot(); snapshot.wifiRxBytes = Snapshot.Entry.DigitEntry.of(stat.wifiRxBytes); snapshot.wifiTxBytes = Snapshot.Entry.DigitEntry.of(stat.wifiTxBytes); + snapshot.wifiRxPackets = Snapshot.Entry.DigitEntry.of(stat.wifiRxPackets); + snapshot.wifiTxPackets = Snapshot.Entry.DigitEntry.of(stat.wifiTxPackets); + snapshot.mobileRxBytes = Snapshot.Entry.DigitEntry.of(stat.mobileRxBytes); snapshot.mobileTxBytes = Snapshot.Entry.DigitEntry.of(stat.mobileTxBytes); + snapshot.mobileRxPackets = Snapshot.Entry.DigitEntry.of(stat.mobileRxPackets); + snapshot.mobileTxPackets = Snapshot.Entry.DigitEntry.of(stat.mobileTxPackets); + return snapshot; + } + + @Nullable + public TrafficMonitorFeature.RadioBpsSnapshot currentRadioBpsSnapshot(Context context) { + RadioStatUtil.RadioBps stat = RadioStatUtil.getCurrentBps(context); + if (stat == null) { + return null; + } + + TrafficMonitorFeature.RadioBpsSnapshot snapshot = new TrafficMonitorFeature.RadioBpsSnapshot(); + snapshot.wifiRxBps = Snapshot.Entry.DigitEntry.of(stat.wifiRxBps); + snapshot.wifiTxBps = Snapshot.Entry.DigitEntry.of(stat.wifiTxBps); + snapshot.mobileRxBps = Snapshot.Entry.DigitEntry.of(stat.mobileRxBps); + snapshot.mobileTxBps = Snapshot.Entry.DigitEntry.of(stat.mobileTxBps); return snapshot; } public static class RadioStatSnapshot extends Snapshot { public Entry.DigitEntry wifiRxBytes = Entry.DigitEntry.of(0L); public Entry.DigitEntry wifiTxBytes = Entry.DigitEntry.of(0L); + public Entry.DigitEntry wifiRxPackets = Entry.DigitEntry.of(0L); + public Entry.DigitEntry wifiTxPackets = Entry.DigitEntry.of(0L); + public Entry.DigitEntry mobileRxBytes = Entry.DigitEntry.of(0L); public Entry.DigitEntry mobileTxBytes = Entry.DigitEntry.of(0L); + public Entry.DigitEntry mobileRxPackets = Entry.DigitEntry.of(0L); + public Entry.DigitEntry mobileTxPackets = Entry.DigitEntry.of(0L); @Override public Delta diff(RadioStatSnapshot bgn) { @@ -57,8 +83,35 @@ protected RadioStatSnapshot computeDelta() { RadioStatSnapshot delta = new RadioStatSnapshot(); delta.wifiRxBytes = DigitDiffer.globalDiff(bgn.wifiRxBytes, end.wifiRxBytes); delta.wifiTxBytes = DigitDiffer.globalDiff(bgn.wifiTxBytes, end.wifiTxBytes); + delta.wifiRxPackets = DigitDiffer.globalDiff(bgn.wifiRxPackets, end.wifiRxPackets); + delta.wifiTxPackets = DigitDiffer.globalDiff(bgn.wifiTxPackets, end.wifiTxPackets); + delta.mobileRxBytes = DigitDiffer.globalDiff(bgn.mobileRxBytes, end.mobileRxBytes); delta.mobileTxBytes = DigitDiffer.globalDiff(bgn.mobileTxBytes, end.mobileTxBytes); + delta.mobileRxPackets = DigitDiffer.globalDiff(bgn.mobileRxPackets, end.mobileRxPackets); + delta.mobileTxPackets = DigitDiffer.globalDiff(bgn.mobileTxPackets, end.mobileTxPackets); + return delta; + } + }; + } + } + + public static class RadioBpsSnapshot extends Snapshot { + public Entry.DigitEntry wifiRxBps = Entry.DigitEntry.of(0L); + public Entry.DigitEntry wifiTxBps = Entry.DigitEntry.of(0L); + public Entry.DigitEntry mobileRxBps = Entry.DigitEntry.of(0L); + public Entry.DigitEntry mobileTxBps = Entry.DigitEntry.of(0L); + + @Override + public Delta diff(RadioBpsSnapshot bgn) { + return new Delta(bgn, this) { + @Override + protected RadioBpsSnapshot computeDelta() { + RadioBpsSnapshot delta = new RadioBpsSnapshot(); + delta.wifiRxBps = DigitDiffer.globalDiff(bgn.wifiRxBps, end.wifiRxBps); + delta.wifiTxBps = DigitDiffer.globalDiff(bgn.wifiTxBps, end.wifiTxBps); + delta.mobileRxBps = DigitDiffer.globalDiff(bgn.mobileRxBps, end.mobileRxBps); + delta.mobileTxBps = DigitDiffer.globalDiff(bgn.mobileTxBps, end.mobileTxBps); return delta; } }; diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/TopThreadFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/TopThreadFeature.java index b6d518050..840453574 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/TopThreadFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/TopThreadFeature.java @@ -9,15 +9,18 @@ import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCallback.BatteryPrinter.Printer; import com.tencent.matrix.batterycanary.monitor.feature.AbsMonitorFeature; +import com.tencent.matrix.batterycanary.monitor.feature.CompositeMonitors; import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature; import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature.JiffiesSnapshot; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import androidx.annotation.Nullable; import androidx.core.util.Pair; +import androidx.core.util.Supplier; /** * Something like 'adb shell top -Hb -u ' @@ -29,8 +32,8 @@ public class TopThreadFeature extends AbsMonitorFeature { private static final String TAG = "Matrix.battery.TopThread"; private boolean sStopShell; - public interface ContinuousCallback> { - boolean onGetDeltas(List> deltas, long windowMillis); + public interface ContinuousCallback { + boolean onGetDeltas(CompositeMonitors monitors, long windowMillis); } @Nullable private Runnable mTopTask; @@ -48,10 +51,18 @@ public int weight() { public void topShell(final int seconds) { sStopShell = false; - top(seconds, new ContinuousCallback() { + top(seconds, new Supplier() { @Override - public boolean onGetDeltas(List> deltaList, long windowMillis) { + public CompositeMonitors get() { + CompositeMonitors monitors = new CompositeMonitors(mCore, CompositeMonitors.SCOPE_TOP_SHELL); + monitors.metric(JiffiesMonitorFeature.UidJiffiesSnapshot.class); + return monitors; + } + }, new ContinuousCallback() { + @Override + public boolean onGetDeltas(CompositeMonitors monitors, long windowMillis) { // Proc Load + List> deltaList = monitors.getAllPidDeltaList(); long allProcJiffies = 0; for (Delta delta : deltaList) { allProcJiffies += delta.dlt.totalJiffies.get(); @@ -103,39 +114,43 @@ public void stopShell() { } - public void top(final int seconds, final ContinuousCallback callback) { + public void top(final int seconds, final Supplier supplier, final ContinuousCallback callback) { final JiffiesMonitorFeature jiffiesFeat = mCore.getMonitorFeature(JiffiesMonitorFeature.class); if (jiffiesFeat == null) { return; } final long windowMillis = seconds * 1000L; - final SparseArray lastHolder = new SparseArray<>(); + final AtomicReference lastMonitors = new AtomicReference<>(null); HandlerThread thread = new HandlerThread("matrix_top"); thread.start(); final Handler handler = new Handler(thread.getLooper()); final Runnable action = new Runnable() { @Override public void run() { - List> deltaList = new ArrayList<>(); - List pidList = getAllPidList(mCore.getContext()); - for (Integer pid : pidList) { - JiffiesSnapshot curr = jiffiesFeat.currentJiffiesSnapshot(pid); - JiffiesSnapshot last = lastHolder.get(pid); - if (last != null) { - Delta delta = curr.diff(last); - if (delta.isValid()) { - deltaList.add(delta); - } - } - lastHolder.put(pid, curr); - } - boolean stop = callback.onGetDeltas(deltaList, windowMillis); - if (stop) { - handler.getLooper().quit(); + CompositeMonitors monitors = lastMonitors.get(); + if (monitors == null) { + // Fist time + scheduleNext(); + } else { - handler.postDelayed(this, windowMillis); + lastMonitors.set(null); + monitors.finish(); + boolean stop = callback.onGetDeltas(monitors, windowMillis); + if (stop) { + handler.getLooper().quit(); + } else { + // Next + scheduleNext(); + } } } + + private void scheduleNext() { + CompositeMonitors monitors = supplier.get(); + monitors.start(); + lastMonitors.set(monitors); + handler.postDelayed(this, windowMillis); + } }; handler.postDelayed(action, windowMillis); } @@ -157,9 +172,11 @@ public static List> getProcList(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am != null) { List processes = am.getRunningAppProcesses(); - for (ActivityManager.RunningAppProcessInfo item : processes) { - if (item.processName.contains(context.getPackageName())) { - list.add(new Pair<>(item.pid, item.processName)); + if (processes != null) { + for (ActivityManager.RunningAppProcessInfo item : processes) { + if (item.processName.contains(context.getPackageName())) { + list.add(new Pair<>(item.pid, item.processName)); + } } } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/ui/TopThreadIndicator.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/ui/TopThreadIndicator.java index 6c4306625..3c7efe83c 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/ui/TopThreadIndicator.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/shell/ui/TopThreadIndicator.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; +import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.TextView; @@ -31,27 +32,39 @@ import com.tencent.matrix.batterycanary.BatteryCanary; import com.tencent.matrix.batterycanary.R; +import com.tencent.matrix.batterycanary.monitor.AppStats; import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCallback; import com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore; +import com.tencent.matrix.batterycanary.monitor.feature.CompositeMonitors; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature; +import com.tencent.matrix.batterycanary.monitor.feature.DeviceStatMonitorFeature; +import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature; import com.tencent.matrix.batterycanary.monitor.feature.JiffiesMonitorFeature.JiffiesSnapshot; import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Delta; +import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Sampler.Result; +import com.tencent.matrix.batterycanary.monitor.feature.TrafficMonitorFeature; import com.tencent.matrix.batterycanary.shell.TopThreadFeature; import com.tencent.matrix.batterycanary.shell.TopThreadFeature.ContinuousCallback; import com.tencent.matrix.batterycanary.stats.BatteryStatsFeature; +import com.tencent.matrix.batterycanary.stats.HealthStatsFeature; +import com.tencent.matrix.batterycanary.stats.HealthStatsHelper; import com.tencent.matrix.batterycanary.stats.ui.BatteryStatsActivity; import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; import com.tencent.matrix.batterycanary.utils.CallStackCollector; import com.tencent.matrix.batterycanary.utils.Consumer; import com.tencent.matrix.util.MatrixLog; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; import androidx.annotation.UiThread; import androidx.core.util.Pair; +import androidx.core.util.Supplier; import static com.tencent.matrix.batterycanary.shell.TopThreadFeature.figureCupLoad; import static com.tencent.matrix.batterycanary.shell.TopThreadFeature.fixedColumn; @@ -67,6 +80,7 @@ final public class TopThreadIndicator { private static final String TAG = "Matrix.TopThreadIndicator"; private static final int MAX_PROC_NUM = 10; private static final int MAX_THREAD_NUM = 10; + private static final int MAX_POWER_NUM = 30; @SuppressLint("StaticFieldLeak") private static final TopThreadIndicator sInstance = new TopThreadIndicator(); @@ -82,6 +96,7 @@ final public class TopThreadIndicator { private BatteryMonitorCore mCore; @Nullable private Delta mCurrDelta; + private boolean mShowPower = false; @NonNull private CallStackCollector mCollector = new CallStackCollector(); @NonNull @@ -291,6 +306,9 @@ public boolean show(Context context) { final TextView tvProc = mRootView.findViewById(R.id.tv_proc); tvProc.setText(mCurrProc.second); + final CheckBox checkPower = mRootView.findViewById(R.id.check_power); + mShowPower = checkPower.isChecked(); + // init thread entryGroup LinearLayout procEntryGroup = mRootView.findViewById(R.id.layout_entry_proc_group); for (int i = 0; i < MAX_PROC_NUM - 1; i++) { @@ -309,6 +327,15 @@ public boolean show(Context context) { entryItemView.setVisibility(View.GONE); threadEntryGroup.addView(entryItemView, layoutParams); } + // init power entryGroup + LinearLayout powerEntryGroup = mRootView.findViewById(R.id.layout_entry_power_group); + for (int i = 0; i < MAX_POWER_NUM - 1; i++) { + View entryItemView = LayoutInflater.from(powerEntryGroup.getContext()).inflate(R.layout.float_item_power_entry, powerEntryGroup, false); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, powerEntryGroup.getContext().getResources().getDisplayMetrics()); + entryItemView.setVisibility(View.GONE); + powerEntryGroup.addView(entryItemView, layoutParams); + } // 3. Drag View.OnTouchListener onTouchListener = new View.OnTouchListener() { @@ -420,6 +447,12 @@ public boolean onMenuItemClick(MenuItem item) { mRootView.findViewById(R.id.iv_logo_minify).setVisibility(View.VISIBLE); return; } + if (v.getId() == R.id.layout_check_power) { + CheckBox view = v.findViewById(R.id.check_power); + view.setChecked(!view.isChecked()); + mShowPower = view.isChecked(); + return; + } if (v == mRootView && mRootView.findViewById(R.id.layout_top).getVisibility() == View.GONE) { // Minify LOGO View anchorView = mRootView.findViewById(R.id.iv_logo_minify); @@ -459,6 +492,7 @@ public void run() { mRootView.findViewById(R.id.layout_dump).setOnClickListener(listener); mRootView.findViewById(R.id.iv_logo).setOnClickListener(listener); mRootView.findViewById(R.id.tv_minify).setOnClickListener(listener); + mRootView.findViewById(R.id.layout_check_power).setOnClickListener(listener); mRootView.setOnClickListener(listener); return true; } catch (Exception e) { @@ -482,10 +516,24 @@ public void start(final int seconds) { BatteryCanary.getMonitorFeature(TopThreadFeature.class, new Consumer() { @Override public void accept(TopThreadFeature topThreadFeat) { - topThreadFeat.top(seconds, new ContinuousCallback() { + topThreadFeat.top(seconds, new Supplier() { @Override - public boolean onGetDeltas(List> deltaList, long windowMillis) { - refresh(deltaList); + public CompositeMonitors get() { + CompositeMonitors monitors = new CompositeMonitors(mCore, CompositeMonitors.SCOPE_TOP_INDICATOR); + monitors.metric(JiffiesMonitorFeature.UidJiffiesSnapshot.class); + monitors.metric(CpuStatFeature.CpuStateSnapshot.class); + monitors.metric(CpuStatFeature.UidCpuStateSnapshot.class); + monitors.metric(HealthStatsFeature.HealthStatsSnapshot.class); + monitors.metric(TrafficMonitorFeature.RadioStatSnapshot.class); + monitors.sample(DeviceStatMonitorFeature.CpuFreqSnapshot.class, 500L); + monitors.sample(DeviceStatMonitorFeature.BatteryCurrentSnapshot.class, 500L); + monitors.sample(TrafficMonitorFeature.RadioBpsSnapshot.class, 500L); + return monitors; + } + }, new ContinuousCallback() { + @Override + public boolean onGetDeltas(final CompositeMonitors monitors, long windowMillis) { + refresh(monitors); if (mRootView == null || !mRunningRef.get(hashcode, false)) { return true; } @@ -523,14 +571,19 @@ private void setTextAlertColor(TextView tv, int level) { } @SuppressLint({"SetTextI18n", "RestrictedApi"}) - private void refresh(final List> deltaList) { + private void refresh(final CompositeMonitors monitors) { mUiHandler.post(new Runnable() { @Override public void run() { if (mRootView == null) { return; } + AppStats appStats = monitors.getAppStats(); + if (appStats == null) { + return; + } + List> deltaList = monitors.getAllPidDeltaList(); int battTemp = BatteryCanaryUtil.getBatteryTemperatureImmediately(mRootView.getContext()); TextView tvBattTemp = mRootView.findViewById(R.id.tv_header_left); tvBattTemp.setText((battTemp > 0 ? battTemp / 10f : "/") + "°C"); @@ -539,7 +592,7 @@ public void run() { tvBattTemp.setText((battTemp > 0 ? battTemp / 10f : "/") + "°C"); setTextAlertColor(tvBattTemp, battTemp >= 350 ? 2 : battTemp >= 300 ? 1 : 0); - // EntryList + // CpuLoad EntryList LinearLayout procEntryGroup = mRootView.findViewById(R.id.layout_entry_proc_group); for (int i = 0; i < procEntryGroup.getChildCount(); i++) { procEntryGroup.getChildAt(i).setVisibility(View.GONE); @@ -578,7 +631,9 @@ public void run() { float threadLoad = figureCupLoad(entryJffies, delta.during / 10L); View threadItemView = threadEntryGroup.getChildAt(idx); - threadItemView.setVisibility(View.VISIBLE); + if (!mShowPower) { + threadItemView.setVisibility(View.VISIBLE); + } TextView tvName = threadItemView.findViewById(R.id.tv_name); TextView tvTid = threadItemView.findViewById(R.id.tv_tid); TextView tvStatus = threadItemView.findViewById(R.id.tv_status); @@ -620,11 +675,152 @@ public void run() { tvLoad.setText(totalLoad); tvLoad = mRootView.findViewById(R.id.tv_load_minify); tvLoad.setText(totalLoad); + + // Power EntryList + LinearLayout powerEntryGroup = mRootView.findViewById(R.id.layout_entry_power_group); + for (int i = 0; i < powerEntryGroup.getChildCount(); i++) { + powerEntryGroup.getChildAt(i).setVisibility(View.GONE); + } + if (mShowPower) { + final Map> powerMap = new LinkedHashMap<>(); + Result result = monitors.getSamplingResult(DeviceStatMonitorFeature.BatteryCurrentSnapshot.class); + if (result != null) { + double power = (result.sampleAvg / -1000) * (appStats.duringMillis * 1f / BatteryCanaryUtil.ONE_HOR); + double deltaPh = (Double) power * BatteryCanaryUtil.ONE_HOR / appStats.duringMillis; + powerMap.put("currency", new Pair<>("mAh", deltaPh / 1000)); + } else { + powerMap.put("currency", new Pair("mAh", null)); + } + final Delta healthStatsDelta = monitors.getDelta(HealthStatsFeature.HealthStatsSnapshot.class); + if (healthStatsDelta != null) { + powerMap.put("total", new Pair<>("mAh", healthStatsDelta.dlt.getTotalPower())); + powerMap.put("cpu", new Pair<>("mAh", healthStatsDelta.dlt.cpuPower.get())); + { + List modes = Arrays.asList("JiffyUid"); + for (String mode : modes) { + Object powers = healthStatsDelta.dlt.extras.get(mode); + if (powers != null) { + if (powers instanceof Map) { + // tuning cpu powers + for (Map.Entry entry : ((Map) powers).entrySet()) { + String key = String.valueOf(entry.getKey()); + Object val = entry.getValue(); + if (key.startsWith("power-cpu") && val instanceof Double) { + double cpuPower = (Double) val; + powerMap.put(key.replace("power-cpu", " - cpu"), new Pair<>("mAh", cpuPower)); + } + } + } + } + } + } + powerMap.put("wakelocks", new Pair<>("mAh", healthStatsDelta.dlt.wakelocksPower.get())); + powerMap.put("mobile", new Pair<>("mAh", healthStatsDelta.dlt.mobilePower.get())); + { + + monitors.getDelta(TrafficMonitorFeature.RadioStatSnapshot.class, new Consumer>() { + @Override + public void accept(final Delta delta) { + if (healthStatsDelta.dlt.extras.containsKey("power-mobile-statByte")) { + Object val = healthStatsDelta.dlt.extras.get("power-mobile-statByte"); + if (val instanceof Double) { + double power = (double) val; + powerMap.put(" - mobile-PowerBytes", new Pair<>("mAh", power)); + powerMap.put(" - mobile-RxBytes", new Pair<>("byte", (double) delta.dlt.mobileRxBytes.get())); + powerMap.put(" - mobile-TxBytes", new Pair<>("byte", (double) delta.dlt.mobileTxBytes.get())); + } + } + } + }); + } + powerMap.put("wifi", new Pair<>("mAh", healthStatsDelta.dlt.wifiPower.get())); + { + + monitors.getDelta(TrafficMonitorFeature.RadioStatSnapshot.class, new Consumer>() { + @Override + public void accept(final Delta delta) { + if (healthStatsDelta.dlt.extras.containsKey("power-wifi-statByte")) { + Object val = healthStatsDelta.dlt.extras.get("power-wifi-statByte"); + if (val instanceof Double) { + double power = (double) val; + powerMap.put(" - wifi-PowerBytes", new Pair<>("mAh", power)); + powerMap.put(" - wifi-RxBytes", new Pair<>("byte", (double) delta.dlt.wifiRxBytes.get())); + powerMap.put(" - wifi-TxBytes", new Pair<>("byte", (double) delta.dlt.wifiTxBytes.get())); + } + } + if (healthStatsDelta.dlt.extras.containsKey("power-wifi-statPacket")) { + Object val = healthStatsDelta.dlt.extras.get("power-wifi-statPacket"); + if (val instanceof Double) { + double power = (double) val; + powerMap.put(" - wifi-PowerPackets", new Pair<>("mAh", power)); + powerMap.put(" - wifi-RxPackets", new Pair<>("packet", (double) delta.dlt.wifiRxPackets.get())); + powerMap.put(" - wifi-TxPackets", new Pair<>("packet", (double) delta.dlt.wifiTxPackets.get())); + } + } + } + }); + } + powerMap.put("blueTooth", new Pair<>("mAh", healthStatsDelta.dlt.blueToothPower.get())); + powerMap.put("gps", new Pair<>("mAh", healthStatsDelta.dlt.gpsPower.get())); + powerMap.put("sensors", new Pair<>("mAh", healthStatsDelta.dlt.sensorsPower.get())); + powerMap.put("camera", new Pair<>("mAh", healthStatsDelta.dlt.cameraPower.get())); + powerMap.put("flashLight", new Pair<>("mAh", healthStatsDelta.dlt.flashLightPower.get())); + powerMap.put("audio", new Pair<>("mAh", healthStatsDelta.dlt.audioPower.get())); + powerMap.put("video", new Pair<>("mAh", healthStatsDelta.dlt.videoPower.get())); + powerMap.put("screen", new Pair<>("mAh", healthStatsDelta.dlt.screenPower.get())); + // powerMap.put("systemService", healthStatsDelta.dlt.systemServicePower.get()); + powerMap.put("idle", new Pair<>("mAh", healthStatsDelta.dlt.idlePower.get())); + } + // for (Iterator> iterator = powerMap.entrySet().iterator(); iterator.hasNext(); ) { + // Map.Entry item = iterator.next(); + // if (item.getValue() == 0d) { + // iterator.remove(); + // } + // } + int idx = 0; + for (Map.Entry> entry : powerMap.entrySet()) { + String module = entry.getKey(); + String unit = ""; + Double value = null; + Pair pair = entry.getValue(); + if (pair.first != null) { + if (pair.first.equals("mAh")) { + unit = ""; + if (pair.second != null) { + double power = (double) pair.second; + value = power * BatteryCanaryUtil.ONE_HOR / appStats.duringMillis; + } + } else { + unit = pair.first; + if (pair.second != null) { + value = pair.second; + } + } + } + View threadItemView = powerEntryGroup.getChildAt(idx); + threadItemView.setVisibility(View.VISIBLE); + TextView tvName = threadItemView.findViewById(R.id.tv_name); + TextView tvUnit = threadItemView.findViewById(R.id.tv_unit); + TextView tvPower = threadItemView.findViewById(R.id.tv_power); + tvName.setText(module); + tvUnit.setText(unit); + if (value == null) { + tvPower.setText("NULL"); + } else { + tvPower.setText(String.valueOf(HealthStatsHelper.round(value, 5))); + } + idx++; + if (idx >= MAX_POWER_NUM) { + break; + } + } + } } }); } - private static String getProcSuffix(String input) { + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static String getProcSuffix(String input) { String proc = "main"; if (input.contains(":")) { proc = input.substring(input.lastIndexOf(":") + 1); diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/BatteryStatsFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/BatteryStatsFeature.java index 07f81e3c4..40cf45e7e 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/BatteryStatsFeature.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/BatteryStatsFeature.java @@ -49,7 +49,7 @@ public void onTurnOn() { mBatteryRecorder = mCore.getConfig().batteryRecorder; mBatteryStats = mCore.getConfig().batteryStats; if (mBatteryRecorder != null) { - mStatsThread = MatrixHandlerThread.getNewHandlerThread("matrix-stats", Thread.NORM_PRIORITY); + mStatsThread = MatrixHandlerThread.getNewHandlerThread("matrix_stats", Thread.NORM_PRIORITY); mStatsHandler = new Handler(mStatsThread.getLooper()); mStatsHandler.post(new Runnable() { @Override diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsFeature.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsFeature.java new file mode 100644 index 000000000..0d81da45e --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsFeature.java @@ -0,0 +1,826 @@ +package com.tencent.matrix.batterycanary.stats; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.health.HealthStats; +import android.os.health.PidHealthStats; +import android.os.health.ProcessHealthStats; +import android.os.health.TimerStat; +import android.os.health.UidHealthStats; + +import com.tencent.matrix.batterycanary.monitor.feature.AbsMonitorFeature; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature; +import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.DigitEntry; +import com.tencent.matrix.batterycanary.utils.BatteryCanaryUtil; +import com.tencent.matrix.batterycanary.utils.PowerProfile; +import com.tencent.matrix.util.MatrixLog; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +/** + * @author Kaede + * @since 18/7/2022 + */ +public class HealthStatsFeature extends AbsMonitorFeature { + private static final String TAG = "Matrix.battery.HealthStats"; + + @Override + protected String getTag() { + return TAG; + } + + @Override + public int weight() { + return 0; + } + + public HealthStats currHealthStats() { + return HealthStatsHelper.getCurrStats(mCore.getContext()); + } + + @SuppressLint("VisibleForTests") + public HealthStatsSnapshot currHealthStatsSnapshot() { + HealthStatsSnapshot snapshot = new HealthStatsSnapshot(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return snapshot; + } + final HealthStats healthStats = currHealthStats(); + if (healthStats != null) { + snapshot.healthStats = healthStats; + + // Power + CpuStatFeature cpuStatFeat = mCore.getMonitorFeature(CpuStatFeature.class); + if (cpuStatFeat != null) { + PowerProfile powerProfile = cpuStatFeat.getPowerProfile(); + if (powerProfile != null && powerProfile.isSupported()) { + snapshot.cpuPower = DigitEntry.of(HealthStatsHelper.calcCpuPower(powerProfile, healthStats)); + snapshot.wakelocksPower = DigitEntry.of(HealthStatsHelper.calcWakelocksPower(powerProfile, healthStats)); + snapshot.mobilePower = DigitEntry.of(HealthStatsHelper.calcMobilePower(powerProfile, healthStats)); + snapshot.wifiPower = DigitEntry.of(HealthStatsHelper.calcWifiPower(powerProfile, healthStats)); + snapshot.blueToothPower = DigitEntry.of(HealthStatsHelper.calcBlueToothPower(powerProfile, healthStats)); + snapshot.gpsPower = DigitEntry.of(HealthStatsHelper.calcGpsPower(powerProfile, healthStats)); + snapshot.sensorsPower = DigitEntry.of(HealthStatsHelper.calcSensorsPower(mCore.getContext(), healthStats)); + snapshot.cameraPower = DigitEntry.of(HealthStatsHelper.calcCameraPower(powerProfile, healthStats)); + snapshot.flashLightPower = DigitEntry.of(HealthStatsHelper.calcFlashLightPower(powerProfile, healthStats)); + snapshot.audioPower = DigitEntry.of(HealthStatsHelper.calcAudioPower(powerProfile, healthStats)); + snapshot.videoPower = DigitEntry.of(HealthStatsHelper.calcVideoPower(powerProfile, healthStats)); + snapshot.screenPower = DigitEntry.of(HealthStatsHelper.calcScreenPower(powerProfile, healthStats)); + snapshot.systemServicePower = DigitEntry.of(HealthStatsHelper.calcSystemServicePower(powerProfile, healthStats)); + snapshot.idlePower = DigitEntry.of(HealthStatsHelper.calcIdlePower(powerProfile, healthStats)); + } + } + + // Meta data + snapshot.cpuPowerMams = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_CPU_POWER_MAMS)); + snapshot.cpuUsrTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_USER_CPU_TIME_MS)); + snapshot.cpuSysTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_SYSTEM_CPU_TIME_MS)); + snapshot.realTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_REALTIME_BATTERY_MS)); + snapshot.upTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_UPTIME_BATTERY_MS)); + snapshot.offRealTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_REALTIME_SCREEN_OFF_BATTERY_MS)); + snapshot.offUpTimeMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_UPTIME_SCREEN_OFF_BATTERY_MS)); + + snapshot.mobilePowerMams = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_POWER_MAMS)); + snapshot.mobileRadioActiveMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_MOBILE_RADIO_ACTIVE) / 1000L); + snapshot.mobileIdleMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_IDLE_MS)); + snapshot.mobileRxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_RX_MS)); + snapshot.mobileTxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_TX_MS)); + + snapshot.mobileRxBytes = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_RX_BYTES)); + snapshot.mobileTxBytes = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_TX_BYTES)); + snapshot.mobileRxPackets = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_RX_PACKETS)); + snapshot.mobileTxPackets = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_TX_PACKETS)); + + snapshot.wifiPowerMams = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_POWER_MAMS)); + snapshot.wifiIdleMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_IDLE_MS)); + snapshot.wifiRxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RX_MS)); + snapshot.wifiTxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_TX_MS)); + snapshot.wifiRunningMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RUNNING_MS)); + snapshot.wifiLockMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_FULL_LOCK_MS)); + snapshot.wifiScanMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_WIFI_SCAN)); + snapshot.wifiMulticastMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_MULTICAST_MS)); + snapshot.wifiRxBytes = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RX_BYTES)); + snapshot.wifiTxBytes = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_TX_BYTES)); + snapshot.wifiRxPackets = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RX_PACKETS)); + snapshot.wifiTxPackets = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_TX_PACKETS)); + + snapshot.blueToothPowerMams = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_POWER_MAMS)); + snapshot.blueToothIdleMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_IDLE_MS)); + snapshot.blueToothRxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_RX_MS)); + snapshot.blueToothTxMs = DigitEntry.of(HealthStatsHelper.getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_TX_MS)); + + { + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL)) { + long timeMs = 0; + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL); + for (Map.Entry item : timers.entrySet()) { + String tag = item.getKey(); + long lockTime = item.getValue().getTime(); + if (snapshot.tagWakelocksPartialMs.isEmpty()) { + snapshot.tagWakelocksPartialMs = new HashMap<>(); + } + snapshot.tagWakelocksPartialMs.put(tag, DigitEntry.of(lockTime)); + timeMs += lockTime; + } + snapshot.wakelocksPartialMs = DigitEntry.of(timeMs); + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_FULL)) { + long timeMs = 0; + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_FULL); + for (Map.Entry item : timers.entrySet()) { + String tag = item.getKey(); + long lockTime = item.getValue().getTime(); + if (snapshot.tagWakelocksFullMs.isEmpty()) { + snapshot.tagWakelocksFullMs = new HashMap<>(); + } + snapshot.tagWakelocksFullMs.put(tag, DigitEntry.of(lockTime)); + timeMs += lockTime; + } + snapshot.wakelocksFullMs = DigitEntry.of(timeMs); + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_WINDOW)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_WINDOW); + long timeMs = 0; + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + snapshot.wakelocksWindowMs = DigitEntry.of(timeMs); + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_DRAW)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_DRAW); + long timeMs = 0; + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + snapshot.wakelocksDrawMs = DigitEntry.of(timeMs); + } + if (healthStats.hasStats(UidHealthStats.STATS_PIDS)) { + long sum = 0; + Map pidStats = healthStats.getStats(UidHealthStats.STATS_PIDS); + for (HealthStats item : pidStats.values()) { + if (item.hasMeasurement(PidHealthStats.MEASUREMENT_WAKE_SUM_MS)) { + sum += item.getMeasurement(PidHealthStats.MEASUREMENT_WAKE_SUM_MS); + } + } + snapshot.wakelocksPidSum = DigitEntry.of(sum); + } + } + snapshot.gpsMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_GPS_SENSOR)); + if (healthStats.hasTimers(UidHealthStats.TIMERS_SENSORS)) { + SensorManager sm = (SensorManager) mCore.getContext().getSystemService(Context.SENSOR_SERVICE); + List sensorList = sm.getSensorList(Sensor.TYPE_ALL); + Map sensorMap = new HashMap<>(); + for (Sensor item : sensorList) { + try { + //noinspection JavaReflectionMemberAccess + @SuppressLint("DiscouragedPrivateApi") + Method method = item.getClass().getDeclaredMethod("getHandle"); + //noinspection ConstantConditions + int handle = (int) method.invoke(item); + sensorMap.put(String.valueOf(handle), item); + } catch (Throwable e) { + MatrixLog.w(TAG, "getSensorHandle err: " + e.getMessage()); + } + } + + long sensorsPowerMams = 0; + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SENSORS); + for (Map.Entry item : timers.entrySet()) { + String handle = item.getKey(); + long timeMs = item.getValue().getTime(); + if (handle.equals("-10000")) { + continue; // skip GPS Sensors + } + Sensor sensor = sensorMap.get(handle); + if (sensor != null) { + sensorsPowerMams += sensor.getPower() * timeMs; + } + } + snapshot.sensorsPowerMams = DigitEntry.of(sensorsPowerMams); + } + snapshot.cameraMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_CAMERA)); + snapshot.flashLightMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_FLASHLIGHT)); + snapshot.audioMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_AUDIO)); + snapshot.videoMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_VIDEO)); + if (healthStats.hasTimers(UidHealthStats.TIMERS_JOBS)) { + long timeMs = 0; + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_JOBS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + snapshot.jobsMs = DigitEntry.of(timeMs); + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_SYNCS)) { + long timeMs = 0; + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SYNCS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + snapshot.syncMs = DigitEntry.of(timeMs); + } + + snapshot.fgActMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_FOREGROUND_ACTIVITY)); + snapshot.procTopAppMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_TOP_MS)); + snapshot.procTopSleepMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_TOP_SLEEPING_MS)); + snapshot.procFgMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_FOREGROUND_MS)); + snapshot.procFgSrvMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_FOREGROUND_SERVICE_MS)); + snapshot.procBgMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_BACKGROUND_MS)); + snapshot.procCacheMs = DigitEntry.of(HealthStatsHelper.getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_CACHED_MS)); + + { + if (healthStats.hasStats(UidHealthStats.STATS_PROCESSES)) { + Map processes = healthStats.getStats(UidHealthStats.STATS_PROCESSES); + for (Map.Entry item : processes.entrySet()) { + String pkg = item.getKey(); + HealthStats procStats = item.getValue(); + if (procStats != null) { + if (snapshot.procStatsCpuUsrTimeMs.isEmpty()) { + snapshot.procStatsCpuUsrTimeMs = new HashMap<>(); + } + snapshot.procStatsCpuUsrTimeMs.put(pkg, DigitEntry.of(HealthStatsHelper.getMeasure(procStats, ProcessHealthStats.MEASUREMENT_USER_TIME_MS))); + + if (snapshot.procStatsCpuSysTimeMs.isEmpty()) { + snapshot.procStatsCpuSysTimeMs = new HashMap<>(); + } + snapshot.procStatsCpuSysTimeMs.put(pkg, DigitEntry.of(HealthStatsHelper.getMeasure(procStats, ProcessHealthStats.MEASUREMENT_SYSTEM_TIME_MS))); + + if (snapshot.procStatsCpuFgTimeMs.isEmpty()) { + snapshot.procStatsCpuFgTimeMs = new HashMap<>(); + } + snapshot.procStatsCpuFgTimeMs.put(pkg, DigitEntry.of(HealthStatsHelper.getMeasure(procStats, ProcessHealthStats.MEASUREMENT_FOREGROUND_MS))); + + if (snapshot.procStatsStartCount.isEmpty()) { + snapshot.procStatsStartCount = new HashMap<>(); + } + snapshot.procStatsStartCount.put(pkg, DigitEntry.of(HealthStatsHelper.getMeasure(procStats, ProcessHealthStats.MEASUREMENT_STARTS_COUNT))); + } + } + } + } + } + return snapshot; + } + + public static class HealthStatsSnapshot extends Snapshot { + @Nullable + public AccCollector accCollector; + + @VisibleForTesting + @Nullable + public HealthStats healthStats; + // For test & tunings values + public Map extras = Collections.emptyMap(); + + // Estimated Powers + public DigitEntry cpuPower = DigitEntry.of(0D); + public DigitEntry wakelocksPower = DigitEntry.of(0D); + public DigitEntry mobilePower = DigitEntry.of(0D); + public DigitEntry wifiPower = DigitEntry.of(0D); + public DigitEntry blueToothPower = DigitEntry.of(0D); + public DigitEntry gpsPower = DigitEntry.of(0D); + public DigitEntry sensorsPower = DigitEntry.of(0D); + public DigitEntry cameraPower = DigitEntry.of(0D); + public DigitEntry flashLightPower = DigitEntry.of(0D); + public DigitEntry audioPower = DigitEntry.of(0D); + public DigitEntry videoPower = DigitEntry.of(0D); + public DigitEntry screenPower = DigitEntry.of(0D); + public DigitEntry systemServicePower = DigitEntry.of(0D); // WIP + public DigitEntry idlePower = DigitEntry.of(0D); + + // Meta Data: + // CPU + public DigitEntry cpuPowerMams = DigitEntry.of(0L); + public DigitEntry cpuUsrTimeMs = DigitEntry.of(0L); + public DigitEntry cpuSysTimeMs = DigitEntry.of(0L); + public DigitEntry realTimeMs = DigitEntry.of(0L); + public DigitEntry upTimeMs = DigitEntry.of(0L); + public DigitEntry offRealTimeMs = DigitEntry.of(0L); + public DigitEntry offUpTimeMs = DigitEntry.of(0L); + + // Network + public DigitEntry mobilePowerMams = DigitEntry.of(0L); + public DigitEntry mobileRadioActiveMs = DigitEntry.of(0L); + public DigitEntry mobileIdleMs = DigitEntry.of(0L); + public DigitEntry mobileRxMs = DigitEntry.of(0L); + public DigitEntry mobileTxMs = DigitEntry.of(0L); + public DigitEntry mobileRxBytes = DigitEntry.of(0L); + public DigitEntry mobileTxBytes = DigitEntry.of(0L); + public DigitEntry mobileRxPackets = DigitEntry.of(0L); + public DigitEntry mobileTxPackets = DigitEntry.of(0L); + + public DigitEntry wifiPowerMams = DigitEntry.of(0L); + public DigitEntry wifiIdleMs = DigitEntry.of(0L); + public DigitEntry wifiRxMs = DigitEntry.of(0L); + public DigitEntry wifiTxMs = DigitEntry.of(0L); + public DigitEntry wifiRunningMs = DigitEntry.of(0L); + public DigitEntry wifiLockMs = DigitEntry.of(0L); + public DigitEntry wifiScanMs = DigitEntry.of(0L); + public DigitEntry wifiMulticastMs = DigitEntry.of(0L); + public DigitEntry wifiRxBytes = DigitEntry.of(0L); + public DigitEntry wifiTxBytes = DigitEntry.of(0L); + public DigitEntry wifiRxPackets = DigitEntry.of(0L); + public DigitEntry wifiTxPackets = DigitEntry.of(0L); + + public DigitEntry blueToothPowerMams = DigitEntry.of(0L); + public DigitEntry blueToothIdleMs = DigitEntry.of(0L); + public DigitEntry blueToothRxMs = DigitEntry.of(0L); + public DigitEntry blueToothTxMs = DigitEntry.of(0L); + + // SystemService & Media + public DigitEntry wakelocksPartialMs = DigitEntry.of(0L); + public DigitEntry wakelocksFullMs = DigitEntry.of(0L); + public DigitEntry wakelocksWindowMs = DigitEntry.of(0L); + public DigitEntry wakelocksDrawMs = DigitEntry.of(0L); + public DigitEntry wakelocksPidSum = DigitEntry.of(0L); + public DigitEntry gpsMs = DigitEntry.of(0L); + public DigitEntry sensorsPowerMams = DigitEntry.of(0L); + public DigitEntry cameraMs = DigitEntry.of(0L); + public DigitEntry flashLightMs = DigitEntry.of(0L); + public DigitEntry audioMs = DigitEntry.of(0L); + public DigitEntry videoMs = DigitEntry.of(0L); + public DigitEntry jobsMs = DigitEntry.of(0L); + public DigitEntry syncMs = DigitEntry.of(0L); + + // Foreground + public DigitEntry fgActMs = DigitEntry.of(0L); + public DigitEntry procTopAppMs = DigitEntry.of(0L); + public DigitEntry procTopSleepMs = DigitEntry.of(0L); + public DigitEntry procFgMs = DigitEntry.of(0L); + public DigitEntry procFgSrvMs = DigitEntry.of(0L); + public DigitEntry procBgMs = DigitEntry.of(0L); + public DigitEntry procCacheMs = DigitEntry.of(0L); + + // Map: Nested data in collections + // Process + public Map> procStatsCpuUsrTimeMs = Collections.emptyMap(); + public Map> procStatsCpuSysTimeMs = Collections.emptyMap(); + public Map> procStatsCpuFgTimeMs = Collections.emptyMap(); + public Map> procStatsStartCount = Collections.emptyMap(); + // Wakelocks + public Map> tagWakelocksPartialMs = Collections.emptyMap(); + public Map> tagWakelocksFullMs = Collections.emptyMap(); + + + public double getTotalPower() { + return cpuPower.get() + + wakelocksPower.get() + + mobilePower.get() + + wifiPower.get() + + blueToothPower.get() + + gpsPower.get() + + sensorsPower.get() + + flashLightPower.get() + + audioPower.get() + + videoPower.get() + + screenPower.get() + // + cameraPower.get() // WIP + // + systemServicePower.get() // WIP + + idlePower.get(); + } + + public AccCollector startAccCollecting() { + accCollector = new AccCollector(this); + return accCollector; + } + + @Nullable + public Delta accCollect(HealthStatsSnapshot curr) { + if (accCollector == null) { + throw new IllegalStateException("Call start collect first!"); + } + return accCollector.collect(curr); + } + + public Delta diffByAccCollector(HealthStatsSnapshot bgn) { + if (bgn.accCollector == null) { + throw new IllegalStateException("Call start collect first!"); + } + bgn.accCollect(this); + HealthStatsSnapshot delta = bgn.accCollector.accDelta; + return new Delta.SimpleDelta<>(bgn, this, delta); + } + + Delta diffInternal(HealthStatsSnapshot bgn) { + return new Delta(bgn, this) { + @Override + protected HealthStatsSnapshot computeDelta() { + HealthStatsSnapshot delta = new HealthStatsSnapshot(); + + // UID + delta.cpuPower = Differ.DigitDiffer.globalDiff(bgn.cpuPower, end.cpuPower); + delta.wakelocksPower = Differ.DigitDiffer.globalDiff(bgn.wakelocksPower, end.wakelocksPower); + delta.mobilePower = Differ.DigitDiffer.globalDiff(bgn.mobilePower, end.mobilePower); + delta.wifiPower = Differ.DigitDiffer.globalDiff(bgn.wifiPower, end.wifiPower); + delta.blueToothPower = Differ.DigitDiffer.globalDiff(bgn.blueToothPower, end.blueToothPower); + delta.gpsPower = Differ.DigitDiffer.globalDiff(bgn.gpsPower, end.gpsPower); + delta.sensorsPower = Differ.DigitDiffer.globalDiff(bgn.sensorsPower, end.sensorsPower); + delta.cameraPower = Differ.DigitDiffer.globalDiff(bgn.cameraPower, end.cameraPower); + delta.flashLightPower = Differ.DigitDiffer.globalDiff(bgn.flashLightPower, end.flashLightPower); + delta.audioPower = Differ.DigitDiffer.globalDiff(bgn.audioPower, end.audioPower); + delta.videoPower = Differ.DigitDiffer.globalDiff(bgn.videoPower, end.videoPower); + delta.screenPower = Differ.DigitDiffer.globalDiff(bgn.screenPower, end.screenPower); + delta.systemServicePower = Differ.DigitDiffer.globalDiff(bgn.systemServicePower, end.systemServicePower); + delta.idlePower = Differ.DigitDiffer.globalDiff(bgn.idlePower, end.idlePower); + + delta.cpuPowerMams = Differ.DigitDiffer.globalDiff(bgn.cpuPowerMams, end.cpuPowerMams); + delta.cpuUsrTimeMs = Differ.DigitDiffer.globalDiff(bgn.cpuUsrTimeMs, end.cpuUsrTimeMs); + delta.cpuSysTimeMs = Differ.DigitDiffer.globalDiff(bgn.cpuSysTimeMs, end.cpuSysTimeMs); + delta.realTimeMs = Differ.DigitDiffer.globalDiff(bgn.realTimeMs, end.realTimeMs); + delta.upTimeMs = Differ.DigitDiffer.globalDiff(bgn.upTimeMs, end.upTimeMs); + + delta.mobilePowerMams = Differ.DigitDiffer.globalDiff(bgn.mobilePowerMams, end.mobilePowerMams); + delta.mobileRadioActiveMs = Differ.DigitDiffer.globalDiff(bgn.mobileRadioActiveMs, end.mobileRadioActiveMs); + delta.mobileIdleMs = Differ.DigitDiffer.globalDiff(bgn.mobileIdleMs, end.mobileIdleMs); + delta.mobileRxMs = Differ.DigitDiffer.globalDiff(bgn.mobileRxMs, end.mobileRxMs); + delta.mobileTxMs = Differ.DigitDiffer.globalDiff(bgn.mobileTxMs, end.mobileTxMs); + delta.mobileRxBytes = Differ.DigitDiffer.globalDiff(bgn.mobileRxBytes, end.mobileRxBytes); + delta.mobileTxBytes = Differ.DigitDiffer.globalDiff(bgn.mobileTxBytes, end.mobileTxBytes); + delta.mobileRxPackets = Differ.DigitDiffer.globalDiff(bgn.mobileRxPackets, end.mobileRxPackets); + delta.mobileTxPackets = Differ.DigitDiffer.globalDiff(bgn.mobileTxPackets, end.mobileTxPackets); + + + delta.wifiPowerMams = Differ.DigitDiffer.globalDiff(bgn.wifiPowerMams, end.wifiPowerMams); + delta.wifiIdleMs = Differ.DigitDiffer.globalDiff(bgn.wifiIdleMs, end.wifiIdleMs); + delta.wifiRxMs = Differ.DigitDiffer.globalDiff(bgn.wifiRxMs, end.wifiRxMs); + delta.wifiTxMs = Differ.DigitDiffer.globalDiff(bgn.wifiTxMs, end.wifiTxMs); + delta.wifiRunningMs = Differ.DigitDiffer.globalDiff(bgn.wifiRunningMs, end.wifiRunningMs); + delta.wifiLockMs = Differ.DigitDiffer.globalDiff(bgn.wifiLockMs, end.wifiLockMs); + delta.wifiScanMs = Differ.DigitDiffer.globalDiff(bgn.wifiScanMs, end.wifiScanMs); + delta.wifiMulticastMs = Differ.DigitDiffer.globalDiff(bgn.wifiMulticastMs, end.wifiMulticastMs); + delta.wifiRxBytes = Differ.DigitDiffer.globalDiff(bgn.wifiRxBytes, end.wifiRxBytes); + delta.wifiTxBytes = Differ.DigitDiffer.globalDiff(bgn.wifiTxBytes, end.wifiTxBytes); + delta.wifiRxPackets = Differ.DigitDiffer.globalDiff(bgn.wifiRxPackets, end.wifiRxPackets); + delta.wifiTxPackets = Differ.DigitDiffer.globalDiff(bgn.wifiTxPackets, end.wifiTxPackets); + + delta.blueToothPowerMams = Differ.DigitDiffer.globalDiff(bgn.blueToothPowerMams, end.blueToothPowerMams); + delta.blueToothIdleMs = Differ.DigitDiffer.globalDiff(bgn.blueToothIdleMs, end.blueToothIdleMs); + delta.blueToothRxMs = Differ.DigitDiffer.globalDiff(bgn.blueToothRxMs, end.blueToothRxMs); + delta.blueToothTxMs = Differ.DigitDiffer.globalDiff(bgn.blueToothTxMs, end.blueToothTxMs); + + delta.wakelocksPartialMs = Differ.DigitDiffer.globalDiff(bgn.wakelocksPartialMs, end.wakelocksPartialMs); + delta.wakelocksFullMs = Differ.DigitDiffer.globalDiff(bgn.wakelocksFullMs, end.wakelocksFullMs); + delta.wakelocksWindowMs = Differ.DigitDiffer.globalDiff(bgn.wakelocksWindowMs, end.wakelocksWindowMs); + delta.wakelocksDrawMs = Differ.DigitDiffer.globalDiff(bgn.wakelocksDrawMs, end.wakelocksDrawMs); + delta.wakelocksPidSum = Differ.DigitDiffer.globalDiff(bgn.wakelocksPidSum, end.wakelocksPidSum); + delta.gpsMs = Differ.DigitDiffer.globalDiff(bgn.gpsMs, end.gpsMs); + delta.sensorsPowerMams = Differ.DigitDiffer.globalDiff(bgn.sensorsPowerMams, end.sensorsPowerMams); + delta.cameraMs = Differ.DigitDiffer.globalDiff(bgn.cameraMs, end.cameraMs); + delta.flashLightMs = Differ.DigitDiffer.globalDiff(bgn.flashLightMs, end.flashLightMs); + delta.audioMs = Differ.DigitDiffer.globalDiff(bgn.audioMs, end.audioMs); + delta.videoMs = Differ.DigitDiffer.globalDiff(bgn.videoMs, end.videoMs); + delta.jobsMs = Differ.DigitDiffer.globalDiff(bgn.jobsMs, end.jobsMs); + delta.syncMs = Differ.DigitDiffer.globalDiff(bgn.syncMs, end.syncMs); + + delta.fgActMs = Differ.DigitDiffer.globalDiff(bgn.fgActMs, end.fgActMs); + delta.procTopAppMs = Differ.DigitDiffer.globalDiff(bgn.procTopAppMs, end.procTopAppMs); + delta.procTopSleepMs = Differ.DigitDiffer.globalDiff(bgn.procTopSleepMs, end.procTopSleepMs); + delta.procFgMs = Differ.DigitDiffer.globalDiff(bgn.procFgMs, end.procFgMs); + delta.procFgSrvMs = Differ.DigitDiffer.globalDiff(bgn.procFgSrvMs, end.procFgSrvMs); + delta.procBgMs = Differ.DigitDiffer.globalDiff(bgn.procBgMs, end.procBgMs); + delta.procCacheMs = Differ.DigitDiffer.globalDiff(bgn.procCacheMs, end.procCacheMs); + + // PID + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.procStatsCpuUsrTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.procStatsCpuUsrTimeMs.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.procStatsCpuUsrTimeMs = map; + } + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.procStatsCpuSysTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.procStatsCpuSysTimeMs.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.procStatsCpuSysTimeMs = map; + } + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.procStatsCpuFgTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.procStatsCpuFgTimeMs.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.procStatsCpuFgTimeMs = map; + } + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.procStatsStartCount.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.procStatsStartCount.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.procStatsStartCount = map; + } + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.tagWakelocksPartialMs.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.tagWakelocksPartialMs.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.tagWakelocksPartialMs = map; + } + { + Map> map = new HashMap<>(); + for (Map.Entry> entry : end.tagWakelocksFullMs.entrySet()) { + String key = entry.getKey(); + DigitEntry endEntry = entry.getValue(); + long bgnValue = 0L; + DigitEntry bgnEntry = bgn.tagWakelocksFullMs.get(key); + if (bgnEntry != null) { + bgnValue = bgnEntry.get(); + } + map.put(key, Differ.DigitDiffer.globalDiff(DigitEntry.of(bgnValue), endEntry)); + } + delta.tagWakelocksFullMs = map; + } + return delta; + } + }; + } + + @Override + public Delta diff(HealthStatsSnapshot bgn) { + Delta delta = diffInternal(bgn); + // Sort + delta.dlt.procStatsCpuUsrTimeMs = decrease(procStatsCpuUsrTimeMs); + delta.dlt.procStatsCpuSysTimeMs = decrease(procStatsCpuSysTimeMs); + delta.dlt.procStatsCpuFgTimeMs = decrease(procStatsCpuFgTimeMs); + delta.dlt.procStatsStartCount = decrease(procStatsStartCount); + delta.dlt.tagWakelocksPartialMs = decrease(tagWakelocksPartialMs); + delta.dlt.tagWakelocksFullMs = decrease(tagWakelocksFullMs); + return delta; + } + + private Map> decrease(Map> input) { + return BatteryCanaryUtil.sortMapByValue(input, new Comparator>>() { + @Override + public int compare(Map.Entry> o1, Map.Entry> o2) { + long sumLeft = o1.getValue().get(), sumRight = o2.getValue().get(); + long minus = sumLeft - sumRight; + if (minus == 0) return 0; + if (minus > 0) return -1; + return 1; + } + }); + } + + public static double getPower(@NonNull Map extra, String key) { + Object val = extra.get(key); + if (val instanceof Double) { + return (double) val; + } + return 0; + } + + + public static class AccCollector { + public int count; + public long beginMs; + public long duringMs; + public HealthStatsSnapshot last; + public HealthStatsSnapshot accDelta; + + public AccCollector(HealthStatsSnapshot bgn) { + beginMs = bgn.time; + last = bgn; + accDelta = new HealthStatsSnapshot(); + accDelta.procStatsCpuUsrTimeMs = new HashMap<>(); + accDelta.procStatsCpuSysTimeMs = new HashMap<>(); + accDelta.procStatsCpuFgTimeMs = new HashMap<>(); + accDelta.procStatsStartCount = new HashMap<>(); + accDelta.tagWakelocksPartialMs = new HashMap<>(); + accDelta.tagWakelocksFullMs = new HashMap<>(); + } + + @Nullable + public Delta collect(HealthStatsSnapshot curr) { + Delta delta = null; + if (isHealthStatsNotReset(last, curr)) { + delta = curr.diffInternal(last); + + accDelta.cpuPower = DigitEntry.of(accDelta.cpuPower.get() + delta.dlt.cpuPower.get()); + accDelta.wakelocksPower = DigitEntry.of(accDelta.wakelocksPower.get() + delta.dlt.wakelocksPower.get()); + accDelta.mobilePower = DigitEntry.of(accDelta.mobilePower.get() + delta.dlt.mobilePower.get()); + accDelta.wifiPower = DigitEntry.of(accDelta.wifiPower.get() + delta.dlt.wifiPower.get()); + accDelta.blueToothPower = DigitEntry.of(accDelta.blueToothPower.get() + delta.dlt.blueToothPower.get()); + accDelta.gpsPower = DigitEntry.of(accDelta.gpsPower.get() + delta.dlt.gpsPower.get()); + accDelta.sensorsPower = DigitEntry.of(accDelta.sensorsPower.get() + delta.dlt.sensorsPower.get()); + accDelta.cameraPower = DigitEntry.of(accDelta.cameraPower.get() + delta.dlt.cameraPower.get()); + accDelta.flashLightPower = DigitEntry.of(accDelta.flashLightPower.get() + delta.dlt.flashLightPower.get()); + accDelta.audioPower = DigitEntry.of(accDelta.audioPower.get() + delta.dlt.audioPower.get()); + accDelta.videoPower = DigitEntry.of(accDelta.videoPower.get() + delta.dlt.videoPower.get()); + accDelta.screenPower = DigitEntry.of(accDelta.screenPower.get() + delta.dlt.screenPower.get()); + accDelta.systemServicePower = DigitEntry.of(accDelta.systemServicePower.get() + delta.dlt.systemServicePower.get()); + accDelta.idlePower = DigitEntry.of(accDelta.idlePower.get() + delta.dlt.idlePower.get()); + + accDelta.cpuPowerMams = DigitEntry.of(accDelta.cpuPowerMams.get() + delta.dlt.cpuPowerMams.get()); + accDelta.cpuUsrTimeMs = DigitEntry.of(accDelta.cpuUsrTimeMs.get() + delta.dlt.cpuUsrTimeMs.get()); + accDelta.cpuSysTimeMs = DigitEntry.of(accDelta.cpuSysTimeMs.get() + delta.dlt.cpuSysTimeMs.get()); + accDelta.realTimeMs = DigitEntry.of(accDelta.realTimeMs.get() + delta.dlt.realTimeMs.get()); + accDelta.upTimeMs = DigitEntry.of(accDelta.upTimeMs.get() + delta.dlt.upTimeMs.get()); + + accDelta.mobilePowerMams = DigitEntry.of(accDelta.mobilePowerMams.get() + delta.dlt.mobilePowerMams.get()); + accDelta.mobileRadioActiveMs = DigitEntry.of(accDelta.mobileRadioActiveMs.get() + delta.dlt.mobileRadioActiveMs.get()); + accDelta.mobileIdleMs = DigitEntry.of(accDelta.mobileIdleMs.get() + delta.dlt.mobileIdleMs.get()); + accDelta.mobileRxMs = DigitEntry.of(accDelta.mobileRxMs.get() + delta.dlt.mobileRxMs.get()); + accDelta.mobileTxMs = DigitEntry.of(accDelta.mobileTxMs.get() + delta.dlt.mobileTxMs.get()); + accDelta.mobileRxBytes = DigitEntry.of(accDelta.mobileRxBytes.get() + delta.dlt.mobileRxBytes.get()); + accDelta.mobileTxBytes = DigitEntry.of(accDelta.mobileTxBytes.get() + delta.dlt.mobileTxBytes.get()); + accDelta.mobileRxPackets = DigitEntry.of(accDelta.mobileRxPackets.get() + delta.dlt.mobileRxPackets.get()); + accDelta.mobileTxPackets = DigitEntry.of(accDelta.mobileTxPackets.get() + delta.dlt.mobileTxPackets.get()); + + + accDelta.wifiPowerMams = DigitEntry.of(accDelta.wifiPowerMams.get() + delta.dlt.wifiPowerMams.get()); + accDelta.wifiIdleMs = DigitEntry.of(accDelta.wifiIdleMs.get() + delta.dlt.wifiIdleMs.get()); + accDelta.wifiRxMs = DigitEntry.of(accDelta.wifiRxMs.get() + delta.dlt.wifiRxMs.get()); + accDelta.wifiTxMs = DigitEntry.of(accDelta.wifiTxMs.get() + delta.dlt.wifiTxMs.get()); + accDelta.wifiRunningMs = DigitEntry.of(accDelta.wifiRunningMs.get() + delta.dlt.wifiRunningMs.get()); + accDelta.wifiLockMs = DigitEntry.of(accDelta.wifiLockMs.get() + delta.dlt.wifiLockMs.get()); + accDelta.wifiScanMs = DigitEntry.of(accDelta.wifiScanMs.get() + delta.dlt.wifiScanMs.get()); + accDelta.wifiMulticastMs = DigitEntry.of(accDelta.wifiMulticastMs.get() + delta.dlt.wifiMulticastMs.get()); + accDelta.wifiRxBytes = DigitEntry.of(accDelta.wifiRxBytes.get() + delta.dlt.wifiRxBytes.get()); + accDelta.wifiTxBytes = DigitEntry.of(accDelta.wifiTxBytes.get() + delta.dlt.wifiTxBytes.get()); + accDelta.wifiRxPackets = DigitEntry.of(accDelta.wifiRxPackets.get() + delta.dlt.wifiRxPackets.get()); + accDelta.wifiTxPackets = DigitEntry.of(accDelta.wifiTxPackets.get() + delta.dlt.wifiTxPackets.get()); + + accDelta.blueToothPowerMams = DigitEntry.of(accDelta.blueToothPowerMams.get() + delta.dlt.blueToothPowerMams.get()); + accDelta.blueToothIdleMs = DigitEntry.of(accDelta.blueToothIdleMs.get() + delta.dlt.blueToothIdleMs.get()); + accDelta.blueToothRxMs = DigitEntry.of(accDelta.blueToothRxMs.get() + delta.dlt.blueToothRxMs.get()); + accDelta.blueToothTxMs = DigitEntry.of(accDelta.blueToothTxMs.get() + delta.dlt.blueToothTxMs.get()); + + accDelta.wakelocksPartialMs = DigitEntry.of(accDelta.wakelocksPartialMs.get() + delta.dlt.wakelocksPartialMs.get()); + accDelta.wakelocksFullMs = DigitEntry.of(accDelta.wakelocksFullMs.get() + delta.dlt.wakelocksFullMs.get()); + accDelta.wakelocksWindowMs = DigitEntry.of(accDelta.wakelocksWindowMs.get() + delta.dlt.wakelocksWindowMs.get()); + accDelta.wakelocksDrawMs = DigitEntry.of(accDelta.wakelocksDrawMs.get() + delta.dlt.wakelocksDrawMs.get()); + accDelta.wakelocksPidSum = DigitEntry.of(accDelta.wakelocksPidSum.get() + delta.dlt.wakelocksPidSum.get()); + accDelta.gpsMs = DigitEntry.of(accDelta.gpsMs.get() + delta.dlt.gpsMs.get()); + accDelta.sensorsPowerMams = DigitEntry.of(accDelta.sensorsPowerMams.get() + delta.dlt.sensorsPowerMams.get()); + accDelta.cameraMs = DigitEntry.of(accDelta.cameraMs.get() + delta.dlt.cameraMs.get()); + accDelta.flashLightMs = DigitEntry.of(accDelta.flashLightMs.get() + delta.dlt.flashLightMs.get()); + accDelta.audioMs = DigitEntry.of(accDelta.audioMs.get() + delta.dlt.audioMs.get()); + accDelta.videoMs = DigitEntry.of(accDelta.videoMs.get() + delta.dlt.videoMs.get()); + accDelta.jobsMs = DigitEntry.of(accDelta.jobsMs.get() + delta.dlt.jobsMs.get()); + accDelta.syncMs = DigitEntry.of(accDelta.syncMs.get() + delta.dlt.syncMs.get()); + + accDelta.fgActMs = DigitEntry.of(accDelta.fgActMs.get() + delta.dlt.fgActMs.get()); + accDelta.procTopAppMs = DigitEntry.of(accDelta.procTopAppMs.get() + delta.dlt.procTopAppMs.get()); + accDelta.procTopSleepMs = DigitEntry.of(accDelta.procTopSleepMs.get() + delta.dlt.procTopSleepMs.get()); + accDelta.procFgMs = DigitEntry.of(accDelta.procFgMs.get() + delta.dlt.procFgMs.get()); + accDelta.procFgSrvMs = DigitEntry.of(accDelta.procFgSrvMs.get() + delta.dlt.procFgSrvMs.get()); + accDelta.procBgMs = DigitEntry.of(accDelta.procBgMs.get() + delta.dlt.procBgMs.get()); + accDelta.procCacheMs = DigitEntry.of(accDelta.procCacheMs.get() + delta.dlt.procCacheMs.get()); + + for (Map.Entry> entry : delta.dlt.procStatsCpuUsrTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.procStatsCpuUsrTimeMs.get(key); + accDelta.procStatsCpuUsrTimeMs.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + for (Map.Entry> entry : delta.dlt.procStatsCpuSysTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.procStatsCpuSysTimeMs.get(key); + accDelta.procStatsCpuSysTimeMs.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + for (Map.Entry> entry : delta.dlt.procStatsCpuFgTimeMs.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.procStatsCpuFgTimeMs.get(key); + accDelta.procStatsCpuFgTimeMs.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + for (Map.Entry> entry : delta.dlt.procStatsStartCount.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.procStatsStartCount.get(key); + accDelta.procStatsStartCount.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + for (Map.Entry> entry : delta.dlt.tagWakelocksPartialMs.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.tagWakelocksPartialMs.get(key); + accDelta.tagWakelocksPartialMs.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + for (Map.Entry> entry : delta.dlt.tagWakelocksFullMs.entrySet()) { + String key = entry.getKey(); + DigitEntry val = entry.getValue(); + DigitEntry acc = accDelta.tagWakelocksFullMs.get(key); + accDelta.tagWakelocksFullMs.put(key, DigitEntry.of(val.get() + (acc == null ? 0 : acc.get()))); + } + + count++; + duringMs += delta.during; + + } + last = curr; + return delta; + } + + public static boolean isHealthStatsNotReset(HealthStatsSnapshot bgn, HealthStatsSnapshot end) { + try { + assertNotNegative("cpuPowerMams", bgn.cpuPowerMams.get(), end.cpuPowerMams.get()); + assertNotNegative("cpuUsrTimeMs", bgn.cpuUsrTimeMs.get(), end.cpuUsrTimeMs.get()); + assertNotNegative("cpuSysTimeMs", bgn.cpuSysTimeMs.get(), end.cpuSysTimeMs.get()); + assertNotNegative("realTimeMs", bgn.realTimeMs.get(), end.realTimeMs.get()); + assertNotNegative("upTimeMs", bgn.upTimeMs.get(), end.upTimeMs.get()); + assertNotNegative("offRealTimeMs", bgn.offRealTimeMs.get(), end.offRealTimeMs.get()); + assertNotNegative("offUpTimeMs", bgn.offUpTimeMs.get(), end.offUpTimeMs.get()); + + assertNotNegative("mobilePowerMams", bgn.mobilePowerMams.get(), end.mobilePowerMams.get()); + assertNotNegative("mobileRadioActiveMs", bgn.mobileRadioActiveMs.get(), end.mobileRadioActiveMs.get()); + assertNotNegative("mobileIdleMs", bgn.mobileIdleMs.get(), end.mobileIdleMs.get()); + assertNotNegative("mobileRxMs", bgn.mobileRxMs.get(), end.mobileRxMs.get()); + assertNotNegative("mobileTxMs", bgn.mobileTxMs.get(), end.mobileTxMs.get()); + assertNotNegative("mobileRxBytes", bgn.mobileRxBytes.get(), end.mobileRxBytes.get()); + assertNotNegative("mobileTxBytes", bgn.mobileTxBytes.get(), end.mobileTxBytes.get()); + assertNotNegative("mobileRxPackets", bgn.mobileRxPackets.get(), end.mobileRxPackets.get()); + assertNotNegative("mobileTxPackets", bgn.mobileTxPackets.get(), end.mobileTxPackets.get()); + + assertNotNegative("wifiPowerMams", bgn.wifiPowerMams.get(), end.wifiPowerMams.get()); + assertNotNegative("wifiIdleMs", bgn.wifiIdleMs.get(), end.wifiIdleMs.get()); + assertNotNegative("wifiRxMs", bgn.wifiRxMs.get(), end.wifiRxMs.get()); + assertNotNegative("wifiTxMs", bgn.wifiTxMs.get(), end.wifiTxMs.get()); + assertNotNegative("wifiRunningMs", bgn.wifiRunningMs.get(), end.wifiRunningMs.get()); + assertNotNegative("wifiLockMs", bgn.wifiLockMs.get(), end.wifiLockMs.get()); + assertNotNegative("wifiScanMs", bgn.wifiScanMs.get(), end.wifiScanMs.get()); + assertNotNegative("wifiMulticastMs", bgn.wifiMulticastMs.get(), end.wifiMulticastMs.get()); + assertNotNegative("wifiRxBytes", bgn.wifiRxBytes.get(), end.wifiRxBytes.get()); + assertNotNegative("wifiTxBytes", bgn.wifiTxBytes.get(), end.wifiTxBytes.get()); + assertNotNegative("wifiRxPackets", bgn.wifiRxPackets.get(), end.wifiRxPackets.get()); + assertNotNegative("wifiTxPackets", bgn.wifiTxPackets.get(), end.wifiTxPackets.get()); + + assertNotNegative("blueToothPowerMams", bgn.blueToothPowerMams.get(), end.blueToothPowerMams.get()); + assertNotNegative("blueToothIdleMs", bgn.blueToothIdleMs.get(), end.blueToothIdleMs.get()); + assertNotNegative("blueToothRxMs", bgn.blueToothRxMs.get(), end.blueToothRxMs.get()); + assertNotNegative("blueToothTxMs", bgn.blueToothTxMs.get(), end.blueToothTxMs.get()); + + assertNotNegative("wakelocksPartialMs", bgn.wakelocksPartialMs.get(), end.wakelocksPartialMs.get()); + assertNotNegative("wakelocksFullMs", bgn.wakelocksFullMs.get(), end.wakelocksFullMs.get()); + assertNotNegative("wakelocksWindowMs", bgn.wakelocksWindowMs.get(), end.wakelocksWindowMs.get()); + assertNotNegative("wakelocksDrawMs", bgn.wakelocksDrawMs.get(), end.wakelocksDrawMs.get()); + assertNotNegative("wakelocksPidSum", bgn.wakelocksPidSum.get(), end.wakelocksPidSum.get()); + assertNotNegative("gpsMs", bgn.gpsMs.get(), end.gpsMs.get()); + assertNotNegative("sensorsPowerMams", bgn.sensorsPowerMams.get(), end.sensorsPowerMams.get()); + assertNotNegative("cameraMs", bgn.cameraMs.get(), end.cameraMs.get()); + assertNotNegative("flashLightMs", bgn.flashLightMs.get(), end.flashLightMs.get()); + assertNotNegative("audioMs", bgn.audioMs.get(), end.audioMs.get()); + assertNotNegative("videoMs", bgn.videoMs.get(), end.videoMs.get()); + assertNotNegative("jobsMs", bgn.jobsMs.get(), end.jobsMs.get()); + assertNotNegative("syncMs", bgn.syncMs.get(), end.syncMs.get()); + + return true; + } catch (Exception e) { + MatrixLog.w(TAG, "skip, " + e.getMessage()); + return false; + } + } + + static void assertNotNegative(String key, long bgn, long end) { + if (bgn > end) { + throw new IllegalStateException("negative stats: " + key); + } + } + } + } +} diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsHelper.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsHelper.java new file mode 100644 index 000000000..b4fb3b139 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/stats/HealthStatsHelper.java @@ -0,0 +1,876 @@ +package com.tencent.matrix.batterycanary.stats; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.health.HealthStats; +import android.os.health.SystemHealthManager; +import android.os.health.TimerStat; +import android.os.health.UidHealthStats; + +import com.tencent.matrix.batterycanary.BatteryCanary; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature; +import com.tencent.matrix.batterycanary.monitor.feature.CpuStatFeature.CpuStateSnapshot; +import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.DigitEntry; +import com.tencent.matrix.batterycanary.monitor.feature.MonitorFeature.Snapshot.Entry.ListEntry; +import com.tencent.matrix.batterycanary.monitor.feature.TrafficMonitorFeature.RadioStatSnapshot; +import com.tencent.matrix.batterycanary.utils.PowerProfile; +import com.tencent.matrix.util.MatrixLog; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.annotation.ChecksSdkIntAtLeast; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.VisibleForTesting; + +/** + * totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + + * sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + + * flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah + * + systemServiceCpuPowerMah; + * if (customMeasuredPowerMah != null) { + * for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) { + * totalPowerMah += customMeasuredPowerMah[idx]; + * } + * } + * // powerAttributedToOtherSippersMah is negative or zero + * totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah; + * totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah; + * + * @see com.android.internal.os.BatterySipper#sumPower + * @see com.android.internal.os.BatteryStatsHelper + * @see com.android.internal.os.BatteryStatsImpl.Uid + * + * @author Kaede + * @since 6/7/2022 + */ +@SuppressWarnings("JavadocReference") +@SuppressLint("RestrictedApi") +public final class HealthStatsHelper { + public static final String TAG = "HealthStatsHelper"; + + public static class UsageBasedPowerEstimator { + private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60; + private final double mAveragePowerMahPerMs; + public UsageBasedPowerEstimator(double averagePowerMilliAmp) { + mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR; + } + public boolean isSupported() { + return mAveragePowerMahPerMs != 0; + } + public double calculatePower(long durationMs) { + return mAveragePowerMahPerMs * durationMs; + } + } + + public static double round(double input, int decimalPlace) { + double decimal = Math.pow(10.0, decimalPlace); + return Math.round(input * decimal) / decimal; + } + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N) + public static boolean isSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + + @Nullable + public static HealthStats getCurrStats(Context context) { + if (isSupported()) { + try { + SystemHealthManager shm = (SystemHealthManager) context.getSystemService(Context.SYSTEM_HEALTH_SERVICE); + return shm.takeMyUidSnapshot(); + } catch (Exception e) { + MatrixLog.w(TAG, "takeMyUidSnapshot err: " + e); + } + } + return null; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + static long getMeasure(HealthStats healthStats, int key) { + if (healthStats.hasMeasurement(key)) { + return healthStats.getMeasurement(key); + } + return 0L; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + static long getTimerTime(HealthStats healthStats, int key) { + if (healthStats.hasTimer(key)) { + return healthStats.getTimerTime(key); + } + return 0L; + } + + /** + * @see com.android.internal.os.CpuPowerCalculator + * @see com.android.internal.os.PowerProfile + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcCpuPower(PowerProfile powerProfile, HealthStats healthStats) { + // double mams = getMeasure(healthStats, UidHealthStats.MEASUREMENT_CPU_POWER_MAMS) / (UsageBasedPowerEstimator.MILLIS_IN_HOUR * 1000L); + // if (mams > 0) { + // MatrixLog.i(TAG, "estimate CPU by mams"); + // return mams; + // } + double power = 0; + /* + * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode. + * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should + * be zero on devices that can go into full CPU power collapse even when a wake + * lock is held. Otherwise, this is the power consumption in addition to + * POWER_CPU_SUSPEND due to a wake lock being held but with no CPU activity. + * POWER_CPU_ACTIVE: Power consumption when CPU is running, excluding power consumed by clusters + * and cores. + * + * CPU Power Equation (assume two clusters): + * Total power = POWER_CPU_SUSPEND (always added) + * + POWER_CPU_IDLE (skip this and below if in power collapse mode) + * + POWER_CPU_ACTIVE (skip this and below if CPU is not running, but a wakelock + * is held) + * + cluster_power.cluster0 + cluster_power.cluster1 (skip cluster not running) + * + core_power.cluster0 * num running cores in cluster 0 + * + core_power.cluster1 * num running cores in cluster 1 + */ + long cpuTimeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_USER_CPU_TIME_MS) + getMeasure(healthStats, UidHealthStats.MEASUREMENT_SYSTEM_CPU_TIME_MS); + power += estimateCpuActivePower(powerProfile, cpuTimeMs); + CpuStatFeature feat = BatteryCanary.getMonitorFeature(CpuStatFeature.class); + if (feat != null && feat.isSupported()) { + CpuStateSnapshot snapshot = feat.currentCpuStateSnapshot(); + if (snapshot != null) { + power += estimateCpuClustersPower(powerProfile, snapshot, cpuTimeMs, false); + power += estimateCpuCoresPower(powerProfile, snapshot, cpuTimeMs, false); + } + } + if (power > 0) { + return power; + } + return 0; + } + + @VisibleForTesting + public static double estimateCpuActivePower(PowerProfile powerProfile, long cpuTimeMs) { + //noinspection UnnecessaryLocalVariable + long timeMs = cpuTimeMs; + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_CPU_ACTIVE); + return new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + + @VisibleForTesting + public static double estimateCpuClustersPower(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs, boolean scaled) { + boolean isUidStatsAvailable = false; + for (ListEntry> listEntry : snapshot.procCpuCoreStates) { + for (DigitEntry item : listEntry.getList()) { + if (item.get() > 0) { + isUidStatsAvailable = true; + break; + } + } + } + if (isUidStatsAvailable) { + return estimateCpuClustersPowerByUidStats(powerProfile, snapshot, cpuTimeMs, scaled); + } else { + MatrixLog.i(TAG, "estimate CPU by device stats"); + return estimateCpuClustersPowerByDevStats(powerProfile, snapshot, cpuTimeMs); + } + } + + @VisibleForTesting + public static double estimateCpuCoresPower(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs, boolean scaled) { + boolean isUidStatsAvailable = false; + for (ListEntry> listEntry : snapshot.procCpuCoreStates) { + for (DigitEntry item : listEntry.getList()) { + if (item.get() > 0) { + isUidStatsAvailable = true; + break; + } + } + } + if (isUidStatsAvailable) { + return estimateCpuCoresPowerByUidStats(powerProfile, snapshot, cpuTimeMs, scaled); + } else { + MatrixLog.i(TAG, "estimate CPU by device stats"); + return estimateCpuCoresPowerByDevStats(powerProfile, snapshot, cpuTimeMs); + } + } + + @VisibleForTesting + public static double estimateCpuClustersPowerByUidStats(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs, boolean scaled) { + if (cpuTimeMs > 0) { + /* + * procCpuCoreStates + * [ + * [step1Jiffies, step2Jiffies ...], // Cluster 1 + * [step1Jiffies, step2Jiffies ...], // Cluster 2 + * ... + * ] + */ + long jiffySum = 0; + for (int i = 0; i < snapshot.procCpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.procCpuCoreStates.get(i).getList(); + int scale = scaled ? powerProfile.getNumCoresInCpuCluster(i) : 1; + for (DigitEntry item : stepJiffies) { + jiffySum += item.get() * scale; + } + } + double powerMah = 0; + for (int i = 0; i < snapshot.procCpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.procCpuCoreStates.get(i).getList(); + int scale = scaled ? powerProfile.getNumCoresInCpuCluster(i) : 1; + long jiffySumInCluster = 0; + for (int j = 0; j < stepJiffies.size(); j++) { + long jiffy = stepJiffies.get(j).get(); + jiffySumInCluster += jiffy * scale; + } + long figuredCpuTimeMs = (long) ((jiffySumInCluster * 1.0f / jiffySum) * cpuTimeMs); + double powerMa = powerProfile.getAveragePowerForCpuCluster(i); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(figuredCpuTimeMs); + } + return powerMah; + } + return 0; + } + + @VisibleForTesting + public static double estimateCpuCoresPowerByUidStats(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs, boolean scaled) { + if (cpuTimeMs > 0) { + /* + * procCpuCoreStates + * [ + * [step1Jiffies, step2Jiffies ...], // Cluster 1 + * [step1Jiffies, step2Jiffies ...], // Cluster 2 + * ... + * ] + */ + long jiffySum = 0; + for (int i = 0; i < snapshot.procCpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.procCpuCoreStates.get(i).getList(); + int scale = scaled ? powerProfile.getNumCoresInCpuCluster(i) : 1; + for (DigitEntry item : stepJiffies) { + jiffySum += item.get() * scale; + } + } + double powerMah = 0; + for (int i = 0; i < snapshot.procCpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.procCpuCoreStates.get(i).getList(); + int scale = scaled ? powerProfile.getNumCoresInCpuCluster(i) : 1; + for (int j = 0; j < stepJiffies.size(); j++) { + long jiffy = stepJiffies.get(j).get(); + long figuredCpuTimeMs = (long) ((jiffy * scale * 1.0f / jiffySum) * cpuTimeMs); + double powerMa = powerProfile.getAveragePowerForCpuCore(i, j); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(figuredCpuTimeMs); + } + } + return powerMah; + } + return 0; + } + + @VisibleForTesting + public static double estimateCpuClustersPowerByDevStats(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs) { + if (cpuTimeMs > 0) { + /* + * cpuCoreStates + * [ + * [step1Jiffies, step2Jiffies ...], // CpuCore 1 + * [step1Jiffies, step2Jiffies ...], // CpuCore 2 + * ... + * ] + */ + long jiffySum = 0; + for (int i = 0; i < snapshot.cpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.cpuCoreStates.get(i).getList(); + for (DigitEntry item : stepJiffies) { + jiffySum += item.get(); + } + } + double powerMah = 0; + for (int i = 0; i < snapshot.cpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.cpuCoreStates.get(i).getList(); + long jiffySumInCluster = 0; + for (int j = 0; j < stepJiffies.size(); j++) { + long jiffy = stepJiffies.get(j).get(); + jiffySumInCluster += jiffy; + } + long figuredCpuTimeMs = (long) ((jiffySumInCluster * 1.0f / jiffySum) * cpuTimeMs); + int clusterNum = powerProfile.getClusterByCpuNum(i); + if (clusterNum >= 0 && clusterNum < powerProfile.getNumCpuClusters()) { + double powerMa = powerProfile.getAveragePowerForCpuCluster(clusterNum); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(figuredCpuTimeMs); + } + } + return powerMah; + } + return 0; + } + + @VisibleForTesting + public static double estimateCpuCoresPowerByDevStats(PowerProfile powerProfile, CpuStateSnapshot snapshot, long cpuTimeMs) { + if (cpuTimeMs > 0) { + /* + * cpuCoreStates + * [ + * [step1Jiffies, step2Jiffies ...], // CpuCore 1 + * [step1Jiffies, step2Jiffies ...], // CpuCore 2 + * ... + * ] + */ + long jiffySum = 0; + for (int i = 0; i < snapshot.cpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.cpuCoreStates.get(i).getList(); + for (DigitEntry item : stepJiffies) { + jiffySum += item.get(); + } + } + double powerMah = 0; + for (int i = 0; i < snapshot.cpuCoreStates.size(); i++) { + List> stepJiffies = snapshot.cpuCoreStates.get(i).getList(); + for (int j = 0; j < stepJiffies.size(); j++) { + long jiffy = stepJiffies.get(j).get(); + long figuredCpuTimeMs = (long) ((jiffy * 1.0f / jiffySum) * cpuTimeMs); + int clusterNum = powerProfile.getClusterByCpuNum(i); + if (clusterNum >= 0 && clusterNum < powerProfile.getNumCpuClusters()) { + double powerMa = powerProfile.getAveragePowerForCpuCore(clusterNum, j); + powerMah += new UsageBasedPowerEstimator(powerMa).calculatePower(figuredCpuTimeMs); + } + } + } + return powerMah; + } + return 0; + } + + /** + * WIP + * Memory TimeStats support needed, see "com.android.internal.os.KernelMemoryBandwidthStats" + * + * @see com.android.internal.os.MemoryPowerCalculator + */ + public static double calcMemoryPower(PowerProfile powerProfile) { + double power = 0; + int numBuckets = powerProfile.getNumElements(PowerProfile.POWER_MEMORY); + for (int i = 0; i < numBuckets; i++) { + long timeMs = 0; + power += new UsageBasedPowerEstimator(powerProfile.getAveragePower(PowerProfile.POWER_MEMORY, i)).calculatePower(timeMs); + } + return power; + } + + /** + * @see com.android.internal.os.WakelockPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcWakelocksPower(PowerProfile powerProfile, HealthStats healthStats) { + double power = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_WAKELOCKS_PARTIAL); + long timeMs = 0; + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_CPU_IDLE); + power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + return power; + } + + /** + * @see com.android.internal.os.MobileRadioPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcMobilePower(PowerProfile powerProfile, HealthStats healthStats) { + // double mams = getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_POWER_MAMS) / UsageBasedPowerEstimator.MILLIS_IN_HOUR; + // if (mams > 0) { + // MatrixLog.i(TAG, "estimate Mobile by mams"); + // return mams; + // } + double power = calcMobilePowerByRadioActive(powerProfile, healthStats); + if (power > 0) { + MatrixLog.i(TAG, "estimate Mobile by radioActive"); + return power; + } + // power = calcMobilePowerByController(powerProfile, healthStats); + // if (power > 0) { + // MatrixLog.i(TAG, "estimate Mobile by controller"); + // return power; + // } + return 0; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @VisibleForTesting + public static double calcMobilePowerByRadioActive(PowerProfile powerProfile, HealthStats healthStats) { + // calc from radio active + // for some aosp mistakes, radio active timer was given in time unit us: + // https://cs.android.com/android/_/android/platform/frameworks/base/+/bee44ae8e5da109cd8273a057b566dc6925d6a71 + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_MOBILE_RADIO_ACTIVE) / 1000; + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_RADIO_ACTIVE); + if (powerMa <= 0) { + double sum = 0; + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + int num = powerProfile.getNumElements(PowerProfile.POWER_MODEM_CONTROLLER_TX); + for (int i = 0; i < num; i++) { + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + powerMa = sum / (num + 1); + } + return new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @VisibleForTesting + public static double calcMobilePowerByController(PowerProfile powerProfile, HealthStats healthStats) { + // calc from controller + double power = 0; + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_IDLE_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_IDLE); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_RX_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_RX); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_TX_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_TX); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + return power; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @VisibleForTesting + public static double calcMobilePowerByPackets(PowerProfile powerProfile, HealthStats healthStats, double rxBps, double txBps) { + double power = 0; + { + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_RADIO_ACTIVE); + if (powerMa <= 0) { + double sum = 0; + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + int num = powerProfile.getNumElements(PowerProfile.POWER_MODEM_CONTROLLER_TX); + for (int i = 0; i < num; i++) { + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + powerMa = sum / (num + 1); + } + double mobileBps = rxBps + txBps; + double powerPs = powerMa / 3600; + double mobilePps = ((double) mobileBps) / 8 / 2048; + double powerMaPerPacket = (powerPs / mobilePps) / (60 * 60); + long packets = getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_RX_PACKETS) + getMeasure(healthStats, UidHealthStats.MEASUREMENT_MOBILE_TX_PACKETS); + power += powerMaPerPacket * packets; + } + return power; + } + + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @VisibleForTesting + public static double calcMobilePowerByNetworkStatBytes(PowerProfile powerProfile, RadioStatSnapshot snapshot, double rxBps, double txBps) { + long rxMs = (long) ((snapshot.mobileRxBytes.get() / (rxBps / 8)) * 1000); + long txMs = (long) ((snapshot.mobileTxBytes.get() / (txBps / 8)) * 1000); + double power = 0; + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_RX); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(rxMs); + } + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_TX); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(txMs); + } + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_MODEM_CONTROLLER_IDLE); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(txMs + rxMs); + } + return power; + } + + @VisibleForTesting + public static double calcMobilePowerByNetworkStatPackets(PowerProfile powerProfile, RadioStatSnapshot snapshot, double rxBps, double txBps) { + double power = 0; + { + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_RADIO_ACTIVE); + if (powerMa <= 0) { + double sum = 0; + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + int num = powerProfile.getNumElements(PowerProfile.POWER_MODEM_CONTROLLER_TX); + for (int i = 0; i < num; i++) { + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + powerMa = sum / (num + 1); + } + double mobileBps = rxBps + txBps; + double powerPs = powerMa / 3600; + double mobilePps = ((double) mobileBps) / 8 / 2048; + double powerMaPerPacket = (powerPs / mobilePps) / (60 * 60); + long packets = snapshot.mobileRxPackets.get() + snapshot.mobileTxPackets.get(); + power += powerMaPerPacket * packets; + } + return power; + } + + + /** + * @see com.android.internal.os.WifiPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcWifiPower(PowerProfile powerProfile, HealthStats healthStats) { + // double mams = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_POWER_MAMS) / UsageBasedPowerEstimator.MILLIS_IN_HOUR; + // if (mams > 0) { + // MatrixLog.i(TAG, "estimate WIFI by mams"); + // return mams; + // } + double power = calcWifiPowerByController(powerProfile, healthStats); + if (power > 0) { + MatrixLog.i(TAG, "estimate WIFI by controller"); + return power; + } + // power = calcWifiPowerByPackets(powerProfile, healthStats, 500000, 500000); + // if (power > 0) { + // MatrixLog.i(TAG, "estimate WIFI by packets"); + // return power; + // } + return 0; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @VisibleForTesting + public static double calcWifiPowerByController(PowerProfile powerProfile, HealthStats healthStats) { + // calc from controller + double power = 0; + { + double wifiIdlePower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); + long idleMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_IDLE_MS); + UsageBasedPowerEstimator etmWifiIdlePower = new UsageBasedPowerEstimator(wifiIdlePower); + power += etmWifiIdlePower.calculatePower(idleMs); + } + { + double wifiRxPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_RX); + UsageBasedPowerEstimator etmWifiRxPower = new UsageBasedPowerEstimator(wifiRxPower); + long rxMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RX_MS); + power += etmWifiRxPower.calculatePower(rxMs); + } + { + double wifiTxPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_TX); + long txMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_TX_MS); + UsageBasedPowerEstimator etmWifiTxPower = new UsageBasedPowerEstimator(wifiTxPower); + power += etmWifiTxPower.calculatePower(txMs); + } + return power; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @VisibleForTesting + public static double calcWifiPowerByPackets(PowerProfile powerProfile, HealthStats healthStats, double rxBps, double txBps) { + // calc from packets + double power = 0; + if (rxBps >= 0 && txBps >= 0) { + if (rxBps == 0 && txBps == 0) { + return power; + } + { + final double wifiBps = rxBps + txBps; + final double averageWifiActivePower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + double powerMaPerPacket = averageWifiActivePower / (((double) wifiBps) / 8 / 2048); + long packets = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RX_PACKETS) + getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_TX_PACKETS); + power += powerMaPerPacket * packets; + } + { + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_ON); + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_WIFI_RUNNING_MS); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + { + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_SCAN); + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_WIFI_SCAN); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + } + return power; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @VisibleForTesting + public static double calcWifiPowerByNetworkStatBytes(PowerProfile powerProfile, RadioStatSnapshot snapshot, double rxBps, double txBps) { + long rxMs = (long) ((snapshot.wifiRxBytes.get() / (rxBps / 8)) * 1000); + long txMs = (long) ((snapshot.wifiTxBytes.get() / (txBps / 8)) * 1000); + double power = 0; + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_RX); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(rxMs); + } + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_TX); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(txMs); + } + { + double avgPower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); + UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(avgPower); + power += estimator.calculatePower(txMs + rxMs); + } + return power; + } + + @VisibleForTesting + public static double calcWifiPowerByNetworkStatPackets(PowerProfile powerProfile, RadioStatSnapshot snapshot, double rxBps, double txBps) { + double power = 0; + { + final double wifiBps = rxBps + txBps; + final double averageWifiActivePower = powerProfile.getAveragePowerUni(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + double powerMaPerPacket = averageWifiActivePower / (((double) wifiBps) / 8 / 2048); + long packets = snapshot.wifiRxPackets.get() + snapshot.wifiTxPackets.get(); + power += powerMaPerPacket * packets; + } + return power; + } + + /** + * @see com.android.internal.os.BluetoothPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcBlueToothPower(PowerProfile powerProfile, HealthStats healthStats) { + // double mams = getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_POWER_MAMS) / UsageBasedPowerEstimator.MILLIS_IN_HOUR; + // if (mams > 0) { + // MatrixLog.i(TAG, "etmMobilePower BLE by mams"); + // return mams; + // } + double power = 0; + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_IDLE_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_RX_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + { + long timeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_BLUETOOTH_TX_MS); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX); + power += new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + } + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.GnssPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcGpsPower(PowerProfile powerProfile, HealthStats healthStats) { + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_GPS_SENSOR); + double powerMa = 0; + if (timeMs > 0) { + powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_GPS_ON); + if (powerMa <= 0) { + int num = powerProfile.getNumElements(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED); + double sumMa = 0; + for (int i = 0; i < num; i++) { + sumMa += powerProfile.getAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i); + } + powerMa = sumMa / num; + } + } + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.SensorPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcSensorsPower(Context context, HealthStats healthStats) { + double power = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_SENSORS)) { + SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + List sensorList = sm.getSensorList(Sensor.TYPE_ALL); + Map sensorMap = new HashMap<>(); + for (Sensor item : sensorList) { + try { + //noinspection JavaReflectionMemberAccess + @SuppressLint("DiscouragedPrivateApi") + Method method = item.getClass().getDeclaredMethod("getHandle"); + //noinspection ConstantConditions + int handle = (int) method.invoke(item); + sensorMap.put(String.valueOf(handle), item); + } catch (Throwable e) { + MatrixLog.w(TAG, "getSensorHandle err: " + e.getMessage()); + } + } + + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SENSORS); + for (Map.Entry item : timers.entrySet()) { + String handle = item.getKey(); + long timeMs = item.getValue().getTime(); + if (handle.equals("-10000")) { + continue; // skip GPS Sensors + } + Sensor sensor = sensorMap.get(handle); + if (sensor != null) { + power += new UsageBasedPowerEstimator(sensor.getPower()).calculatePower(timeMs); + } + } + } + if (power > 0) { + return power; + } + return 0; + } + + /** + * WIP + * Calculate camera power usage. Right now, this is a (very) rough estimate based on the + * average power usage for a typical camera application. + * + * @see com.android.internal.os.CameraPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcCameraPower(PowerProfile powerProfile, HealthStats healthStats) { + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_CAMERA); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_CAMERA); + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.FlashlightPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcFlashLightPower(PowerProfile powerProfile, HealthStats healthStats) { + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_FLASHLIGHT); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_FLASHLIGHT); + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.MediaPowerCalculator + * @see com.android.internal.os.AudioPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcAudioPower(PowerProfile powerProfile, HealthStats healthStats) { + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_AUDIO); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_AUDIO); + if (powerMa == 0) { + powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_AUDIO_DSP); + } + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.MediaPowerCalculator + * @see com.android.internal.os.VideoPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcVideoPower(PowerProfile powerProfile, HealthStats healthStats) { + long timeMs = getTimerTime(healthStats, UidHealthStats.TIMER_VIDEO); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_VIDEO); + if (powerMa == 0) { + powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_VIDEO_DSP); + } + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(timeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.ScreenPowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcScreenPower(PowerProfile powerProfile, HealthStats healthStats) { + long topAppMs = getTimerTime(healthStats, UidHealthStats.TIMER_PROCESS_STATE_TOP_MS); + long fgActivityMs = getTimerTime(healthStats, UidHealthStats.TIMER_FOREGROUND_ACTIVITY); + long screenOnTimeMs = Math.min(topAppMs, fgActivityMs); + double powerMa = powerProfile.getAveragePowerUni(PowerProfile.POWER_SCREEN_ON); + double power = new UsageBasedPowerEstimator(powerMa).calculatePower(screenOnTimeMs); + if (power > 0) { + return power; + } + return 0; + } + + /** + * WIP + * Binder cup time_in_state can not be collected right now + * + * @see com.android.internal.os.SystemServicePowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcSystemServicePower(PowerProfile powerProfile, HealthStats healthStats) { + double power = 0; + long timeMs = 0; + if (healthStats.hasTimers(UidHealthStats.TIMERS_JOBS)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_JOBS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + } + if (healthStats.hasTimers(UidHealthStats.TIMERS_SYNCS)) { + Map timers = healthStats.getTimers(UidHealthStats.TIMERS_SYNCS); + for (TimerStat item : timers.values()) { + timeMs += item.getTime(); + } + } + + power += estimateCpuActivePower(powerProfile, timeMs); + CpuStatFeature feat = BatteryCanary.getMonitorFeature(CpuStatFeature.class); + if (feat != null && feat.isSupported()) { + CpuStateSnapshot snapshot = feat.currentCpuStateSnapshot(); + if (snapshot != null) { + power += estimateCpuClustersPower(powerProfile, snapshot, timeMs, false); + power += estimateCpuCoresPower(powerProfile, snapshot, timeMs, false); + } + } + if (power > 0) { + return power; + } + return 0; + } + + /** + * @see com.android.internal.os.IdlePowerCalculator + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static double calcIdlePower(PowerProfile powerProfile, HealthStats healthStats) { + long batteryRealtimeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_REALTIME_BATTERY_MS); + long batteryUptimeMs = getMeasure(healthStats, UidHealthStats.MEASUREMENT_UPTIME_BATTERY_MS); + double suspendPowerMah = new UsageBasedPowerEstimator(powerProfile.getAveragePowerUni(PowerProfile.POWER_CPU_SUSPEND)).calculatePower(batteryRealtimeMs); + double idlePowerMah = new UsageBasedPowerEstimator(powerProfile.getAveragePowerUni(PowerProfile.POWER_CPU_IDLE)).calculatePower(batteryUptimeMs); + double power = suspendPowerMah + idlePowerMah; + if (power > 0) { + return power; + } + return 0; + } +} diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCanaryUtil.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCanaryUtil.java index 8d34fa7e2..f1f609d7d 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCanaryUtil.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCanaryUtil.java @@ -40,8 +40,11 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.regex.Pattern; import androidx.annotation.IntRange; @@ -78,6 +81,7 @@ public final class BatteryCanaryUtil { public static final int JIFFY_MILLIS = 1000 / JIFFY_HZ; public interface Proxy { + String getProcessName(); String getPackageName(); int getBatteryTemperature(Context context); @@ -87,13 +91,15 @@ public interface Proxy { void updateDevStat(int value); int getBatteryPercentage(Context context); int getBatteryCapacity(Context context); + long getBatteryCurrency(Context context); + int getCpuCoreNum(); - final class ExpireRef { - final int value; + final class ExpireRef { + final T value; final long aliveMillis; final long lastMillis; - ExpireRef(int value, long aliveMillis) { + ExpireRef(T value, long aliveMillis) { this.value = value; this.aliveMillis = aliveMillis; this.lastMillis = SystemClock.uptimeMillis(); @@ -109,11 +115,13 @@ boolean isExpired() { static Proxy sCacheStub = new Proxy() { private String mProcessName; private String mPackageName; - private ExpireRef mBatteryTemp; - private ExpireRef mLastAppStat; - private ExpireRef mLastDevStat; - private ExpireRef mLastBattPct; - private ExpireRef mLastBattCap; + private ExpireRef mBatteryTemp; + private ExpireRef mLastAppStat; + private ExpireRef mLastDevStat; + private ExpireRef mLastBattPct; + private ExpireRef mLastBattCap; + private ExpireRef mLastBattCur; + private ExpireRef mLastCpuCoreNum; @Override public String getProcessName() { @@ -147,7 +155,7 @@ public int getBatteryTemperature(Context context) { return mBatteryTemp.value; } int tmp = getBatteryTemperatureImmediately(context); - mBatteryTemp = new ExpireRef(tmp, DEFAULT_AMS_CACHE_MILLIS); + mBatteryTemp = new ExpireRef<>(tmp, DEFAULT_AMS_CACHE_MILLIS); return mBatteryTemp.value; } @@ -158,7 +166,7 @@ public int getAppStat(Context context, boolean isForeground) { return mLastAppStat.value; } int value = getAppStatImmediately(context, false); - mLastAppStat = new ExpireRef(value, DEFAULT_AMS_CACHE_MILLIS); + mLastAppStat = new ExpireRef<>(value, DEFAULT_AMS_CACHE_MILLIS); return mLastAppStat.value; } @@ -168,21 +176,21 @@ public int getDevStat(Context context) { return mLastDevStat.value; } int value = getDeviceStatImmediately(context); - mLastDevStat = new ExpireRef(value, DEFAULT_AMS_CACHE_MILLIS); + mLastDevStat = new ExpireRef<>(value, DEFAULT_AMS_CACHE_MILLIS); return mLastDevStat.value; } @Override public void updateAppStat(int value) { synchronized (this) { - mLastAppStat = new ExpireRef(value, DEFAULT_AMS_CACHE_MILLIS); + mLastAppStat = new ExpireRef<>(value, DEFAULT_AMS_CACHE_MILLIS); } } @Override public void updateDevStat(int value) { synchronized (this) { - mLastDevStat = new ExpireRef(value, DEFAULT_AMS_CACHE_MILLIS); + mLastDevStat = new ExpireRef<>(value, DEFAULT_AMS_CACHE_MILLIS); } } @@ -192,7 +200,7 @@ public int getBatteryPercentage(Context context) { return mLastBattPct.value; } int val = getBatteryPercentageImmediately(context); - mLastBattPct = new ExpireRef(val, ONE_MIN); + mLastBattPct = new ExpireRef<>(val, ONE_MIN); return mLastBattPct.value; } @@ -202,9 +210,32 @@ public int getBatteryCapacity(Context context) { return mLastBattCap.value; } int val = getBatteryCapacityImmediately(context); - mLastBattCap = new ExpireRef(val, ONE_MIN); + mLastBattCap = new ExpireRef<>(val, ONE_MIN); return mLastBattCap.value; } + + @Override + public long getBatteryCurrency(Context context) { + if (mLastBattCur != null && !mLastBattCur.isExpired()) { + return mLastBattCur.value; + } + long val = getBatteryCurrencyImmediately(context); + mLastBattCur = new ExpireRef<>(val, ONE_MIN); + return mLastBattCur.value; + } + + @Override + public int getCpuCoreNum() { + if (mLastCpuCoreNum != null && !mLastCpuCoreNum.isExpired()) { + return mLastCpuCoreNum.value; + } + int val = getCpuCoreNumImmediately(); + if (val <= 1) { + return val; + } + mLastCpuCoreNum = new ExpireRef<>(val, ONE_HOR); + return mLastCpuCoreNum.value; + } }; public static void setProxy(Proxy stub) { @@ -307,8 +338,9 @@ public static String getAlarmTypeString(final int type) { } public static int[] getCpuCurrentFreq() { - int[] output = new int[getCpuCoreNum()]; - for (int i = 0; i < getCpuCoreNum(); i++) { + int cpuCoreNum = getCpuCoreNum(); + int[] output = new int[cpuCoreNum]; + for (int i = 0; i < cpuCoreNum; i++) { output[i] = 0; String path = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/scaling_cur_freq"; String cat = cat(path); @@ -323,7 +355,35 @@ public static int[] getCpuCurrentFreq() { return output; } + public static List getCpuFreqSteps() { + int cpuCoreNum = getCpuCoreNum(); + List output = new ArrayList<>(cpuCoreNum); + for (int i = 0; i < cpuCoreNum; i++) { + String path = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/scaling_available_frequencies"; + String cat = cat(path); + if (!TextUtils.isEmpty(cat)) { + //noinspection ConstantConditions + String[] split = cat.split(" "); + int[] steps = new int[split.length]; + for (int j = 0, splitLength = split.length; j < splitLength; j++) { + try { + String item = split[j]; + steps[j] = Integer.parseInt(item) / 1000; + } catch (Exception ignored) { + steps[j] = 0; + } + } + output.add(steps); + } + } + return output; + } + public static int getCpuCoreNum() { + return sCacheStub.getCpuCoreNum(); + } + + public static int getCpuCoreNumImmediately() { try { // Get directory containing CPU info File dir = new File("/sys/devices/system/cpu/"); @@ -335,13 +395,19 @@ public boolean accept(File pathname) { } }); // Return the number of cores (virtual CPU devices) + // noinspection ConstantConditions return files.length; } catch (Exception ignored) { // Default to return 1 core - return 1; + return getCpuCoreNumFromRuntime(); } } + public static int getCpuCoreNumFromRuntime() { + // fastest + return Runtime.getRuntime().availableProcessors(); + } + @Nullable public static String cat(String path) { if (TextUtils.isEmpty(path)) return null; @@ -381,7 +447,7 @@ public static int getThermalStatImmediately(Context context) { MatrixLog.w(TAG, "getCurrentThermalStatus failed: " + e.getMessage()); } } - return 0; + return -1; } public static float getThermalHeadroom(Context context, @IntRange(from = 0, to = 60) int forecastSeconds) { @@ -397,7 +463,7 @@ public static float getThermalHeadroomImmediately(Context context, int forecastS MatrixLog.w(TAG, "getThermalHeadroom failed: " + e.getMessage()); } } - return 0f; + return -1f; } public static int getChargingWatt(Context context) { @@ -416,7 +482,7 @@ public static int getChargingWattImmediately(Context context) { return (maxCurrent / 1000) * (maxVoltage / 1000) / 1000000; } } - return 0; + return -1; } @AppStats.AppStatusDef @@ -602,22 +668,19 @@ public static int getBatteryPercentageImmediately(Context context) { public static int getBatteryCapacity(Context context) { return sCacheStub.getBatteryCapacity(context); - }; + } @SuppressWarnings("ConstantConditions") @SuppressLint("PrivateApi") public static int getBatteryCapacityImmediately(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - BatteryManager mBatteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); - int chargeCounter = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER); - int capacity = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); - if (chargeCounter > 0 && capacity > 0) { - return (int) (((chargeCounter / (float) capacity) * 100) / 1000); + /* + * Matrix PowerProfile (static) >> OS PowerProfile (static) >> BatteryManager (dynamic) + */ + try { + if (PowerProfile.getResType().equals("framework") || PowerProfile.getResType().equals("custom")) { + return (int) PowerProfile.init(context).getBatteryCapacity(); } - } - - if (PowerProfile.getInstance() != null) { - return (int) PowerProfile.getInstance().getBatteryCapacity(); + } catch (Throwable ignored) { } try { @@ -626,14 +689,35 @@ public static int getBatteryCapacityImmediately(Context context) { Method method; try { method = profileClass.getMethod("getAveragePower", String.class); - return (int) method.invoke(profileObject, "battery.capacity"); + double capacity = (double) method.invoke(profileObject, PowerProfile.POWER_BATTERY_CAPACITY); + return (int) capacity; } catch (Throwable e) { MatrixLog.w(TAG, "get PowerProfile failed: " + e.getMessage()); } method = profileClass.getMethod("getBatteryCapacity"); return (int) method.invoke(profileObject); - } catch (Throwable e) { - MatrixLog.w(TAG, "get PowerProfile failed: " + e.getMessage()); + } catch (Throwable ignored) { + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + BatteryManager mBatteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); + int chargeCounter = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER); + int capacity = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + if (chargeCounter > 0 && capacity > 0) { + return (int) (((chargeCounter / (float) capacity) * 100) / 1000); + } + } + return -1; + } + + public static long getBatteryCurrency(Context context) { + return sCacheStub.getBatteryCurrency(context); + } + + public static long getBatteryCurrencyImmediately(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + BatteryManager mBatteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); + return mBatteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW); } return -1; } @@ -710,4 +794,15 @@ public static long computeAvgByMinute(long input, long millis) { return input / Math.max(1, (millis) / ONE_MIN); } } + + public static Map sortMapByValue(Map map, Comparator> comparator) { + List> list = new ArrayList<>(map.entrySet()); + Collections.sort(list, comparator); + + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspector.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspector.java new file mode 100644 index 000000000..c3186b072 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/BatteryCurrencyInspector.java @@ -0,0 +1,67 @@ +package com.tencent.matrix.batterycanary.utils; + +import android.content.Context; +import android.os.BatteryManager; + +import androidx.annotation.Nullable; + +/** + * Since {@link BatteryManager#BATTERY_PROPERTY_CURRENT_NOW} will return microAmp/millisAmp & + * positive/negative value in difference devices, here we figure out the unit somehow. + * + * @author Kaede + * @since 9/9/2022 + */ +public class BatteryCurrencyInspector { + + /** + * To get a high currency value, you are supposed to call this API within a high cpu-load + * activity & without charging. (When the device's cpu-load is very low, we can not tell + * microAmp or millisAmp.) + */ + @Nullable + public static Boolean isMicroAmpCurr(Context context, int threshold) { + if (BatteryCanaryUtil.isDeviceCharging(context)) { + // Currency might be very low in charge + return null; + } + long val = BatteryCanaryUtil.getBatteryCurrencyImmediately(context); + if (val == -1) { + return null; + } + return isMicroAmp(val, threshold); + } + + @Nullable + public static Boolean isMicroAmpCurr(Context context) { + return isMicroAmpCurr(context, 1000); + } + + public static boolean isMicroAmp(long amp, int threshold) { + return amp > threshold; + } + + @Nullable + public static Boolean isPositiveInChargeCurr(Context context) { + if (!BatteryCanaryUtil.isDeviceCharging(context)) { + return null; + } + long val = BatteryCanaryUtil.getBatteryCurrencyImmediately(context); + if (val == -1) { + return null; + } + return val > 0; + } + + @Nullable + public static Boolean isPositiveOutOfChargeCurr(Context context) { + if (BatteryCanaryUtil.isDeviceCharging(context)) { + return null; + } + long val = BatteryCanaryUtil.getBatteryCurrencyImmediately(context); + if (val == -1) { + return null; + } + return val > 0; + } +} diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/Function.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/Function.java new file mode 100644 index 000000000..0886b57dd --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/Function.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.tencent.matrix.batterycanary.utils; + +/** + * Compat version of {@link java.util.function.Function} + * + * @param the type of the input to the function + * @param the type of the result of the function + * @since 1.8 + */ +public interface Function { + R apply(T t); +} + diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuSpeedReader.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuSpeedReader.java index cd927965a..010aa133e 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuSpeedReader.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuSpeedReader.java @@ -39,7 +39,7 @@ public void smoke() throws IOException { } } - public long readTotoal() throws IOException { + public long readTotal() throws IOException { long sum = 0; for (long item : readAbsolute()) { sum += item; diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuUidFreqTimeReader.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuUidFreqTimeReader.java index 248afed47..2d4d3e0c6 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuUidFreqTimeReader.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/KernelCpuUidFreqTimeReader.java @@ -54,7 +54,7 @@ public void smoke() throws IOException { } } - public List readTotoal() throws IOException { + public List readTotal() throws IOException { List cpuCoreStepJiffies = readAbsolute(); List cpuCoreJiffies = new ArrayList<>(cpuCoreStepJiffies.size()); for (long[] stepJiffies : cpuCoreStepJiffies) { diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/PowerProfile.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/PowerProfile.java index 09f6881b2..6b38c364b 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/PowerProfile.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/PowerProfile.java @@ -3,16 +3,29 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.os.Build; +import android.system.Os; + +import com.tencent.matrix.util.MatrixLog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.concurrent.Callable; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; +import androidx.annotation.StringDef; +import androidx.annotation.VisibleForTesting; /** * @see com.android.internal.os.PowerProfile @@ -20,6 +33,17 @@ @RestrictTo(RestrictTo.Scope.LIBRARY) @SuppressWarnings({"JavadocReference", "ConstantConditions", "TryFinallyCanBeTryWithResources"}) public class PowerProfile { + @StringDef(value = { + "unknown", + "framework", + "custom", + "test" + }) + @Retention(RetentionPolicy.SOURCE) + @interface ResType { + } + + private static final String TAG = "PowerProfile"; private static PowerProfile sInstance = null; @Nullable @@ -30,15 +54,17 @@ public static PowerProfile getInstance() { public static PowerProfile init(Context context) throws IOException { synchronized (sLock) { try { - sInstance = new PowerProfile(context).smoke(); + if (sInstance == null) { + sInstance = new PowerProfile(context).smoke(); + } return sInstance; } catch (Throwable e) { - throw new IOException(e); + throw new IOException("Compat err: " + e.getMessage(), e); } } } - public PowerProfile smoke() throws IOException { + PowerProfile smoke() throws IOException { if (getNumCpuClusters() <= 0) { throw new IOException("Invalid cpu clusters: " + getNumCpuClusters()); } @@ -66,6 +92,21 @@ public boolean isSupported() { } } + public double getAveragePowerUni(String type) { + int num = getNumElements(type); + if (num > 0) { + // Array + double sum = 0; + for (int i = 0; i < num; i++) { + sum += getAveragePower(type, i); + } + return sum / num; + } else { + // Item + return getAveragePower(type); + } + } + public int getCpuCoreNum() { int cpuCoreNumInProfile = 0; for (int i = 0; i < getNumCpuClusters(); i++) { @@ -203,12 +244,14 @@ public int getClusterByCpuNum(int cpuCoreNum) { * to the CPU power, probably due to a DSP and / or amplifier. */ public static final String POWER_AUDIO = "audio"; + public static final String POWER_AUDIO_DSP = "dsp.audio"; /** * Power consumed by any media hardware when playing back video content. This is in addition * to the CPU power, probably due to a DSP. */ public static final String POWER_VIDEO = "video"; + public static final String POWER_VIDEO_DSP = "dsp.video"; /** * Average power consumption when camera flashlight is on. @@ -257,22 +300,154 @@ public int getClusterByCpuNum(int cpuCoreNum) { private static final Object sLock = new Object(); - PowerProfile(Context context) { // Read the XML file for the given profile (normally only one per device) synchronized (sLock) { if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { - readPowerValuesFromXml(context); + try { + readPowerValuesCompat(context); + } catch (IOException e) { + MatrixLog.w(TAG, "Failed to read power values: " + e); + } } initCpuClusters(); } } + @VisibleForTesting + public static HashMap getPowerItemMap() { + return sPowerItemMap; + } + + @VisibleForTesting + public static HashMap getPowerArrayMap() { + return sPowerArrayMap; + } + + private static String mResType = "unknown"; + + @ResType + public static String getResType() { + return mResType; + } + + private void readPowerValuesCompat(Context context) throws IOException { + Exception exception = null; + try { + readPowerValuesFromRes(context, "power_profile"); + initCpuClusters(); + smoke(); + mResType = "framework"; + } catch (Exception e) { + MatrixLog.w(TAG, "read from framework failed: " + e); + clear(); + exception = e; + } + + if (exception != null) { + Callable findBlock = new Callable() { + @SuppressWarnings("checkstyle:RegexpSingleline") + @Override + public File call() throws FileNotFoundException { + String targetFileName = "/xml/power_profile.xml"; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + String customDirs = Os.getenv("CUST_POLICY_DIRS"); + for (String dir : customDirs.split(":")) { + // example: /hw_product/etc/xml/power_profile.xml + File file = new File(dir, targetFileName); + if (file.exists() && file.canRead()) { + MatrixLog.i(TAG, "find profile xml: " + file); + return file; + } + } + } + throw new FileNotFoundException(targetFileName); + } + }; + try { + exception = null; + readPowerValuesFromFilePath(context, findBlock.call()); + initCpuClusters(); + smoke(); + mResType = "custom"; + } catch (Exception e) { + MatrixLog.w(TAG, "read from custom failed: " + e); + clear(); + exception = e; + } + } + + if (exception != null) { + try { + exception = null; + readPowerValuesFromRes(context, "power_profile_test"); + initCpuClusters(); + smoke(); + mResType = "test"; + } catch (Exception e) { + MatrixLog.w(TAG, "read from test failed: " + e); + clear(); + exception = e; + } + } + + if (exception != null) { + throw new IOException("readPowerValuesCompat failed", exception); + } + } + + @VisibleForTesting + public void readPowerValuesFromRes(Context context, String fileName) { + XmlResourceParser parser = null; + try { + final int id = context.getResources().getIdentifier(fileName, "xml", "android"); + final Resources resources = context.getResources(); + parser = resources.getXml(id); + readPowerValuesFromXml(context, parser); + } catch (Exception e) { + throw new RuntimeException("Error reading res " + fileName + ": " + e.getMessage(), e); + } finally { + if (parser != null) { + try { + parser.close(); + } catch (Exception e) { + // ignore + } + } + } + } + + @VisibleForTesting + public void readPowerValuesFromFilePath(Context context, File xmlFile) { + FileInputStream is = null; + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser parser = factory.newPullParser(); + is = new FileInputStream(xmlFile); + parser.setInput(is, null); + readPowerValuesFromXml(context, parser); + } catch (Exception e) { + throw new RuntimeException("Error reading file " + xmlFile + ": " + e.getMessage(), e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + @VisibleForTesting + public void clear() { + sPowerItemMap.clear(); + sPowerArrayMap.clear(); + } + @SuppressWarnings({"ToArrayCallWithZeroLengthArrayArgument", "UnnecessaryBoxing", "CatchMayIgnoreException", "TryWithIdenticalCatches"}) - private void readPowerValuesFromXml(Context context) { - final int id = context.getResources().getIdentifier("power_profile", "xml", "android"); - final Resources resources = context.getResources(); - XmlResourceParser parser = resources.getXml(id); + private void readPowerValuesFromXml(Context context, XmlPullParser parser) { boolean parsingArray = false; ArrayList array = new ArrayList<>(); String arrayName = null; @@ -320,8 +495,6 @@ private void readPowerValuesFromXml(Context context) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); - } finally { - parser.close(); } // Now collect other config variables. @@ -346,7 +519,7 @@ private void readPowerValuesFromXml(Context context) { if ((sPowerItemMap.containsKey(key) && sPowerItemMap.get(key) > 0)) { continue; } - int value = resources.getInteger(configResIds[i]); + int value = context.getResources().getInteger(configResIds[i]); if (value > 0) { sPowerItemMap.put(key, (double) value); } @@ -360,7 +533,7 @@ private void readPowerValuesFromXml(Context context) { private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster"; private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster"; - private void initCpuClusters() { + void initCpuClusters() { if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) { final Double[] data = sPowerArrayMap.get(CPU_PER_CLUSTER_CORE_COUNT); mCpuClusters = new CpuClusterKey[data.length]; diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/ProcStatUtil.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/ProcStatUtil.java index bd39ba55b..3aec48917 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/ProcStatUtil.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/ProcStatUtil.java @@ -39,6 +39,14 @@ static byte[] getLocalBuffers() { ProcStatUtil() { } + public static boolean exists(int pid) { + return new File("/proc/" + pid + "/stat").exists(); + } + + public static boolean exists(int pid, int tid) { + return new File("/proc/" + pid + "/task/" + tid + "/stat").exists(); + } + @Nullable public static ProcStat currentPid() { return of(Process.myPid()); diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/RadioStatUtil.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/RadioStatUtil.java index a8afc57be..58499fc7f 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/RadioStatUtil.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/RadioStatUtil.java @@ -1,15 +1,24 @@ package com.tencent.matrix.batterycanary.utils; +import android.Manifest; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; +import android.net.NetworkInfo; +import android.os.Build; import com.tencent.matrix.batterycanary.BuildConfig; import com.tencent.matrix.util.MatrixLog; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.RestrictTo; +import androidx.core.util.Pair; + /** * @author Kaede * @since 2020/12/8 @@ -20,6 +29,7 @@ public final class RadioStatUtil { private static final String TAG = "Matrix.battery.ProcStatUtil"; static final long MIN_QUERY_INTERVAL = BuildConfig.DEBUG ? 0L : 2000L; static long sLastQueryMillis; + static RadioStat sLastRef; private static boolean checkIfFrequently() { long currentTimeMillis = System.currentTimeMillis(); @@ -35,10 +45,10 @@ public static RadioStat getCurrentStat(Context context) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { return null; } - if (checkIfFrequently()) { - MatrixLog.i(TAG, "over frequently just return"); - return null; - } + + // if (checkIfFrequently()) { + // return sLastRef; + // } try { NetworkStatsManager network = (NetworkStatsManager) context.getSystemService(Context.NETWORK_STATS_SERVICE); @@ -53,6 +63,8 @@ public static RadioStat getCurrentStat(Context context) { if (bucket.getUid() == android.os.Process.myUid()) { stat.wifiRxBytes += bucket.getRxBytes(); stat.wifiTxBytes += bucket.getTxBytes(); + stat.wifiRxPackets += bucket.getRxPackets(); + stat.wifiTxPackets += bucket.getTxPackets(); } } } @@ -64,23 +76,84 @@ public static RadioStat getCurrentStat(Context context) { if (bucket.getUid() == android.os.Process.myUid()) { stat.mobileRxBytes += bucket.getRxBytes(); stat.mobileTxBytes += bucket.getTxBytes(); + stat.mobileRxPackets += bucket.getRxPackets(); + stat.mobileTxPackets += bucket.getTxPackets(); } } } } - + sLastRef = stat; return stat; } catch (Throwable e) { MatrixLog.w(TAG, "querySummary fail: " + e.getMessage()); + sLastRef = null; + return null; + } + } + + @Nullable + public static RadioBps getCurrentBps(Context context) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return null; + } + try { + RadioBps stat = new RadioBps(); + Pair wifi = getCurrentBps(context, "WIFI"); + stat.wifiRxBps = wifi.first == null ? 0 : wifi.first; + stat.wifiTxBps = wifi.second == null ? 0 : wifi.second; + + Pair mobile = getCurrentBps(context, "MOBILE"); + stat.mobileRxBps = mobile.first == null ? 0 : mobile.first; + stat.mobileTxBps = mobile.second == null ? 0 : mobile.second; + return stat; + } catch (Exception e) { + MatrixLog.w(TAG, "getBps err: " + e.getMessage()); return null; } } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private static Pair getCurrentBps(Context context, String typeName) { + long rxBwBps = 0, txBwBps = 0; + if (context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + for (Network item : manager.getAllNetworks()) { + NetworkInfo networkInfo = manager.getNetworkInfo(item); + if (networkInfo != null + && (networkInfo.isConnected() || networkInfo.isConnectedOrConnecting()) + && networkInfo.getTypeName().equalsIgnoreCase(typeName)) { + NetworkCapabilities capabilities = manager.getNetworkCapabilities(item); + if (capabilities != null) { + rxBwBps = capabilities.getLinkDownstreamBandwidthKbps() * 1024L; + txBwBps = capabilities.getLinkUpstreamBandwidthKbps() * 1024L; + if (rxBwBps > 0 || txBwBps > 0) { + break; + } + } + } + } + } + return new Pair<>(rxBwBps, txBwBps); + } + public static final class RadioStat { public long wifiRxBytes; public long wifiTxBytes; + public long wifiRxPackets; + public long wifiTxPackets; + public long mobileRxBytes; public long mobileTxBytes; + public long mobileRxPackets; + public long mobileTxPackets; + } + + public static final class RadioBps { + public long wifiRxBps; + public long wifiTxBps; + + public long mobileRxBps; + public long mobileTxBps; } } diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/TimeBreaker.java b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/TimeBreaker.java index 4b797953a..5e477b7ca 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/TimeBreaker.java +++ b/matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/TimeBreaker.java @@ -11,6 +11,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; /** * Configure timeline portions & ratio for the given stamps and return split-portions with each weight. @@ -185,6 +186,22 @@ public Stamp(String key, long upTime) { this.upTime = upTime; this.statMillis = System.currentTimeMillis(); } + + @VisibleForTesting + public Stamp(String key, long upTime, long statMillis) { + this.key = key; + this.upTime = upTime; + this.statMillis = statMillis; + } + + @Override + public String toString() { + return "Stamp{" + + "key='" + key + '\'' + + ", upTime=" + upTime + + ", statMillis=" + statMillis + + '}'; + } } public static final class TimePortions { diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_item_power_entry.xml b/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_item_power_entry.xml new file mode 100644 index 000000000..c04f18b50 --- /dev/null +++ b/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_item_power_entry.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_top_thread.xml b/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_top_thread.xml index 7576bf7b2..de6771a4d 100644 --- a/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_top_thread.xml +++ b/matrix/matrix-android/matrix-battery-canary/src/main/res/layout/float_top_thread.xml @@ -28,6 +28,41 @@ android:text="Matrix TOP" android:textSize="12sp" /> + + + + + + + + + + + + + + + + + + + 4) { - String typeName = entry.substring(4, entry.lastIndexOf('/')); + int prefixLength = entry.indexOf('/'); + if (prefixLength == -1) return ""; + if (!Util.isNullOrNil(entry)) { + String typeName = entry.substring(prefixLength + 1, entry.lastIndexOf('/')); if (!Util.isNullOrNil(typeName)) { int index = typeName.indexOf('-'); if (index >= 0) { @@ -87,7 +105,7 @@ public static String parseEntryResourceType(String entry) { return ""; } - public static boolean isSameResourceType(Set entries) { + public static boolean isSameResourceType(Set entries, @Nullable String obfuscatedDirName) { String resType = ""; for (String entry : entries) { if (!Util.isNullOrNil(entry)) { @@ -198,7 +216,7 @@ public static void sevenZipFile(String sevenZipPath, String inputFile, String ou new File(sevenZipPath).setExecutable(true); } ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(sevenZipPath, "a", "-tzip", outputFile, inputFile, deflated ? "-mx9" : "-mx0"); + processBuilder.command(sevenZipPath, "a", "-tzip", outputFile, inputFile, deflated ? "-mx5" : "-mx0"); //Log.i(TAG, "%s", processBuilder.command()) Process process = processBuilder.start(); // process.waitForProcessOutput(System.out, System.err); diff --git a/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/extension/MatrixRemoveUnusedResExtension.kt b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/extension/MatrixRemoveUnusedResExtension.kt index 2ad288ee7..4deb5364e 100644 --- a/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/extension/MatrixRemoveUnusedResExtension.kt +++ b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/extension/MatrixRemoveUnusedResExtension.kt @@ -29,6 +29,8 @@ open class MatrixRemoveUnusedResExtension( var variant: String = "", + var obfuscatedResourcesDirectoryName: String? = null, + // WIP. Should not use these options yet. var use7zip: Boolean = false, var zipAlign: Boolean = false, @@ -36,6 +38,7 @@ open class MatrixRemoveUnusedResExtension( var embedResGuard: Boolean = false, var sevenZipPath: String = "", var zipAlignPath: String = "", + var report: String? = null, // Deprecated var unusedResources: HashSet = HashSet() @@ -55,6 +58,8 @@ open class MatrixRemoveUnusedResExtension( | sevenZipPath = ${sevenZipPath} | zipAlignPath = ${zipAlignPath} | ignoreResources = ${ignoreResources} + | obfuscatedResourcesDirectoryName = ${obfuscatedResourcesDirectoryName} + | """.trimMargin() } } diff --git a/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/task/RemoveUnusedResourcesTaskV2.kt b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/task/RemoveUnusedResourcesTaskV2.kt index 9285b3ac0..778a052fb 100644 --- a/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/task/RemoveUnusedResourcesTaskV2.kt +++ b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/plugin/task/RemoveUnusedResourcesTaskV2.kt @@ -27,6 +27,7 @@ import com.tencent.matrix.javalib.util.Util import com.tencent.matrix.plugin.compat.AgpCompat import com.tencent.matrix.plugin.compat.CreationConfig import com.tencent.matrix.plugin.extension.MatrixRemoveUnusedResExtension +import com.tencent.matrix.resguard.ResguardMapping import com.tencent.matrix.shrinker.ApkUtil import com.tencent.matrix.shrinker.ProguardStringBuilder import com.tencent.mm.arscutil.ArscUtil @@ -69,6 +70,11 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { private var is7zipEnabled: Boolean = false private var isResGuardEnabled: Boolean = false + private var obfuscatedResourcesDirectoryName: String? = null + private var overrideInputApkFiles: List = emptyList() + private var resguardMappingFile: File? = null + private var resguardMapping: ResguardMapping? = null + private lateinit var pathOfApkChecker: String private lateinit var pathOfApkSigner: String private lateinit var pathOfZipAlign: String @@ -76,20 +82,33 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { private lateinit var rulesOfIgnore: Set + private var reportFile: File? = null + + fun overrideInputApkFiles(inputApkFiles: List) { + this.overrideInputApkFiles = inputApkFiles.map { File(it) } + } + + fun withResguardMapping(path: String) { + resguardMappingFile = File(path) + resguardMapping = ResguardMapping(resguardMappingFile!!) + } + @TaskAction fun removeResources() { - - variant.outputs.forEach { output -> - val startTime = System.currentTimeMillis() - removeResourcesV2( + overrideInputApkFiles + .ifEmpty { + variant.outputs.map { it.outputFile } + } + .forEach { apk -> + val startTime = System.currentTimeMillis() + removeResourcesV2( project = project, - originalApkFile = output.outputFile, + originalApkFile = apk, signingConfig = AgpCompat.getSigningConfig(variant), nameOfSymbolDirectory = AgpCompat.getIntermediatesSymbolDirName() - ) - Log.i(TAG, "cost time %f s", (System.currentTimeMillis() - startTime) / 1000.0f) - - } + ) + Log.i(TAG, "cost time %f s", (System.currentTimeMillis() - startTime) / 1000.0f) + } } class CreationAction( @@ -113,12 +132,23 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { task.is7zipEnabled = use7zip task.isResGuardEnabled = embedResGuard + task.obfuscatedResourcesDirectoryName = obfuscatedResourcesDirectoryName?.let { + if (it.endsWith("/")) it else "${it}/" + } + task.pathOfApkChecker = apkCheckerPath task.pathOfApkSigner = apksignerPath task.pathOfZipAlign = zipAlignPath task.pathOfSevenZip = sevenZipPath task.rulesOfIgnore = ignoreResources + + task.reportFile = report?.let { + File(it).apply { + parentFile.mkdirs() + if (exists()) delete() + } + } } task.parametersInvalidation() @@ -190,10 +220,10 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { val replaceIterator = mapOfDuplicatesReplacements.keys.iterator() while (replaceIterator.hasNext()) { val sourceFile = replaceIterator.next() - val sourceRes = ApkUtil.entryToResourceName(sourceFile) + val sourceRes = ApkUtil.entryToResourceName(sourceFile, resguardMapping) val sourceId = mapOfResources[sourceRes]!! val targetFile = mapOfDuplicatesReplacements[sourceFile] - val targetRes = ApkUtil.entryToResourceName(targetFile) + val targetRes = ApkUtil.entryToResourceName(targetFile, resguardMapping) val targetId = mapOfResources[targetRes]!! val success = ArscUtil.replaceFileResource(resTable, sourceId, sourceFile, targetId, targetFile) if (!success) { @@ -225,17 +255,18 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { val compressedEntry = HashSet() for (zipEntry in zipInputFile.entries()) { + if (zipEntry.isDirectory) continue var destFile = unzipDir.canonicalPath + File.separator + zipEntry.name.replace('/', File.separatorChar) - if (zipEntry.name.startsWith("res/")) { - val resourceName = ApkUtil.entryToResourceName(zipEntry.name) + if (zipEntry.name.startsWith(obfuscatedResourcesDirectoryName ?: "res/")) { + val resourceName = ApkUtil.entryToResourceName(zipEntry.name, resguardMapping) if (!Util.isNullOrNil(resourceName)) { if (mapOfResourcesGonnaRemoved.containsKey(resourceName)) { - Log.i(TAG, "remove unused resource %s file %s", resourceName, zipEntry.name) + Log.d(TAG, "remove unused resource %s file %s", resourceName, zipEntry.name) continue } else if (mapOfDuplicatesReplacements.containsKey(zipEntry.name)) { - Log.i(TAG, "remove duplicated resource file %s", zipEntry.name) + Log.d(TAG, "remove duplicated resource file %s", zipEntry.name) continue } else { if (arsc != null && isResGuardEnabled) { @@ -247,7 +278,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { val proguardDir = dirProguard.generateNextProguardFileName() resultOfObfuscatedDirs[dir] = "$RES_DIR_PROGUARD_NAME/$proguardDir" dirFileProguard[dir] = ProguardStringBuilder() - Log.i(TAG, "dir %s, proguard builder", dir) + Log.d(TAG, "dir %s, proguard builder", dir) } resultOfObfuscatedFiles[zipEntry.name] = resultOfObfuscatedDirs[dir] + "/" + dirFileProguard[dir]!!.generateNextProguardFileName() + suffix val success = ArscUtil.replaceResFileName(resTable, mapOfResources[resourceName]!!, zipEntry.name, resultOfObfuscatedFiles[zipEntry.name]) @@ -294,7 +325,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { ApkUtil.sevenZipFile(pathOfSevenZip, unzipDir.canonicalPath + "${File.separator}*", toShrunkApkFile.canonicalPath, false) if (compressedEntry.isNotEmpty()) { - Log.i(TAG, "7zip %d DEFLATED files to apk", compressedEntry.size) + Log.d(TAG, "7zip %d DEFLATED files to apk", compressedEntry.size) val deflateDir = File(fromOriginalApkFile.parentFile, fromOriginalApkFile.name.substring(0, fromOriginalApkFile.name.lastIndexOf(".")) + "_deflated") FileUtils.deleteRecursivelyIfExists(deflateDir) deflateDir.mkdir() @@ -336,7 +367,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { } else { val zipEntry = ZipEntry(file.canonicalPath.substring(rootDir.length + 1)) val method = if (compressedEntry.contains(file.canonicalPath)) ZipEntry.DEFLATED else ZipEntry.STORED - Log.i(TAG, "zip file %s -> entry %s, DEFLATED %s", file.canonicalPath, zipEntry.name, method == ZipEntry.DEFLATED) + Log.d(TAG, "zip file %s -> entry %s, DEFLATED %s", file.canonicalPath, zipEntry.name, method == ZipEntry.DEFLATED) zipEntry.method = method ApkUtil.addZipEntry(zipOutputStream, zipEntry, file) } @@ -352,6 +383,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { pathOfOriginalApk: String, pathOfMapping: String, pathOfRTxt: String, + resguardMappingFile: File?, resultOfUnused: MutableSet, resultOfDuplicates: MutableMap> ) { @@ -374,6 +406,11 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { parameters.add(pathOfMapping) } + if (resguardMappingFile?.exists() == true) { + parameters.add("--resMappingTxt") + parameters.add(resguardMappingFile.absolutePath) + } + parameters.add("--format") parameters.add("json") parameters.add("-unusedResources") @@ -483,7 +520,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { resultOfResourcesGonnaRemoved[resName] = resId } } else { - Log.i(TAG, "ignore remove unused resources %s", resName) + Log.d(TAG, "ignore remove unused resources %s", resName) } } @@ -495,17 +532,17 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { val duplicatesNames = HashMap() if (duplicatesEntries != null) { for (entry in duplicatesEntries) { - if (!entry.startsWith("res/")) { + if (!entry.startsWith(obfuscatedResourcesDirectoryName ?: "res/")) { Log.w(TAG, " %s is not resource file!", entry) continue } else { - duplicatesNames[entry] = ApkUtil.entryToResourceName(entry) + duplicatesNames[entry] = ApkUtil.entryToResourceName(entry, resguardMapping) } } } if (duplicatesNames.size > 0) { - if (!ApkUtil.isSameResourceType(duplicatesNames.keys)) { + if (!ApkUtil.isSameResourceType(duplicatesNames.keys, obfuscatedResourcesDirectoryName)) { Log.w(TAG, "the type of duplicated resources %s are not same!", duplicatesEntries) continue } else { @@ -523,7 +560,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { val replace = it.next() while (it.hasNext()) { val dup = it.next() - Log.i(TAG, "replace %s with %s", dup, replace) + Log.d(TAG, "replace %s with %s", dup, replace) resultOfDuplicatesReplacements[dup] = replace } } @@ -551,7 +588,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { if (!checkIfIgnored(resName, regexpRules)) { resultOfObfuscatedNames[resName] = "R." + resType + "." + resTypeBuilder.generateNextProguard() } else { - Log.i(TAG, "ignore proguard resource name %s", resName) + Log.d(TAG, "ignore proguard resource name %s", resName) } } } @@ -581,7 +618,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { } } if (attrs.isNotEmpty() && j == attrs.size) { - Log.i(TAG, "removed styleable $styleable") + Log.d(TAG, "removed styleable $styleable") styleableIterator.remove() } } @@ -645,9 +682,9 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { var startTime = System.currentTimeMillis() val rTxtFile = File(project.buildDir, "intermediates") - .resolve(nameOfSymbolDirectory) - .resolve(variant.name) - .resolve("R.txt") + .resolve(nameOfSymbolDirectory) + .resolve(variant.name) + .resolve("R.txt") val mappingTxtFile = File(project.buildDir, "outputs") .resolve("mapping") @@ -666,6 +703,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { pathOfOriginalApk = originalApkPath, pathOfMapping = mappingTxtFile.absolutePath, pathOfRTxt = rTxtFile.absolutePath, + resguardMappingFile = resguardMappingFile, // result resultOfUnused = setOfUnusedResources, @@ -675,7 +713,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { Log.i(TAG, "find out %d unused resources:\n %s", setOfUnusedResources.size, setOfUnusedResources) Log.i(TAG, "find out %d duplicated resources:", mapOfDuplicatedResources.size) for (md5 in mapOfDuplicatedResources.keys) { - Log.i(TAG, "> md5:%s, files:%s", md5, mapOfDuplicatedResources[md5]) + Log.d(TAG, "> md5:%s, files:%s", md5, mapOfDuplicatedResources[md5]) } Log.i(TAG, "find unused resources cost time %fs ", (System.currentTimeMillis() - startTime) / 1000.0f) @@ -734,7 +772,7 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { // Zip align if (isZipAlignEnabled) { val alignedApk = originalApkFile.parentFile.canonicalPath + File.separator + originalApkFile.name.substring(0, originalApkFile.name.indexOf(".")) + "_aligned.apk" - Log.i(TAG, "Zipalign apk...") + Log.d(TAG, "Zipalign apk...") ApkUtil.zipAlignApk(shrunkApkPath, alignedApk, pathOfZipAlign) shrunkApkFile.delete() FileUtils.copyFile(File(alignedApk), shrunkApkFile) @@ -743,10 +781,27 @@ abstract class RemoveUnusedResourcesTaskV2 : DefaultTask() { // Signing apk if (isSigningEnabled) { - Log.i(TAG, "Signing apk...") + Log.d(TAG, "Signing apk...") ApkUtil.signApk(shrunkApkPath, pathOfApkSigner, signingConfig) } + reportFile?.bufferedWriter()?.use { writer -> + writer.write("removed resources:") + writer.newLine() + mapOfResourcesGonnaRemoved.keys.forEach { name -> + writer.write(" $name") + writer.newLine() + } + writer.newLine() + + writer.write("duplicated resources:") + writer.newLine() + mapOfDuplicatesReplacements.forEach { (origin, replacement) -> + writer.write(" $origin -> $replacement") + writer.newLine() + } + } + // Backup original apk and swap shrunk apk FileUtils.copyFile(originalApkFile, File(fileOfBackup, "backup.apk")) originalApkFile.delete() diff --git a/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/resguard/ResguardMapping.kt b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/resguard/ResguardMapping.kt new file mode 100644 index 000000000..b320bb3df --- /dev/null +++ b/matrix/matrix-android/matrix-gradle-plugin/src/main/kotlin/com/tencent/matrix/resguard/ResguardMapping.kt @@ -0,0 +1,59 @@ +package com.tencent.matrix.resguard + +import java.io.File + +internal class ResguardMapping(mappingFile: File) { + + private enum class ParseState { + None, Path, ID; + } + + private val pathMapping: Map + + private val idMapping: Map> + + companion object { + private val parsePathRegex = "^\\s+(.*) -> (.*)\$".toRegex() + private val parseIDRegex = "^\\s+(\\S+)?R\\.(\\S+?)\\.(\\S+) -> (\\S+)?R\\.(\\S+?)\\.(\\S+)\$".toRegex() + } + + init { + var state = ParseState.None + val pathMappingRaw = mutableMapOf() + val idMappingRaw = mutableMapOf>() + mappingFile.forEachLine { line -> + when { + line == "res path mapping:" -> { + state = ParseState.Path + } + line == "res id mapping:" -> { + state = ParseState.ID + } + line.isEmpty() -> { + state = ParseState.None + } + else -> { + if (state == ParseState.Path) { + parsePathRegex.matchEntire(line)?.groupValues?.let { + pathMappingRaw[it[2]] = it[1] + } + } else if (state == ParseState.ID) { + parseIDRegex.matchEntire(line)?.groupValues?.let { + idMappingRaw.getOrPut(it[2]) { mutableMapOf() }[it[6]] = it[3] + } + } + } + } + } + pathMapping = pathMappingRaw + idMapping = idMappingRaw + } + + fun originPath(path: String): String { + return pathMapping[path] ?: path + } + + fun originID(type: String, name: String): String { + return idMapping[type]?.get(name) ?: name + } +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/CMakeLists.txt b/matrix/matrix-android/matrix-hooks/CMakeLists.txt index 209eb6620..293ab5e25 100644 --- a/matrix/matrix-android/matrix-hooks/CMakeLists.txt +++ b/matrix/matrix-android/matrix-hooks/CMakeLists.txt @@ -95,4 +95,21 @@ target_link_libraries( PRIVATE matrix-hookcommon PRIVATE -Wl,--version-script=${SOURCE_DIR}/pthread/pthread.ver ) +################################################################################# + +################################# ART Misc ################################## +set(TARGET matrix-artmisc) + +add_library( + ${TARGET} + SHARED + ${SOURCE_DIR}/art/RuntimeVerifyMuteJNI.cpp + ${SOURCE_DIR}/art/RuntimeVerifyMute.cpp +) + +target_link_libraries( + ${TARGET} + PRIVATE matrix-hookcommon + PRIVATE -Wl,--version-script=${SOURCE_DIR}/art/art_misc.ver +) ################################################################################# \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.cpp b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.cpp new file mode 100644 index 000000000..748f4e524 --- /dev/null +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.cpp @@ -0,0 +1,69 @@ +// +// Created by tomystang on 2022/11/17. +// + +#include +#include +#include +#include +#include +#include +#include +#include "RuntimeVerifyMute.h" + +#define LOG_TAG "Matrix.RuntimeVerifyMute" + +static bool sInitialized = false; +static std::mutex sInitLock; + +bool matrix::art_misc::Install(JNIEnv* env) { + if (sInitialized) { + LOGI(LOG_TAG, "[!] Already installed."); + return true; + } + std::lock_guard lock(sInitLock); + if (sInitialized) { + LOGI(LOG_TAG, "[!] Already installed."); + return true; + } + + int sdk_ver = android_get_device_api_level(); + if (sdk_ver == -1) { + LOGE(LOG_TAG, "[-] Fail to get sdk version."); + return false; + } + if (sdk_ver < __ANDROID_API_P__) { + LOGE(LOG_TAG, "[-] SDK version is lower than P."); + return false; + } + + void* h_libart = semi_dlopen("libart.so"); + if (h_libart == nullptr) { + LOGE(LOG_TAG, "[-] Fail to open libart.so."); + return false; + } + auto h_libart_cleaner = MakeScopedCleaner([&h_libart]() { + if (h_libart != nullptr) { + semi_dlclose(h_libart); + } + }); + + void** art_runtime_instance_ptr = + reinterpret_cast(semi_dlsym(h_libart, "_ZN3art7Runtime9instance_E")); + if (art_runtime_instance_ptr == nullptr) { + LOGE(LOG_TAG, "[-] Fail to find Runtime::instance_."); + return false; + } + void (*art_runtime_disable_verifier_fn)(void*) = + reinterpret_cast(semi_dlsym(h_libart, "_ZN3art7Runtime15DisableVerifierEv")); + if (art_runtime_disable_verifier_fn == nullptr) { + LOGE(LOG_TAG, "[-] Fail to find Runtime::DisableVerifier()."); + return false; + } + + art_runtime_disable_verifier_fn(*art_runtime_instance_ptr); + + LOGI(LOG_TAG, "[+] Runtime::DisableVerifier() was invoked."); + + return true; +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.h b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.h new file mode 100644 index 000000000..60c62426c --- /dev/null +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMute.h @@ -0,0 +1,16 @@ +// +// Created by tomystang on 2022/11/17. +// + +#ifndef MATRIX_ANDROID_RUNTIMEVERIFYMUTE_H +#define MATRIX_ANDROID_RUNTIMEVERIFYMUTE_H + + +namespace matrix { + namespace art_misc { + bool Install(JNIEnv* env); + } +} + + +#endif //MATRIX_ANDROID_RUNTIMEVERIFYMUTE_H diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMuteJNI.cpp b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMuteJNI.cpp new file mode 100644 index 000000000..0111fb7c8 --- /dev/null +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/RuntimeVerifyMuteJNI.cpp @@ -0,0 +1,11 @@ +// +// Created by tomystang on 2022/11/17. +// + +#include +#include "RuntimeVerifyMute.h" + +extern "C" jboolean JNIEXPORT +Java_com_tencent_matrix_hook_art_RuntimeVerifyMute_nativeInstall(JNIEnv* env, jobject) { + return matrix::art_misc::Install(env) ? JNI_TRUE : JNI_FALSE; +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/art/art_misc.ver b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/art_misc.ver new file mode 100644 index 000000000..9490940fd --- /dev/null +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/art/art_misc.ver @@ -0,0 +1,6 @@ +{ + global: + JNI_OnLoad; + JNI_OnUnload; + Java_*; +}; \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/common/HookCommon.h b/matrix/matrix-android/matrix-hooks/src/main/cpp/common/HookCommon.h index 1e6952c54..885014dec 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/cpp/common/HookCommon.h +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/common/HookCommon.h @@ -27,6 +27,7 @@ #include #include "JNICommon.h" #include "Macros.h" +#include "EnhanceDlsym.h" // 0x01 was occupied by thread priority trace hook in MatrixTracer.cc. #define HOOK_REQUEST_GROUPID_DLOPEN_MON 0x02 @@ -34,6 +35,7 @@ #define HOOK_REQUEST_GROUPID_PTHREAD 0x04 #define HOOK_REQUEST_GROUPID_MEMGUARD 0x05 #define HOOK_REQUEST_GROUPID_MEMGUARD_2 0x06 +#define HOOK_REQUEST_GROUPID_EGL_HOOK 0x07 #define GET_CALLER_ADDR(__caller_addr) \ void * __caller_addr = __builtin_return_address(0) @@ -49,34 +51,30 @@ extern ORIGINAL_FUNC_PTR(sym); \ ret HANDLER_FUNC_NAME(sym)(params); +#define DECLARE_HOOK_ORIG_ATTR(ret, sym, params...) \ + typedef ret (*FUNC_TYPE(sym))(params); \ + extern ORIGINAL_FUNC_PTR(sym); \ + ret HANDLER_FUNC_NAME(sym)(params) + #define DEFINE_HOOK_FUN(ret, sym, params...) \ ORIGINAL_FUNC_PTR(sym); \ ret HANDLER_FUNC_NAME(sym)(params) -#define FETCH_ORIGIN_FUNC(sym) \ - if (!ORIGINAL_FUNC_NAME(sym)) { \ - void *handle = dlopen(ORIGINAL_LIB, RTLD_LAZY); \ +#define FETCH_ORIGIN_FUNC_OF_SO(sym, target_so) \ + if (!ORIGINAL_FUNC_NAME(sym)) { \ + void *handle = dlopen(target_so, RTLD_LAZY); \ if (handle) { \ ORIGINAL_FUNC_NAME(sym) = (FUNC_TYPE(sym))dlsym(handle, #sym); \ } \ - } + }\ -#define CALL_ORIGIN_FUNC_RET(retType, ret, sym, params...) \ - if (!ORIGINAL_FUNC_NAME(sym)) { \ - void *handle = dlopen(ORIGINAL_LIB, RTLD_LAZY); \ - if (handle) { \ - ORIGINAL_FUNC_NAME(sym) = (FUNC_TYPE(sym))dlsym(handle, #sym); \ - } \ - } \ +#define FETCH_ORIGIN_FUNC(sym) \ + FETCH_ORIGIN_FUNC_OF_SO(sym, ORIGINAL_LIB) + +#define CALL_ORIGIN_FUNC_RET(handle, retType, ret, sym, params...) \ retType ret = ORIGINAL_FUNC_NAME(sym)(params) -#define CALL_ORIGIN_FUNC_VOID(sym, params...) \ - if (!ORIGINAL_FUNC_NAME(sym)) { \ - void *handle = dlopen(ORIGINAL_LIB, RTLD_LAZY); \ - if (handle) { \ - ORIGINAL_FUNC_NAME(sym) = (FUNC_TYPE(sym))dlsym(handle, #sym); \ - } \ - } \ +#define CALL_ORIGIN_FUNC_VOID(handle, sym, params...) \ ORIGINAL_FUNC_NAME(sym)(params) #define NOTIFY_COMMON_IGNORE_LIBS(group_id) \ @@ -100,20 +98,13 @@ xhook_grouped_ignore(group_id, ".*/libmatrix-opengl-leak\\.so$", NULL); \ xhook_grouped_ignore(group_id, ".*/libmatrix-memguard\\.so$", NULL);\ xhook_grouped_ignore(group_id, ".*/libTcpOptimizer\\.mobiledata\\.samsung\\.so$", NULL); \ + xhook_grouped_ignore(group_id, ".*/libmatrix-traffic\\.so$", NULL);\ } while (0) -#include - #ifdef __cplusplus extern "C" { #endif -typedef struct { - const char *name; - void *handler_ptr; - void **origin_ptr; -} HookFunction; - EXPORT bool get_java_stacktrace(char *stack_dst, size_t size); #ifdef __cplusplus diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.cpp b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.cpp index 6755b58e3..ae025fb7e 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.cpp +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.cpp @@ -26,6 +26,7 @@ #include "MemoryHook.h" #define ORIGINAL_LIB "libc.so" +static void *libc_handle = nullptr; #define DO_HOOK_ACQUIRE(p, size) \ GET_CALLER_ADDR(caller); \ @@ -35,21 +36,21 @@ on_free_memory(p) DEFINE_HOOK_FUN(void *, malloc, size_t __byte_count) { - CALL_ORIGIN_FUNC_RET(void*, p, malloc, __byte_count); + CALL_ORIGIN_FUNC_RET(libc_handle, void*, p, malloc, __byte_count); LOGI(TAG, "+ malloc %p", p); DO_HOOK_ACQUIRE(p, __byte_count); return p; } DEFINE_HOOK_FUN(void *, calloc, size_t __item_count, size_t __item_size) { - CALL_ORIGIN_FUNC_RET(void *, p, calloc, __item_count, __item_size); + CALL_ORIGIN_FUNC_RET(libc_handle, void *, p, calloc, __item_count, __item_size); LOGI(TAG, "+ calloc %p", p); DO_HOOK_ACQUIRE(p, __item_count * __item_size); return p; } DEFINE_HOOK_FUN(void *, realloc, void *__ptr, size_t __byte_count) { - CALL_ORIGIN_FUNC_RET(void *, p, realloc, __ptr, __byte_count); + CALL_ORIGIN_FUNC_RET(libc_handle, void *, p, realloc, __ptr, __byte_count); GET_CALLER_ADDR(caller); @@ -76,14 +77,14 @@ DEFINE_HOOK_FUN(void *, realloc, void *__ptr, size_t __byte_count) { } DEFINE_HOOK_FUN(void *, memalign, size_t __alignment, size_t __byte_count) { - CALL_ORIGIN_FUNC_RET(void *, p, memalign, __alignment, __byte_count); + CALL_ORIGIN_FUNC_RET(libc_handle, void *, p, memalign, __alignment, __byte_count); LOGI(TAG, "+ memalign %p", p); DO_HOOK_ACQUIRE(p, __byte_count); return p; } DEFINE_HOOK_FUN(int, posix_memalign, void** __memptr, size_t __alignment, size_t __size) { - CALL_ORIGIN_FUNC_RET(int, ret, posix_memalign, __memptr, __alignment, __size); + CALL_ORIGIN_FUNC_RET(libc_handle, int, ret, posix_memalign, __memptr, __alignment, __size); if (ret == 0) { LOGI(TAG, "+ posix_memalign %p", *__memptr); DO_HOOK_ACQUIRE(*__memptr, __size); @@ -94,7 +95,7 @@ DEFINE_HOOK_FUN(int, posix_memalign, void** __memptr, size_t __alignment, size_t DEFINE_HOOK_FUN(void, free, void *__ptr) { LOGI(TAG, "- free %p", __ptr); DO_HOOK_RELEASE(__ptr); - CALL_ORIGIN_FUNC_VOID(free, __ptr); + CALL_ORIGIN_FUNC_VOID(libc_handle, free, __ptr); } #if defined(__USE_FILE_OFFSET64) @@ -108,7 +109,7 @@ void*h_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_ DEFINE_HOOK_FUN(void *, mmap, void *__addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset) { - CALL_ORIGIN_FUNC_RET(void *, p, mmap, __addr, __size, __prot, __flags, __fd, __offset); + CALL_ORIGIN_FUNC_RET(libc_handle, void *, p, mmap, __addr, __size, __prot, __flags, __fd, __offset); if (p == MAP_FAILED) { return p;// just return } @@ -122,9 +123,8 @@ DEFINE_HOOK_FUN(void *, mmap, void *__addr, size_t __size, int __prot, int __fla #endif #if __ANDROID_API__ >= __ANDROID_API_L__ - -void *h_mmap64(void *__addr, size_t __size, int __prot, int __flags, int __fd, - off64_t __offset) __INTRODUCED_IN(21) { +DEFINE_HOOK_FUN(void *, mmap64, void *__addr, size_t __size, int __prot, int __flags, int __fd, + off64_t __offset) { void *p = mmap64(__addr, __size, __prot, __flags, __fd, __offset); if (p == MAP_FAILED) { return p;// just return @@ -134,7 +134,6 @@ void *h_mmap64(void *__addr, size_t __size, int __prot, int __flags, int __fd, on_mmap_memory(caller, p, __size); return p; } - #endif DEFINE_HOOK_FUN(void *, mremap, void *__old_addr, size_t __old_size, size_t __new_size, int __flags, @@ -167,14 +166,14 @@ DEFINE_HOOK_FUN(int, munmap, void *__addr, size_t __size) { } DEFINE_HOOK_FUN(char*, strdup, const char *str) { - CALL_ORIGIN_FUNC_RET(char *, p, strdup, str); + CALL_ORIGIN_FUNC_RET(libc_handle, char *, p, strdup, str); LOGI(TAG, "+ strdup %p", (void *)p); DO_HOOK_ACQUIRE(p, sizeof(str)); return p; } DEFINE_HOOK_FUN(char*, strndup, const char *str, size_t n) { - CALL_ORIGIN_FUNC_RET(char *, p, strndup, str, n); + CALL_ORIGIN_FUNC_RET(libc_handle, char *, p, strndup, str, n); LOGI(TAG, "+ strndup %p", (void *)p); DO_HOOK_ACQUIRE(p, sizeof(str) < n ? sizeof(str) : n); return p; @@ -182,18 +181,19 @@ DEFINE_HOOK_FUN(char*, strndup, const char *str, size_t n) { #undef ORIGINAL_LIB #define ORIGINAL_LIB "libc++_shared.so" +static void *libcxx_handle = nullptr; #ifndef __LP64__ DEFINE_HOOK_FUN(void*, _Znwj, size_t size) { - CALL_ORIGIN_FUNC_RET(void*, p, _Znwj, size); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _Znwj, size); LOGI(TAG, "+ _Znwj %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnwjSt11align_val_t, size_t size, std::align_val_t align_val) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnwjSt11align_val_t, size, align_val); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnwjSt11align_val_t, size, align_val); LOGI(TAG, "- _ZnwjSt11align_val_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -201,28 +201,28 @@ DEFINE_HOOK_FUN(void*, _ZnwjSt11align_val_t, size_t size, std::align_val_t align DEFINE_HOOK_FUN(void*, _ZnwjSt11align_val_tRKSt9nothrow_t, size_t size, std::align_val_t align_val, std::nothrow_t const& nothrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnwjSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnwjSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); LOGI(TAG, "+ _ZnwjSt11align_val_tRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnwjRKSt9nothrow_t, size_t size, std::nothrow_t const& nothrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnwjRKSt9nothrow_t, size, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnwjRKSt9nothrow_t, size, nothrow); LOGI(TAG, "+ _ZnwjRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _Znaj, size_t size) { - CALL_ORIGIN_FUNC_RET(void*, p, _Znaj, size); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _Znaj, size); LOGI(TAG, "+ _Znaj %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnajSt11align_val_t, size_t size, std::align_val_t align_val) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnajSt11align_val_t, size, align_val); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnajSt11align_val_t, size, align_val); LOGI(TAG, "+ _ZnajSt11align_val_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -230,14 +230,14 @@ DEFINE_HOOK_FUN(void*, _ZnajSt11align_val_t, size_t size, std::align_val_t align DEFINE_HOOK_FUN(void*, _ZnajSt11align_val_tRKSt9nothrow_t, size_t size, std::align_val_t align_val, std::nothrow_t const& nothrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnajSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnajSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); LOGI(TAG, "+ _ZnajSt11align_val_tRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnajRKSt9nothrow_t, size_t size, std::nothrow_t const& nothrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnajRKSt9nothrow_t, size, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnajRKSt9nothrow_t, size, nothrow); LOGI(TAG, "+ _ZnajRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -246,40 +246,40 @@ DEFINE_HOOK_FUN(void*, _ZnajRKSt9nothrow_t, size_t size, std::nothrow_t const& n DEFINE_HOOK_FUN(void, _ZdaPvj, void* ptr, size_t size) { LOGI(TAG, "- _ZdaPvj %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvj, ptr, size); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvj, ptr, size); } DEFINE_HOOK_FUN(void, _ZdaPvjSt11align_val_t, void* ptr, size_t size, std::align_val_t align_val) { LOGI(TAG, "- _ZdaPvjSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvjSt11align_val_t, ptr, size, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvjSt11align_val_t, ptr, size, align_val); } DEFINE_HOOK_FUN(void, _ZdlPvj, void* ptr, size_t size) { LOGI(TAG, "- _ZdlPvj %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvj, ptr, size); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvj, ptr, size); } DEFINE_HOOK_FUN(void, _ZdlPvjSt11align_val_t, void* ptr, size_t size, std::align_val_t align_val) { LOGI(TAG, "- _ZdlPvjSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvjSt11align_val_t, ptr, size, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvjSt11align_val_t, ptr, size, align_val); } #else DEFINE_HOOK_FUN(void*, _Znwm, size_t size) { - CALL_ORIGIN_FUNC_RET(void*, p, _Znwm, size); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _Znwm, size); LOGI(TAG, "+ _Znwm %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnwmSt11align_val_t, size_t size, std::align_val_t align_val) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnwmSt11align_val_t, size, align_val); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnwmSt11align_val_t, size, align_val); LOGI(TAG, "+ _ZnwmSt11align_val_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -288,28 +288,28 @@ DEFINE_HOOK_FUN(void*, _ZnwmSt11align_val_t, size_t size, std::align_val_t align DEFINE_HOOK_FUN(void*, _ZnwmSt11align_val_tRKSt9nothrow_t, size_t size, std::align_val_t align_val, std::nothrow_t const ¬hrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnwmSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnwmSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); LOGI(TAG, "+ _ZnwmSt11align_val_tRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const ¬hrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _Znwm, size); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _Znwm, size); LOGI(TAG, "+ _ZnwmRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _Znam, size_t size) { - CALL_ORIGIN_FUNC_RET(void*, p, _Znam, size); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _Znam, size); LOGI(TAG, "+ _Znam %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnamSt11align_val_t, size_t size, std::align_val_t align_val) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnamSt11align_val_t, size, align_val); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnamSt11align_val_t, size, align_val); LOGI(TAG, "+ _ZnamSt11align_val_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -318,14 +318,14 @@ DEFINE_HOOK_FUN(void*, _ZnamSt11align_val_t, size_t size, std::align_val_t align DEFINE_HOOK_FUN(void*, _ZnamSt11align_val_tRKSt9nothrow_t, size_t size, std::align_val_t align_val, std::nothrow_t const ¬hrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnamSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnamSt11align_val_tRKSt9nothrow_t, size, align_val, nothrow); LOGI(TAG, "+ _ZnamSt11align_val_tRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; } DEFINE_HOOK_FUN(void*, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const ¬hrow) { - CALL_ORIGIN_FUNC_RET(void*, p, _ZnamRKSt9nothrow_t, size, nothrow); + CALL_ORIGIN_FUNC_RET(libcxx_handle, void*, p, _ZnamRKSt9nothrow_t, size, nothrow); LOGI(TAG, "+ _ZnamRKSt9nothrow_t %p", p); DO_HOOK_ACQUIRE(p, size); return p; @@ -334,27 +334,27 @@ DEFINE_HOOK_FUN(void*, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const &n DEFINE_HOOK_FUN(void, _ZdlPvm, void *ptr, size_t size) { LOGI(TAG, "- _ZdlPvm %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvm, ptr, size); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvm, ptr, size); } DEFINE_HOOK_FUN(void, _ZdlPvmSt11align_val_t, void *ptr, size_t size, std::align_val_t align_val) { LOGI(TAG, "- _ZdlPvmSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvmSt11align_val_t, ptr, size, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvmSt11align_val_t, ptr, size, align_val); } DEFINE_HOOK_FUN(void, _ZdaPvm, void *ptr, size_t size) { LOGI(TAG, "- _ZdaPvm %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvm, ptr, size); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvm, ptr, size); } DEFINE_HOOK_FUN(void, _ZdaPvmSt11align_val_t, void *ptr, size_t size, std::align_val_t align_val) { LOGI(TAG, "- _ZdaPvmSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvmSt11align_val_t, ptr, size, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvmSt11align_val_t, ptr, size, align_val); } #endif @@ -362,13 +362,13 @@ DEFINE_HOOK_FUN(void, _ZdaPvmSt11align_val_t, void *ptr, size_t size, DEFINE_HOOK_FUN(void, _ZdlPv, void *p) { LOGI(TAG, "- _ZdlPv %p", p); DO_HOOK_RELEASE(p); - CALL_ORIGIN_FUNC_VOID(_ZdlPv, p); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPv, p); } DEFINE_HOOK_FUN(void, _ZdlPvSt11align_val_t, void *ptr, std::align_val_t align_val) { LOGI(TAG, "- _ZdlPvSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvSt11align_val_t, ptr, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvSt11align_val_t, ptr, align_val); } DEFINE_HOOK_FUN(void, _ZdlPvSt11align_val_tRKSt9nothrow_t, void *ptr, @@ -376,25 +376,25 @@ DEFINE_HOOK_FUN(void, _ZdlPvSt11align_val_tRKSt9nothrow_t, void *ptr, std::nothrow_t const ¬hrow) { LOGI(TAG, "- _ZdlPvSt11align_val_tRKSt9nothrow_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvSt11align_val_tRKSt9nothrow_t, ptr, align_val, nothrow); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvSt11align_val_tRKSt9nothrow_t, ptr, align_val, nothrow); } DEFINE_HOOK_FUN(void, _ZdlPvRKSt9nothrow_t, void *ptr, std::nothrow_t const ¬hrow) { LOGI(TAG, "- _ZdlPvRKSt9nothrow_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdlPvRKSt9nothrow_t, ptr, nothrow); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdlPvRKSt9nothrow_t, ptr, nothrow); } DEFINE_HOOK_FUN(void, _ZdaPv, void *ptr) { LOGI(TAG, "- _ZdaPv %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPv, ptr); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPv, ptr); } DEFINE_HOOK_FUN(void, _ZdaPvSt11align_val_t, void *ptr, std::align_val_t align_val) { LOGI(TAG, "- _ZdaPvSt11align_val_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvSt11align_val_t, ptr, align_val); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvSt11align_val_t, ptr, align_val); } DEFINE_HOOK_FUN(void, _ZdaPvSt11align_val_tRKSt9nothrow_t, void *ptr, @@ -402,13 +402,13 @@ DEFINE_HOOK_FUN(void, _ZdaPvSt11align_val_tRKSt9nothrow_t, void *ptr, std::nothrow_t const ¬hrow) { LOGI(TAG, "- _ZdaPvSt11align_val_tRKSt9nothrow_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvSt11align_val_tRKSt9nothrow_t, ptr, align_val, nothrow); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvSt11align_val_tRKSt9nothrow_t, ptr, align_val, nothrow); } DEFINE_HOOK_FUN(void, _ZdaPvRKSt9nothrow_t, void *ptr, std::nothrow_t const ¬hrow) { LOGI(TAG, "- _ZdaPvRKSt9nothrow_t %p", ptr); DO_HOOK_RELEASE(ptr); - CALL_ORIGIN_FUNC_VOID(_ZdaPvRKSt9nothrow_t, ptr, nothrow); + CALL_ORIGIN_FUNC_VOID(libcxx_handle, _ZdaPvRKSt9nothrow_t, ptr, nothrow); } #undef ORIGINAL_LIB \ No newline at end of file diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.h b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.h index 11ec15d7e..9dd9c26f7 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.h +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookFunctions.h @@ -47,16 +47,14 @@ DECLARE_HOOK_ORIG(void *, memalign, size_t __alignment, size_t __byte_count); DECLARE_HOOK_ORIG(int, posix_memalign, void** __memptr, size_t __alignment, size_t __size); #if defined(__USE_FILE_OFFSET64) -// DECLARE_HOOK_ORIG not supports attrbute -void *h_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset) __RENAME(mmap64); +DECLARE_HOOK_ORIG_ATTR(void *, mmap, void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset) __RENAME(mmap64); #else DECLARE_HOOK_ORIG(void *, mmap, void *__addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset); #endif #if __ANDROID_API__ >= __ANDROID_API_L__ -// DECLARE_HOOK_ORIG not supports attrbute -void *h_mmap64(void *__addr, size_t __size, int __prot, int __flags, int __fd, - off64_t __offset) __INTRODUCED_IN(21); +DECLARE_HOOK_ORIG_ATTR(void *, mmap64, void *__addr, size_t __size, int __prot, int __flags, int __fd, + off64_t __offset) __INTRODUCED_IN(21); #endif DECLARE_HOOK_ORIG(void *, mremap, void*, size_t, size_t, int, ...) diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookJNI.cpp b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookJNI.cpp index 3fad302ab..f48858978 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookJNI.cpp +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/memory/MemoryHookJNI.cpp @@ -29,83 +29,88 @@ #ifdef __cplusplus extern "C" { #endif -// @formatter:off -const HookFunction HOOK_MALL_FUNCTIONS[] = { - {"malloc", (void *) h_malloc, NULL}, - {"calloc", (void *) h_calloc, NULL}, - {"realloc", (void *) h_realloc, NULL}, - {"free", (void *) h_free, NULL}, - {"memalign", (void *) HANDLER_FUNC_NAME(memalign), NULL}, - {"posix_memalign", (void *) HANDLER_FUNC_NAME(posix_memalign), NULL}, - // CXX functions -#ifndef __LP64__ - {"_Znwj", (void*) HANDLER_FUNC_NAME(_Znwj), NULL}, - {"_ZnwjSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnwjSt11align_val_t), NULL}, - {"_ZnwjSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwjSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZnwjRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwjRKSt9nothrow_t), NULL}, - - {"_Znaj", (void*) HANDLER_FUNC_NAME(_Znaj), NULL}, - {"_ZnajSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnajSt11align_val_t), NULL}, - {"_ZnajSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnajSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZnajRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnajRKSt9nothrow_t), NULL}, - - {"_ZdlPvj", (void*) HANDLER_FUNC_NAME(_ZdlPvj), NULL}, - {"_ZdlPvjSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdlPvjSt11align_val_t), NULL}, - {"_ZdaPvj", (void*) HANDLER_FUNC_NAME(_ZdaPvj), NULL}, - {"_ZdaPvjSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdaPvjSt11align_val_t), NULL}, -#else - {"_Znwm", (void*) HANDLER_FUNC_NAME(_Znwm), NULL}, - {"_ZnwmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnwmSt11align_val_t), NULL}, - {"_ZnwmSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwmSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZnwmRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwmRKSt9nothrow_t), NULL}, - - {"_Znam", (void*) HANDLER_FUNC_NAME(_Znam), NULL}, - {"_ZnamSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnamSt11align_val_t), NULL}, - {"_ZnamSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnamSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZnamRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnamRKSt9nothrow_t), NULL}, - - {"_ZdlPvm", (void*) HANDLER_FUNC_NAME(_ZdlPvm), NULL}, - {"_ZdlPvmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdlPvmSt11align_val_t), NULL}, - {"_ZdaPvm", (void*) HANDLER_FUNC_NAME(_ZdaPvm), NULL}, - {"_ZdaPvmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdaPvmSt11align_val_t), NULL}, -#endif - {"_ZdlPv", (void*) HANDLER_FUNC_NAME(_ZdlPv), NULL}, - {"_ZdlPvSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdlPvSt11align_val_t), NULL}, - {"_ZdlPvSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdlPvSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZdlPvRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdlPvRKSt9nothrow_t), NULL}, - - {"_ZdaPv", (void*) HANDLER_FUNC_NAME(_ZdaPv), NULL}, - {"_ZdaPvSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdaPvSt11align_val_t), NULL}, - {"_ZdaPvSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdaPvSt11align_val_tRKSt9nothrow_t), NULL}, - {"_ZdaPvRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdaPvRKSt9nothrow_t), NULL}, - - {"strdup", (void*) HANDLER_FUNC_NAME(strdup), (void **) ORIGINAL_FUNC_NAME(strdup)}, - {"strndup", (void*) HANDLER_FUNC_NAME(strndup), (void **) ORIGINAL_FUNC_NAME(strndup)}, -}; - -static const HookFunction HOOK_MMAP_FUNCTIONS[] = { - {"mmap", (void *) h_mmap, NULL}, - {"munmap", (void *) h_munmap, NULL}, - {"mremap", (void *) h_mremap, NULL}, -#if __ANDROID_API__ >= __ANDROID_API_L__ - {"mmap64", (void *) h_mmap64, NULL}, -#endif -}; -// @formatter:on + +#define HOOK_REGISTER(regex, target_sym, target_so) \ + do { \ + FETCH_ORIGIN_FUNC_OF_SO(target_sym, target_so); \ + if(!ORIGINAL_FUNC_NAME(target_sym)) { \ + LOGE(TAG, "hook failed: fetch origin func failed: %s", #target_sym); \ + break; \ + } \ + int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, #target_sym, (void*) HANDLER_FUNC_NAME(target_sym), nullptr); \ + LOGD(TAG, "hook fn, regex: %s, sym: %s, ret: %d", regex, #target_sym, ret); \ + } while(0); + +#define HOOK_REGISTER_REGEX_LIBC(target_sym) \ + HOOK_REGISTER(regex, target_sym, "libc.so") + +#define HOOK_REGISTER_REGEX_LIBCXX(target_sym) \ + HOOK_REGISTER(regex, target_sym, "libc++_shared.so") + bool enable_mmap_hook = false; static void hook(const char *regex) { + HOOK_REGISTER_REGEX_LIBC(malloc) + HOOK_REGISTER_REGEX_LIBC(calloc) + HOOK_REGISTER_REGEX_LIBC(realloc) + HOOK_REGISTER_REGEX_LIBC(free) + + HOOK_REGISTER_REGEX_LIBC(memalign) + HOOK_REGISTER_REGEX_LIBC(posix_memalign) + HOOK_REGISTER_REGEX_LIBC(strdup) + HOOK_REGISTER_REGEX_LIBC(strndup) + + // CXX functions +#ifndef __LP64__ + HOOK_REGISTER_REGEX_LIBCXX(_Znwj) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwjSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwjSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwjRKSt9nothrow_t) + + HOOK_REGISTER_REGEX_LIBCXX(_Znaj) + HOOK_REGISTER_REGEX_LIBCXX(_ZnajSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnajSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnajRKSt9nothrow_t) + + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvj) + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvjSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvj) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvjSt11align_val_t) +#else + HOOK_REGISTER_REGEX_LIBCXX(_Znwm) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwmSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwmSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnwmRKSt9nothrow_t) + + HOOK_REGISTER_REGEX_LIBCXX(_Znam) + HOOK_REGISTER_REGEX_LIBCXX(_ZnamSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnamSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZnamRKSt9nothrow_t) + + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvm) + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvmSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvm) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvmSt11align_val_t) +#endif + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPv) + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdlPvRKSt9nothrow_t) + + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPv) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvSt11align_val_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvSt11align_val_tRKSt9nothrow_t) + HOOK_REGISTER_REGEX_LIBCXX(_ZdaPvRKSt9nothrow_t) - for (auto f : HOOK_MALL_FUNCTIONS) { - int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, f.name, f.handler_ptr, f.origin_ptr); - LOGD(TAG, "hook fn, regex: %s, sym: %s, ret: %d", regex, f.name, ret); - } LOGD(TAG, "mmap enabled ? %d", enable_mmap_hook); if (enable_mmap_hook) { - for (auto f: HOOK_MMAP_FUNCTIONS) { - xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, f.name, f.handler_ptr, f.origin_ptr); - } + HOOK_REGISTER_REGEX_LIBC(mmap) + HOOK_REGISTER_REGEX_LIBC(munmap) + HOOK_REGISTER_REGEX_LIBC(mremap) +#if __ANDROID_API__ >= __ANDROID_API_L__ + HOOK_REGISTER_REGEX_LIBC(mmap64) +#endif } } diff --git a/matrix/matrix-android/matrix-hooks/src/main/cpp/pthread/PthreadHook.cpp b/matrix/matrix-android/matrix-hooks/src/main/cpp/pthread/PthreadHook.cpp index 8f9b64cad..56a3307ae 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/cpp/pthread/PthreadHook.cpp +++ b/matrix/matrix-android/matrix-hooks/src/main/cpp/pthread/PthreadHook.cpp @@ -32,6 +32,7 @@ #define LOG_TAG "Matrix.PthreadHook" #define ORIGINAL_LIB "libc.so" +static void *pthread_handle = nullptr; static volatile bool sThreadTraceEnabled = false; static volatile bool sThreadStackShrinkEnabled = false; @@ -67,11 +68,11 @@ DEFINE_HOOK_FUN(int, pthread_create, int ret = 0; if (sThreadTraceEnabled) { auto *routine_wrapper = thread_trace::wrap_pthread_routine(start_routine, args); - CALL_ORIGIN_FUNC_RET(int, tmpRet, pthread_create, pthread, &tmpAttr, routine_wrapper->wrapped_func, + CALL_ORIGIN_FUNC_RET(pthread_handle, int, tmpRet, pthread_create, pthread, &tmpAttr, routine_wrapper->wrapped_func, routine_wrapper); ret = tmpRet; } else { - CALL_ORIGIN_FUNC_RET(int, tmpRet, pthread_create, pthread, &tmpAttr, start_routine, args); + CALL_ORIGIN_FUNC_RET(pthread_handle, int, tmpRet, pthread_create, pthread, &tmpAttr, start_routine, args); ret = tmpRet; } @@ -87,7 +88,7 @@ DEFINE_HOOK_FUN(int, pthread_create, } DEFINE_HOOK_FUN(int, pthread_setname_np, pthread_t pthread, const char* name) { - CALL_ORIGIN_FUNC_RET(int, ret, pthread_setname_np, pthread, name); + CALL_ORIGIN_FUNC_RET(pthread_handle, int, ret, pthread_setname_np, pthread, name); if (LIKELY(ret == 0) && sThreadTraceEnabled) { thread_trace::handle_pthread_setname_np(pthread, name); } @@ -95,7 +96,7 @@ DEFINE_HOOK_FUN(int, pthread_setname_np, pthread_t pthread, const char* name) { } DEFINE_HOOK_FUN(int, pthread_detach, pthread_t pthread) { - CALL_ORIGIN_FUNC_RET(int, ret, pthread_detach, pthread); + CALL_ORIGIN_FUNC_RET(pthread_handle, int, ret, pthread_detach, pthread); LOGD(LOG_TAG, "pthread_detach : %d", ret); if (LIKELY(ret == 0) && sThreadTraceEnabled) { thread_trace::handle_pthread_release(pthread); @@ -104,7 +105,7 @@ DEFINE_HOOK_FUN(int, pthread_detach, pthread_t pthread) { } DEFINE_HOOK_FUN(int, pthread_join, pthread_t pthread, void** return_value_ptr) { - CALL_ORIGIN_FUNC_RET(int, ret, pthread_join, pthread, return_value_ptr); + CALL_ORIGIN_FUNC_RET(pthread_handle, int, ret, pthread_join, pthread, return_value_ptr); LOGD(LOG_TAG, "pthread_join : %d", ret); if (LIKELY(ret == 0) && sThreadTraceEnabled) { thread_trace::handle_pthread_release(pthread); diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/AbsHook.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/AbsHook.java index a7f142c87..fea1c8225 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/AbsHook.java +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/AbsHook.java @@ -16,7 +16,7 @@ package com.tencent.matrix.hook; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; /** * Created by Yves on 2020-03-18 @@ -40,7 +40,7 @@ public Status getStatus() { return mStatus; } - @Nullable + @NonNull protected abstract String getNativeLibraryName(); protected abstract boolean onConfigure(); diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/HookManager.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/HookManager.java index 5db9f99db..70759879a 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/HookManager.java +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/HookManager.java @@ -16,6 +16,7 @@ package com.tencent.matrix.hook; +import android.os.Build; import android.text.TextUtils; import androidx.annotation.Keep; @@ -24,6 +25,10 @@ import com.tencent.matrix.util.MatrixLog; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.HashSet; import java.util.Set; @@ -83,6 +88,47 @@ public void commitHooks() throws HookFailedException { } } + private boolean enableLibCxxSharedCheck = false; + public HookManager enableLibCxxSharedCheck(boolean enable) { + enableLibCxxSharedCheck = enable; + return this; + } + + private boolean checkLibCxxSharedLoaded() { + if (Build.VERSION.SDK_INT != Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT != Build.VERSION_CODES.LOLLIPOP_MR1) { + return true; + } + + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/maps")))) { + String line; + while ((line = br.readLine()) != null) { + if (line.endsWith("libc++_shared.so")) { + return true; + } + } + } catch (IOException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + + return false; + } + + private void ensureLibCxxSharedLoadedForLollipop() throws RuntimeException { + if (!enableLibCxxSharedCheck) { + return; + } + enableLibCxxSharedCheck = false; // mark loaded + if (checkLibCxxSharedLoaded()) { + return; + } + if (mNativeLibLoader != null) { + mNativeLibLoader.loadLibrary("c++_shared"); + } else { + System.loadLibrary("c++_shared"); + } + } + + private void commitHooksLocked() throws HookFailedException { synchronized (mPendingHooks) { for (AbsHook hook : mPendingHooks) { @@ -91,6 +137,7 @@ private void commitHooksLocked() throws HookFailedException { continue; } try { + ensureLibCxxSharedLoadedForLollipop(); if (mNativeLibLoader != null) { mNativeLibLoader.loadLibrary(nativeLibName); } else { @@ -160,7 +207,11 @@ public HookManager clearHooks() { @Keep public static String getStack() { - return stackTraceToString(Thread.currentThread().getStackTrace()); + try { + return stackTraceToString(Thread.currentThread().getStackTrace()); + } catch (Throwable e) { + return "ERROR: " + stackTraceToString(e.getStackTrace()); + } } private static String stackTraceToString(final StackTraceElement[] arr) { diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/art/RuntimeVerifyMute.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/art/RuntimeVerifyMute.java new file mode 100644 index 000000000..07efcc95f --- /dev/null +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/art/RuntimeVerifyMute.java @@ -0,0 +1,56 @@ +package com.tencent.matrix.hook.art; + +import androidx.annotation.Nullable; + +import com.tencent.matrix.hook.HookManager.NativeLibraryLoader; +import com.tencent.matrix.util.MatrixLog; + +/** + * Created by tomystang on 2022/11/16. + */ +public final class RuntimeVerifyMute { + private static final String TAG = "Matrix.RuntimeVerifyMute"; + + public static final RuntimeVerifyMute INSTANCE = new RuntimeVerifyMute(); + + private NativeLibraryLoader mNativeLibLoader = null; + private boolean mNativeLibLoaded = false; + + public RuntimeVerifyMute setNativeLibraryLoader(@Nullable NativeLibraryLoader loader) { + mNativeLibLoader = loader; + return this; + } + + private boolean ensureNativeLibLoaded() { + synchronized (this) { + if (mNativeLibLoaded) { + return true; + } + try { + if (mNativeLibLoader != null) { + mNativeLibLoader.loadLibrary("matrix-hookcommon"); + mNativeLibLoader.loadLibrary("matrix-artmisc"); + } else { + System.loadLibrary("matrix-hookcommon"); + System.loadLibrary("matrix-artmisc"); + } + mNativeLibLoaded = true; + } catch (Throwable thr) { + MatrixLog.printErrStackTrace(TAG, thr, "Fail to load native library."); + mNativeLibLoaded = false; + } + return mNativeLibLoaded; + } + } + + public boolean install() { + if (!ensureNativeLibLoaded()) { + return false; + } + return nativeInstall(); + } + + private static native boolean nativeInstall(); + + private RuntimeVerifyMute() { } +} diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/MemoryHook.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/MemoryHook.java index 59441b1f7..24b4d10bc 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/MemoryHook.java +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/MemoryHook.java @@ -19,7 +19,7 @@ import android.text.TextUtils; import androidx.annotation.Keep; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import com.tencent.matrix.hook.AbsHook; import com.tencent.matrix.hook.HookManager; @@ -123,7 +123,7 @@ public void hook() throws HookManager.HookFailedException { .commitHooks(); } - @Nullable + @NonNull @Override protected String getNativeLibraryName() { return "matrix-memoryhook"; diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/WVPreAllocHook.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/WVPreAllocHook.java index f8bfb2d28..ced401446 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/WVPreAllocHook.java +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/memory/WVPreAllocHook.java @@ -18,14 +18,14 @@ import android.os.Build; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import com.tencent.matrix.hook.AbsHook; public class WVPreAllocHook extends AbsHook { public static final WVPreAllocHook INSTANCE = new WVPreAllocHook(); - @Nullable + @NonNull @Override protected String getNativeLibraryName() { return "matrix-memoryhook"; diff --git a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/pthread/PthreadHook.java b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/pthread/PthreadHook.java index 4cbf51a44..1cb7917fc 100644 --- a/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/pthread/PthreadHook.java +++ b/matrix/matrix-android/matrix-hooks/src/main/java/com/tencent/matrix/hook/pthread/PthreadHook.java @@ -146,7 +146,7 @@ public void enableLogger(boolean enable) { } } - @Nullable + @NonNull @Override protected String getNativeLibraryName() { return "matrix-pthreadhook"; diff --git a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeCtl.cpp b/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeCtl.cpp deleted file mode 100644 index d79cd10c1..000000000 --- a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeCtl.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making wechat-matrix available. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * 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. - */ - -// -// Created by Yves on 2020/7/15. -// - -#include -#include -#include -#include -#include -#include "EnhanceDlsym.h" -#include "JeLog.h" -#include "JeHooks.h" -#include -//#include "internal/arena_structs_b.h" - -#define JECTL_OK 0 -#define ERR_INIT_FAILED 1 -#define ERR_VERSION 2 -#define ERR_64_BIT 3 -#define ERR_CTL 4 -#define ERR_ALLOC_FAILED 5 - -#define CACHELINE 64 - -#define TAG "Matrix.JeCtl" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef int (*mallctl_t)(const char *name, - void *oldp, - size_t *oldlenp, - void *newp, - size_t newlen); - -typedef void *(*arena_extent_alloc_large_t)(void *tsdn, - void *arena, - size_t usize, - size_t alignment, - bool *zero); - -typedef void (*large_dalloc_t)(void *tsdn, void *extent); - -typedef void *(*arena_choose_hard_t)(void *tsd, bool internal); - -typedef void (*arena_extent_dalloc_large_prep_t)(void *tsdn, void *arena, void *extent); - -typedef void (*arena_extents_dirty_dalloc_t)(void *tsdn, void *arena, - extent_hooks_t **r_extent_hooks, void *extent); - -#define MAX_RETRY_TIMES 10 - -void *handle = nullptr; -bool initialized = false; - -mallctl_t mallctl = nullptr; -arena_extent_alloc_large_t arena_extent_alloc_large = nullptr; -large_dalloc_t large_dalloc = nullptr; -arena_choose_hard_t arena_choose_hard = nullptr; -arena_extent_dalloc_large_prep_t arena_extent_dalloc_large_prep = nullptr; -arena_extents_dirty_dalloc_t arena_extents_dirty_dalloc = nullptr; -bool * p_je_opt_retain = nullptr; - - -static inline bool end_with(std::string const &value, std::string const &ending) { - if (ending.size() > value.size()) { - return false; - } - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); -} - -static bool init() { - - handle = enhance::dlopen("libc.so", 0); - - if (handle == nullptr) { - return false; - } - - mallctl = (mallctl_t) enhance::dlsym(handle, "je_mallctl"); - - if (!mallctl) { - return false; - } - - const char *version; - size_t size = sizeof(version); - mallctl("version", &version, &size, nullptr, 0); - LOGD(TAG, "jemalloc version: %s", version); - - if (0 != strncmp(version, "5.1.0", 5)) { - return false; - } - -// Dl_info mallctl_info{}; -// if (0 == dladdr((void *) mallctl, &mallctl_info) -// || !end_with(mallctl_info.dli_fname, "/libc.so")) { -// LOGD(TAG, "mallctl = %p, is a fault address, fname = %s, sname = %s", mallctl, -// mallctl_info.dli_fname, mallctl_info.dli_sname); -// mallctl = nullptr; -// return false; -// } - - p_je_opt_retain = (bool *) enhance::dlsym(handle, "je_opt_retain"); - if (!p_je_opt_retain) { - return false; - } - - arena_extent_alloc_large = - (arena_extent_alloc_large_t) enhance::dlsym(handle, "je_arena_extent_alloc_large"); - if (!arena_extent_alloc_large) { - return false; - } - - arena_choose_hard = (arena_choose_hard_t) enhance::dlsym(handle, "je_arena_choose_hard"); - if (!arena_choose_hard) { - return false; - } - - large_dalloc = (large_dalloc_t) enhance::dlsym(handle, "je_large_dalloc"); - if (!large_dalloc) { - return false; - } - - arena_extent_dalloc_large_prep = (arena_extent_dalloc_large_prep_t) enhance::dlsym(handle, - "je_arena_extent_dalloc_large_prep"); - if (!arena_extent_dalloc_large_prep) { - return false; - } - - arena_extents_dirty_dalloc = (arena_extents_dirty_dalloc_t) enhance::dlsym(handle, - "je_arena_extents_dirty_dalloc"); - if (!arena_extents_dirty_dalloc) { - return false; - } - - return true; -} - -static void flush_decay_purge() { - assert(mallctl != nullptr); - mallctl("thread.tcache.flush", nullptr, nullptr, nullptr, 0); - mallctl("arena.0.decay", nullptr, nullptr, nullptr, 0); - mallctl("arena.1.decay", nullptr, nullptr, nullptr, 0); - mallctl("arena.0.purge", nullptr, nullptr, nullptr, 0); - mallctl("arena.1.purge", nullptr, nullptr, nullptr, 0); -} - -static void enable_dss() { - int err = 0; - - char *stat_dss; - size_t stat_dss_size = sizeof(char *); - mallctl("stats.arenas.0.dss", &stat_dss, &stat_dss_size, nullptr, 0); - LOGD(TAG, "stat_dss.0 = %s", stat_dss); - - mallctl("stats.arenas.1.dss", &stat_dss, &stat_dss_size, nullptr, 0); - LOGD(TAG, "stat_dss.1 = %s", stat_dss); - -// *opt_retain = false; - const char *setting = "primary"; - size_t setting_size = sizeof(const char); - char *old_setting; - size_t old_setting_size = sizeof(char *); - mallctl("arena.0.dss", &old_setting, &old_setting_size, &setting, sizeof(const char *)); - mallctl("arena.1.dss", &old_setting, &old_setting_size, &setting, 8); - - mallctl("stats.arenas.0.dss", &stat_dss, &stat_dss_size, nullptr, 0); - LOGD(TAG, "after stat_dss.0 = %s", stat_dss); - - mallctl("stats.arenas.1.dss", &stat_dss, &stat_dss_size, nullptr, 0); - LOGD(TAG, "after stat_dss.1 = %s", stat_dss); - - bool old_val; - size_t old_val_size = sizeof(bool); - bool new_val = false; - mallctl("thread.tcache.enabled", &old_val, &old_val_size, &new_val, sizeof(new_val)); - LOGD(TAG, "thread.tcache.enabled: %d", old_val); - mallctl("thread.tcache.enabled", &old_val, &old_val_size, nullptr, 0); - LOGD(TAG, "thread.tcache.enabled: %d", old_val); - - ssize_t dirty_ms; - size_t dirty_ms_size = sizeof(ssize_t); - ssize_t new_dirty_ms = 0; - mallctl("arena.0.dirty_decay_ms", &dirty_ms, &dirty_ms_size, &new_dirty_ms, sizeof(ssize_t)); - LOGD(TAG, "arena.0.dirty_decay_ms: %zu", dirty_ms); - mallctl("arena.0.dirty_decay_ms", &dirty_ms, &dirty_ms_size, nullptr, 0); - LOGD(TAG, "arena.0.dirty_decay_ms: %zu", dirty_ms); - - bool background; - size_t background_size = sizeof(bool); - bool new_background = true; - err = mallctl("background_thread", &background, &background_size, &new_background, - background_size); - LOGD(TAG, "background_thread = %d, err = %d", background, err); - err = mallctl("background_thread", &background, &background_size, - nullptr, 0); - LOGD(TAG, "background_thread = %d, err = %d", background, err); - - bool opt_background; - size_t opt_background_size = sizeof(bool); - mallctl("opt.background_thread", &opt_background, &opt_background_size, nullptr, 0); - LOGD(TAG, "opt.background_thread = %d", opt_background); - - -} - -JNIEXPORT void JNICALL -Java_com_tencent_matrix_jectl_JeCtl_initNative(JNIEnv *env, jclass clazz) { -#ifdef __LP64__ - return ; -#else - if (!initialized) { - initialized = init(); - } -#endif -} - -JNIEXPORT jint JNICALL -Java_com_tencent_matrix_jectl_JeCtl_compactNative(JNIEnv *env, jclass clazz) { - -#ifdef __LP64__ - return ERR_64_BIT; -#else - - if (!initialized) { - return ERR_INIT_FAILED; - } - assert(mallctl != nullptr); - - flush_decay_purge(); - - return JECTL_OK; -#endif -} - -static void call_alloc_large_in_arena1(size_t __size) { - // 延迟时机, 失败也不影响 arena0 的预分配 - auto tsd_tsd = (pthread_t *) enhance::dlsym(handle, "je_tsd_tsd"); - if (!tsd_tsd) { - LOGE(TAG, "tsd_tsd not found"); - return; - } - - auto tsd = pthread_getspecific(*tsd_tsd); - if (tsd == nullptr) { - LOGE(TAG, "tsd id null"); - return; - } - - void *arena1 = arena_choose_hard(tsd, false); // choose 另一个 arena - - unsigned which_arena = 0; - size_t which_arena_size = sizeof(unsigned); - mallctl("thread.arena", &which_arena, &which_arena_size, nullptr, 0); - - bool zero = false; - LOGD(TAG, "args : tsd=%p, arena1=%p, size=%zu, align=%d, zero=%p", tsd, (void *) arena1, __size, - CACHELINE, &zero); - void *extent = arena_extent_alloc_large(tsd, (void *) arena1, __size, CACHELINE, &zero); -// large_dalloc(tsd, extent); - - arena_extent_dalloc_large_prep(tsd, arena1, extent); - extent_hooks_t *hooks = nullptr; - arena_extents_dirty_dalloc(tsd, arena1, &hooks, extent); -} - -static void *sub_routine(void *arg) { - LOGD(TAG, "arg = %zu ", *((size_t *) arg)); - - void *p = malloc(1024);// 确保当前线程的 tsd 已经初始化 - - unsigned which_arena = 0; - size_t which_arena_size = sizeof(unsigned); - - int ret = mallctl("thread.arena", &which_arena, &which_arena_size, nullptr, - 0); - LOGD(TAG, "thread.arena: which_arena = %u, ret = %d", which_arena, ret); - - if (which_arena == 0) { - call_alloc_large_in_arena1(*(size_t *) arg); - free(p); - free(arg); - flush_decay_purge(); - return nullptr; - } - - pthread_t next_thread; - pthread_create(&next_thread, nullptr, sub_routine, arg); - - pthread_join(next_thread, nullptr); - - free(p); - return nullptr; -} - -// Android: Force all huge allocations to always take place in the first arena. -// see: https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:external/jemalloc_new/src/large.c;l=46 -// so we have to hack jemalloc to make sure that the large allocation takes place in second arena -static int hack_prealloc_within_arena1(size_t size) { - LOGD(TAG, "hack_arena1"); - if (!initialized) { - LOGD(TAG, "hack_arena1:init fialed"); - return ERR_INIT_FAILED; - } - - LOGD(TAG, "size1 = %zu", size); - auto p_size = (size_t *) malloc(sizeof(size_t)); - *p_size = size; - - pthread_t next_thread; - pthread_create(&next_thread, nullptr, sub_routine, p_size); - - return JECTL_OK; -} - -void *arena0_alloc_opt_prevent; - -JNIEXPORT jint JNICALL -Java_com_tencent_matrix_jectl_JeCtl_preAllocRetainNative(JNIEnv *env, jclass clazz, jint __size0, - jint __size1, jint __limit0, - jint __limit1) { - -#ifdef __LP64__ - return ERR_64_BIT; -#else - if (!initialized) { - return ERR_INIT_FAILED; - } - assert(mallctl != nullptr); - - assert(__size0 > 0); - assert(__size1 > 0); - assert(__limit0 > 0); - assert(__limit1 > 0); - - int ctl_result = 0; - int ret_code = JECTL_OK; - - size_t dirty_ms; - size_t new_dirty_ms = 0; - size_t ms_size = sizeof(size_t); - ctl_result = mallctl("arena.0.muzzy_decay_ms", &dirty_ms, &ms_size, &new_dirty_ms, ms_size); - LOGD(TAG, "arena.0.muzzy_decay_ms ret = %d", ctl_result); - ctl_result = mallctl("arena.1.muzzy_decay_ms", &dirty_ms, &ms_size, &new_dirty_ms, ms_size); - LOGD(TAG, "arena.1.muzzy_decay_ms ret = %d", ctl_result); - -// ret = mallctl("arena.0.dirty_decay_ms", &dirty_ms, &dirty_ms_size, &new_dirty_ms, dirty_ms_size); -// LOGD(TAG, "arena.0.dirty_decay_ms ret = %d", ret); -// ret = mallctl("arena.1.dirty_decay_ms", &dirty_ms, &dirty_ms_size, &new_dirty_ms, dirty_ms_size); -// LOGD(TAG, "arena.1.dirty_decay_ms ret = %d", ret); - - size_t old_limit = 0; - size_t new_limit = __limit0; - size_t limit_size = sizeof(size_t); - ctl_result = mallctl("arena.0.retain_grow_limit", &old_limit, &limit_size, &new_limit, - limit_size); - LOGD(TAG, "arena.0.retain_grow_limit ret = %d, old limit = %zu", ctl_result, old_limit); - new_limit = __limit1; - ctl_result = mallctl("arena.1.retain_grow_limit", &old_limit, &limit_size, &new_limit, - limit_size); - LOGD(TAG, "arena.1.retain_grow_limit ret = %d, old limit = %zu", ctl_result, old_limit); - - LOGD(TAG, "prepare alloc"); - void *p = malloc(__size0); - if (!p) { - ret_code = ERR_ALLOC_FAILED; - } - arena0_alloc_opt_prevent = p; - LOGD(TAG, "prepare alloc arena0 done %p", p); - free(p); - hack_prealloc_within_arena1(__size1); - - flush_decay_purge(); - - return ret_code; -#endif -} - -JNIEXPORT jstring JNICALL -Java_com_tencent_matrix_jectl_JeCtl_getVersionNative(JNIEnv *env, jclass clazz) { -#ifdef __LP64__ - return env->NewStringUTF("64-bit"); -#else - if (!mallctl) { - return env->NewStringUTF("Error"); - } - - const char *version; - size_t size = sizeof(version); - mallctl("version", &version, &size, nullptr, 0); - LOGD(TAG, "jemalloc version: %s", version); - - return env->NewStringUTF(version); -#endif -} - -JNIEXPORT jboolean JNICALL -Java_com_tencent_matrix_jectl_JeCtl_setRetain(JNIEnv *env, jclass clazz, jboolean enable) { -#ifdef __LP64__ - return true; -#else - bool old = true; - if (initialized && p_je_opt_retain) { - old = *p_je_opt_retain; - *p_je_opt_retain = enable; - LOGD(TAG, "retain = %d", *p_je_opt_retain); - } - return old; -#endif -} - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.cpp b/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.cpp deleted file mode 100644 index 915a007d9..000000000 --- a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making wechat-matrix available. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * 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. - */ - -// -// Created by Yves on 2020/8/20. -// - -#include -#include "JeHooks.h" -#include "JeLog.h" - -#define TAG "Matrix.JeCtl.Hooks" - -extent_hooks_t *origin_extent_hooks; -//bool arena_1 = false; - -void *hook_extent_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, - size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { - if (arena_ind != 0) { -// arena_1 = true; - } - LOGD(TAG, "hook_extent_alloc: size = %zu, arena_ind = %u", size, arena_ind); // should not log here - return origin_extent_hooks->alloc(origin_extent_hooks, new_addr, size, alignment, zero, commit, arena_ind); -} - -bool hook_extent_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, - unsigned arena_ind) { - return origin_extent_hooks->dalloc(origin_extent_hooks, addr, size, committed, arena_ind); -} - - -void hook_extent_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, - unsigned arena_ind) { - origin_extent_hooks->destroy(origin_extent_hooks, addr, size, committed, arena_ind); -} - -bool hook_extent_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, - size_t length, unsigned arena_ind) { - return origin_extent_hooks->commit(origin_extent_hooks, addr, size, offset, length, arena_ind); -} - - -bool hook_extent_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, - size_t length, unsigned arena_ind) { - return origin_extent_hooks->decommit(origin_extent_hooks, addr, size, offset, length, arena_ind); -} - -bool hook_extent_purge_lazy(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, - size_t length, unsigned arena_ind) { - if (origin_extent_hooks->purge_lazy) { - return origin_extent_hooks->purge_lazy(origin_extent_hooks, addr, size, offset, length, - arena_ind); - } - return true; // 当 default_extent_hooks->purge_lazy == NULL, 返回 true 以保持 jemalloc 的逻辑一致 -} - -bool hook_extent_purge_forced(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, - size_t length, unsigned arena_ind) { - if (origin_extent_hooks->purge_forced) { - return origin_extent_hooks->purge_forced(origin_extent_hooks, addr, size, offset, length, - arena_ind); - } - return true; // 当 default_extent_hooks->purge_forced == NULL, 返回 true 以保持 jemalloc 的逻辑一致 -} - -extent_split_t *original_split; - -bool hook_extent_split(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t size_a, - size_t size_b, bool committed, unsigned arena_ind) { -// bool ret = original_split(origin_extent_hooks, addr, size, size_a, size_b, committed, arena_ind); -// LOGD(TAG, "hook_extent_split: arena_ind = %u, ret = %d", arena_ind, /*ret*/0); -// return ret; - return origin_extent_hooks->split(origin_extent_hooks, addr, size, size_a, size_b, committed, arena_ind); -} - - -bool hook_extent_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, - size_t size_b, bool committed, unsigned arena_ind) { - return origin_extent_hooks->merge(origin_extent_hooks, addr_a, size_a, addr_b, size_b, committed, - arena_ind); -} - -extent_hooks_t extent_hooks = { - hook_extent_alloc, - hook_extent_dalloc, - hook_extent_destroy, - hook_extent_commit, - hook_extent_decommit, - hook_extent_purge_lazy, - hook_extent_purge_forced, - hook_extent_split, - hook_extent_merge -}; \ No newline at end of file diff --git a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.h b/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.h deleted file mode 100644 index da5fac90f..000000000 --- a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeHooks.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making wechat-matrix available. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * 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. - */ - -// -// Created by Yves on 2020/8/20. -// - -#ifndef LIBMATRIX_JNI_JEHOOKS_H -#define LIBMATRIX_JNI_JEHOOKS_H - -typedef struct extent_hooks_s extent_hooks_t; - -extern extent_hooks_t *origin_extent_hooks; -extern extent_hooks_t extent_hooks; - -/* - * void * - * extent_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, - * size_t alignment, bool *zero, bool *commit, unsigned arena_ind); - */ -typedef void *(extent_alloc_t)(extent_hooks_t *, void *, size_t, size_t, bool *, - bool *, unsigned); - -/* - * bool - * extent_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, - * bool committed, unsigned arena_ind); - */ -typedef bool (extent_dalloc_t)(extent_hooks_t *, void *, size_t, bool, - unsigned); - -/* - * void - * extent_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, - * bool committed, unsigned arena_ind); - */ -typedef void (extent_destroy_t)(extent_hooks_t *, void *, size_t, bool, - unsigned); - -/* - * bool - * extent_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, - * size_t offset, size_t length, unsigned arena_ind); - */ -typedef bool (extent_commit_t)(extent_hooks_t *, void *, size_t, size_t, size_t, - unsigned); - -/* - * bool - * extent_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size, - * size_t offset, size_t length, unsigned arena_ind); - */ -typedef bool (extent_decommit_t)(extent_hooks_t *, void *, size_t, size_t, - size_t, unsigned); - -/* - * bool - * extent_purge(extent_hooks_t *extent_hooks, void *addr, size_t size, - * size_t offset, size_t length, unsigned arena_ind); - */ -typedef bool (extent_purge_t)(extent_hooks_t *, void *, size_t, size_t, size_t, - unsigned); - -/* - * bool - * extent_split(extent_hooks_t *extent_hooks, void *addr, size_t size, - * size_t size_a, size_t size_b, bool committed, unsigned arena_ind); - */ -typedef bool (extent_split_t)(extent_hooks_t *, void *, size_t, size_t, size_t, - bool, unsigned); - -/* - * bool - * extent_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, - * void *addr_b, size_t size_b, bool committed, unsigned arena_ind); - */ -typedef bool (extent_merge_t)(extent_hooks_t *, void *, size_t, void *, size_t, - bool, unsigned); - - -struct extent_hooks_s { - extent_alloc_t *alloc; - extent_dalloc_t *dalloc; - extent_destroy_t *destroy; - extent_commit_t *commit; - extent_decommit_t *decommit; - extent_purge_t *purge_lazy; - extent_purge_t *purge_forced; - extent_split_t *split; - extent_merge_t *merge; -}; - -#endif //LIBMATRIX_JNI_JEHOOKS_H diff --git a/matrix/matrix-android/matrix-jectl/src/main/java/com/tencent/matrix/jectl/JeCtl.java b/matrix/matrix-android/matrix-jectl/src/main/java/com/tencent/matrix/jectl/JeCtl.java deleted file mode 100644 index 2d7086954..000000000 --- a/matrix/matrix-android/matrix-jectl/src/main/java/com/tencent/matrix/jectl/JeCtl.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making wechat-matrix available. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * 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.tencent.matrix.jectl; - - -import androidx.annotation.Keep; - -import com.tencent.matrix.util.MatrixLog; - -/** - * Created by Yves on 2020/7/15 - */ -public class JeCtl { - private static final String TAG = "Matrix.JeCtl"; - - private static boolean initialized = false; - - static { - try { - System.loadLibrary("matrix-jectl"); - initNative(); - initialized = true; - } catch (Throwable e) { - MatrixLog.printErrStackTrace(TAG, e, ""); - } - } - - // 必须和 native 保持一致 - public static final int JECTL_OK = 0; - public static final int ERR_INIT_FAILED = 1; - public static final int ERR_VERSION = 2; - public static final int ERR_64_BIT = 3; - public static final int ERR_CTL = 4; - public static final int ERR_ALLOC_FAILED = 5; - - public synchronized static int compact() { - if (!initialized) { - MatrixLog.e(TAG, "JeCtl init failed! check if so exists"); - return ERR_INIT_FAILED; - } - return compactNative(); - } - - private static boolean hasAllocated; - private static int sLastPreAllocRet; - - public synchronized static int preAllocRetain(int size0, int size1, int limit0, int limit1) { - if (!initialized) { - MatrixLog.e(TAG, "JeCtl init failed! check if so exists"); - return ERR_INIT_FAILED; - } - if (!hasAllocated) { - hasAllocated = true; - sLastPreAllocRet = preAllocRetainNative(size0, size1, limit0, limit1); - } - - return sLastPreAllocRet; - } - - public synchronized static String version() { - if (!initialized) { - MatrixLog.e(TAG, "JeCtl init failed! check if so exists"); - return "VER_UNKNOWN"; - } - - return getVersionNative(); - } - - @Keep - private static native void initNative(); - - @Keep - private static native int compactNative(); - - @Keep - private static native int preAllocRetainNative(int size0, int size1, int limit0, int limit1); - - @Keep - private static native String getVersionNative(); - - @Keep - public static native boolean setRetain(boolean enable); -} diff --git a/matrix/matrix-android/matrix-jectl/CMakeLists.txt b/matrix/matrix-android/matrix-mallctl/CMakeLists.txt similarity index 95% rename from matrix/matrix-android/matrix-jectl/CMakeLists.txt rename to matrix/matrix-android/matrix-mallctl/CMakeLists.txt index 931537f6f..cf1e64739 100644 --- a/matrix/matrix-android/matrix-jectl/CMakeLists.txt +++ b/matrix/matrix-android/matrix-mallctl/CMakeLists.txt @@ -10,14 +10,13 @@ cmake_minimum_required(VERSION 3.4.1) # and CMake builds them for you. When you build your app, Gradle # automatically packages shared libraries with your APK. -set(TARGET matrix-jectl) +set(TARGET matrix-mallctl) set(SOURCE_DIR src/main/cpp) set( SOURCE_FILES - ${SOURCE_DIR}/jectl/JeCtl.cpp - ${SOURCE_DIR}/jectl/JeHooks.cpp + ${SOURCE_DIR}/mallctl/MallCtl.cpp ) add_library( # Specifies the name of the library. diff --git a/matrix/matrix-android/matrix-jectl/build.gradle b/matrix/matrix-android/matrix-mallctl/build.gradle similarity index 100% rename from matrix/matrix-android/matrix-jectl/build.gradle rename to matrix/matrix-android/matrix-mallctl/build.gradle diff --git a/matrix/matrix-android/matrix-jectl/consumer-rules.pro b/matrix/matrix-android/matrix-mallctl/consumer-rules.pro similarity index 100% rename from matrix/matrix-android/matrix-jectl/consumer-rules.pro rename to matrix/matrix-android/matrix-mallctl/consumer-rules.pro diff --git a/matrix/matrix-android/matrix-jectl/gradle.properties b/matrix/matrix-android/matrix-mallctl/gradle.properties similarity index 53% rename from matrix/matrix-android/matrix-jectl/gradle.properties rename to matrix/matrix-android/matrix-mallctl/gradle.properties index f90e4fc10..3606fb76f 100644 --- a/matrix/matrix-android/matrix-jectl/gradle.properties +++ b/matrix/matrix-android/matrix-mallctl/gradle.properties @@ -1,2 +1,2 @@ POM_NAME=Matrix jectl for jemalloc -POM_ARTIFACT_ID=matrix-jectl \ No newline at end of file +POM_ARTIFACT_ID=matrix-mallctl \ No newline at end of file diff --git a/matrix/matrix-android/matrix-jectl/proguard-rules.pro b/matrix/matrix-android/matrix-mallctl/proguard-rules.pro similarity index 100% rename from matrix/matrix-android/matrix-jectl/proguard-rules.pro rename to matrix/matrix-android/matrix-mallctl/proguard-rules.pro diff --git a/matrix/matrix-android/matrix-jectl/src/androidTest/java/com/tencent/matrix/jectl/ExampleInstrumentedTest.java b/matrix/matrix-android/matrix-mallctl/src/androidTest/java/com/tencent/matrix/jectl/ExampleInstrumentedTest.java similarity index 100% rename from matrix/matrix-android/matrix-jectl/src/androidTest/java/com/tencent/matrix/jectl/ExampleInstrumentedTest.java rename to matrix/matrix-android/matrix-mallctl/src/androidTest/java/com/tencent/matrix/jectl/ExampleInstrumentedTest.java diff --git a/matrix/matrix-android/matrix-jectl/src/main/AndroidManifest.xml b/matrix/matrix-android/matrix-mallctl/src/main/AndroidManifest.xml similarity index 65% rename from matrix/matrix-android/matrix-jectl/src/main/AndroidManifest.xml rename to matrix/matrix-android/matrix-mallctl/src/main/AndroidManifest.xml index 1ad0a915f..37af2b71f 100644 --- a/matrix/matrix-android/matrix-jectl/src/main/AndroidManifest.xml +++ b/matrix/matrix-android/matrix-mallctl/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ + package="com.tencent.matrix.mallctl"> \ No newline at end of file diff --git a/matrix/matrix-android/matrix-mallctl/src/main/cpp/mallctl/MallCtl.cpp b/matrix/matrix-android/matrix-mallctl/src/main/cpp/mallctl/MallCtl.cpp new file mode 100644 index 000000000..9ed4bc4bd --- /dev/null +++ b/matrix/matrix-android/matrix-mallctl/src/main/cpp/mallctl/MallCtl.cpp @@ -0,0 +1,175 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +// +// Created by Yves on 2020/7/15. +// + +#include +#include +#include +#include +#include +#include "EnhanceDlsym.h" +#include "MallLog.h" +#include + +#define TAG "Matrix.JeCtl" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*je_mallctl_t)(const char *name, + void *oldp, + size_t *oldlenp, + void *newp, + size_t newlen); + +typedef void *(*je_arena_extent_alloc_large_t)(void *tsdn, + void *arena, + size_t usize, + size_t alignment, + bool *zero); + +typedef void (*je_large_dalloc_t)(void *tsdn, void *extent); + +typedef void *(*je_arena_choose_hard_t)(void *tsd, bool internal); + +typedef void (*je_arena_extent_dalloc_large_prep_t)(void *tsdn, void *arena, void *extent); + +typedef int (*mallopt_t)(int param, int value); + +#define MAX_RETRY_TIMES 10 + +void *handle = nullptr; +bool initialized = false; + +je_mallctl_t je_mallctl = nullptr; +je_arena_extent_alloc_large_t je_arena_extent_alloc_large = nullptr; +je_large_dalloc_t je_large_dalloc = nullptr; +je_arena_choose_hard_t je_arena_choose_hard = nullptr; +je_arena_extent_dalloc_large_prep_t je_arena_extent_dalloc_large_prep = nullptr; + +bool *je_opt_retain_ptr = nullptr; + +mallopt_t libc_mallopt = nullptr; + +static bool init() { + + handle = enhance::dlopen("libc.so", 0); + + if (handle == nullptr) { + return false; + } + + libc_mallopt = (mallopt_t) enhance::dlsym(handle, "mallopt"); + + je_mallctl = (je_mallctl_t) enhance::dlsym(handle, "je_mallctl"); + + if (!je_mallctl) { + return false; + } + + const char *version; + size_t size = sizeof(version); + je_mallctl("version", &version, &size, nullptr, 0); + LOGD(TAG, "jemalloc version: %s", version); + + je_opt_retain_ptr = (bool *) enhance::dlsym(handle, "je_opt_retain"); + if (!je_opt_retain_ptr) { + return false; + } + + return true; +} + +static void flush_decay_purge() { + assert(je_mallctl != nullptr); + je_mallctl("thread.tcache.flush", nullptr, nullptr, nullptr, 0); + je_mallctl("arena.0.decay", nullptr, nullptr, nullptr, 0); + je_mallctl("arena.1.decay", nullptr, nullptr, nullptr, 0); + je_mallctl("arena.0.purge", nullptr, nullptr, nullptr, 0); + je_mallctl("arena.1.purge", nullptr, nullptr, nullptr, 0); +} + +JNIEXPORT void JNICALL +Java_com_tencent_matrix_mallctl_MallCtl_initNative(JNIEnv *env, jclass clazz) { + if (!initialized) { + initialized = init(); + } +} + +JNIEXPORT jstring JNICALL +Java_com_tencent_matrix_mallctl_MallCtl_getVersionNative(JNIEnv *env, jclass clazz) { +#ifdef __LP64__ + return env->NewStringUTF("64-bit"); +#else + if (!je_mallctl) { + return env->NewStringUTF("Error"); + } + + const char *version; + size_t size = sizeof(version); + je_mallctl("version", &version, &size, nullptr, 0); + LOGD(TAG, "jemalloc version: %s", version); + + return env->NewStringUTF(version); +#endif +} + +#define MALLOPT_SYM_NOT_FOUND -1 + +JNIEXPORT jint JNICALL +Java_com_tencent_matrix_mallctl_MallCtl_malloptNative(JNIEnv *env, jclass clazz) { + if (!libc_mallopt) { + return MALLOPT_SYM_NOT_FOUND; + } + // On success, mallopt() returns 1. On error, it returns 0. + int ret = libc_mallopt(M_PURGE, 0); + if (ret == 0) { + // try fallback je ctls + flush_decay_purge(); + } + return ret; +} + +JNIEXPORT jboolean JNICALL +Java_com_tencent_matrix_mallctl_MallCtl_setRetainNative(JNIEnv *env, jclass clazz, + jboolean enable) { +#ifdef __LP64__ + return true; +#else + bool old = true; + if (initialized && je_opt_retain_ptr) { + old = *je_opt_retain_ptr; + *je_opt_retain_ptr = enable; + LOGD(TAG, "retain = %d", *je_opt_retain_ptr); + } + return old; +#endif +} + +JNIEXPORT jint JNICALL +Java_com_tencent_matrix_mallctl_MallCtl_flushReadOnlyFilePagesNative(JNIEnv *env, jclass clazz, + jlong begin, jlong size) { + + return madvise((void *)begin, (size_t) size, MADV_DONTNEED); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeLog.h b/matrix/matrix-android/matrix-mallctl/src/main/cpp/mallctl/MallLog.h similarity index 100% rename from matrix/matrix-android/matrix-jectl/src/main/cpp/jectl/JeLog.h rename to matrix/matrix-android/matrix-mallctl/src/main/cpp/mallctl/MallLog.h diff --git a/matrix/matrix-android/matrix-mallctl/src/main/java/com/tencent/matrix/mallctl/MallCtl.java b/matrix/matrix-android/matrix-mallctl/src/main/java/com/tencent/matrix/mallctl/MallCtl.java new file mode 100644 index 000000000..57d310424 --- /dev/null +++ b/matrix/matrix-android/matrix-mallctl/src/main/java/com/tencent/matrix/mallctl/MallCtl.java @@ -0,0 +1,158 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.mallctl; + + +import androidx.annotation.Keep; + +import com.tencent.matrix.util.MatrixLog; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by Yves on 2020/7/15 + */ +public class MallCtl { + private static final String TAG = "Matrix.JeCtl"; + + private static boolean initialized = false; + + static { + try { + System.loadLibrary("matrix-mallctl"); + initNative(); + initialized = true; + } catch (Throwable e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + } + + public synchronized static String jeVersion() { + if (!initialized) { + MatrixLog.e(TAG, "JeCtl init failed! check if so exists"); + return "VER_UNKNOWN"; + } + + return getVersionNative(); + } + + public synchronized static boolean jeSetRetain(boolean enable) { + try { + return setRetainNative(enable); + } catch (Throwable e) { + MatrixLog.printErrStackTrace(TAG, e, "set retain failed"); + } + return false; + } + + public static final int MALLOPT_FAILED = 0; + public static final int MALLOPT_SUCCESS = 1; + public static final int MALLOPT_SYM_NOT_FOUND = -1; + public static final int MALLOPT_EXCEPTION = -2; + + /** + * @return On success, mallopt() returns 1. On error, it returns 0. + */ + public synchronized static int mallopt() { + try { + return malloptNative(); + } catch (Throwable e) { + MatrixLog.printErrStackTrace(TAG, e, "mallopt failed"); + } + return MALLOPT_EXCEPTION; + } + + public interface TrimPrediction { + boolean canBeTrim(String pathName, String permission); + } + + public static class DefaultPrediction implements TrimPrediction { + @Override + public boolean canBeTrim(String pathName, String permission) { + if (pathName.endsWith(" (deleted)")) { + pathName = pathName.substring(0, pathName.length() - " (deleted)".length()); + } else if (pathName.endsWith("]")) { + pathName = pathName.substring(0, pathName.length() - "]".length()); + } + return !permission.contains("w") + && (pathName.endsWith(".so") + || pathName.endsWith(".dex") + || pathName.endsWith(".apk") + || pathName.endsWith(".vdex") + || pathName.endsWith(".odex") + || pathName.endsWith(".oat") + || pathName.endsWith(".art") + || pathName.endsWith(".ttf") + || pathName.endsWith(".otf") + || pathName.endsWith(".jar")); + } + } + + public synchronized static void flushReadOnlyFilePages(TrimPrediction prediction) { + if (prediction == null) { + prediction = new DefaultPrediction(); + } + Pattern pattern = Pattern.compile("^([0-9a-f]+)-([0-9a-f]+)\\s+([rwxps-]{4})\\s+[0-9a-f]+\\s+[0-9a-f]+:[0-9a-f]+\\s+\\d+\\s*(.*)$"); + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/maps")))) { + String line = br.readLine(); + while (line != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + String beginStr = matcher.group(1); + String endStr = matcher.group(2); + String permission = matcher.group(3); + String name = matcher.group(4); + if (name == null || name.isEmpty()) { + name = "[no-name]"; + } + if (prediction.canBeTrim(name, permission) && beginStr != null && endStr != null) { + try { + long beginPtr = Long.parseLong(beginStr, 16); + long endPtr = Long.parseLong(endStr, 16); + long size = endPtr - beginPtr; + flushReadOnlyFilePagesNative(beginPtr, size); + } catch (Throwable e) { + MatrixLog.printErrStackTrace(TAG, e, "%s-%s %s %s", beginStr, endStr, permission, name); + } + } + } + line = br.readLine(); + } + } catch (IOException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + } + + @Keep + private static native void initNative(); + + @Keep + private static native String getVersionNative(); + + @Keep + private static native int malloptNative(); + + @Keep + private static native boolean setRetainNative(boolean enable); + + private static native int flushReadOnlyFilePagesNative(long begin, long size); +} diff --git a/matrix/matrix-android/matrix-jectl/src/test/java/com/tencent/matrix/jectl/ExampleUnitTest.java b/matrix/matrix-android/matrix-mallctl/src/test/java/com/tencent/matrix/jectl/ExampleUnitTest.java similarity index 100% rename from matrix/matrix-android/matrix-jectl/src/test/java/com/tencent/matrix/jectl/ExampleUnitTest.java rename to matrix/matrix-android/matrix-mallctl/src/test/java/com/tencent/matrix/jectl/ExampleUnitTest.java diff --git a/matrix/matrix-android/matrix-memguard/src/main/java/com/tencent/matrix/memguard/MemGuard.java b/matrix/matrix-android/matrix-memguard/src/main/java/com/tencent/matrix/memguard/MemGuard.java index 7829c8b16..39dd2a6e8 100644 --- a/matrix/matrix-android/matrix-memguard/src/main/java/com/tencent/matrix/memguard/MemGuard.java +++ b/matrix/matrix-android/matrix-memguard/src/main/java/com/tencent/matrix/memguard/MemGuard.java @@ -455,13 +455,15 @@ private static String getProcessSuffix(@NonNull Context context) { final List runningProcs = am.getRunningAppProcesses(); final int myUid = Process.myUid(); final int myPid = Process.myPid(); - for (ActivityManager.RunningAppProcessInfo procInfo : runningProcs) { - if (procInfo.uid == myUid && procInfo.pid == myPid) { - final int colIdx = procInfo.processName.lastIndexOf(':'); - if (colIdx >= 0) { - return procInfo.processName.substring(colIdx + 1); - } else { - return "main"; + if (runningProcs != null) { + for (ActivityManager.RunningAppProcessInfo procInfo : runningProcs) { + if (procInfo.uid == myUid && procInfo.pid == myPid) { + final int colIdx = procInfo.processName.lastIndexOf(':'); + if (colIdx >= 0) { + return procInfo.processName.substring(colIdx + 1); + } else { + return "main"; + } } } } diff --git a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemoryCanaryPlugin.kt b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemoryCanaryPlugin.kt index d44283b70..7dc9a908e 100644 --- a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemoryCanaryPlugin.kt +++ b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/MemoryCanaryPlugin.kt @@ -47,9 +47,7 @@ class MemoryCanaryPlugin( if (isSupervisor) { MatrixLog.d(tag, "supervisor is ${MatrixUtil.getProcessName(application)}") - appBgSumPssMonitorConfigs.forEach { - AppBgSumPssMonitor(it).init() - } + AppBgSumPssMonitor.init(appBgSumPssMonitorConfigs) } processBgMemoryMonitorConfigs.forEach { ProcessBgMemoryMonitor(it).init() diff --git a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/AppBgSumPssMonitor.kt b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/AppBgSumPssMonitor.kt index 326ab49c1..85b46c1b7 100644 --- a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/AppBgSumPssMonitor.kt +++ b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/AppBgSumPssMonitor.kt @@ -1,11 +1,10 @@ package com.tencent.matrix.memory.canary.monitor import com.tencent.matrix.lifecycle.IBackgroundStatefulOwner -import com.tencent.matrix.lifecycle.IStateObserver +import com.tencent.matrix.lifecycle.IMatrixBackgroundCallback import com.tencent.matrix.lifecycle.supervisor.AppStagedBackgroundOwner -import com.tencent.matrix.memory.canary.MemInfo -import com.tencent.matrix.util.MatrixHandlerThread -import com.tencent.matrix.util.MatrixLog +import com.tencent.matrix.lifecycle.supervisor.ProcessSupervisor +import com.tencent.matrix.util.* import java.util.concurrent.TimeUnit private const val TAG = "Matrix.monitor.AppBgSumPssMonitor" @@ -17,58 +16,111 @@ class AppBgSumPssMonitorConfig( val enable: Boolean = true, val bgStatefulOwner: IBackgroundStatefulOwner = AppStagedBackgroundOwner, val checkInterval: Long = TimeUnit.MINUTES.toMillis(3), - thresholdKB: Long = 2 * 1024 * 1024L + 500 * 1024L, // 2.5G + amsPssThresholdKB: Long = Long.MAX_VALUE, + debugPssThresholdKB: Long = Long.MAX_VALUE, checkTimes: Int = 3, - val callback: (sumPssKB: Int, Array) -> Unit = { sumPssKB, memInfos -> + val callback: (amsPssSumKB: Int, debugPssSumKB: Int, amsMemInfos: Array, debugMemInfos: Array) -> Unit = { amsSumPssKB, debugSumPssKB, amsMemInfos, debugMemInfos -> MatrixLog.e( TAG, - "sum pss of all process over threshold: $sumPssKB KB, detail: ${memInfos.contentToString()}" + "sum pss of all process over threshold: amsSumPss = $amsSumPssKB KB, debugSumPss = $debugSumPssKB KB " + + "amsMemDetail: ${amsMemInfos.contentToString()}" + + "\n==========\n" + + "debugMemDetail: ${debugMemInfos.contentToString()}" ) - } + }, + val extraPssFactory: () -> Array = { emptyArray() } /*{ arrayOf(MemInfo(processInfo = ProcessInfo(pid = -1, name = "extra_isolate", activity = "none"), amsPssInfo = PssInfo(totalPssK = 0)))}*/ ) { - internal val thresholdKB = thresholdKB.asThreshold(checkTimes, TimeUnit.MINUTES.toMillis(5)) + internal val amsPssThresholdKB = + amsPssThresholdKB.asThreshold(checkTimes, TimeUnit.MINUTES.toMillis(5)) + internal val debugPssThresholdKB = debugPssThresholdKB.asThreshold(checkTimes) + + // internal val override fun toString(): String { - return "AppBgSumPssMonitorConfig(enable=$enable, bgStatefulOwner=$bgStatefulOwner, checkInterval=$checkInterval, callback=${callback.javaClass.name}, thresholdKB=$thresholdKB)" + return "AppBgSumPssMonitorConfig(enable=$enable, bgStatefulOwner=$bgStatefulOwner, checkInterval=$checkInterval, callback=${callback.javaClass.name}, thresholdKB=$amsPssThresholdKB, extraPssFactory=${extraPssFactory.javaClass.name})" } } internal class AppBgSumPssMonitor( - private val config: AppBgSumPssMonitorConfig + private val checkInterval: Long, + private val bgStatefulOwner: IBackgroundStatefulOwner, + private val configs: Array ) { + companion object { + fun init(configs: Array) { + configs.groupBy { cfg -> cfg.checkInterval }.forEach { e -> + e.value.groupBy { it.bgStatefulOwner }.forEach { e2 -> + AppBgSumPssMonitor(e.key, e2.key, e2.value.toTypedArray()).start() + } + } + } + } private val checkTask = Runnable { check() } private val runningHandler = MatrixHandlerThread.getDefaultHandler() - fun init() { - MatrixLog.i(TAG, "$config") - if (!config.enable) { + private fun start() { + MatrixLog.i(TAG, configs.contentToString()) + if (configs.none { it.enable }) { + MatrixLog.i(TAG, "none enabled") return } - config.bgStatefulOwner.observeForever(object : IStateObserver { - override fun off() { // app foreground - runningHandler.removeCallbacks(checkTask) + + bgStatefulOwner.addLifecycleCallback(object : IMatrixBackgroundCallback() { + override fun onEnterBackground() { + runningHandler.postDelayed(checkTask, checkInterval) } - override fun on() { // app background - runningHandler.postDelayed(checkTask, config.checkInterval) + override fun onExitBackground() { + runningHandler.removeCallbacks(checkTask) } }) } private fun check() { - val memInfos = MemInfo.getAllProcessPss() + val amsMemInfos = MemInfo.getAllProcessPss() + val debugMemInfos = ProcessSupervisor.getAllProcessMemInfo() ?: emptyArray() - val sum = memInfos.onEach { info -> + val amsPssSum = amsMemInfos.onEach { info -> MatrixLog.i( TAG, "${info.processInfo?.pid}-${info.processInfo?.name}: ${info.amsPssInfo!!.totalPssK} KB" ) }.sumBy { it.amsPssInfo!!.totalPssK }.also { MatrixLog.i(TAG, "sumPss = $it KB") } - MatrixLog.i(TAG, "check end sum = $sum ${memInfos.contentToString()}") + val debugPssSum = safeLet(tag = TAG, defVal = 0) { + debugMemInfos.filter { it.processInfo != null && it.debugPssInfo != null && it.amsPssInfo != null }.onEach { + MatrixLog.i( + TAG, + "${it.processInfo?.pid}-${it.processInfo?.name}: dbgPss = ${it.debugPssInfo!!.totalPssK} KB, amsPss = ${it.amsPssInfo!!.totalPssK} KB" + ) + }.sumBy { it.debugPssInfo!!.totalPssK }.also { MatrixLog.i(TAG, "ipc sumDbgPss = $it KB") } + } + + MatrixLog.i(TAG, "check with interval [$checkInterval] amsPssSum = $amsPssSum KB, ${amsMemInfos.contentToString()}") + MatrixLog.i(TAG, "check with interval [$checkInterval] debugPssSum = $debugPssSum KB, ${debugMemInfos.contentToString()}") + + configs.forEach { config -> + var shouldCallback = false + + val extraPssInfo = config.extraPssFactory() + val extraPssSum = extraPssInfo.onEach { + MatrixLog.i(TAG, + "${it.processInfo!!.pid}-${it.processInfo!!.name}: extra total pss = ${it.amsPssInfo!!.totalPssK} KB" + ) + }.sumBy { it.amsPssInfo!!.totalPssK }.also { if (extraPssInfo.isNotEmpty()) { MatrixLog.i(TAG, "extra sum pss = $it KB") } } - config.thresholdKB.check(sum.toLong()) { - config.callback.invoke(sum, memInfos) + val overThreshold = config.run { + // @formatter:off + arrayOf("amsPss" to amsPssThresholdKB.check((amsPssSum + extraPssSum).toLong()) { shouldCallback = true }, + "debugPss" to debugPssThresholdKB.check((debugPssSum + extraPssSum).toLong()) { shouldCallback = true } + ).onEach { MatrixLog.i(TAG, "is over threshold? $it") }.any { it.second } + // @formatter:on + } + + if (overThreshold && shouldCallback) { + MatrixLog.i(TAG, "report over threshold") + config.callback.invoke(amsPssSum + extraPssSum, debugPssSum + extraPssSum, amsMemInfos + extraPssInfo, debugMemInfos + extraPssInfo) + } } } } \ No newline at end of file diff --git a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/ProcessBgMemoryMonitor.kt b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/ProcessBgMemoryMonitor.kt index bfcfc2a3f..dd76dcc20 100644 --- a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/ProcessBgMemoryMonitor.kt +++ b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/monitor/ProcessBgMemoryMonitor.kt @@ -3,7 +3,7 @@ package com.tencent.matrix.memory.canary.monitor import com.tencent.matrix.lifecycle.IStateObserver import com.tencent.matrix.lifecycle.IBackgroundStatefulOwner import com.tencent.matrix.lifecycle.owners.ProcessStagedBackgroundOwner -import com.tencent.matrix.memory.canary.MemInfo +import com.tencent.matrix.util.MemInfo import com.tencent.matrix.util.MatrixHandlerThread import com.tencent.matrix.util.MatrixLog import java.util.concurrent.TimeUnit diff --git a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/trim/TrimMemoryNotifier.kt b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/trim/TrimMemoryNotifier.kt index 9e3a36960..371048811 100644 --- a/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/trim/TrimMemoryNotifier.kt +++ b/matrix/matrix-android/matrix-memory-canary/src/main/java/com/tencent/matrix/memory/canary/trim/TrimMemoryNotifier.kt @@ -2,13 +2,16 @@ package com.tencent.matrix.memory.canary.trim import android.content.ComponentCallbacks2 import android.content.res.Configuration +import android.os.Handler import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent import com.tencent.matrix.Matrix -import com.tencent.matrix.lifecycle.IStateObserver +import com.tencent.matrix.lifecycle.IBackgroundStatefulOwner +import com.tencent.matrix.lifecycle.IMatrixBackgroundCallback import com.tencent.matrix.lifecycle.owners.ProcessDeepBackgroundOwner +import com.tencent.matrix.lifecycle.owners.ProcessExplicitBackgroundOwner import com.tencent.matrix.lifecycle.owners.ProcessStagedBackgroundOwner import com.tencent.matrix.lifecycle.supervisor.AppDeepBackgroundOwner import com.tencent.matrix.lifecycle.supervisor.AppStagedBackgroundOwner @@ -24,7 +27,7 @@ interface TrimCallback { data class TrimMemoryConfig( val enable: Boolean = false, - val delayMillis: Long = TimeUnit.MINUTES.toMillis(1) + val delayMillis: ArrayList = arrayListOf(TimeUnit.MINUTES.toMillis(1)) ) /** @@ -38,21 +41,79 @@ object TrimMemoryNotifier { private val appTrimCallbacks = ArrayList() private fun ArrayList.backgroundTrim() { - synchronized(this) { - forEach { - safeApply(TAG) { - it.backgroundTrim() - } - } + val copy = synchronized(this) { ArrayList(this) } + copy.forEach { + it.safeApply(TAG) { backgroundTrim() } } + Runtime.getRuntime().gc() } private fun ArrayList.systemTrim(level: Int) { - synchronized(this) { - forEach { - safeApply(TAG) { - it.systemTrim(level) + val copy = synchronized(this) { ArrayList(this) } + copy.forEach { + it.safeApply(TAG) { systemTrim(level) } + } + Runtime.getRuntime().gc() + } + + class TrimTask( + private val name: String, + private val backgroundOwner: IBackgroundStatefulOwner, + private val trimCallback: ArrayList, + private val config: TrimMemoryConfig, + private val immediate: Boolean + ) : Runnable { + + private val runningHandler = + Handler(MatrixHandlerThread.getDefaultHandlerThread().looper) + + @Volatile + private var delayIndex = 0 + + fun init() { + backgroundOwner.addLifecycleCallback(object : IMatrixBackgroundCallback() { + override fun onEnterBackground() { + delayIndex = 0 + val delay = config.delayMillis[delayIndex] + runningHandler.removeCallbacksAndMessages(null) + if (immediate) { + trimCallback.backgroundTrim() + MatrixLog.i(TAG, "[$name] trim immediately") + } + runningHandler.postDelayed(this@TrimTask, delay) + MatrixLog.i( + TAG, + "...[$name] trim delay[${delayIndex + 1}/${config.delayMillis.size}] $delay" + ) } + + override fun onExitBackground() { + runningHandler.removeCallbacks(this@TrimTask) + delayIndex = 0 + } + }) + } + + override fun run() { + val currIndex = delayIndex + if (currIndex >= config.delayMillis.size) { + MatrixLog.e(TAG, "index[$currIndex] out of bounds[${config.delayMillis.size}]") + return + } + MatrixLog.i( + TAG, + "!!![$name] trim timeout [${currIndex + 1}/${config.delayMillis.size}] ${config.delayMillis[currIndex]}" + ) + trimCallback.backgroundTrim() + val nextIndex = currIndex + 1 + if (nextIndex < config.delayMillis.size) { + delayIndex = nextIndex + val delay = config.delayMillis[nextIndex] + runningHandler.postDelayed(this, delay) + MatrixLog.i( + TAG, + "...[$name] trim delay[${nextIndex + 1}/${config.delayMillis.size}] $delay" + ) } } } @@ -62,14 +123,45 @@ object TrimMemoryNotifier { return } + if (config.delayMillis.isEmpty()) { + throw IllegalArgumentException("config.delayMillis is empty") + } + if (!Matrix.isInstalled()) { MatrixLog.e(TAG, "Matrix NOT installed yet") return } - + // system trim Matrix.with().application.registerComponentCallbacks(object : ComponentCallbacks2 { + + private var lastTrimTimeMillis = 0L + + @Volatile + private var trimCounter = 0 + private val maxTrimCount = 10 + + init { + ProcessExplicitBackgroundOwner.addLifecycleCallback(object : + IMatrixBackgroundCallback() { + + override fun onEnterBackground() { + // reset trim + trimCounter = 0 + } + + override fun onExitBackground() {} + }) + } + override fun onLowMemory() { + val current = System.currentTimeMillis() + if (current - lastTrimTimeMillis < TimeUnit.MINUTES.toMillis((trimCounter + 1).toLong()) || trimCounter >= maxTrimCount) { + MatrixLog.w(TAG, "onLowMemory skip for frequency") + return + } + lastTrimTimeMillis = current + trimCounter++ MatrixLog.e(TAG, "onLowMemory post") MatrixHandlerThread.getDefaultHandler().post { MatrixLog.e(TAG, "onLowMemory") @@ -80,6 +172,13 @@ object TrimMemoryNotifier { override fun onTrimMemory(level: Int) { if (level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + val current = System.currentTimeMillis() + if (current - lastTrimTimeMillis < TimeUnit.MINUTES.toMillis((trimCounter + 1).toLong()) || trimCounter >= maxTrimCount) { + MatrixLog.w(TAG, "onLowMemory skip for frequency") + return + } + lastTrimTimeMillis = current + trimCounter++ MatrixLog.e(TAG, "onTrimMemory post: $level") MatrixHandlerThread.getDefaultHandler().post { MatrixLog.e(TAG, "onTrimMemory: $level") @@ -92,65 +191,19 @@ object TrimMemoryNotifier { override fun onConfigurationChanged(newConfig: Configuration) {} }) + // @formatter:off + // process staged bg trim + TrimTask("ProcessStagedBg", ProcessStagedBackgroundOwner, procTrimCallbacks, config, false).init() - val procTrimTask = Runnable { - MatrixLog.i(TAG, "trim: process staged bg timeout ${config.delayMillis}") - procTrimCallbacks.backgroundTrim() - } - - object : IStateObserver { - val runningHandler = MatrixHandlerThread.getDefaultHandler() - - override fun on() { - runningHandler.removeCallbacksAndMessages(null) - runningHandler.postDelayed(procTrimTask, config.delayMillis) - } - - override fun off() { - runningHandler.removeCallbacks(procTrimTask) - } - }.let { - ProcessStagedBackgroundOwner.observeForever(it) - } - - ProcessDeepBackgroundOwner.observeForever(object : IStateObserver { - override fun on() { - MatrixLog.i(TAG, "trim: process deep bg") - procTrimCallbacks.backgroundTrim() - } - - override fun off() {} - }) - + // process deep bg trim + TrimTask("ProcessDeepBg", ProcessDeepBackgroundOwner, procTrimCallbacks, config, true).init() - val appTrimTask = Runnable { - MatrixLog.i(TAG, "trim: app staged bg timeout ${config.delayMillis}") - appTrimCallbacks.backgroundTrim() - } - - object : IStateObserver { - val runningHandler = MatrixHandlerThread.getDefaultHandler() - - override fun on() { - runningHandler.removeCallbacksAndMessages(null) - runningHandler.postDelayed(appTrimTask, config.delayMillis) - } - - override fun off() { - runningHandler.removeCallbacks(appTrimTask) - } - }.let { - AppStagedBackgroundOwner.observeForever(it) - } - - AppDeepBackgroundOwner.observeForever(object : IStateObserver { - override fun on() { - MatrixLog.i(TAG, "trim: app deep bg") - appTrimCallbacks.backgroundTrim() - } + // app staged bg trim + TrimTask("AppStagedBg", AppStagedBackgroundOwner, appTrimCallbacks, config, false).init() - override fun off() {} - }) + // app deep bg trim + TrimTask("AppDeepBg", AppDeepBackgroundOwner, appTrimCallbacks, config, true).init() + // @formatter:on } fun addProcessBackgroundTrimCallback(callback: TrimCallback) { @@ -201,4 +254,9 @@ object TrimMemoryNotifier { appTrimCallbacks.remove(callback) } } + + fun triggerTrim() { + procTrimCallbacks.backgroundTrim() + appTrimCallbacks.backgroundTrim() + } } \ No newline at end of file diff --git a/matrix/matrix-android/matrix-opengl-leak/CMakeLists.txt b/matrix/matrix-android/matrix-opengl-leak/CMakeLists.txt index 47c3ffaa1..28dac4690 100644 --- a/matrix/matrix-android/matrix-opengl-leak/CMakeLists.txt +++ b/matrix/matrix-android/matrix-opengl-leak/CMakeLists.txt @@ -76,6 +76,8 @@ target_link_libraries( # Specifies the target library. GLESv2 GLESv3 EGL + PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libxhook.a + PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libsemi_dlfcn.a PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libwechatbacktrace.so ) diff --git a/matrix/matrix-android/matrix-opengl-leak/build.gradle b/matrix/matrix-android/matrix-opengl-leak/build.gradle index e44c150f6..0ffb14c84 100644 --- a/matrix/matrix-android/matrix-opengl-leak/build.gradle +++ b/matrix/matrix-android/matrix-opengl-leak/build.gradle @@ -7,7 +7,7 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion + minSdkVersion MIN_SDK_VERSION_FOR_HOOK targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName rootProject.ext.VERSION_NAME diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.cpp b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.cpp index ab5d3e160..77a8a6435 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.cpp +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.cpp @@ -12,6 +12,9 @@ #include "get_tls.h" #include "type.h" #include "my_functions.h" +#include + +#define HOOK_REQUEST_GROUPID_EGL_HOOK 0x07 extern "C" JNIEXPORT jboolean JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_init (JNIEnv *env, jobject thiz) { @@ -62,6 +65,11 @@ extern "C" JNIEXPORT jboolean JNICALL Java_com_tencent_matrix_openglleak_hook_Op method_getThrowable = env->GetStaticMethodID(class_OpenGLHook, "getThrowable", "()I"); + method_onEglContextCreate = env->GetStaticMethodID(class_OpenGLHook, "onEglContextCreate", + "(Ljava/lang/String;IJJJLjava/lang/String;)V"); + method_onEglContextDestroy = env->GetStaticMethodID(class_OpenGLHook, "onEglContextDestroy", + "(Ljava/lang/String;JI)V"); + messages_containers = new BufferManagement(); messages_containers->start_process(); pthread_key_create(&g_thread_name_key, [](void *thread_name) { @@ -93,6 +101,27 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_VERSION_1_6; } +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_tencent_matrix_openglleak_hook_OpenGLHook_hookEgl(JNIEnv *env, jclass clazz) { + system_eglCreateContext = eglCreateContext; + system_eglDestroyContext = eglDestroyContext; + // TODO hook eglCreateXxxSurface() / eglDestroySurface() + + int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_EGL_HOOK, ".*\\.so$", "eglCreateContext", + (void *) my_egl_context_create, nullptr); + + ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_EGL_HOOK, ".*\\.so$", "eglDestroyContext", + (void *) my_egl_context_destroy, + nullptr); + + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_EGL_HOOK, ".*libmatrix-opengl-leak\\.so$", nullptr); + + xhook_refresh(false); + xhook_export_symtable_hook("libEGL.so", "eglCreateContext", (void *) my_egl_context_create, nullptr); + xhook_export_symtable_hook("libEGL.so", "eglDestroyContext", (void *) my_egl_context_destroy, nullptr); + return ret == 0; +} /* * Class: com_tencent_matrix_openglleak_hook_OpenGLHook @@ -437,8 +466,7 @@ Java_com_tencent_matrix_openglleak_hook_OpenGLHook_hookGlRenderbufferStorage(JNI return true; } -void get_native_stack(wechat_backtrace::Backtrace *backtrace, char *&stack) { - std::string caller_so_name; +void get_native_stack(wechat_backtrace::Backtrace *backtrace, char *&stack, bool brief = false) { std::stringstream full_stack_builder; std::stringstream brief_stack_builder; std::string last_so_name; @@ -450,8 +478,7 @@ void get_native_stack(wechat_backtrace::Backtrace *backtrace, char *&stack) { int status = 0; demangled_name = abi::__cxa_demangle(it.function_name, nullptr, 0, &status); - full_stack_builder << " | " - << "#pc " << std::hex << it.rel_pc << " " + full_stack_builder << "#pc " << std::hex << it.rel_pc << " " << (demangled_name ? demangled_name : "(null)") << " (" << it.map_name @@ -468,22 +495,20 @@ void get_native_stack(wechat_backtrace::Backtrace *backtrace, char *&stack) { if (demangled_name) { free(demangled_name); } - - if (caller_so_name.empty()) { // fallback - if (/*so_name.find("com.tencent.mm") == std::string::npos ||*/ - so_name.find("libwechatbacktrace.so") != std::string::npos || - so_name.find("libmatrix-hooks.so") != std::string::npos) { - return; - } - caller_so_name = so_name; - } }; wechat_backtrace::restore_frame_detail(backtrace->frames.get(), backtrace->frame_size, _callback); - stack = new char[full_stack_builder.str().size() + 1]; - strcpy(stack, full_stack_builder.str().c_str()); + if (brief) { + auto brief_stack = brief_stack_builder.str(); + stack = new char[brief_stack.size() + 1]; + strcpy(stack, brief_stack.c_str()); + } else { + auto full_stack = full_stack_builder.str(); + stack = new char[full_stack.size() + 1]; + strcpy(stack, full_stack.c_str()); + } } extern "C" @@ -491,14 +516,32 @@ JNIEXPORT jstring JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_dumpNativeStack(JNIEnv *env, jclass clazz, jlong native_stack_ptr) { int64_t addr = native_stack_ptr; - wechat_backtrace::Backtrace *ptr = (wechat_backtrace::Backtrace *) addr; + auto *ptr = (wechat_backtrace::Backtrace *) addr; char *native_stack = nullptr; get_native_stack(ptr, native_stack); jstring ret = env->NewStringUTF(native_stack); if (native_stack != nullptr) { - free(native_stack); + delete[] native_stack; + } + return ret; +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_com_tencent_matrix_openglleak_hook_OpenGLHook_dumpBriefNativeStack(JNIEnv *env, jclass clazz, + jlong native_stack_ptr) { + int64_t addr = native_stack_ptr; + auto *ptr = (wechat_backtrace::Backtrace *) addr; + + char *native_stack = nullptr; + get_native_stack(ptr, native_stack, true); + + jstring ret = env->NewStringUTF(native_stack); + + if (native_stack != nullptr) { + delete[] native_stack; } return ret; } @@ -560,17 +603,19 @@ Java_com_tencent_matrix_openglleak_hook_OpenGLHook_isEglContextAlive( EGL_NONE }; auto origin_context = (EGLContext) egl_context; - eglCreateContext(display, eglConfig, origin_context, attrib_ctx_list); + EGLContext test_context = eglCreateContext(display, eglConfig, origin_context, attrib_ctx_list); if (eglGetError() == EGL_BAD_CONTEXT) { return false; } + eglDestroyContext(display, test_context); return true; } extern "C" JNIEXPORT jint JNICALL -Java_com_tencent_matrix_openglleak_hook_OpenGLHook_getResidualQueueSize(JNIEnv *env, jobject clazz) { +Java_com_tencent_matrix_openglleak_hook_OpenGLHook_getResidualQueueSize(JNIEnv *env, + jobject clazz) { return messages_containers->get_queue_size(); } diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.h b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.h index dde337945..f636d7de2 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.h +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/com_tencent_matrix_openglleak_hook_OpenGLHook.h @@ -26,6 +26,9 @@ JNIEXPORT void JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_releas JNIEXPORT jstring JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_dumpNativeStack (JNIEnv *, jclass thiz, jlong); +JNIEXPORT jstring JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_dumpBriefNativeStack + (JNIEnv *, jclass thiz, jlong); + /* * Class: com_tencent_matrix_openglleak_hook_OpenGLHook * Method: init @@ -159,6 +162,10 @@ extern "C" JNIEXPORT jint JNICALL Java_com_tencent_matrix_openglleak_hook_OpenGLHook_getResidualQueueSize(JNIEnv *env, jobject clazz); +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_tencent_matrix_openglleak_hook_OpenGLHook_hookEgl(JNIEnv *env, jclass clazz); + #ifdef __cplusplus } #endif diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/my_functions.h b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/my_functions.h index 04f2d7492..ee9742e16 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/my_functions.h +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/my_functions.h @@ -1,6 +1,7 @@ // // Created by 邓沛堆 on 2020-05-28. // +// TODO: refactor #include "type.h" #include @@ -43,6 +44,8 @@ static System_GlBind_TYPE system_glBindFramebuffer = NULL; static System_GlBind_TYPE system_glBindRenderbuffer = NULL; static System_GlBufferData system_glBufferData = NULL; static System_GlRenderbufferStorage system_glRenderbufferStorage = NULL; +static System_eglCreateContext system_eglCreateContext = NULL; +static System_eglDestroyContext system_eglDestroyContext = NULL; static JavaVM *m_java_vm; @@ -65,6 +68,8 @@ static jmethodID method_onGlTexImage3D; static jmethodID method_onGlBufferData; static jmethodID method_onGlRenderbufferStorage; static jmethodID method_getThrowable; +static jmethodID method_onEglContextCreate; +static jmethodID method_onEglContextDestroy; const size_t BUF_SIZE = 1024; static pthread_once_t g_onceInitTls = PTHREAD_ONCE_INIT; @@ -74,7 +79,7 @@ static bool is_stacktrace_enabled = true; static bool is_javastack_enabled = true; static matrix::BufferManagement *messages_containers; -static char* curr_activity_info = nullptr; +static char *curr_activity_info = nullptr; void enable_stacktrace(bool enable) { is_stacktrace_enabled = enable; @@ -84,7 +89,7 @@ void enable_javastack(bool enable) { is_javastack_enabled = enable; } -void thread_id_to_string(thread::id thread_id, char *&result) { +void thread_id_to_string(pid_t thread_id, char *&result) { stringstream stream; stream << thread_id; result = new char[stream.str().size() + 1]; @@ -178,10 +183,10 @@ int get_java_throwable() { } void -gen_jni_callback(int alloc_count, GLuint *copy_resource, int throwable, const thread::id thread_id, +gen_jni_callback(int alloc_count, GLuint *copy_resource, int throwable, const pid_t tid, wechat_backtrace::Backtrace *backtracePtr, EGLContext egl_context, EGLSurface egl_draw_surface, EGLSurface egl_read_surface, - char* activity_info, jmethodID jmethodId) { + char *activity_info, jmethodID jmethodId) { JNIEnv *env = GET_ENV(); int *result = new int[alloc_count]; @@ -193,7 +198,7 @@ gen_jni_callback(int alloc_count, GLuint *copy_resource, int throwable, const th env->SetIntArrayRegion(newArr, 0, alloc_count, result); char *thread_id_c_str; - thread_id_to_string(thread_id, thread_id_c_str); + thread_id_to_string(tid, thread_id_c_str); jstring j_thread_id = env->NewStringUTF(thread_id_c_str); jstring j_activity_info = env->NewStringUTF(activity_info); @@ -230,7 +235,7 @@ gen_jni_callback(int alloc_count, GLuint *copy_resource, int throwable, const th } } -void delete_jni_callback(int delete_count, GLuint *copy_resource, const thread::id thread_id, +void delete_jni_callback(int delete_count, GLuint *copy_resource, const pid_t tid, EGLContext egl_context, jmethodID jmethodId) { JNIEnv *env = GET_ENV(); @@ -242,7 +247,7 @@ void delete_jni_callback(int delete_count, GLuint *copy_resource, const thread:: env->SetIntArrayRegion(newArr, 0, delete_count, result); char *thread_id_c_str; - thread_id_to_string(thread_id, thread_id_c_str); + thread_id_to_string(tid, thread_id_c_str); jstring j_thread_id = env->NewStringUTF(thread_id_c_str); env->CallStaticVoidMethod(class_OpenGLHook, @@ -279,22 +284,27 @@ GL_APICALL void GL_APIENTRY my_glGenTextures(GLsizei n, GLuint *textures) { int throwable = get_java_throwable(); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); EGLSurface egl_draw_surface = eglGetCurrentSurface(EGL_DRAW); EGLSurface egl_read_surface = eglGetCurrentSurface(EGL_READ); - char* activity_info = static_cast(malloc(BUF_SIZE)); - strcpy(activity_info, curr_activity_info); + char *activity_info = static_cast(malloc(BUF_SIZE)); + if (curr_activity_info != nullptr) { + strcpy(activity_info, curr_activity_info); + } else { + strcpy(activity_info, "null"); + } messages_containers-> enqueue_message((uintptr_t) egl_context, - [n, copy_textures, throwable, thread_id, backtracePrt, egl_context, egl_read_surface, egl_draw_surface, activity_info]() { + [n, copy_textures, throwable, tid, backtracePrt, egl_context, egl_read_surface, egl_draw_surface, activity_info]() { - gen_jni_callback(n, copy_textures, throwable, thread_id, - backtracePrt, egl_context, egl_draw_surface, egl_read_surface, + gen_jni_callback(n, copy_textures, throwable, tid, + backtracePrt, egl_context, egl_draw_surface, + egl_read_surface, activity_info, method_onGlGenTextures); }); @@ -312,15 +322,15 @@ GL_APICALL void GL_APIENTRY my_glDeleteTextures(GLsizei n, GLuint *textures) { GLuint *copy_textures = new GLuint[n]; memcpy(copy_textures, textures, n * sizeof(GLuint)); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_textures, thread_id, egl_context] { + [n, copy_textures, tid, egl_context] { - delete_jni_callback(n, copy_textures, thread_id, egl_context, + delete_jni_callback(n, copy_textures, tid, egl_context, method_onGlDeleteTextures); }); @@ -342,22 +352,26 @@ GL_APICALL void GL_APIENTRY my_glGenBuffers(GLsizei n, GLuint *buffers) { int throwable = get_java_throwable(); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); EGLSurface egl_draw_surface = eglGetCurrentSurface(EGL_DRAW); EGLSurface egl_read_surface = eglGetCurrentSurface(EGL_READ); - char* activity_info = static_cast(malloc(BUF_SIZE)); - strcpy(activity_info, curr_activity_info); - + char *activity_info = static_cast(malloc(BUF_SIZE)); + if (curr_activity_info != nullptr) { + strcpy(activity_info, curr_activity_info); + } else { + strcpy(activity_info, "null"); + } messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, throwable, thread_id, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { + [n, copy_buffers, throwable, tid, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { - gen_jni_callback(n, copy_buffers, throwable, thread_id, - backtracePrt, egl_context, egl_draw_surface,egl_read_surface, + gen_jni_callback(n, copy_buffers, throwable, tid, + backtracePrt, egl_context, egl_draw_surface, + egl_read_surface, activity_info, method_onGlGenBuffers); }); @@ -376,15 +390,15 @@ GL_APICALL void GL_APIENTRY my_glDeleteBuffers(GLsizei n, GLuint *buffers) { GLuint *copy_buffers = new GLuint[n]; memcpy(copy_buffers, buffers, n * sizeof(GLuint)); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, thread_id, egl_context]() { + [n, copy_buffers, tid, egl_context]() { - delete_jni_callback(n, copy_buffers, thread_id, egl_context, + delete_jni_callback(n, copy_buffers, tid, egl_context, method_onGlDeleteBuffers); }); @@ -405,22 +419,27 @@ GL_APICALL void GL_APIENTRY my_glGenFramebuffers(GLsizei n, GLuint *buffers) { int throwable = get_java_throwable(); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); EGLSurface egl_draw_surface = eglGetCurrentSurface(EGL_DRAW); EGLSurface egl_read_surface = eglGetCurrentSurface(EGL_READ); - char* activity_info = static_cast(malloc(BUF_SIZE)); - strcpy(activity_info, curr_activity_info); + char *activity_info = static_cast(malloc(BUF_SIZE)); + if (curr_activity_info != nullptr) { + strcpy(activity_info, curr_activity_info); + } else { + strcpy(activity_info, "null"); + } messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, throwable, thread_id, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { + [n, copy_buffers, throwable, tid, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { - gen_jni_callback(n, copy_buffers, throwable, thread_id, - backtracePrt, egl_context, egl_draw_surface,egl_read_surface, + gen_jni_callback(n, copy_buffers, throwable, tid, + backtracePrt, egl_context, egl_draw_surface, + egl_read_surface, activity_info, method_onGlGenFramebuffers); }); @@ -439,14 +458,14 @@ GL_APICALL void GL_APIENTRY my_glDeleteFramebuffers(GLsizei n, GLuint *buffers) GLuint *copy_buffers = new GLuint[n]; memcpy(copy_buffers, buffers, n * sizeof(GLuint)); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, thread_id, egl_context]() { - delete_jni_callback(n, copy_buffers, thread_id, egl_context, + [n, copy_buffers, tid, egl_context]() { + delete_jni_callback(n, copy_buffers, tid, egl_context, method_onGlDeleteFramebuffers); }); @@ -468,22 +487,27 @@ GL_APICALL void GL_APIENTRY my_glGenRenderbuffers(GLsizei n, GLuint *buffers) { int throwable = get_java_throwable(); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); EGLSurface egl_draw_surface = eglGetCurrentSurface(EGL_DRAW); EGLSurface egl_read_surface = eglGetCurrentSurface(EGL_READ); - char* activity_info = static_cast(malloc(BUF_SIZE)); - strcpy(activity_info, curr_activity_info); + char *activity_info = static_cast(malloc(BUF_SIZE)); + if (curr_activity_info != nullptr) { + strcpy(activity_info, curr_activity_info); + } else { + strcpy(activity_info, "null"); + } messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, throwable, thread_id, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { + [n, copy_buffers, throwable, tid, backtracePrt, egl_context, egl_draw_surface, egl_read_surface, activity_info]() { - gen_jni_callback(n, copy_buffers, throwable, thread_id, - backtracePrt, egl_context, egl_draw_surface,egl_read_surface, + gen_jni_callback(n, copy_buffers, throwable, tid, + backtracePrt, egl_context, egl_draw_surface, + egl_read_surface, activity_info, method_onGlGenRenderbuffers); }); @@ -501,15 +525,15 @@ GL_APICALL void GL_APIENTRY my_glDeleteRenderbuffers(GLsizei n, GLuint *buffers) GLuint *copy_buffers = new GLuint[n]; memcpy(copy_buffers, buffers, n * sizeof(GLuint)); - thread::id thread_id = this_thread::get_id(); + pid_t tid = pthread_gettid_np(pthread_self()); EGLContext egl_context = eglGetCurrentContext(); messages_containers ->enqueue_message((uintptr_t) egl_context, - [n, copy_buffers, thread_id, egl_context]() { + [n, copy_buffers, tid, egl_context]() { - delete_jni_callback(n, copy_buffers, thread_id, egl_context, + delete_jni_callback(n, copy_buffers, tid, egl_context, method_onGlDeleteRenderbuffers); }); @@ -785,5 +809,108 @@ my_glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GL } } +EGLAPI EGLContext EGLAPIENTRY +my_egl_context_create(EGLDisplay dpy, EGLConfig config, EGLContext share_context, + const EGLint *attrib_list) { + EGLContext ret = NULL; + if (NULL != system_eglCreateContext) { + ret = system_eglCreateContext(dpy, config, share_context, attrib_list); + + if (is_render_thread()) { + return ret; + } + + wechat_backtrace::Backtrace *backtracePrt = get_native_backtrace(); + + int throwable = get_java_throwable(); + + char *thread_id_c_str; + thread_id_to_string(pthread_gettid_np(pthread_self()), thread_id_c_str); + + char *activity_info = static_cast(malloc(BUF_SIZE)); + if (curr_activity_info != nullptr) { + strcpy(activity_info, curr_activity_info); + } else { + strcpy(activity_info, "null"); + } + + messages_containers + ->enqueue_message((uintptr_t) ret, + [backtracePrt, throwable, thread_id_c_str, ret, share_context, activity_info]() { + + JNIEnv *env = GET_ENV(); + + wechat_backtrace::Backtrace *backtrace = deduplicate_backtrace( + backtracePrt); + + jstring j_activity_info = env->NewStringUTF(activity_info); + jstring j_thread_id = env->NewStringUTF(thread_id_c_str); + + env->CallStaticVoidMethod(class_OpenGLHook, + method_onEglContextCreate, + j_thread_id, + (jint) throwable, + (jlong) backtrace, + (jlong) ret, + (jlong) share_context, + j_activity_info); + + env->DeleteLocalRef(j_activity_info); + env->DeleteLocalRef(j_thread_id); + + if (activity_info != nullptr) { + free(activity_info); + } + + if (thread_id_c_str) { + free(thread_id_c_str); + } + }); + } + return ret; +} + +/** + * EGL_BAD_DISPLAY is generated if display is not an EGL display connection. + * EGL_NOT_INITIALIZED is generated if display has not been initialized. + * EGL_BAD_CONTEXT is generated if context is not an EGL rendering context. + * + * @param dpy + * @param ctx + * @return EGL_FALSE is returned if destruction of the context fails, EGL_TRUE otherwise. + */ +EGLAPI EGLBoolean EGLAPIENTRY my_egl_context_destroy(EGLDisplay dpy, EGLContext ctx) { + EGLBoolean ret = EGL_FALSE; + if (NULL != system_eglDestroyContext) { + ret = system_eglDestroyContext(dpy, ctx); + + if (is_render_thread()) { + return ret; + } + + char *thread_id_c_str; + thread_id_to_string(pthread_gettid_np(pthread_self()), thread_id_c_str); + + messages_containers + ->enqueue_message((uintptr_t) ctx, + [thread_id_c_str, ctx, ret]() { + + JNIEnv *env = GET_ENV(); + jstring j_thread_id = env->NewStringUTF(thread_id_c_str); + + env->CallStaticVoidMethod(class_OpenGLHook, + method_onEglContextDestroy, + j_thread_id, + (jlong) ctx, + ret); + + env->DeleteLocalRef(j_thread_id); + if (thread_id_c_str) { + free(thread_id_c_str); + } + }); + } + return ret; +} #endif //OPENGL_API_HOOK_MY_FUNCTIONS_H \ No newline at end of file diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/type.h b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/type.h index 3c34fa337..22c4d535c 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/type.h +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/cpp/type.h @@ -13,6 +13,7 @@ #include #include #include +#include #ifndef OPENGL_API_HOOK_TYPE_H #define OPENGL_API_HOOK_TYPE_H @@ -61,6 +62,11 @@ typedef void (*System_GlRenderbufferStorage)(GLenum target, GLsizei width, GLsizei height); +typedef EGLContext (*System_eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, + const EGLint *attrib_list); + +typedef EGLBoolean (*System_eglDestroyContext)(EGLDisplay dpy, EGLContext ctx); + System_GlNormal_TYPE get_target_func_ptr(const char *func_name); System_GlBind_TYPE get_bind_func_ptr(const char *func_name); diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/OpenglLeakPlugin.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/OpenglLeakPlugin.java index 6650427b7..48f34af1f 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/OpenglLeakPlugin.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/OpenglLeakPlugin.java @@ -7,7 +7,6 @@ import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; - import com.tencent.matrix.openglleak.comm.FuncNameString; import com.tencent.matrix.openglleak.detector.IOpenglIndexDetector; import com.tencent.matrix.openglleak.detector.OpenglIndexDetectorService; @@ -100,6 +99,7 @@ private void executeHook(IBinder iBinder) { } // hook + OpenGLHook.hookEgl(); // hook eglCreateContext/eglDestroyContext first OpenGLHook.getInstance().hook(FuncNameString.GL_GEN_TEXTURES, map.get(FuncNameString.GL_GEN_TEXTURES)); OpenGLHook.getInstance().hook(FuncNameString.GL_DELETE_TEXTURES, map.get(FuncNameString.GL_DELETE_TEXTURES)); OpenGLHook.getInstance().hook(FuncNameString.GL_GEN_BUFFERS, map.get(FuncNameString.GL_GEN_BUFFERS)); @@ -118,7 +118,7 @@ private void executeHook(IBinder iBinder) { OpenGLHook.getInstance().hook(FuncNameString.GL_RENDER_BUFFER_STORAGE, map.get(FuncNameString.GL_RENDER_BUFFER_STORAGE)); MatrixLog.e(TAG, "hook finish"); } catch (Throwable e) { - e.printStackTrace(); + MatrixLog.printErrStackTrace(TAG, e, ""); } finally { // 销毁实验进程 try { @@ -132,22 +132,27 @@ private void executeHook(IBinder iBinder) { private void startImpl() { Intent service = new Intent(context, OpenglIndexDetectorService.class); - boolean result = context.bindService(service, new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, final IBinder iBinder) { - new Thread(new Runnable() { - @Override - public void run() { - executeHook(iBinder); - } - }).start(); - } + boolean result = false; + try { + result = context.bindService(service, new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, final IBinder iBinder) { + new Thread(new Runnable() { + @Override + public void run() { + executeHook(iBinder); + } + }).start(); + } - @Override - public void onServiceDisconnected(ComponentName componentName) { - context.unbindService(this); - } - }, Context.BIND_AUTO_CREATE); + @Override + public void onServiceDisconnected(ComponentName componentName) { + context.unbindService(this); + } + }, Context.BIND_AUTO_CREATE); + } catch (Exception e) { + MatrixLog.d(TAG, "bindService error = " + e.getCause()); + } MatrixLog.d(TAG, "bindService result = " + result); if (result) { diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/detector/FuncSeeker.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/detector/FuncSeeker.java index 363a90ce8..596ca8a13 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/detector/FuncSeeker.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/detector/FuncSeeker.java @@ -1,7 +1,10 @@ package com.tencent.matrix.openglleak.detector; +import androidx.annotation.Keep; + import com.tencent.matrix.openglleak.comm.FuncNameString; +@Keep class FuncSeeker { public static int getFuncIndex(String targetFuncName) { diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/hook/OpenGLHook.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/hook/OpenGLHook.java index 496418895..4676e4777 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/hook/OpenGLHook.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/hook/OpenGLHook.java @@ -2,6 +2,10 @@ import static android.opengl.GLES30.GL_PIXEL_UNPACK_BUFFER; +import android.opengl.EGL14; + +import androidx.annotation.Keep; + import com.tencent.matrix.openglleak.comm.FuncNameString; import com.tencent.matrix.openglleak.statistics.BindCenter; import com.tencent.matrix.openglleak.statistics.resource.MemoryInfo; @@ -13,6 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger; +@Keep public class OpenGLHook { static { @@ -131,8 +136,12 @@ public boolean hook(String targetFuncName, int index) { private static native boolean hookGlRenderbufferStorage(int index); + public static native boolean hookEgl(); + public static native String dumpNativeStack(long nativeStackPtr); + public static native String dumpBriefNativeStack(long nativeStackPtr); + public static native void releaseNative(long nativeStackPtr); public static native boolean isEglContextAlive(long eglContext); @@ -259,6 +268,36 @@ public static void onGlDeleteRenderbuffers(int[] ids, String threadId, final lon } } + public static void onEglContextCreate(String threadId, final int throwable, long nativeStackPtr, final long eglContext, final long shareContext, String activityInfo) { + AtomicInteger counter = new AtomicInteger(1); + + JavaStacktrace.Trace trace = JavaStacktrace.getBacktraceValue(throwable); + + final OpenGLInfo openGLInfo = new OpenGLInfo(OpenGLInfo.TYPE.EGL_CONTEXT, -1, threadId, eglContext, 0, 0, trace, nativeStackPtr, ActivityRecorder.revertActivityInfo(activityInfo), counter); + ResRecordManager.getInstance().gen(openGLInfo); + ResRecordManager.getInstance().createContext(shareContext, eglContext); + + if (getInstance().mResourceListener != null) { + getInstance().mResourceListener.onEglContextCreate(openGLInfo); + } + } + + public static void onEglContextDestroy(String threadId, final long eglContext, final int ret) { + final OpenGLInfo openGLInfo = new OpenGLInfo(OpenGLInfo.TYPE.EGL_CONTEXT, -1, threadId, eglContext); + + if (ret == EGL14.EGL_FALSE) { + MatrixLog.e(TAG, "eglContextDestroy failed: thread=%s, context=%s, ret=%s, errno=%s", threadId, eglContext, ret, EGL14.eglGetError()); + return; + } + + ResRecordManager.getInstance().delete(openGLInfo); + ResRecordManager.getInstance().destroyContext(eglContext); + + if (getInstance().mResourceListener != null) { + getInstance().mResourceListener.onEglContextDestroy(openGLInfo); + } + } + public static void onGetError(int eid) { if (getInstance().mErrorListener != null) { getInstance().mErrorListener.onGlError(eid); @@ -317,7 +356,7 @@ public static void onGlBindRenderbuffer(final int target, final int id, final lo public static void onGlTexImage2D(final int target, final int level, final int internalFormat, final int width, final int height, final int border, final int format, final int type, final long size, final int throwable, final long nativeStack, final long eglContext) { final OpenGLInfo openGLInfo = BindCenter.getInstance().findCurrentResourceIdByTarget(OpenGLInfo.TYPE.TEXTURE, eglContext, target); if (openGLInfo == null) { - MatrixLog.e(TAG, "onGlTexImage2D: getCurrentResourceIdByTarget openGLID == null, maybe undo glBindTextures()"); + MatrixLog.e(TAG, "onGlTexImage2D: getCurrentResourceIdByTarget openGLID == null, maybe didn't call glBindTextures()"); return; } @@ -337,7 +376,7 @@ public static void onGlTexImage2D(final int target, final int level, final int i public static void onGlTexImage3D(final int target, final int level, final int internalFormat, final int width, final int height, final int depth, final int border, final int format, final int type, final long size, final int throwable, final long nativeStack, final long eglContext) { final OpenGLInfo openGLInfo = BindCenter.getInstance().findCurrentResourceIdByTarget(OpenGLInfo.TYPE.TEXTURE, eglContext, target); if (openGLInfo == null) { - MatrixLog.e(TAG, "onGlTexImage3D: getCurrentResourceIdByTarget result == null, maybe undo glBindTextures()"); + MatrixLog.e(TAG, "onGlTexImage3D: getCurrentResourceIdByTarget result == null, maybe didn't call glBindTextures()"); return; } @@ -357,7 +396,7 @@ public static void onGlTexImage3D(final int target, final int level, final int i public static void onGlBufferData(final int target, final int usage, final long size, final int throwable, final long nativeStack, final long eglContext) { final OpenGLInfo openGLInfo = BindCenter.getInstance().findCurrentResourceIdByTarget(OpenGLInfo.TYPE.BUFFER, eglContext, target); if (openGLInfo == null) { - MatrixLog.e(TAG, "onGlBufferData: getCurrentResourceIdByTarget result == null, maybe undo glBindBuffer()"); + MatrixLog.e(TAG, "onGlBufferData: getCurrentResourceIdByTarget result == null, maybe didn't call glBindBuffer()"); return; } @@ -385,7 +424,7 @@ public static void onGlBufferData(final int target, final int usage, final long public static void onGlRenderbufferStorage(final int target, final int internalformat, final int width, final int height, final long size, final int key, final long nativeStack, final long eglContext) { final OpenGLInfo openGLInfo = BindCenter.getInstance().findCurrentResourceIdByTarget(OpenGLInfo.TYPE.RENDER_BUFFERS, eglContext, target); if (openGLInfo == null) { - MatrixLog.e(TAG, "onGlRenderbufferStorage: getCurrentResourceIdByTarget result == null, maybe undo glBindRenderbuffer()"); + MatrixLog.e(TAG, "onGlRenderbufferStorage: getCurrentResourceIdByTarget result == null, maybe didn't call glBindRenderbuffer()"); return; } @@ -434,6 +473,10 @@ public interface MemoryListener { public interface ResourceListener { + void onEglContextCreate(OpenGLInfo info); + + void onEglContextDestroy(OpenGLInfo info); + void onGlGenTextures(OpenGLInfo info); void onGlDeleteTextures(OpenGLInfo info); diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/OpenGLInfo.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/OpenGLInfo.java index 1ae790b1b..fe72fe47c 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/OpenGLInfo.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/OpenGLInfo.java @@ -24,7 +24,7 @@ public class OpenGLInfo { private AtomicInteger counter; public enum TYPE { - TEXTURE, BUFFER, FRAME_BUFFERS, RENDER_BUFFERS + TEXTURE, BUFFER, FRAME_BUFFERS, RENDER_BUFFERS, EGL_CONTEXT } public OpenGLInfo(OpenGLInfo clone) { @@ -101,6 +101,10 @@ public String getNativeStack() { return ResRecordManager.getInstance().getNativeStack(this); } + public String getBriefNativeStack() { + return ResRecordManager.getInstance().getBriefNativeStack(this); + } + public AtomicInteger getCounter() { return counter; } @@ -157,17 +161,17 @@ public String toString() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof OpenGLInfo)) return false; + if (!(o instanceof OpenGLInfo)) return false; OpenGLInfo that = (OpenGLInfo) o; return id == that.id && getEglContextNativeHandle() == that.getEglContextNativeHandle() && - threadId.equals(that.threadId) && + /*threadId.equals(that.threadId) &&*/ type == that.type; } @Override public int hashCode() { - return Objects.hash(id, getEglContextNativeHandle(), threadId, type); + return Objects.hash(id, getEglContextNativeHandle(), type); } } diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/ResRecordManager.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/ResRecordManager.java index f6170f600..9e3d6b0ef 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/ResRecordManager.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/statistics/resource/ResRecordManager.java @@ -4,6 +4,7 @@ import com.tencent.matrix.openglleak.hook.OpenGLHook; import com.tencent.matrix.openglleak.utils.AutoWrapBuilder; +import com.tencent.matrix.util.MatrixLog; import java.io.BufferedWriter; import java.io.File; @@ -14,20 +15,26 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +// TODO: 2022/12/22 should be deprecated and move to native public class ResRecordManager { + private static final String TAG = "Matrix.ResRecordManager"; private static final ResRecordManager mInstance = new ResRecordManager(); private final List mCallbackList = new LinkedList<>(); private final List mInfoList = new LinkedList<>(); - private final List mReleaseContext = new LinkedList<>(); private final List mReleaseSurface = new LinkedList<>(); + private final Map> mContextGroup = new HashMap<>(); + private final List mContextRecord = new ArrayList<>(); + private ResRecordManager() { } @@ -36,6 +43,38 @@ public static ResRecordManager getInstance() { return mInstance; } + public void createContext(Long shareContext, Long newContext) { + synchronized (mContextRecord) { + mContextRecord.add(newContext); + } + if (shareContext != 0) { + synchronized (mContextGroup) { + Set group = mContextGroup.get(shareContext); + if (group == null) { + group = new HashSet<>(); + mContextGroup.put(shareContext, group); + } + mContextGroup.put(newContext, group); + group.add(newContext); + group.add(shareContext); + } + } + } + + public void destroyContext(Long context) { + synchronized (mContextRecord) { + mContextRecord.remove(context); + } + synchronized (mContextGroup) { + if (mContextGroup.containsKey(context)) { + Set group = mContextGroup.remove(context); + if (group != null) { + group.remove(context); + } + } + } + } + public void gen(final OpenGLInfo gen) { if (gen == null) { return; @@ -54,15 +93,36 @@ public void gen(final OpenGLInfo gen) { } } - public void delete(final OpenGLInfo del) { + public void delete(OpenGLInfo del) { if (del == null) { return; } + OpenGLInfo infoDel; + + Set ctxGroup; + + synchronized (mContextGroup) { + ctxGroup = mContextGroup.get(del.getEglContextNativeHandle()); + } + synchronized (mInfoList) { // 之前可能释放过 int index = mInfoList.indexOf(del); + + if (-1 == index && ctxGroup != null) { // is shared context + for (Long ctx : ctxGroup) { + OpenGLInfo sharedInfo = new OpenGLInfo(del.getType(), del.getId(), del.getThreadId(), ctx); + index = mInfoList.indexOf(sharedInfo); + if (index != -1) { + MatrixLog.d(TAG, "del info found with shared context: %d, %s", index, ctx); + break; + } + } + } + if (-1 == index) { + MatrixLog.d(TAG, "del info not found"); return; } @@ -71,6 +131,8 @@ public void delete(final OpenGLInfo del) { return; } + infoDel = info; + AtomicInteger counter = info.getCounter(); counter.set(counter.get() - 1); if (counter.get() == 0) { @@ -89,13 +151,13 @@ public void delete(final OpenGLInfo del) { } } - mInfoList.remove(del); + mInfoList.remove(info); } synchronized (mCallbackList) { for (Callback cb : mCallbackList) { if (null != cb) { - cb.delete(del); + cb.delete(infoDel); } } } @@ -139,6 +201,29 @@ public String getNativeStack(OpenGLInfo item) { } } + public String getBriefNativeStack(OpenGLInfo item) { + synchronized (mInfoList) { + // 之前可能释放过 + int index = mInfoList.indexOf(item); + if (-1 == index) { + return "res already released, can not get native stack"; + } + + String ret = ""; + + OpenGLInfo info = mInfoList.get(index); + if (info == null) { + return ret; + } + long nativeStackPtr = info.getNativeStackPtr(); + if (nativeStackPtr != 0L) { + ret = OpenGLHook.dumpBriefNativeStack(nativeStackPtr); + } + + return ret; + } + } + protected void registerCallback(Callback callback) { if (null == callback) { return; @@ -172,30 +257,30 @@ public void clear() { synchronized (mInfoList) { mInfoList.clear(); } - synchronized (mReleaseContext) { - mReleaseContext.clear(); - } } public boolean isEglContextReleased(OpenGLInfo info) { - synchronized (mReleaseContext) { - long eglContextNativeHandle = info.getEglContextNativeHandle(); - if (0L == eglContextNativeHandle) { - return true; - } - - for (long item : mReleaseContext) { - if (item == eglContextNativeHandle) { - return true; - } - } - - boolean alive = OpenGLHook.isEglContextAlive(info.getEglContextNativeHandle()); - if (!alive) { - mReleaseContext.add(info.getEglContextNativeHandle()); - } - return !alive; + synchronized (mContextRecord) { + return !mContextRecord.contains(info.getEglContextNativeHandle()); } +// synchronized (mReleaseContext) { +// long eglContextNativeHandle = info.getEglContextNativeHandle(); +// if (0L == eglContextNativeHandle) { +// return true; +// } +// +// for (long item : mReleaseContext) { +// if (item == eglContextNativeHandle) { +// return true; +// } +// } +// +// boolean alive = OpenGLHook.isEglContextAlive(info.getEglContextNativeHandle()); +// if (!alive) { +// mReleaseContext.add(info.getEglContextNativeHandle()); +// } +// return !alive; +// } } public boolean isEglSurfaceReleased(OpenGLInfo info) { @@ -347,6 +432,7 @@ public String dumpGLToString() { List bufferList = new ArrayList<>(); List framebufferList = new ArrayList<>(); List renderbufferList = new ArrayList<>(); + List eglContextList = new ArrayList<>(); for (OpenGLDumpInfo reportInfo : infoMap.values()) { if (reportInfo.innerInfo.getType() == OpenGLInfo.TYPE.TEXTURE) { @@ -361,6 +447,9 @@ public String dumpGLToString() { if (reportInfo.innerInfo.getType() == OpenGLInfo.TYPE.RENDER_BUFFERS) { renderbufferList.add(reportInfo); } + if (reportInfo.innerInfo.getType() == OpenGLInfo.TYPE.EGL_CONTEXT) { + eglContextList.add(reportInfo); + } } Comparator comparator = new Comparator() { @@ -380,6 +469,7 @@ public int compare(OpenGLDumpInfo o1, OpenGLDumpInfo o2) { Collections.sort(bufferList, comparator); Collections.sort(framebufferList, comparator); Collections.sort(renderbufferList, comparator); + Collections.sort(eglContextList, comparator); AutoWrapBuilder builder = new AutoWrapBuilder(); builder.appendDotted() @@ -387,6 +477,7 @@ public int compare(OpenGLDumpInfo o1, OpenGLDumpInfo o2) { .appendWithSpace(String.format("buffer Count = %d", bufferList.size()), 3) .appendWithSpace(String.format("framebuffer Count = %d", framebufferList.size()), 3) .appendWithSpace(String.format("renderbuffer Count = %d", renderbufferList.size()), 3) + .appendWithSpace(String.format("egl context Count = %d", eglContextList.size()), 3) .appendDotted() .appendWave() .appendWithSpace("texture part :", 3) @@ -400,6 +491,10 @@ public int compare(OpenGLDumpInfo o1, OpenGLDumpInfo o2) { .appendWithSpace("renderbuffer part :", 3) .appendWave() .append(getResListString(renderbufferList)) + .appendWave() + .appendWithSpace("egl context part :", 3) + .appendWave() + .append(getResListString(eglContextList)) .wrap(); return builder.toString(); diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/EGLHelper.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/EGLHelper.java index cdbce64e1..96f8cdbf1 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/EGLHelper.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/EGLHelper.java @@ -20,15 +20,15 @@ public class EGLHelper { @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) - public static void initOpenGL() { + public static EGLContext initOpenGL() { mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { - return; + return null; } int[] version = new int[2]; if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { - return; + return null; } int[] eglConfigAttribList = new int[]{ @@ -41,7 +41,7 @@ public static void initOpenGL() { EGLConfig[] eglConfigs = new EGLConfig[1]; if (!EGL14.eglChooseConfig(mEGLDisplay, eglConfigAttribList, 0, eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) { - return; + return null; } mEglConfig = eglConfigs[0]; @@ -53,7 +53,7 @@ public static void initOpenGL() { mEglContext = EGL14.eglCreateContext(mEGLDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT, eglContextAttribList, 0); if (mEglContext == EGL14.EGL_NO_CONTEXT) { - return; + return null; } int[] surfaceAttribList = { @@ -65,14 +65,75 @@ public static void initOpenGL() { // Java 线程不进行实际绘制,因此创建 PbufferSurface 而非 WindowSurface mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEglConfig, surfaceAttribList, 0); if (mEglSurface == EGL14.EGL_NO_SURFACE) { - return; + return null; } if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) { - return; + return null; } GLES20.glFlush(); + return mEglContext; } + + public static EGLContext initOpenGLSharedContext(EGLContext shareContext) { + if (shareContext == null) { + return null; + } + + EGLDisplay eGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (eGLDisplay == EGL14.EGL_NO_DISPLAY) { + return null; + } + + int[] version = new int[2]; + if (!EGL14.eglInitialize(eGLDisplay, version, 0, version, 1)) { + return null; + } + + int[] eglConfigAttribList = new int[]{ + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_NONE + }; + int[] numEglConfigs = new int[1]; + EGLConfig[] eglConfigs = new EGLConfig[1]; + if (!EGL14.eglChooseConfig(eGLDisplay, eglConfigAttribList, 0, eglConfigs, 0, + eglConfigs.length, numEglConfigs, 0)) { + return null; + } + + int[] eglContextAttribList = new int[]{ + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + + EGLContext eglContext = EGL14.eglCreateContext(eGLDisplay, eglConfigs[0], shareContext, eglContextAttribList, 0); + + if (eglContext == EGL14.EGL_NO_CONTEXT) { + return null; + } + + int[] surfaceAttribList = { + EGL14.EGL_WIDTH, 64, + EGL14.EGL_HEIGHT, 64, + EGL14.EGL_NONE + }; + + // Java 线程不进行实际绘制,因此创建 PbufferSurface 而非 WindowSurface + mEglSurface = EGL14.eglCreatePbufferSurface(eGLDisplay, eglConfigs[0], surfaceAttribList, 0); + if (mEglSurface == EGL14.EGL_NO_SURFACE) { + return null; + } + + if (!EGL14.eglMakeCurrent(eGLDisplay, mEglSurface, mEglSurface, eglContext)) { + return null; + } + + GLES20.glFlush(); + + return eglContext; + } } diff --git a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/JavaStacktrace.java b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/JavaStacktrace.java index 63589bd13..6d29e3414 100644 --- a/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/JavaStacktrace.java +++ b/matrix/matrix-android/matrix-opengl-leak/src/main/java/com/tencent/matrix/openglleak/utils/JavaStacktrace.java @@ -3,6 +3,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +// todo: deprecated and move to native public class JavaStacktrace { private static final Map sThrowableMap = new ConcurrentHashMap<>(); @@ -29,7 +30,7 @@ public static Trace getBacktraceValue(int key) { if (throwable == null) { return new Trace(); } - String traceKey = stackTraceToString(throwable.getStackTrace()); + String traceKey = android.util.Log.getStackTraceString(throwable); //stackTraceToString(throwable.getStackTrace()); Trace mapTrace = sString2Trace.get(traceKey); if (mapTrace == null) { Trace resultTrace = new Trace(traceKey); diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ActivityLeakFixer.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ActivityLeakFixer.java index 46b93ce8e..4e8a0f703 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ActivityLeakFixer.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ActivityLeakFixer.java @@ -130,12 +130,17 @@ public static void fixInputMethodManagerLeak(Context destContext) { MatrixLog.i(TAG, "fixInputMethodManagerLeak done, cost: %s ms.", System.currentTimeMillis() - startTick); } + public static boolean sSupportSplit = false; + public static void unbindDrawables(Activity ui) { final long startTick = System.currentTimeMillis(); if (ui != null && ui.getWindow() != null && ui.getWindow().peekDecorView() != null) { - final View viewRoot = ui.getWindow().peekDecorView().getRootView(); + View viewRoot = ui.getWindow().peekDecorView().getRootView(); try { unbindDrawablesAndRecycle(viewRoot); + if (Build.VERSION.SDK_INT >= 31 && sSupportSplit) { + viewRoot = ui.getWindow().getDecorView().findViewById(android.R.id.content); + } if (viewRoot instanceof ViewGroup) { ((ViewGroup) viewRoot).removeAllViews(); } diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/CanaryWorkerService.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/CanaryWorkerService.java index 6322137d1..e4943e591 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/CanaryWorkerService.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/CanaryWorkerService.java @@ -25,7 +25,6 @@ import com.tencent.matrix.Matrix; import com.tencent.matrix.resource.analyzer.model.HeapDump; -import com.tencent.matrix.resource.dumper.DumpStorageManager; import com.tencent.matrix.resource.hproflib.HprofBufferShrinker; import com.tencent.matrix.util.MatrixLog; @@ -126,9 +125,9 @@ private void doShrinkHprofAndReport(HeapDump heapDump) { private String getShrinkHprofName(File origHprof) { final String origHprofName = origHprof.getName(); - final int extPos = origHprofName.indexOf(DumpStorageManager.HPROF_EXT); + final int extPos = origHprofName.indexOf(".hprof"); final String namePrefix = origHprofName.substring(0, extPos); - return namePrefix + "_shrink" + DumpStorageManager.HPROF_EXT; + return namePrefix + "_shrink.hprof"; } private String getResultZipName(String prefix) { diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ResourcePlugin.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ResourcePlugin.java index da2c97ceb..c9887fbd5 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ResourcePlugin.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/ResourcePlugin.java @@ -25,8 +25,10 @@ import com.tencent.matrix.plugin.PluginListener; import com.tencent.matrix.resource.config.ResourceConfig; import com.tencent.matrix.resource.config.SharePluginInfo; +import com.tencent.matrix.resource.dumper.HprofFileManager; import com.tencent.matrix.resource.processor.BaseLeakProcessor; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; +import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; /** @@ -78,6 +80,12 @@ public void start() { return; } mWatcher.start(); + MatrixHandlerThread.getDefaultHandler().post(new Runnable() { + @Override + public void run() { + HprofFileManager.INSTANCE.checkExpiredFile(); + } + }); } @Override diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/ResourceConfig.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/ResourceConfig.java index 71e42aea1..aa67603c8 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/ResourceConfig.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/ResourceConfig.java @@ -49,13 +49,15 @@ public enum DumpMode { private final boolean mDetectDebugger; private final String mTargetActivity; private final String mManufacture; + private final boolean enableManualDumpNotification; - private ResourceConfig(IDynamicConfig dynamicConfig, DumpMode dumpHprofMode, boolean detectDebuger, String targetActivity, String manufacture) { + private ResourceConfig(IDynamicConfig dynamicConfig, DumpMode dumpHprofMode, boolean detectDebuger, String targetActivity, String manufacture, boolean enableManualDumpNotification) { this.mDynamicConfig = dynamicConfig; this.mDumpHprofMode = dumpHprofMode; this.mDetectDebugger = detectDebuger; this.mTargetActivity = targetActivity; this.mManufacture = manufacture; + this.enableManualDumpNotification = enableManualDumpNotification; } public long getScanIntervalMillis() { @@ -86,11 +88,16 @@ public String getManufacture() { return mManufacture; } + public boolean isManualDumpNotificationEnabled() { + return enableManualDumpNotification; + } + public static final class Builder { private DumpMode mDefaultDumpHprofMode = DEFAULT_DUMP_HPROF_MODE; private IDynamicConfig dynamicConfig; private String mTargetActivity; + private boolean enableManualDumpNotification = true; private boolean mDetectDebugger = false; private String mManufacture; @@ -109,6 +116,11 @@ public Builder setDetectDebuger(boolean enabled) { return this; } + public Builder enableManualDumpNotification(boolean enable) { + enableManualDumpNotification = enable; + return this; + } + public Builder setManualDumpTargetActivity(String targetActivity) { mTargetActivity = targetActivity; return this; @@ -120,7 +132,7 @@ public Builder setManufacture(String manufacture) { } public ResourceConfig build() { - return new ResourceConfig(dynamicConfig, mDefaultDumpHprofMode, mDetectDebugger, mTargetActivity, mManufacture); + return new ResourceConfig(dynamicConfig, mDefaultDumpHprofMode, mDetectDebugger, mTargetActivity, mManufacture, enableManualDumpNotification); } } } diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/SharePluginInfo.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/SharePluginInfo.java index cbf2632eb..4db4b158c 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/SharePluginInfo.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/config/SharePluginInfo.java @@ -32,7 +32,9 @@ public class SharePluginInfo { public static final String ISSUE_COST_MILLIS = "cost_millis"; public static final String ISSUE_RETRY_COUNT = "retry_count"; public static final String ISSUE_LEAK_PROCESS = "leak_process"; + @Deprecated public static final String ISSUE_DUMP_DATA = "dump_data"; + public static final String ISSUE_HPROF_PATH = "hprof_path"; public static final String ISSUE_NOTIFICATION_ID = "notification_id"; public static final class IssueType { diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/AndroidHeapDumper.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/AndroidHeapDumper.java index 920bf4580..52e7f9eb5 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/AndroidHeapDumper.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/AndroidHeapDumper.java @@ -30,6 +30,7 @@ import com.tencent.matrix.util.MatrixLog; import java.io.File; +import java.io.FileNotFoundException; import java.util.concurrent.TimeUnit; /** @@ -42,25 +43,28 @@ public class AndroidHeapDumper { private static final String TAG = "Matrix.AndroidHeapDumper"; private final Context mContext; - private final DumpStorageManager mDumpStorageManager; private final Handler mMainHandler; public interface HeapDumpHandler { void process(HeapDump result); } - public AndroidHeapDumper(Context context, DumpStorageManager dumpStorageManager) { - this(context, dumpStorageManager, new Handler(Looper.getMainLooper())); + public AndroidHeapDumper(Context context) { + this(context, new Handler(Looper.getMainLooper())); } - public AndroidHeapDumper(Context context, DumpStorageManager dumpStorageManager, Handler mainHandler) { + public AndroidHeapDumper(Context context, Handler mainHandler) { mContext = context; - mDumpStorageManager = dumpStorageManager; mMainHandler = mainHandler; } public File dumpHeap(boolean isShowToast) { - final File hprofFile = mDumpStorageManager.newHprofFile(); + File hprofFile = null; + try { + hprofFile = HprofFileManager.INSTANCE.prepareHprofFile("", false); + } catch (FileNotFoundException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } if (null == hprofFile) { MatrixLog.w(TAG, "hprof file is null."); diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/DumpStorageManager.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/DumpStorageManager.java deleted file mode 100644 index 5d0db2cc7..000000000 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/DumpStorageManager.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * 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.tencent.matrix.resource.dumper; - -import android.content.Context; -import android.os.Environment; -import android.os.Process; - -import com.tencent.matrix.util.MatrixLog; -import com.tencent.matrix.util.MatrixUtil; - -import java.io.File; -import java.io.FilenameFilter; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** - * Created by tangyinsheng on 2017/6/2. - *

- * This class is ported from LeakCanary. - */ - -public class DumpStorageManager { - private static final String TAG = "Matrix.DumpStorageManager"; - - public static final String HPROF_EXT = ".hprof"; - - public static final int DEFAULT_MAX_STORED_HPROF_FILECOUNT = 5; - - protected final Context mContext; - protected final int mMaxStoredHprofFileCount; - - public DumpStorageManager(Context context) { - this(context, DEFAULT_MAX_STORED_HPROF_FILECOUNT); - } - - public DumpStorageManager(Context context, int maxStoredHprofFileCount) { - if (maxStoredHprofFileCount <= 0) { - throw new IllegalArgumentException("illegal max stored hprof file count: " - + maxStoredHprofFileCount); - } - mContext = context; - mMaxStoredHprofFileCount = maxStoredHprofFileCount; - } - - public File newHprofFile() { - final File storageDir = prepareStorageDirectory(); - if (storageDir == null) { - return null; - } - final String hprofFileName = "dump" - + "_" + MatrixUtil.getProcessName(mContext).replace(".", "_").replace(":", "_") - + "_" + Process.myPid() - + "_" - + new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()) - + HPROF_EXT; - return new File(storageDir, hprofFileName); - } - - private File prepareStorageDirectory() { - final File storageDir = getStorageDirectory(); - if (!storageDir.exists() && (!storageDir.mkdirs() || !storageDir.canWrite())) { - MatrixLog.w(TAG, "failed to allocate new hprof file since path: %s is not writable.", - storageDir.getAbsolutePath()); - return null; - } - final File[] hprofFiles = storageDir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(HPROF_EXT); - } - }); - if (hprofFiles != null && hprofFiles.length > mMaxStoredHprofFileCount) { - for (File file : hprofFiles) { - if (file.exists() && !file.delete()) { - MatrixLog.w(TAG, "faile to delete hprof file: " + file.getAbsolutePath()); - } - } - } - return storageDir; - } - - private File getStorageDirectory() { - final String sdcardState = Environment.getExternalStorageState(); - File root = null; - if (Environment.MEDIA_MOUNTED.equals(sdcardState)) { - root = mContext.getExternalCacheDir(); - } else { - root = mContext.getCacheDir(); - } - final File result = new File(root, "matrix_resource"); - - MatrixLog.i(TAG, "path to store hprof and result: %s", result.getAbsolutePath()); - - return result; - } -} diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/HprofFileManager.kt b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/HprofFileManager.kt new file mode 100644 index 000000000..cfa5f6e54 --- /dev/null +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/dumper/HprofFileManager.kt @@ -0,0 +1,116 @@ +package com.tencent.matrix.resource.dumper + +import android.os.Process +import com.tencent.matrix.Matrix +import com.tencent.matrix.util.MatrixLog +import com.tencent.matrix.util.MatrixUtil +import com.tencent.matrix.util.safeApply +import java.io.File +import java.io.FileNotFoundException +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * manage hprof files with LRU + */ +object HprofFileManager { + + private const val TAG = "Matrix.HprofFileManager" + + private const val MAX_FILE_COUNT = 10 + private const val CLEAN_THRESHOLD = 12 * 1024 * 1024 * 1024L // 12G + private const val MIN_FREE_SPACE = 1024 * 1024 * 1024L // 1G + + private val EXPIRED_TIME_MILLIS = TimeUnit.DAYS.toMillis(7) + + private val hprofStorageDir by lazy { File(Matrix.with().application.cacheDir.absolutePath + "/hprofs") } + + private val format by lazy { SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US) } + + fun checkExpiredFile() { + safeApply(TAG) { + val current = System.currentTimeMillis() + if (hprofStorageDir.exists() && hprofStorageDir.isDirectory) { + hprofStorageDir.listFiles { it: File -> + current - it.lastModified() > EXPIRED_TIME_MILLIS + }?.forEach { + if (it.exists()) { + MatrixLog.i(TAG, "expired: ${it.absolutePath}") + it.delete() + } + } + } + } + } + + @Throws(FileNotFoundException::class) + fun prepareHprofFile(prefix: String = "", deleteSoon: Boolean = false): File { + hprofStorageDir.prepare(deleteSoon) + return File(hprofStorageDir, getHprofFileName(prefix)) + } + + fun clearAll() { + hprofStorageDir.deleteRecursively() + } + + private fun File.prepare(deleteSoon: Boolean) { + reserve() + makeSureEnoughSpace(deleteSoon) + } + + private fun File.reserve() { + if (!exists() && (!mkdirs() || !canWrite())) { + "fialed to create new hprof file since path: $absolutePath is not writable".let { + MatrixLog.e(TAG, it) + throw FileNotFoundException(it) + } + } + } + + private fun File.makeSureEnoughSpace(deleteSoon: Boolean) { + if (!isDirectory) { + return + } + lru() + if (freeSpace < CLEAN_THRESHOLD) { + listFiles()?.forEach { it.delete() } + } + if (freeSpace < if (deleteSoon) MIN_FREE_SPACE else CLEAN_THRESHOLD) { + throw FileNotFoundException("free space($freeSpace) less than $CLEAN_THRESHOLD, skip dump hprof") + } + } + + private fun File.lru() { + if (!isDirectory) { + return + } + val files = listFiles() ?: return + files.sortBy { it.lastModified() } + files.forEach { + MatrixLog.d(TAG, "==> list sorted: ${it.absolutePath}, last mod = ${it.lastModified()}") + } + if (files.size >= MAX_FILE_COUNT) { + files.take(files.size - MAX_FILE_COUNT + 1).forEach { + it.delete() + } + } + } + + private val processSuffix by lazy { + if (MatrixUtil.isInMainProcess(Matrix.with().application)) { + "Main" + } else { + val split = + MatrixUtil.getProcessName(Matrix.with().application).split(":").toTypedArray() + if (split.size > 1) { + split[1] + } else { + "unknown" + } + } + } + + private fun getHprofFileName(prefix: String) = + "$prefix-${processSuffix}-${Process.myPid()}-${format.format(Calendar.getInstance().time)}.hprof" +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/BaseLeakProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/BaseLeakProcessor.java index bcfab87de..0145fdf99 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/BaseLeakProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/BaseLeakProcessor.java @@ -16,7 +16,6 @@ import com.tencent.matrix.resource.config.ResourceConfig; import com.tencent.matrix.resource.config.SharePluginInfo; import com.tencent.matrix.resource.dumper.AndroidHeapDumper; -import com.tencent.matrix.resource.dumper.DumpStorageManager; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; import com.tencent.matrix.util.MatrixLog; @@ -34,7 +33,6 @@ public abstract class BaseLeakProcessor { private final ActivityRefWatcher mWatcher; - private DumpStorageManager mDumpStorageManager; private AndroidHeapDumper mHeapDumper; private AndroidHeapDumper.HeapDumpHandler mHeapDumpHandler; @@ -44,16 +42,10 @@ public BaseLeakProcessor(ActivityRefWatcher watcher) { public abstract boolean process(DestroyedActivityInfo destroyedActivityInfo); - public DumpStorageManager getDumpStorageManager() { - if (mDumpStorageManager == null) { - mDumpStorageManager = new DumpStorageManager(mWatcher.getContext()); - } - return mDumpStorageManager; - } - + @Deprecated public AndroidHeapDumper getHeapDumper() { if (mHeapDumper == null) { - mHeapDumper = new AndroidHeapDumper(mWatcher.getContext(), getDumpStorageManager()); + mHeapDumper = new AndroidHeapDumper(mWatcher.getContext()); } return mHeapDumper; } @@ -110,6 +102,10 @@ final protected void publishIssue(int issueType, ResourceConfig.DumpMode dumpMod } final protected void publishIssue(int issueType, ResourceConfig.DumpMode dumpMode, String activity, String refKey, String detail, String cost, int retryCount) { + publishIssue(issueType, dumpMode, activity, refKey, detail, cost, retryCount, null); + } + + final protected void publishIssue(int issueType, ResourceConfig.DumpMode dumpMode, String activity, String refKey, String detail, String cost, int retryCount, String hprofPath) { Issue issue = new Issue(issueType); JSONObject content = new JSONObject(); try { @@ -119,6 +115,7 @@ final protected void publishIssue(int issueType, ResourceConfig.DumpMode dumpMod content.put(SharePluginInfo.ISSUE_LEAK_DETAIL, detail); content.put(SharePluginInfo.ISSUE_COST_MILLIS, cost); content.put(SharePluginInfo.ISSUE_RETRY_COUNT, retryCount); + content.put(SharePluginInfo.ISSUE_HPROF_PATH, hprofPath); } catch (JSONException jsonException) { MatrixLog.printErrStackTrace(TAG, jsonException, ""); } diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkAnalyseProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkAnalyseProcessor.java index 96fc07404..69afce5f5 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkAnalyseProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkAnalyseProcessor.java @@ -7,10 +7,12 @@ import com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo; import com.tencent.matrix.resource.config.ResourceConfig; import com.tencent.matrix.resource.config.SharePluginInfo; +import com.tencent.matrix.resource.dumper.HprofFileManager; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; import com.tencent.matrix.util.MatrixLog; import java.io.File; +import java.io.FileNotFoundException; /** * HPROF file analysis processor using fork dump. @@ -28,6 +30,8 @@ public ForkAnalyseProcessor(ActivityRefWatcher watcher) { @Override public boolean process(DestroyedActivityInfo destroyedActivityInfo) { + publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0"); + if (Build.VERSION.SDK_INT > ResourceConfig.FORK_DUMP_SUPPORTED_API_GUARD) { MatrixLog.e(TAG, "cannot fork-dump with unsupported API version " + Build.VERSION.SDK_INT); publishIssue( @@ -57,7 +61,12 @@ private boolean dumpAndAnalyse(String activity, String key) { final long dumpStart = System.currentTimeMillis(); - final File hprof = getDumpStorageManager().newHprofFile(); + File hprof = null; + try { + hprof = HprofFileManager.INSTANCE.prepareHprofFile("FAP", true); + } catch (FileNotFoundException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } if (hprof != null) { if (!MemoryUtil.dump(hprof.getPath(), 600)) { diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkDumpProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkDumpProcessor.java index 1b79092aa..d498a1514 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkDumpProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ForkDumpProcessor.java @@ -6,10 +6,13 @@ import com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo; import com.tencent.matrix.resource.analyzer.model.HeapDump; import com.tencent.matrix.resource.config.ResourceConfig; +import com.tencent.matrix.resource.config.SharePluginInfo; +import com.tencent.matrix.resource.dumper.HprofFileManager; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; import com.tencent.matrix.util.MatrixLog; import java.io.File; +import java.io.FileNotFoundException; /** * HPROF file dump processor using fork dump. @@ -27,6 +30,8 @@ public ForkDumpProcessor(ActivityRefWatcher watcher) { @Override public boolean process(DestroyedActivityInfo destroyedActivityInfo) { + publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0"); + if (Build.VERSION.SDK_INT > ResourceConfig.FORK_DUMP_SUPPORTED_API_GUARD) { MatrixLog.e(TAG, "unsupported API version " + Build.VERSION.SDK_INT); return false; @@ -34,7 +39,12 @@ public boolean process(DestroyedActivityInfo destroyedActivityInfo) { final long dumpStart = System.currentTimeMillis(); - final File hprof = getDumpStorageManager().newHprofFile(); + File hprof = null; + try { + hprof = HprofFileManager.INSTANCE.prepareHprofFile("FDP", true); + } catch (FileNotFoundException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } if (hprof == null) { MatrixLog.e(TAG, "cannot create hprof file, just ignore"); diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/LazyForkAnalyzeProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/LazyForkAnalyzeProcessor.java index 4a9265229..fd23bd76a 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/LazyForkAnalyzeProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/LazyForkAnalyzeProcessor.java @@ -11,11 +11,13 @@ import com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo; import com.tencent.matrix.resource.config.ResourceConfig; import com.tencent.matrix.resource.config.SharePluginInfo; +import com.tencent.matrix.resource.dumper.HprofFileManager; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; import java.io.File; +import java.io.FileNotFoundException; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; @@ -111,6 +113,8 @@ public void onDestroy() { @Override public boolean process(DestroyedActivityInfo destroyedActivityInfo) { + publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0"); + if (Build.VERSION.SDK_INT > ResourceConfig.FORK_DUMP_SUPPORTED_API_GUARD) { MatrixLog.e(TAG, "cannot fork-dump with unsupported API version " + Build.VERSION.SDK_INT); publishIssue( @@ -138,7 +142,12 @@ private boolean dumpAndAnalyse(String activity, String key) { final long dumpStart = System.currentTimeMillis(); - final File hprof = getDumpStorageManager().newHprofFile(); + File hprof = null; + try { + hprof = HprofFileManager.INSTANCE.prepareHprofFile("LFAP", true); + } catch (FileNotFoundException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } if (hprof != null) { if (!MemoryUtil.dump(hprof.getPath(), 600)) { diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ManualDumpProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ManualDumpProcessor.java index ee3d295ff..3fa377160 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ManualDumpProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/ManualDumpProcessor.java @@ -1,5 +1,7 @@ package com.tencent.matrix.resource.processor; +import static android.os.Build.VERSION.SDK_INT; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -7,8 +9,7 @@ import android.content.Context; import android.content.Intent; import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; +import android.util.Pair; import com.tencent.matrix.resource.MemoryUtil; import com.tencent.matrix.resource.R; @@ -16,21 +17,16 @@ import com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo; import com.tencent.matrix.resource.config.ResourceConfig; import com.tencent.matrix.resource.config.SharePluginInfo; +import com.tencent.matrix.resource.dumper.HprofFileManager; import com.tencent.matrix.resource.watcher.ActivityRefWatcher; -import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; import com.tencent.matrix.util.MatrixUtil; import java.io.File; -import java.util.ArrayList; -import java.util.List; +import java.io.FileNotFoundException; import java.util.Locale; import java.util.concurrent.TimeUnit; -import static android.os.Build.VERSION.SDK_INT; - -import androidx.annotation.Nullable; - /** * X process leaked -> send notification -> main process activity -> dump and analyse in X process -> show result in main process activity * Created by Yves on 2021/3/4 @@ -45,8 +41,6 @@ public class ManualDumpProcessor extends BaseLeakProcessor { private final NotificationManager mNotificationManager; - private final List mLeakedActivities = new ArrayList<>(); - private boolean isMuted; public ManualDumpProcessor(ActivityRefWatcher watcher, String targetActivity) { @@ -64,8 +58,6 @@ public boolean process(final DestroyedActivityInfo destroyedActivityInfo) { return true; } - mLeakedActivities.add(destroyedActivityInfo); - MatrixLog.i(TAG, "show notification for activity leak. %s", destroyedActivityInfo.mActivityName); if (isMuted) { @@ -73,24 +65,26 @@ public boolean process(final DestroyedActivityInfo destroyedActivityInfo) { return true; } - dumpAndAnalyzeAsync(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, new ManualDumpCallback() { - @Override - public void onDumpComplete(@Nullable ManualDumpData data) { - if (data != null) { - if (!isMuted) { - MatrixLog.i(TAG, "shown notification!!!3"); - sendResultNotification(destroyedActivityInfo, data); - } else { - MatrixLog.i(TAG, "mute mode, notification will not be shown."); - } - } + Pair data = dumpAndAnalyse(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey); + if (data != null) { + if (!isMuted) { + MatrixLog.i(TAG, "shown notification!!!3"); + sendResultNotification(destroyedActivityInfo, data.first, data.second); + } else { + MatrixLog.i(TAG, "mute mode, notification will not be shown."); } - }); + } + return true; } - private void sendResultNotification(DestroyedActivityInfo activityInfo, ManualDumpData data) { + private void sendResultNotification(DestroyedActivityInfo activityInfo, String hprofPath, String refChain) { + if (!getWatcher().getResourcePlugin().getConfig().isManualDumpNotificationEnabled()) { + MatrixLog.i(TAG, "Manual dump notification is disabled"); + return; + } + final Context context = getWatcher().getContext(); Intent targetIntent = new Intent(); @@ -98,9 +92,10 @@ private void sendResultNotification(DestroyedActivityInfo activityInfo, ManualDu targetIntent.putExtra(SharePluginInfo.ISSUE_ACTIVITY_NAME, activityInfo.mActivityName); targetIntent.putExtra(SharePluginInfo.ISSUE_REF_KEY, activityInfo.mKey); targetIntent.putExtra(SharePluginInfo.ISSUE_LEAK_PROCESS, MatrixUtil.getProcessName(context)); - targetIntent.putExtra(SharePluginInfo.ISSUE_DUMP_DATA, data); + targetIntent.putExtra(SharePluginInfo.ISSUE_HPROF_PATH, hprofPath); + targetIntent.putExtra(SharePluginInfo.ISSUE_LEAK_DETAIL, refChain); - PendingIntent pIntent = PendingIntent.getActivity(context, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, targetIntent, Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT); String dumpingHeapTitle = context.getString(R.string.resource_canary_leak_tip); ResourceConfig config = getWatcher().getResourcePlugin().getConfig(); @@ -149,19 +144,6 @@ public void setMuted(boolean mute) { isMuted = mute; } - private void dumpAndAnalyzeAsync(final String activity, final String refString, final ManualDumpCallback callback) { - MatrixHandlerThread.getDefaultHandler().postAtFrontOfQueue(new Runnable() { - @Override - public void run() { - callback.onDumpComplete(dumpAndAnalyse(activity, refString)); - } - }); - } - - private interface ManualDumpCallback { - void onDumpComplete(@Nullable ManualDumpData data); - } - /** * run in leaked process * @@ -169,67 +151,42 @@ private interface ManualDumpCallback { * @param key * @return */ - private ManualDumpData dumpAndAnalyse(String activity, String key) { + private Pair dumpAndAnalyse(String activity, String key) { getWatcher().triggerGc(); - File file = getDumpStorageManager().newHprofFile(); + File file = null; + try { + file = HprofFileManager.INSTANCE.prepareHprofFile("MDP", false); + } catch (FileNotFoundException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + + if (file == null) { + MatrixLog.e(TAG, "prepare hprof file failed, see log above"); + return null; + } + final ActivityLeakResult result = MemoryUtil.dumpAndAnalyze(file.getAbsolutePath(), key, 600); if (result.mLeakFound) { final String leakChain = result.toString(); publishIssue( SharePluginInfo.IssueType.LEAK_FOUND, - ResourceConfig.DumpMode.FORK_ANALYSE, - activity, key, leakChain, String.valueOf(result.mAnalysisDurationMs) + ResourceConfig.DumpMode.MANUAL_DUMP, + activity, key, leakChain, String.valueOf(result.mAnalysisDurationMs), + 0, + file.getAbsolutePath() ); - return new ManualDumpData(file.getAbsolutePath(), leakChain); + return new Pair<>(file.getAbsolutePath(), leakChain); } else if (result.mFailure != null) { publishIssue( SharePluginInfo.IssueType.ERR_EXCEPTION, - ResourceConfig.DumpMode.FORK_ANALYSE, + ResourceConfig.DumpMode.MANUAL_DUMP, activity, key, result.mFailure.toString(), "0" ); return null; } else { - return new ManualDumpData(file.getAbsolutePath(), null); - } - } - - public static class ManualDumpData implements Parcelable { - public final String hprofPath; - public final String refChain; - - public ManualDumpData(String hprofPath, String refChain) { - this.hprofPath = hprofPath; - this.refChain = refChain; - } - - protected ManualDumpData(Parcel in) { - hprofPath = in.readString(); - refChain = in.readString(); + return new Pair<>(file.getAbsolutePath(), null); } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(hprofPath); - dest.writeString(refChain); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = new Creator() { - @Override - public ManualDumpData createFromParcel(Parcel in) { - return new ManualDumpData(in); - } - - @Override - public ManualDumpData[] newArray(int size) { - return new ManualDumpData[size]; - } - }; } } diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/NativeForkAnalyzeProcessor.kt b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/NativeForkAnalyzeProcessor.kt index a850925d6..1efddbfb0 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/NativeForkAnalyzeProcessor.kt +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/NativeForkAnalyzeProcessor.kt @@ -8,12 +8,14 @@ import com.tencent.matrix.resource.MemoryUtil import com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo import com.tencent.matrix.resource.config.ResourceConfig import com.tencent.matrix.resource.config.SharePluginInfo +import com.tencent.matrix.resource.dumper.HprofFileManager import com.tencent.matrix.resource.watcher.ActivityRefWatcher import com.tencent.matrix.util.MatrixLog import com.tencent.matrix.util.MatrixUtil +import com.tencent.matrix.util.safeLet +import com.tencent.matrix.util.safeLetOrNull import java.io.File import java.util.* -import java.util.concurrent.Executors private fun File.deleteIfExist() { if (exists()) delete() @@ -72,7 +74,7 @@ private class RetryRepository(private val dir: File) { } } - fun process(action: (File, String, String, String) -> Unit) { + fun process(action: (File, File, String, String, String) -> Unit) { val hprofs = synchronized(accessLock) { hprofDir.listFiles() ?: emptyArray() } @@ -83,7 +85,7 @@ private class RetryRepository(private val dir: File) { val (activity, key, failure) = keyFile.bufferedReader().use { Triple(it.readLine(), it.readLine(), it.readLine()) } - action.invoke(hprofFile, activity, key, failure) + action.invoke(hprofFile, keyFile, activity, key, failure) } } catch (throwable: Throwable) { MatrixLog.printErrStackTrace( @@ -101,16 +103,7 @@ private class RetryRepository(private val dir: File) { class NativeForkAnalyzeProcessor(watcher: ActivityRefWatcher) : BaseLeakProcessor(watcher) { companion object { - - private const val RETRY_THREAD_NAME = "matrix_res_native_analyze_retry" - private const val RETRY_REPO_NAME = "matrix_res_process_retry" - - private val retryExecutor by lazy { - Executors.newSingleThreadExecutor { - Thread(it, RETRY_THREAD_NAME) - } - } } private val retryRepo: RetryRepository? by lazy { @@ -134,8 +127,11 @@ class NativeForkAnalyzeProcessor(watcher: ActivityRefWatcher) : BaseLeakProcesso private val screenStateReceiver by lazy { return@lazy object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - retryExecutor.execute { - retryRepo?.process { hprof, activity, key, failure -> + // run in detector thread to prevent parallel analyzing + watcher.detectHandler.post { + // fixme : should separate native analyse and java analyse into smaller parts. + // It is possible to unlock screen and turn foreground when analyzing + retryRepo?.process { hprof, keyFile, activity, key, failure -> MatrixLog.i(TAG, "Found record ${activity}(${hprof.name}).") val historyFailure = mutableListOf().apply { add(failure) @@ -144,8 +140,17 @@ class NativeForkAnalyzeProcessor(watcher: ActivityRefWatcher) : BaseLeakProcesso var result = MemoryUtil.analyze(hprof.absolutePath, key, timeout = 1200) if (result.mFailure != null) { historyFailure.add(result.mFailure.toString()) - retryCount++ - result = analyze(hprof, key) +// retryCount++ +// safeLetOrNull(TAG) { +// HprofFileManager.prepareHprofFile("RETRY", false) // prevent duplicated analyse after OOM +// }?.let { cpy -> +// hprof.copyTo(cpy, true) +// hprof.deleteIfExist() +// keyFile.deleteIfExist() +// safeLet({ +// result = analyze(cpy, key) // if crashed, the copied file could be auto-cleared by HprofFileManager later (lru or expired) +// }, success = { cpy.deleteIfExist() }, failed = { cpy.deleteIfExist() }) +// } } if (result.mLeakFound) { watcher.markPublished(activity, false) @@ -191,12 +196,13 @@ class NativeForkAnalyzeProcessor(watcher: ActivityRefWatcher) : BaseLeakProcesso } override fun process(destroyedActivityInfo: DestroyedActivityInfo): Boolean { + publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0") - val hprof = dumpStorageManager.newHprofFile() ?: run { + val hprof = safeLetOrNull { HprofFileManager.prepareHprofFile("NFAP", true) } ?: run { publishIssue( SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.FORK_ANALYSE, - "[unknown]", "[unknown]", "Failed to create hprof file.", "0" + destroyedActivityInfo.mActivityName, "[unknown]", "Failed to create hprof file.", "0" ) return true } @@ -206,7 +212,9 @@ class NativeForkAnalyzeProcessor(watcher: ActivityRefWatcher) : BaseLeakProcesso watcher.triggerGc() + MatrixLog.i(TAG, "fork dump and analyse") val result = MemoryUtil.dumpAndAnalyze(hprof.absolutePath, key, timeout = 600) + MatrixLog.i(TAG, "fork dump and analyse done") if (result.mFailure != null) { // Copies file to retry repository and analyzes it again when the screen is locked. MatrixLog.i(TAG, "Process failed, move into retry repository.") diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/SilenceAnalyseProcessor.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/SilenceAnalyseProcessor.java index 3362abbc3..446bc5f05 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/SilenceAnalyseProcessor.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/processor/SilenceAnalyseProcessor.java @@ -55,6 +55,7 @@ public void onReceive(Context context, Intent intent) { @Override public boolean process(DestroyedActivityInfo destroyedActivityInfo) { + publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0"); return onLeak(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey); } diff --git a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/watcher/ActivityRefWatcher.java b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/watcher/ActivityRefWatcher.java index d9d55cfaf..39e30a970 100644 --- a/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/watcher/ActivityRefWatcher.java +++ b/matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/watcher/ActivityRefWatcher.java @@ -202,6 +202,7 @@ && isPublished(activityName)) { final UUID uuid = UUID.randomUUID(); final StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(ACTIVITY_REFKEY_PREFIX).append(activityName) + .append("@").append(activity.hashCode()) .append('_').append(Long.toHexString(uuid.getMostSignificantBits())).append(Long.toHexString(uuid.getLeastSignificantBits())); final String key = keyBuilder.toString(); final DestroyedActivityInfo destroyedActivityInfo @@ -312,6 +313,10 @@ public ResourcePlugin getResourcePlugin() { return mResourcePlugin; } + public Handler getDetectHandler() { + return mHandler; + } + public Collection getDestroyedActivityInfos() { return mDestroyedActivityInfos; } diff --git a/matrix/matrix-android/matrix-trace-canary/CMakeLists.txt b/matrix/matrix-android/matrix-trace-canary/CMakeLists.txt index a914613dc..0f74c6bf1 100644 --- a/matrix/matrix-android/matrix-trace-canary/CMakeLists.txt +++ b/matrix/matrix-android/matrix-trace-canary/CMakeLists.txt @@ -14,8 +14,14 @@ add_library(trace-canary src/main/cpp/nativehelper/managed_jnienv.cc ) -TARGET_INCLUDE_DIRECTORIES(trace-canary PRIVATE ${EXT_DEP}/include) +target_include_directories( + trace-canary + PRIVATE ${EXT_DEP}/include + PRIVATE ${EXT_DEP}/include/backtrace + PRIVATE ${EXT_DEP}/include/backtrace/common +) +TARGET_INCLUDE_DIRECTORIES(trace-canary PRIVATE ${EXT_DEP}/include) find_library( log-lib log ) @@ -25,6 +31,7 @@ target_link_libraries( trace-canary PRIVATE ${log-lib} PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libxhook.a PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libsemi_dlfcn.a + PRIVATE ${EXT_DEP}/lib/${ANDROID_ABI}/libwechatbacktrace.so ) set_target_properties(trace-canary PROPERTIES diff --git a/matrix/matrix-android/matrix-trace-canary/build.gradle b/matrix/matrix-android/matrix-trace-canary/build.gradle index d7bc40620..70adce006 100644 --- a/matrix/matrix-android/matrix-trace-canary/build.gradle +++ b/matrix/matrix-android/matrix-trace-canary/build.gradle @@ -17,7 +17,7 @@ android { cppFlags "-std=gnu++11 -frtti -fexceptions" } ndk { - abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" + abiFilters "armeabi-v7a", "arm64-v8a" } } @@ -69,6 +69,7 @@ dependencies { testImplementation 'junit:junit:4.12' implementation project(':matrix-android-lib') implementation project(':matrix-android-commons') + implementation project(':matrix-backtrace') } version = rootProject.ext.VERSION_NAME diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/cpp/MatrixTracer.cc b/matrix/matrix-android/matrix-trace-canary/src/main/cpp/MatrixTracer.cc index f3c178618..2a9a0d553 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/cpp/MatrixTracer.cc +++ b/matrix/matrix-android/matrix-trace-canary/src/main/cpp/MatrixTracer.cc @@ -31,6 +31,11 @@ #include #include #include +#include + +#include "BacktraceDefine.h" +#include "Backtrace.h" +#include "cxxabi.h" #include #include @@ -55,8 +60,10 @@ #define HOOK_CONNECT_PATH "/dev/socket/tombstoned_java_trace" #define HOOK_OPEN_PATH "/data/anr/traces.txt" #define VALIDATE_RET 50 +#define KEY_VALID_FLAG (1 << 31) -#define HOOK_REQUEST_GROUPID_THREAD_PRIO_TRACE 0x01 +#define HOOK_REQUEST_GROUPID_THREAD_PRIORITY_TRACE 0x01 +#define HOOK_REQUEST_GROUPID_THREAD_PTHREAD_KEY_TRACE 0x13 #define HOOK_REQUEST_GROUPID_TOUCH_EVENT_TRACE 0x07 #define HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE 0x12 @@ -85,6 +92,7 @@ static struct StacktraceJNI { jmethodID ThreadPriorityDetective_onMainThreadPriorityModified; jmethodID ThreadPriorityDetective_onMainThreadTimerSlackModified; + jmethodID ThreadPriorityDetective_pthreadKeyCallback; jmethodID TouchEventLagTracer_onTouchEvenLag; jmethodID TouchEventLagTracer_onTouchEvenLagDumpTrace; @@ -92,7 +100,6 @@ static struct StacktraceJNI { int (*original_setpriority)(int __which, id_t __who, int __priority); int my_setpriority(int __which, id_t __who, int __priority) { - if ((__who == 0 && getpid() == gettid()) || __who == getpid()) { int priorityBefore = getpriority(__which, __who); JNIEnv *env = JniInvocation::getEnv(); @@ -224,6 +231,106 @@ ssize_t my_sendto(int sockfd, const void *buf, size_t len, int flags, return ret; } +void pthreadKeyCallback(int type, int ret, int keySeq, const char* soName, const char* backtrace) { + JNIEnv *env = JniInvocation::getEnv(); + if (!env) return; + + jstring soNameJS = env->NewStringUTF(soName); + jstring nativeBacktraceJS = env->NewStringUTF(backtrace); + + env->CallStaticVoidMethod(gJ.ThreadPriorityDetective, gJ.ThreadPriorityDetective_pthreadKeyCallback, type, ret, keySeq, soNameJS, nativeBacktraceJS); + env->DeleteLocalRef(soNameJS); + env->DeleteLocalRef(nativeBacktraceJS); +} + +void makeNativeStack(wechat_backtrace::Backtrace* backtrace, char *&stack) { + std::string caller_so_name; + std::stringstream full_stack_builder; + std::stringstream brief_stack_builder; + std::string last_so_name; + int index = 0; + auto _callback = [&](wechat_backtrace::FrameDetail it) { + std::string so_name = it.map_name; + + char *demangled_name = nullptr; + int status = 0; + + demangled_name = abi::__cxa_demangle(it.function_name, nullptr, 0, &status); + + if (strstr(it.map_name, "libtrace-canary.so") || strstr(it.map_name, "libwechatbacktrace.so")) { + return; + } + + full_stack_builder + << "#" << std::dec << (index++) + << " pc " << std::hex << it.rel_pc << " " + << it.map_name + << " (" + << (demangled_name ? demangled_name : "null") + << ")" + << std::endl; + if (last_so_name != it.map_name) { + last_so_name = it.map_name; + brief_stack_builder << it.map_name << ";"; + } + + brief_stack_builder << std::hex << it.rel_pc << ";"; + + if (demangled_name) { + free(demangled_name); + } + }; + + wechat_backtrace::restore_frame_detail(backtrace->frames.get(), backtrace->frame_size, + _callback); + + stack = new char[full_stack_builder.str().size() + 1]; + strcpy(stack, full_stack_builder.str().c_str()); +} + +static char* getNativeBacktrace() { + wechat_backtrace::Backtrace backtrace_zero = BACKTRACE_INITIALIZER( + 16); + wechat_backtrace::unwind_adapter(backtrace_zero.frames.get(), backtrace_zero.max_frames, + backtrace_zero.frame_size); + char* nativeStack; + makeNativeStack(&backtrace_zero, nativeStack); + return nativeStack; +} + +int (*original_pthread_key_create)(pthread_key_t *key, void (*destructor)(void*)); +int my_pthread_key_create(pthread_key_t *key, void (*destructor)(void*)) { + int ret = original_pthread_key_create(key, destructor); + int keySeq = *key & ~KEY_VALID_FLAG; + + void * __caller_addr = __builtin_return_address(0); + Dl_info dl_info; + dladdr(__caller_addr, &dl_info); + const char* soName = dl_info.dli_fname; + char* backtrace = getNativeBacktrace(); + if (!strstr(soName, "libc.so")) { + pthreadKeyCallback(0, ret, keySeq, soName, backtrace); + } + delete[] backtrace; + return ret; +} + +int (*original_pthread_key_delete)(pthread_key_t key); +int my_pthread_key_delete(pthread_key_t key) { + int ret = original_pthread_key_delete(key); + int keySeq = key & ~KEY_VALID_FLAG; + void * __caller_addr = __builtin_return_address(0); + Dl_info dl_info; + dladdr(__caller_addr, &dl_info); + const char* soName = dl_info.dli_fname; + char* backtrace = getNativeBacktrace(); + if (!strstr(soName, "libc.so")) { + pthreadKeyCallback(1, ret, keySeq, soName, backtrace); + } + delete[] backtrace; + return 0; +} + bool anrDumpCallback() { JNIEnv *env = JniInvocation::getEnv(); if (!env) return false; @@ -319,12 +426,37 @@ static void nativeFreeSignalAnrDetective(JNIEnv *env, jclass) { sAnrDumper.reset(); } -static void nativeInitMainThreadPriorityDetective(JNIEnv *env, jclass) { - xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PRIO_TRACE, ".*\\.so$", "setpriority", - (void *) my_setpriority, (void **) (&original_setpriority)); - xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PRIO_TRACE, ".*\\.so$", "prctl", - (void *) my_prctl, (void **) (&original_prctl)); - xhook_refresh(true); +static int nativeGetPthreadKeySeq(JNIEnv *env, jclass) { + pthread_key_t key; + pthread_key_create(&key, nullptr); + int key_seq = key & ~KEY_VALID_FLAG; + pthread_key_delete(key); + return key_seq; +} + +static void nativeInitThreadHook(JNIEnv *env, jclass, jint priority, jint pthreadKey) { + if (priority == 1) { + xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PRIORITY_TRACE, ".*\\.so$", "setpriority", + (void *) my_setpriority, (void **) (&original_setpriority)); + xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PRIORITY_TRACE, ".*\\.so$", "prctl", + (void *) my_prctl, (void **) (&original_prctl)); + } + + if (pthreadKey == 1) { + xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PTHREAD_KEY_TRACE, ".*\\.so$", "pthread_key_create", + (void *) my_pthread_key_create, (void **) (&original_pthread_key_create)); + + xhook_grouped_register(HOOK_REQUEST_GROUPID_THREAD_PTHREAD_KEY_TRACE, ".*\\.so$", "pthread_key_delete", + (void *) my_pthread_key_delete, (void **) (&original_pthread_key_delete)); + + xhook_export_symtable_hook("libc.so", "pthread_key_create", + (void *) my_pthread_key_create, (void **) (&original_pthread_key_create)); + xhook_export_symtable_hook("libc.so", "pthread_key_delete", + (void *) my_pthread_key_delete, (void **) (&original_pthread_key_delete)); + } + + xhook_enable_sigsegv_protection(0); + xhook_refresh(0); } static void nativeInitTouchEventLagDetective(JNIEnv *env, jclass, jint threshold) { @@ -355,8 +487,8 @@ static const JNINativeMethod ANR_METHODS[] = { }; static const JNINativeMethod THREAD_PRIORITY_METHODS[] = { - {"nativeInitMainThreadPriorityDetective", "()V", (void *) nativeInitMainThreadPriorityDetective}, - + {"nativeInitThreadHook", "(II)V", (void *) nativeInitThreadHook}, + {"nativeGetPthreadKeySeq", "()I", (void *) nativeGetPthreadKeySeq}, }; static const JNINativeMethod TOUCH_EVENT_TRACE_METHODS[] = { @@ -374,6 +506,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { jclass anrDetectiveCls = env->FindClass("com/tencent/matrix/trace/tracer/SignalAnrTracer"); if (!anrDetectiveCls) return -1; + + gJ.AnrDetective = static_cast(env->NewGlobalRef(anrDetectiveCls)); gJ.AnrDetector_onANRDumped = env->GetStaticMethodID(anrDetectiveCls, "onANRDumped", "()V"); @@ -393,7 +527,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { env->DeleteLocalRef(anrDetectiveCls); - jclass threadPriorityDetectiveCls = env->FindClass("com/tencent/matrix/trace/tracer/ThreadPriorityTracer"); + jclass threadPriorityDetectiveCls = env->FindClass("com/tencent/matrix/trace/tracer/ThreadTracer"); jclass touchEventLagTracerCls = env->FindClass("com/tencent/matrix/trace/tracer/TouchEventLagTracer"); @@ -405,6 +539,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { gJ.ThreadPriorityDetective_onMainThreadPriorityModified = env->GetStaticMethodID(threadPriorityDetectiveCls, "onMainThreadPriorityModified", "(II)V"); + + gJ.ThreadPriorityDetective_pthreadKeyCallback = + env->GetStaticMethodID(threadPriorityDetectiveCls, "pthreadKeyCallback", "(IIILjava/lang/String;Ljava/lang/String;)V"); + gJ.ThreadPriorityDetective_onMainThreadTimerSlackModified = env->GetStaticMethodID(threadPriorityDetectiveCls, "onMainThreadTimerSlackModified", "(J)V"); diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/cpp/TouchEventTracer.cc b/matrix/matrix-android/matrix-trace-canary/src/main/cpp/TouchEventTracer.cc index c0c229ce1..f465be880 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/cpp/TouchEventTracer.cc +++ b/matrix/matrix-android/matrix-trace-canary/src/main/cpp/TouchEventTracer.cc @@ -15,7 +15,7 @@ using namespace std; static mutex queueMutex; -static lock_guard lock(queueMutex); +static condition_variable cv; static bool loopRunning = false; static bool startDetect = false; static int LAG_THRESHOLD; @@ -37,7 +37,7 @@ void TouchEventTracer::touchRecv(int fd) { lastRecvTouchEventTimeStamp = 0; } else { lastRecvTouchEventTimeStamp = time(nullptr); - queueMutex.unlock(); + cv.notify_one(); } } @@ -52,10 +52,10 @@ void TouchEventTracer::touchSendFinish(int fd) { void recvQueueLooper() { - queueMutex.lock(); + unique_lock lk(queueMutex); while (loopRunning) { if (lastRecvTouchEventTimeStamp == 0) { - queueMutex.lock(); + cv.wait(lk); } else { long lastRecvTouchEventTimeStampNow = lastRecvTouchEventTimeStamp; if (lastRecvTouchEventTimeStampNow <= 0) { @@ -65,7 +65,7 @@ void recvQueueLooper() { if (time(nullptr) - lastRecvTouchEventTimeStampNow >= LAG_THRESHOLD && startDetect) { lagFd = currentFd; onTouchEventLagDumpTrace(currentFd); - queueMutex.lock(); + cv.wait(lk); } } } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/TracePlugin.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/TracePlugin.java index 319924708..5b709dbaf 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/TracePlugin.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/TracePlugin.java @@ -32,7 +32,6 @@ import com.tencent.matrix.trace.tracer.LooperAnrTracer; import com.tencent.matrix.trace.tracer.SignalAnrTracer; import com.tencent.matrix.trace.tracer.StartupTracer; -import com.tencent.matrix.trace.tracer.ThreadPriorityTracer; import com.tencent.matrix.trace.tracer.TouchEventLagTracer; import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; @@ -51,8 +50,7 @@ public class TracePlugin extends Plugin { private SignalAnrTracer signalAnrTracer; private IdleHandlerLagTracer idleHandlerLagTracer; private TouchEventLagTracer touchEventLagTracer; - private ThreadPriorityTracer threadPriorityTracer; - private static boolean supportFrameMetrics; + private final int sdkInt = Build.VERSION.SDK_INT; public TracePlugin(TraceConfig config) { this.traceConfig = config; @@ -62,18 +60,15 @@ public TracePlugin(TraceConfig config) { public void init(Application app, PluginListener listener) { super.init(app, listener); MatrixLog.i(TAG, "trace plugin init, trace config: %s", traceConfig.toString()); - int sdkInt = Build.VERSION.SDK_INT; if (sdkInt < Build.VERSION_CODES.JELLY_BEAN) { MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported"); unSupportPlugin(); return; - } else if (sdkInt >= Build.VERSION_CODES.O) { - supportFrameMetrics = true; } looperAnrTracer = new LooperAnrTracer(traceConfig); - frameTracer = new FrameTracer(traceConfig, supportFrameMetrics); + frameTracer = new FrameTracer(traceConfig); evilMethodTracer = new EvilMethodTracer(traceConfig); @@ -92,10 +87,10 @@ public void start() { @Override public void run() { - if (willUiThreadMonitorRunning(traceConfig)) { + if (sdkInt < Build.VERSION_CODES.N && willUiThreadMonitorRunning(traceConfig)) { if (!UIThreadMonitor.getMonitor().isInit()) { try { - UIThreadMonitor.getMonitor().init(traceConfig, supportFrameMetrics); + UIThreadMonitor.getMonitor().init(traceConfig); } catch (java.lang.RuntimeException e) { MatrixLog.e(TAG, "[start] RuntimeException:%s", e); return; @@ -132,11 +127,6 @@ public void run() { } } - if (traceConfig.isMainThreadPriorityTraceEnable()) { - threadPriorityTracer = new ThreadPriorityTracer(); - threadPriorityTracer.onStartTrace(); - } - if (traceConfig.isFPSEnable()) { frameTracer.onStartTrace(); } @@ -192,11 +182,6 @@ public void run() { if (idleHandlerLagTracer != null) { idleHandlerLagTracer.onCloseTrace(); } - - if (threadPriorityTracer != null) { - threadPriorityTracer.onCloseTrace(); - } - } }; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/SharePluginInfo.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/SharePluginInfo.java index f2acb387b..966090d3c 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/SharePluginInfo.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/SharePluginInfo.java @@ -32,7 +32,6 @@ public class SharePluginInfo { public static final String ISSUE_DROP_LEVEL = "dropLevel"; public static final String ISSUE_DROP_SUM = "dropSum"; public static final String ISSUE_FPS = "fps"; - public static final String ISSUE_SUM_TASK_FRAME = "dropTaskFrameSum"; public static final String ISSUE_TRACE_STACK = "stack"; public static final String ISSUE_THREAD_STACK = "threadStack"; public static final String ISSUE_PROCESS_PRIORITY = "processPriority"; @@ -45,7 +44,6 @@ public class SharePluginInfo { public static final String ISSUE_MEMORY_DALVIK = "dalvik_heap"; public static final String ISSUE_MEMORY_VM_SIZE = "vm_size"; public static final String ISSUE_COST = "cost"; - public static final String ISSUE_CPU_USAGE = "usage"; public static final String ISSUE_STACK_TYPE = "detail"; public static final String ISSUE_IS_WARM_START_UP = "is_warm_start_up"; public static final String ISSUE_SUB_TYPE = "subType"; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/TraceConfig.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/TraceConfig.java index b6aa3901b..052c71cb9 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/TraceConfig.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/config/TraceConfig.java @@ -35,6 +35,7 @@ public class TraceConfig implements IDefaultConfig { private static final String TAG = "Matrix.TraceConfig"; public static final int STACK_STYLE_SIMPLE = 0; public static final int STACK_STYLE_WHOLE = 1; + public static final int STACK_STYLE_RAW = 2; public IDynamicConfig dynamicConfig; public boolean defaultFpsEnable; public boolean defaultMethodTraceEnable; @@ -49,7 +50,6 @@ public class TraceConfig implements IDefaultConfig { public boolean isDevEnv; public boolean defaultSignalAnrEnable; public int stackStyle = STACK_STYLE_SIMPLE; - public boolean defaultMainThreadPriorityTraceEnable; public String splashActivities; public Set splashActivitiesSet; public String anrTraceFilePath = ""; @@ -137,11 +137,6 @@ public boolean isSignalAnrTraceEnable() { return defaultSignalAnrEnable; } - @Override - public boolean isMainThreadPriorityTraceEnable() { - return defaultMainThreadPriorityTraceEnable; - } - @Override public String getAnrTraceFilePath() { return anrTraceFilePath; @@ -245,7 +240,7 @@ public int getNormalThreshold() { public static class Builder { - private TraceConfig config = new TraceConfig(); + private final TraceConfig config = new TraceConfig(); public Builder dynamicConfig(IDynamicConfig dynamicConfig) { config.dynamicConfig = dynamicConfig; @@ -337,11 +332,6 @@ public Builder setTouchEventThreshold(int threshold) { return this; } - public Builder enableMainThreadPriorityTrace(boolean enable) { - config.defaultMainThreadPriorityTraceEnable = enable; - return this; - } - public Builder enableHistoryMsgRecorder(boolean enable) { config.historyMsgRecorder = enable; return this; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/AppMethodBeat.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/AppMethodBeat.java index 959fd3310..70999c90d 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/AppMethodBeat.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/AppMethodBeat.java @@ -26,6 +26,7 @@ import com.tencent.matrix.trace.constants.Constants; import com.tencent.matrix.trace.hacker.ActivityThreadHacker; import com.tencent.matrix.trace.listeners.IAppMethodBeatListener; +import com.tencent.matrix.trace.listeners.ILooperListener; import com.tencent.matrix.trace.util.Utils; import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; @@ -68,21 +69,19 @@ public interface MethodEnterListener { private static final Object updateTimeLock = new Object(); private static volatile boolean isPauseUpdateTime = false; private static Runnable checkStartExpiredRunnable = null; - private static LooperMonitor.LooperDispatchListener looperMonitorListener = new LooperMonitor.LooperDispatchListener() { + private static ILooperListener looperMonitorListener = new ILooperListener() { @Override public boolean isValid() { return status >= STATUS_READY; } @Override - public void dispatchStart() { - super.dispatchStart(); + public void onDispatchBegin(String log) { AppMethodBeat.dispatchBegin(); } @Override - public void dispatchEnd() { - super.dispatchEnd(); + public void onDispatchEnd(String log, long beginNs, long endNs) { AppMethodBeat.dispatchEnd(); } }; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/LooperMonitor.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/LooperMonitor.java index 9e78ef7fd..d3a9b9452 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/LooperMonitor.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/LooperMonitor.java @@ -22,16 +22,18 @@ import android.os.Looper; import android.os.MessageQueue; import android.os.SystemClock; -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; - import android.util.Log; import android.util.Printer; +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; + +import com.tencent.matrix.trace.listeners.ILooperListener; import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; import com.tencent.matrix.util.ReflectUtils; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -46,17 +48,20 @@ public class LooperMonitor implements MessageQueue.IdleHandler { private static final HandlerThread historyMsgHandlerThread = MatrixHandlerThread.getNewHandlerThread("historyMsgHandlerThread", HandlerThread.NORM_PRIORITY); private static final Handler historyMsgHandler = new Handler(historyMsgHandlerThread.getLooper()); - private static long messageStartTime = 0; private static final int HISTORY_QUEUE_MAX_SIZE = 200; private static final int RECENT_QUEUE_MAX_SIZE = 5000; - private static final Queue anrHistoryMQ = new ConcurrentLinkedQueue<>(); - private static final Queue recentMsgQ = new ConcurrentLinkedQueue<>(); + private final Queue anrHistoryMQ = new ConcurrentLinkedQueue<>(); + private final Queue recentMsgQ = new ConcurrentLinkedQueue<>(); - private static String latestMsgLog = ""; - private static long recentMCount = 0; - private static long recentMDuration = 0; + private long messageStartTime = 0; + private String latestMsgLog = ""; + private long recentMCount = 0; + private long recentMDuration = 0; + private boolean denseMsgTracer = false; + private boolean historyMsgRecorder = false; + @Deprecated public abstract static class LooperDispatchListener { boolean isHasDispatchStart = false; @@ -83,14 +88,18 @@ public void dispatchStart() { @CallSuper public void onDispatchStart(String x) { - this.isHasDispatchStart = true; - dispatchStart(); + if (!isHasDispatchStart) { + isHasDispatchStart = true; + dispatchStart(); + } } @CallSuper public void onDispatchEnd(String x) { - this.isHasDispatchStart = false; - dispatchEnd(); + if (isHasDispatchStart) { + isHasDispatchStart = false; + dispatchEnd(); + } } @@ -98,6 +107,39 @@ public void dispatchEnd() { } } + private final static class DispatchListenerWrapper { + private boolean isHasDispatchStart = false; + private long beginNs; + private final ILooperListener dispatchListener; + + DispatchListenerWrapper(ILooperListener dispatchListener) { + this.dispatchListener = dispatchListener; + } + + public boolean isValid() { + return dispatchListener.isValid(); + } + + public void onDispatchBegin(String x) { + if (!isHasDispatchStart) { + isHasDispatchStart = true; + beginNs = System.nanoTime(); + dispatchListener.onDispatchBegin(x); + } + } + + public void onDispatchEnd(String x) { + if (isHasDispatchStart) { + isHasDispatchStart = false; + dispatchListener.onDispatchEnd(x, beginNs, System.nanoTime()); + } + } + } + + public static LooperMonitor getMainMonitor() { + return sMainMonitor; + } + public static LooperMonitor of(@NonNull Looper looper) { LooperMonitor looperMonitor = sLooperMonitorMap.get(looper); if (looperMonitor == null) { @@ -107,15 +149,27 @@ public static LooperMonitor of(@NonNull Looper looper) { return looperMonitor; } + @Deprecated static void register(LooperDispatchListener listener) { sMainMonitor.addListener(listener); } + @Deprecated static void unregister(LooperDispatchListener listener) { sMainMonitor.removeListener(listener); } - private final HashSet listeners = new HashSet<>(); + public static void register(ILooperListener listener) { + sMainMonitor.addListener(listener); + } + + public static void unregister(ILooperListener listener) { + sMainMonitor.removeListener(listener); + } + + @Deprecated + private final HashSet oldListeners = new HashSet<>(); + private final Map listeners = new HashMap<>(); private LooperPrinter printer; private Looper looper; private static final long CHECK_TIME = 60 * 1000L; @@ -125,17 +179,32 @@ static void unregister(LooperDispatchListener listener) { * It will be thread-unsafe if you get the listeners and literate. */ @Deprecated - public HashSet getListeners() { - return listeners; + public HashSet getOldListeners() { + return oldListeners; } + @Deprecated public void addListener(LooperDispatchListener listener) { - synchronized (listeners) { - listeners.add(listener); + synchronized (oldListeners) { + oldListeners.add(listener); } } + @Deprecated public void removeListener(LooperDispatchListener listener) { + synchronized (oldListeners) { + oldListeners.remove(listener); + } + } + + public void addListener(ILooperListener listener) { + synchronized (listeners) { + DispatchListenerWrapper wrapper = new DispatchListenerWrapper(listener); + listeners.put(listener, wrapper); + } + } + + public void removeListener(ILooperListener listener) { synchronized (listeners) { listeners.remove(listener); } @@ -163,12 +232,15 @@ public boolean queueIdle() { public synchronized void onRelease() { if (printer != null) { + synchronized (oldListeners) { + oldListeners.clear(); + } synchronized (listeners) { listeners.clear(); } MatrixLog.v(TAG, "[onRelease] %s, origin printer:%s", looper.getThread().getName(), printer.origin); - looper.setMessageLogging(printer.origin); removeIdleHandler(looper); + looper.setMessageLogging(printer.origin); looper = null; printer = null; } @@ -270,7 +342,7 @@ public void println(String x) { } } - private static void recordMsg(final String log, final long duration, boolean denseMsgTracer) { + private void recordMsg(final String log, final long duration) { historyMsgHandler.post(new Runnable() { @Override public void run() { @@ -288,7 +360,7 @@ public void run() { } } - private static void enqueueRecentMQ(M m) { + private void enqueueRecentMQ(M m) { if (recentMsgQ.size() == RECENT_QUEUE_MAX_SIZE) { recentMsgQ.poll(); } @@ -297,59 +369,73 @@ private static void enqueueRecentMQ(M m) { recentMDuration += m.d; } - private static void enqueueHistoryMQ(M m) { + private void enqueueHistoryMQ(M m) { if (anrHistoryMQ.size() == HISTORY_QUEUE_MAX_SIZE) { anrHistoryMQ.poll(); } anrHistoryMQ.offer(m); } - public static Queue getHistoryMQ() { + public Queue getHistoryMQ() { enqueueHistoryMQ(new M(latestMsgLog, System.currentTimeMillis() - messageStartTime)); return anrHistoryMQ; } - public static Queue getRecentMsgQ() { + public Queue getRecentMsgQ() { return recentMsgQ; } - public static void cleanRecentMQ() { + public void cleanRecentMQ() { recentMsgQ.clear(); recentMCount = 0; recentMDuration = 0; } - public static long getRecentMCount() { + public long getRecentMCount() { return recentMCount; } - public static long getRecentMDuration() { + public long getRecentMDuration() { return recentMDuration; } private void dispatch(boolean isBegin, String log) { - synchronized (listeners) { - for (LooperDispatchListener listener : listeners) { - if (listener.isValid()) { - if (isBegin) { - if (!listener.isHasDispatchStart) { - if (listener.historyMsgRecorder) { - messageStartTime = System.currentTimeMillis(); - latestMsgLog = log; - recentMCount++; - } - listener.onDispatchStart(log); - } - } else { - if (listener.isHasDispatchStart) { - if (listener.historyMsgRecorder) { - recordMsg(log, System.currentTimeMillis() - messageStartTime, listener.denseMsgTracer); - } - listener.onDispatchEnd(log); - } + if (isBegin) { + if (historyMsgRecorder) { + messageStartTime = System.currentTimeMillis(); + latestMsgLog = log; + recentMCount++; + } + synchronized (oldListeners) { + for (LooperDispatchListener listener : oldListeners) { + if (listener.isValid()) { + listener.onDispatchStart(log); + } + } + } + synchronized (listeners) { + for (DispatchListenerWrapper listener : listeners.values()) { + if (listener.isValid()) { + listener.onDispatchBegin(log); + } + } + } + } else { + if (historyMsgRecorder) { + recordMsg(log, System.currentTimeMillis() - messageStartTime); + } + synchronized (oldListeners) { + for (LooperDispatchListener listener : oldListeners) { + if (listener.isValid()) { + listener.onDispatchEnd(log); + } + } + } + synchronized (listeners) { + for (DispatchListenerWrapper listener : listeners.values()) { + if (listener.isValid()) { + listener.onDispatchEnd(log); } - } else if (!isBegin && listener.isHasDispatchStart) { - listener.dispatchEnd(); } } } @@ -358,6 +444,7 @@ private void dispatch(boolean isBegin, String log) { public static class M { public String l; public long d; + M(String l, long d) { this.l = l; this.d = d; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/UIThreadMonitor.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/UIThreadMonitor.java index 74c9f3a4b..f68b68b06 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/UIThreadMonitor.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/UIThreadMonitor.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.HashSet; +@Deprecated public class UIThreadMonitor implements BeatLifecycle, Runnable { private static final String TAG = "Matrix.UIThreadMonitor"; @@ -79,7 +80,6 @@ public class UIThreadMonitor implements BeatLifecycle, Runnable { private final static UIThreadMonitor sInstance = new UIThreadMonitor(); private TraceConfig config; - private static boolean useFrameMetrics; private Object callbackQueueLock; private Object[] callbackQueues; private Method addTraversalQueue; @@ -104,9 +104,8 @@ public boolean isInit() { return isInit; } - public void init(TraceConfig config, boolean supportFrameMetrics) { + public void init(TraceConfig config) { this.config = config; - useFrameMetrics = supportFrameMetrics; if (Thread.currentThread() != Looper.getMainLooper().getThread()) { throw new AssertionError("must be init in main thread!"); @@ -135,30 +134,28 @@ public void dispatchEnd() { }); this.isInit = true; + choreographer = Choreographer.getInstance(); frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION); - if (!useFrameMetrics) { - choreographer = Choreographer.getInstance(); - callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object()); - callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null); - if (null != callbackQueues) { - addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class); - addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class); - addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class); - } - vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null); + callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object()); + callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null); + if (null != callbackQueues) { + addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class); + addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class); + addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class); + } + vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null); - MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null, - addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos); + MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null, + addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos); - if (config.isDevEnv()) { - addObserver(new LooperObserver() { - @Override - public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { - MatrixLog.i(TAG, "focusedActivity[%s] frame cost:%sms isVsyncFrame=%s intendedFrameTimeNs=%s [%s|%s|%s]ns", - focusedActivity, (endNs - startNs) / Constants.TIME_MILLIS_TO_NANO, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); - } - }); - } + if (config.isDevEnv()) { + addObserver(new LooperObserver() { + @Override + public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { + MatrixLog.i(TAG, "focusedActivity[%s] frame cost:%sms isVsyncFrame=%s intendedFrameTimeNs=%s [%s|%s|%s]ns", + focusedActivity, (endNs - startNs) / Constants.TIME_MILLIS_TO_NANO, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + } + }); } } @@ -264,7 +261,7 @@ private void dispatchEnd() { traceBegin = System.nanoTime(); } - if (config.isFPSEnable() && !useFrameMetrics) { + if (config.isFPSEnable()) { long startNs = token; long intendedFrameTimeNs = startNs; if (isVsyncFrame) { @@ -330,11 +327,9 @@ public synchronized void onStart() { MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack()); callbackExist = new boolean[CALLBACK_LAST + 1]; } - if (!useFrameMetrics) { - queueStatus = new int[CALLBACK_LAST + 1]; - queueCost = new long[CALLBACK_LAST + 1]; - addFrameCallback(CALLBACK_INPUT, this, true); - } + queueStatus = new int[CALLBACK_LAST + 1]; + queueCost = new long[CALLBACK_LAST + 1]; + addFrameCallback(CALLBACK_INPUT, this, true); } } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDefaultConfig.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDefaultConfig.java index 00ab53187..872d2eb61 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDefaultConfig.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDefaultConfig.java @@ -36,8 +36,6 @@ public interface IDefaultConfig { boolean isSignalAnrTraceEnable(); - boolean isMainThreadPriorityTraceEnable(); - boolean isDebug(); boolean isDevEnv(); diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDoFrameListener.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDoFrameListener.java index 9844dba2a..eb9cf774d 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDoFrameListener.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDoFrameListener.java @@ -26,7 +26,10 @@ /** * Created by caichongyang on 2017/5/26. + *
+ * Use {@link IFrameListener} or {@link ISceneFrameListener} instead. **/ +@Deprecated public class IDoFrameListener { private Executor executor; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDropFrameListener.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDropFrameListener.java new file mode 100644 index 000000000..f957a74a3 --- /dev/null +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IDropFrameListener.java @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.trace.listeners; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.N) +public interface IDropFrameListener extends IFrameListener { +} diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IFrameListener.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IFrameListener.java new file mode 100644 index 000000000..51aed5589 --- /dev/null +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/IFrameListener.java @@ -0,0 +1,31 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.trace.listeners; + +import android.os.Build; +import android.view.FrameMetrics; + +import androidx.annotation.RequiresApi; + +/** + * Use {@link ISceneFrameListener} to analyze frame metrics of specified scene, or use + * {@link IDropFrameListener} to only analyze dropped frame. + */ +@RequiresApi(Build.VERSION_CODES.N) +public interface IFrameListener { + void onFrameMetricsAvailable(String sceneName, FrameMetrics frameMetrics, float droppedFrames, float refreshRate); +} diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ILooperListener.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ILooperListener.java new file mode 100644 index 000000000..cf3ed1398 --- /dev/null +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ILooperListener.java @@ -0,0 +1,23 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.trace.listeners; + +public interface ILooperListener { + boolean isValid(); + void onDispatchBegin(String log); + void onDispatchEnd(String log, long beginNs, long endNs); +} diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ISceneFrameListener.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ISceneFrameListener.java new file mode 100644 index 000000000..768d037cd --- /dev/null +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/ISceneFrameListener.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making wechat-matrix available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.matrix.trace.listeners; + + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +public interface ISceneFrameListener { + + /** + * The interval returned indicates how long to call back {@code onFrameMetricsAvailable}. + * Usually, this value should not less than 17. Because for those display with a 60Hz + * refresh rate, it takes at least 16.6ms to generate a frame. + * + * @return The interval, measured in milliseconds. + */ + @IntRange(from = 1) + int getIntervalMs(); + + /** + * The name returned will be used to match the specified scene. + * + * @return Name of the specified scene, null or empty string will match all scene. + */ + String getName(); + + /** + * Whether skip the first frame. + * + * @return true for skip, false for not. + */ + boolean skipFirstFrame(); + + /** + * Frame metrics whose dropped frames less than threshold will be skipped. + * We always assume the refresh rate of display is 60Hz, and the threshold + * will be converted to corresponding value while the real refresh rate + * is not 60Hz. + *
+ * For example, if the threshold is 10 and refresh rate is 90Hz, all frame + * metrics whose dropped frames less than 15 will be skipped. + * + * @return integer value of threshold, zero indicates all frame metrics will be + * added to calculation. + */ + @IntRange(from = 0) + int getThreshold(); + + /** + * This method will be called when average frame metrics available. + * + * @param sceneName name of scene. + * @param avgDurations average avgDurations, include draw duration, animation duration, etc. + * See {@link com.tencent.matrix.trace.tracer.FrameTracer.FrameDuration}. + * @param dropLevel drop level distribution, sum of this array equals value returned by getSize(). + * See {@link com.tencent.matrix.trace.tracer.FrameTracer.DropStatus}. + * @param dropSum dropped frame distribution. + */ + void onFrameMetricsAvailable(@NonNull String sceneName, long[] avgDurations, int[] dropLevel, int[] dropSum, float avgDroppedFrame, float avgRefreshRate, float avgFps); +} diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/LooperObserver.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/LooperObserver.java index 36bc1d3b5..e993f52b5 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/LooperObserver.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/listeners/LooperObserver.java @@ -18,6 +18,10 @@ import androidx.annotation.CallSuper; +/** + * Use {@link ILooperListener} or {@link IFrameListener} instead. + */ +@Deprecated public abstract class LooperObserver { private boolean isDispatchBegin = false; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/EvilMethodTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/EvilMethodTracer.java index 70fad2d2b..9a7dbad07 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/EvilMethodTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/EvilMethodTracer.java @@ -26,8 +26,9 @@ import com.tencent.matrix.trace.config.TraceConfig; import com.tencent.matrix.trace.constants.Constants; import com.tencent.matrix.trace.core.AppMethodBeat; -import com.tencent.matrix.trace.core.UIThreadMonitor; +import com.tencent.matrix.trace.core.LooperMonitor; import com.tencent.matrix.trace.items.MethodItem; +import com.tencent.matrix.trace.listeners.ILooperListener; import com.tencent.matrix.trace.util.TraceDataUtils; import com.tencent.matrix.trace.util.Utils; import com.tencent.matrix.util.DeviceUtil; @@ -41,14 +42,13 @@ import java.util.LinkedList; import java.util.List; -public class EvilMethodTracer extends Tracer { +public class EvilMethodTracer extends Tracer implements ILooperListener { private static final String TAG = "Matrix.EvilMethodTracer"; private final TraceConfig config; private AppMethodBeat.IndexRecord indexRecord; - private long[] queueTypeCosts = new long[3]; private long evilThresholdMs; - private boolean isEvilMethodTraceEnable; + private final boolean isEvilMethodTraceEnable; public EvilMethodTracer(TraceConfig config) { this.config = config; @@ -60,7 +60,7 @@ public EvilMethodTracer(TraceConfig config) { public void onAlive() { super.onAlive(); if (isEvilMethodTraceEnable) { - UIThreadMonitor.getMonitor().addObserver(this); + LooperMonitor.register(this); } } @@ -69,45 +69,31 @@ public void onAlive() { public void onDead() { super.onDead(); if (isEvilMethodTraceEnable) { - UIThreadMonitor.getMonitor().removeObserver(this); + LooperMonitor.unregister(this); } } - @Override - public void dispatchBegin(long beginNs, long cpuBeginMs, long token) { - super.dispatchBegin(beginNs, cpuBeginMs, token); - indexRecord = AppMethodBeat.getInstance().maskIndex("EvilMethodTracer#dispatchBegin"); + public boolean isValid() { + return true; } - @Override - public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { - queueTypeCosts[0] = inputCostNs; - queueTypeCosts[1] = animationCostNs; - queueTypeCosts[2] = traversalCostNs; + public void onDispatchBegin(String log) { + indexRecord = AppMethodBeat.getInstance().maskIndex("EvilMethodTracer#dispatchBegin"); } @Override - public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isVsyncFrame) { - super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isVsyncFrame); - long start = config.isDevEnv() ? System.currentTimeMillis() : 0; + public void onDispatchEnd(String log, long beginNs, long endNs) { long dispatchCost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO; try { if (dispatchCost >= evilThresholdMs) { long[] data = AppMethodBeat.getInstance().copyData(indexRecord); - long[] queueCosts = new long[3]; - System.arraycopy(queueTypeCosts, 0, queueCosts, 0, 3); String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene(); - MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(isForeground(), scene, data, queueCosts, cpuEndMs - cpuBeginMs, dispatchCost, endNs / Constants.TIME_MILLIS_TO_NANO)); + MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(isForeground(), scene, data, dispatchCost, endNs)); } } finally { indexRecord.release(); - if (config.isDevEnv()) { - String usage = Utils.calculateCpuUsage(cpuEndMs - cpuBeginMs, dispatchCost); - MatrixLog.v(TAG, "[dispatchEnd] token:%s cost:%sms cpu:%sms usage:%s innerCost:%s", - token, dispatchCost, cpuEndMs - cpuBeginMs, usage, System.currentTimeMillis() - start); - } } } @@ -115,22 +101,18 @@ public void modifyEvilThresholdMs(long evilThresholdMs) { this.evilThresholdMs = evilThresholdMs; } - private class AnalyseTask implements Runnable { - long[] queueCost; + private static class AnalyseTask implements Runnable { long[] data; - long cpuCost; long cost; long endMs; String scene; boolean isForeground; - AnalyseTask(boolean isForeground, String scene, long[] data, long[] queueCost, long cpuCost, long cost, long endMs) { + AnalyseTask(boolean isForeground, String scene, long[] data, long cost, long endMs) { this.isForeground = isForeground; this.scene = scene; this.cost = cost; - this.cpuCost = cpuCost; this.data = data; - this.queueCost = queueCost; this.endMs = endMs; } @@ -138,14 +120,13 @@ void analyse() { // process int[] processStat = Utils.getProcessPriority(Process.myPid()); - String usage = Utils.calculateCpuUsage(cpuCost, cost); - LinkedList stack = new LinkedList(); + LinkedList stack = new LinkedList<>(); if (data.length > 0) { TraceDataUtils.structuredDataToStack(data, stack, true, endMs); TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() { @Override public boolean isFilter(long during, int filterCount) { - return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS; + return during < (long) filterCount * Constants.TIME_UPDATE_CYCLE_MS; } @Override @@ -156,7 +137,7 @@ public int getFilterMaxCount() { @Override public void fallback(List stack, int size) { MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack); - Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK)); + Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK)); while (iterator.hasNext()) { iterator.next(); iterator.remove(); @@ -171,7 +152,7 @@ public void fallback(List stack, int size) { long stackCost = Math.max(cost, TraceDataUtils.stackToString(stack, reportBuilder, logcatBuilder)); String stackKey = TraceDataUtils.getTreeKey(stack, stackCost); - MatrixLog.w(TAG, "%s", printEvil(scene, processStat, isForeground, logcatBuilder, stack.size(), stackKey, usage, queueCost[0], queueCost[1], queueCost[2], cost)); // for logcat + MatrixLog.w(TAG, "%s", printEvil(scene, processStat, isForeground, logcatBuilder, stack.size(), stackKey, cost)); // for logcat // report try { @@ -180,11 +161,10 @@ public void fallback(List stack, int size) { return; } JSONObject jsonObject = new JSONObject(); - jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); + DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.NORMAL); jsonObject.put(SharePluginInfo.ISSUE_COST, stackCost); - jsonObject.put(SharePluginInfo.ISSUE_CPU_USAGE, usage); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); jsonObject.put(SharePluginInfo.ISSUE_TRACE_STACK, reportBuilder.toString()); jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, stackKey); @@ -205,8 +185,7 @@ public void run() { analyse(); } - private String printEvil(String scene, int[] processStat, boolean isForeground, StringBuilder stack, long stackSize, String stackKey, String usage, long inputCost, - long animationCost, long traversalCost, long allCost) { + private String printEvil(String scene, int[] processStat, boolean isForeground, StringBuilder stack, long stackSize, String stackKey, long allCost) { StringBuilder print = new StringBuilder(); print.append(String.format("-\n>>>>>>>>>>>>>>>>>>>>> maybe happens Jankiness!(%sms) <<<<<<<<<<<<<<<<<<<<<\n", allCost)); print.append("|* [Status]").append("\n"); @@ -214,10 +193,6 @@ private String printEvil(String scene, int[] processStat, boolean isForeground, print.append("|*\t\tForeground: ").append(isForeground).append("\n"); print.append("|*\t\tPriority: ").append(processStat[0]).append("\tNice: ").append(processStat[1]).append("\n"); print.append("|*\t\tis64BitRuntime: ").append(DeviceUtil.is64BitRuntime()).append("\n"); - print.append("|*\t\tCPU: ").append(usage).append("\n"); - print.append("|* [doFrame]").append("\n"); - print.append("|*\t\tinputCost:animationCost:traversalCost").append("\n"); - print.append("|*\t\t").append(inputCost).append(":").append(animationCost).append(":").append(traversalCost).append("\n"); if (stackSize > 0) { print.append("|*\t\tStackKey: ").append(stackKey).append("\n"); print.append(stack.toString()); diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/FrameTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/FrameTracer.java index 34a42b0d5..e73c9222d 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/FrameTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/FrameTracer.java @@ -16,6 +16,7 @@ package com.tencent.matrix.trace.tracer; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.Application; import android.os.Build; @@ -24,6 +25,10 @@ import android.os.SystemClock; import android.view.FrameMetrics; import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import com.tencent.matrix.Matrix; import com.tencent.matrix.lifecycle.owners.ProcessUILifecycleOwner; @@ -34,7 +39,10 @@ import com.tencent.matrix.trace.constants.Constants; import com.tencent.matrix.trace.core.UIThreadMonitor; import com.tencent.matrix.trace.listeners.IDoFrameListener; -import com.tencent.matrix.trace.util.Utils; +import com.tencent.matrix.trace.listeners.IDropFrameListener; +import com.tencent.matrix.trace.listeners.IFrameListener; +import com.tencent.matrix.trace.listeners.ISceneFrameListener; +import com.tencent.matrix.trace.listeners.LooperObserver; import com.tencent.matrix.util.DeviceUtil; import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; @@ -46,343 +54,481 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; - -import androidx.annotation.RequiresApi; public class FrameTracer extends Tracer implements Application.ActivityLifecycleCallbacks { - private static final String TAG = "Matrix.FrameTracer"; - private static boolean useFrameMetrics; - private final HashSet listeners = new HashSet<>(); - private DropFrameListener dropFrameListener; - private int dropFrameListenerThreshold = 0; - private long frameIntervalNs; - private int refreshRate; - private final TraceConfig config; - private final long timeSliceMs; - private boolean isFPSEnable; - private long frozenThreshold; - private long highThreshold; - private long middleThreshold; - private long normalThreshold; - private int droppedSum = 0; + + private static final long HALF_MAX = (Long.MAX_VALUE >>> 1); + public final static int sdkInt = Build.VERSION.SDK_INT; + public static float defaultRefreshRate = 60; + + private double droppedSum = 0; + + @Deprecated private long durationSum = 0; - private Map lastResumeTimeMap = new HashMap<>(); - private Map frameListenerMap = new ConcurrentHashMap<>(); + @Deprecated + private final HashSet oldListeners = new HashSet<>(); + @Deprecated + private long frameIntervalNs; + @Deprecated + private LooperObserver looperObserver = new LooperObserver() { + @Override + public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { + if (isForeground()) { + notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + } + } + + @Deprecated + private void notifyListener(final String focusedActivity, final long startNs, final long endNs, final boolean isVsyncFrame, + final long intendedFrameTimeNs, final long inputCostNs, final long animationCostNs, final long traversalCostNs) { + long traceBegin = System.currentTimeMillis(); + try { + final long jitter = endNs - intendedFrameTimeNs; + final int dropFrame = (int) (jitter / frameIntervalNs); + if (oldDropFrameListener != null && dropFrame > dropFrameListenerThreshold) { + try { + if (MatrixUtil.getTopActivityName() != null) { + oldDropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName()); + } + } catch (Exception e) { + MatrixLog.e(TAG, "dropFrameListener error e:" + e.getMessage()); + } + } + + droppedSum += dropFrame; + durationSum += Math.max(jitter, frameIntervalNs); + + synchronized (oldListeners) { + for (final IDoFrameListener listener : oldListeners) { + if (config.isDevEnv()) { + listener.time = SystemClock.uptimeMillis(); + } + if (null != listener.getExecutor()) { + if (listener.getIntervalFrameReplay() > 0) { + listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, + intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + } else { + listener.getExecutor().execute(new Runnable() { + @Override + public void run() { + listener.doFrameAsync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, + intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + } + }); + } + } else { + listener.doFrameSync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, + intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + } - public FrameTracer(TraceConfig config, boolean supportFrameMetrics) { - useFrameMetrics = supportFrameMetrics; + if (config.isDevEnv()) { + listener.time = SystemClock.uptimeMillis() - listener.time; + MatrixLog.d(TAG, "[notifyListener] cost:%sms listener:%s", listener.time, listener); + } + } + } + } finally { + long cost = System.currentTimeMillis() - traceBegin; + if (config.isDebug() && cost > frameIntervalNs) { + MatrixLog.w(TAG, "[notifyListener] warm! maybe do heavy work in doFrameSync! size:%s cost:%sms", oldListeners.size(), cost); + } + } + } + }; + + @Deprecated + private DropFrameListener oldDropFrameListener; + private IDropFrameListener dropFrameListener; + private int dropFrameListenerThreshold = 0; + + private final TraceConfig config; + private final HashSet listeners = new HashSet<>(); + private final long frozenThreshold; + private final long highThreshold; + private final long middleThreshold; + private final long normalThreshold; + SceneFrameCollector sceneFrameCollector; + private final Map frameListenerMap = new ConcurrentHashMap<>(); + + public FrameTracer(TraceConfig config) { this.config = config; this.frameIntervalNs = UIThreadMonitor.getMonitor().getFrameIntervalNanos(); - this.timeSliceMs = config.getTimeSliceMs(); - this.isFPSEnable = config.isFPSEnable(); this.frozenThreshold = config.getFrozenThreshold(); this.highThreshold = config.getHighThreshold(); this.normalThreshold = config.getNormalThreshold(); this.middleThreshold = config.getMiddleThreshold(); - MatrixLog.i(TAG, "[init] frameIntervalMs:%s isFPSEnable:%s", frameIntervalNs, isFPSEnable); - if (isFPSEnable) { - addListener(new FPSCollector()); - } + MatrixLog.i(TAG, "[init] frameIntervalMs:%s isFPSEnable:%s", frameIntervalNs, config.isFPSEnable()); } + @Deprecated public void addListener(IDoFrameListener listener) { + synchronized (oldListeners) { + oldListeners.add(listener); + } + } + + @Deprecated + public void removeListener(IDoFrameListener listener) { + synchronized (oldListeners) { + oldListeners.remove(listener); + } + } + + @RequiresApi(Build.VERSION_CODES.N) + public void addListener(IFrameListener listener) { synchronized (listeners) { listeners.add(listener); } } - public void removeListener(IDoFrameListener listener) { + @RequiresApi(Build.VERSION_CODES.N) + public void removeListener(IFrameListener listener) { synchronized (listeners) { listeners.remove(listener); } } + @RequiresApi(Build.VERSION_CODES.N) + public void register(ISceneFrameListener listener) { + if (sceneFrameCollector != null) { + sceneFrameCollector.register(listener); + } + } + + @RequiresApi(Build.VERSION_CODES.N) + public void unregister(ISceneFrameListener listener, boolean isCallbackRestAfterUnregister) { + if (sceneFrameCollector != null) { + sceneFrameCollector.unregister(listener, isCallbackRestAfterUnregister); + } + } + + @RequiresApi(Build.VERSION_CODES.N) + public void unregister(ISceneFrameListener listener) { + unregister(listener, false); + } + + @RequiresApi(Build.VERSION_CODES.N) + public void reset(ISceneFrameListener listener, boolean isCallbackRestBeforeReset) { + if (sceneFrameCollector != null) { + sceneFrameCollector.reset(listener, isCallbackRestBeforeReset); + } + } + + @RequiresApi(Build.VERSION_CODES.N) + public void reset(ISceneFrameListener listener) { + unregister(listener, false); + } + @Override public void onAlive() { super.onAlive(); - if (isFPSEnable) { - if (!useFrameMetrics) { - UIThreadMonitor.getMonitor().addObserver(this); - } - Matrix.with().getApplication().registerActivityLifecycleCallbacks(this); + if (config.isFPSEnable()) { + forceEnable(); } } public void forceEnable() { MatrixLog.i(TAG, "forceEnable"); - if (!useFrameMetrics) { - UIThreadMonitor.getMonitor().addObserver(this); + if (sdkInt >= Build.VERSION_CODES.N) { + Matrix.with().getApplication().registerActivityLifecycleCallbacks(this); + sceneFrameCollector = new SceneFrameCollector(); + addListener(sceneFrameCollector); + register(new AllSceneFrameListener()); + } else { + UIThreadMonitor.getMonitor().addObserver(looperObserver); } - Matrix.with().getApplication().registerActivityLifecycleCallbacks(this); } public void forceDisable() { MatrixLog.i(TAG, "forceDisable"); removeDropFrameListener(); - UIThreadMonitor.getMonitor().removeObserver(this); - Matrix.with().getApplication().unregisterActivityLifecycleCallbacks(this); - frameListenerMap.clear(); - } - - @Override - public void onDead() { - super.onDead(); - removeDropFrameListener(); - if (isFPSEnable) { - UIThreadMonitor.getMonitor().removeObserver(this); + if (sdkInt >= Build.VERSION_CODES.N) { Matrix.with().getApplication().unregisterActivityLifecycleCallbacks(this); + listeners.clear(); + frameListenerMap.clear(); + } else { + UIThreadMonitor.getMonitor().removeObserver(looperObserver); + oldListeners.clear(); } } @Override - public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { - if (isForeground()) { - notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); + public void onDead() { + super.onDead(); + if (config.isFPSEnable()) { + forceDisable(); } } public int getDroppedSum() { - return droppedSum; + return (int) droppedSum; } + @Deprecated public long getDurationSum() { return durationSum; } - private void notifyListener(final String focusedActivity, final long startNs, final long endNs, final boolean isVsyncFrame, - final long intendedFrameTimeNs, final long inputCostNs, final long animationCostNs, final long traversalCostNs) { - long traceBegin = System.currentTimeMillis(); - try { - final long jitter = endNs - intendedFrameTimeNs; - final int dropFrame = (int) (jitter / frameIntervalNs); - if (dropFrameListener != null) { - if (dropFrame > dropFrameListenerThreshold) { - try { - if (MatrixUtil.getTopActivityName() != null) { - long lastResumeTime = lastResumeTimeMap.get(MatrixUtil.getTopActivityName()); - dropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName(), lastResumeTime); - } - } catch (Exception e) { - MatrixLog.e(TAG, "dropFrameListener error e:" + e.getMessage()); + @RequiresApi(Build.VERSION_CODES.N) + private class SceneFrameCollector implements IFrameListener { + + private final Handler frameHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper()); + private final HashMap specifiedSceneMap = new HashMap<>(); + private final HashMap unspecifiedSceneMap = new HashMap<>(); + + public synchronized void register(@NonNull ISceneFrameListener listener) { + if (listener.getIntervalMs() < 1 || listener.getThreshold() < 0) { + MatrixLog.e(TAG, "Illegal value, intervalMs=%d, threshold=%d, activity=%s", + listener.getIntervalMs(), listener.getThreshold(), listener.getClass().getName()); + return; + } + String scene = listener.getName(); + SceneFrameCollectItem collectItem = new SceneFrameCollectItem(listener); + if (scene == null || scene.isEmpty()) { + unspecifiedSceneMap.put(listener, collectItem); + } else { + specifiedSceneMap.put(scene, collectItem); + } + } + + public synchronized void unregister(@NonNull ISceneFrameListener listener, boolean isCallbackRestAfterUnregister) { + final String scene = listener.getName(); + final SceneFrameCollectItem target = scene == null || scene.isEmpty() + ? unspecifiedSceneMap.remove(listener) + : specifiedSceneMap.remove(scene); + + if (target != null && isCallbackRestAfterUnregister) { + frameHandler.post(new Runnable() { + @Override + public void run() { + target.tryCallBackAndReset(); } - } + }); } + } - droppedSum += dropFrame; - durationSum += Math.max(jitter, frameIntervalNs); + public synchronized void reset(ISceneFrameListener listener, boolean isCallbackRestBeforeReset) { + final String scene = listener.getName(); + final SceneFrameCollectItem target = scene == null || scene.isEmpty() + ? unspecifiedSceneMap.get(listener) + : specifiedSceneMap.get(scene); + if (target != null && isCallbackRestBeforeReset) { + target.tryCallBackAndReset(); + } + } - synchronized (listeners) { - for (final IDoFrameListener listener : listeners) { - if (config.isDevEnv()) { - listener.time = SystemClock.uptimeMillis(); - } - if (null != listener.getExecutor()) { - if (listener.getIntervalFrameReplay() > 0) { - listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, - intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); - } else { - listener.getExecutor().execute(new Runnable() { - @Override - public void run() { - listener.doFrameAsync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, - intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); - } - }); - } - } else { - listener.doFrameSync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, - intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); - } + public synchronized void resetAllAndCallBack() { + for (SceneFrameCollectItem value : unspecifiedSceneMap.values()) { + value.tryCallBackAndReset(); + } + for (SceneFrameCollectItem value : specifiedSceneMap.values()) { + value.tryCallBackAndReset(); + } + } - if (config.isDevEnv()) { - listener.time = SystemClock.uptimeMillis() - listener.time; - MatrixLog.d(TAG, "[notifyListener] cost:%sms listener:%s", listener.time, listener); + @Override + public void onFrameMetricsAvailable(final String sceneName, final FrameMetrics frameMetrics, final float droppedFrames, final float refreshRate) { + frameHandler.post(new Runnable() { + @Override + public void run() { + String scene = sceneName.getClass().getName(); + synchronized (SceneFrameCollector.this) { + SceneFrameCollectItem collectItem = specifiedSceneMap.get(scene); + if (collectItem != null) { + collectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate); + } + for (SceneFrameCollectItem frameCollectItem : unspecifiedSceneMap.values()) { + frameCollectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate); + } } } - } - } finally { - long cost = System.currentTimeMillis() - traceBegin; - if (config.isDebug() && cost > frameIntervalNs) { - MatrixLog.w(TAG, "[notifyListener] warm! maybe do heavy work in doFrameSync! size:%s cost:%sms", listeners.size(), cost); - } + }); } } + public enum DropStatus { + DROPPED_BEST, DROPPED_NORMAL, DROPPED_MIDDLE, DROPPED_HIGH, DROPPED_FROZEN; - private class FPSCollector extends IDoFrameListener { - - private Handler frameHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper()); + public static String stringify(int[] level, int[] sum) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); - Executor executor = new Executor() { - @Override - public void execute(Runnable command) { - frameHandler.post(command); + for (DropStatus item : values()) { + sb.append('(').append(item.name()).append("_LEVEL=").append(level[item.ordinal()]).append(" "); + sb.append(item.name()).append("_SUM=").append(sum[item.ordinal()]).append("); "); } - }; - - private HashMap map = new HashMap<>(); + sb.setLength(sb.length() - 2); // remove the last "; " + sb.append("}"); - @Override - public Executor getExecutor() { - return executor; + return sb.toString(); } + } - @Override - public int getIntervalFrameReplay() { - return 300; - } + public enum FrameDuration { + UNKNOWN_DELAY_DURATION, INPUT_HANDLING_DURATION, ANIMATION_DURATION, LAYOUT_MEASURE_DURATION, DRAW_DURATION, + SYNC_DURATION, COMMAND_ISSUE_DURATION, SWAP_BUFFERS_DURATION, TOTAL_DURATION, GPU_DURATION; - @Override - public void doReplay(List list) { - super.doReplay(list); - for (FrameReplay replay : list) { - doReplayInner(replay.focusedActivity, replay.startNs, replay.endNs, replay.dropFrame, replay.isVsyncFrame, - replay.intendedFrameTimeNs, replay.inputCostNs, replay.animationCostNs, replay.traversalCostNs); + @SuppressLint("InlinedApi") + static final int[] indices = {FrameMetrics.UNKNOWN_DELAY_DURATION, FrameMetrics.INPUT_HANDLING_DURATION, + FrameMetrics.ANIMATION_DURATION, FrameMetrics.LAYOUT_MEASURE_DURATION, FrameMetrics.DRAW_DURATION, + FrameMetrics.SYNC_DURATION, FrameMetrics.COMMAND_ISSUE_DURATION, FrameMetrics.SWAP_BUFFERS_DURATION, + FrameMetrics.TOTAL_DURATION, FrameMetrics.GPU_DURATION}; + + public static String stringify(long[] durations) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + + for (FrameDuration item : values()) { + sb.append(item.name()).append('=').append(durations[item.ordinal()]).append("; "); } - } + sb.setLength(sb.length() - 2); // remove the last "; " + sb.append("}"); - public void doReplayInner(String visibleScene, long startNs, long endNs, int droppedFrames, - boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, - long animationCostNs, long traversalCostNs) { + return sb.toString(); + } + } - if (Utils.isEmpty(visibleScene)) return; - if (!isVsyncFrame) return; + @RequiresApi(Build.VERSION_CODES.N) + private class SceneFrameCollectItem { + private final long[] durations = new long[FrameDuration.values().length]; + private final int[] dropLevel = new int[DropStatus.values().length]; + private final int[] dropSum = new int[DropStatus.values().length]; + private float dropCount; + private float refreshRate; + private float totalDuration; + private long beginMs; + private String lastScene; + private int count = 0; + + ISceneFrameListener listener; + + SceneFrameCollectItem(ISceneFrameListener listener) { + this.listener = listener; + } - FrameCollectItem item = map.get(visibleScene); - if (null == item) { - item = new FrameCollectItem(visibleScene); - map.put(visibleScene, item); + public void append(String scene, FrameMetrics frameMetrics, float droppedFrames, float refreshRate) { + if ((listener.skipFirstFrame() && frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1) + || droppedFrames < (refreshRate / 60) * listener.getThreshold()) { + return; + } + if (count == 0) { + beginMs = SystemClock.uptimeMillis(); + } + for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) { + durations[i] += frameMetrics.getMetric(FrameDuration.indices[i]); + } + if (sdkInt >= Build.VERSION_CODES.S) { + durations[FrameDuration.GPU_DURATION.ordinal()] += frameMetrics.getMetric(FrameMetrics.GPU_DURATION); } - item.collect(droppedFrames); - if (item.sumFrameCost >= timeSliceMs) { // report - map.remove(visibleScene); - item.report(); + dropCount += droppedFrames; + collect(Math.round(droppedFrames)); + this.refreshRate += refreshRate; + float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / refreshRate; + totalDuration += Math.max(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION), frameIntervalNanos); + ++count; + + lastScene = scene; + if (SystemClock.uptimeMillis() - beginMs >= listener.getIntervalMs()) { + tryCallBackAndReset(); } } - } - private class FrameCollectItem { - String visibleScene; - long sumFrameCost; - int sumFrame = 0; - int sumDroppedFrames; - // record the level of frames dropped each time - int[] dropLevel = new int[DropStatus.values().length]; - int[] dropSum = new int[DropStatus.values().length]; - - FrameCollectItem(String visibleScene) { - this.visibleScene = visibleScene; + void tryCallBackAndReset() { + if (count > 20) { + dropCount /= count; + this.refreshRate /= count; + totalDuration /= count; + for (int i = 0; i < durations.length; i++) { + durations[i] /= count; + } + listener.onFrameMetricsAvailable(lastScene, durations, dropLevel, dropSum, + dropCount, this.refreshRate, Constants.TIME_SECOND_TO_NANO / totalDuration); + } + reset(); } - void collect(int droppedFrames) { - float frameIntervalCost = 1f * FrameTracer.this.frameIntervalNs - / Constants.TIME_MILLIS_TO_NANO; - sumFrameCost += (droppedFrames + 1) * frameIntervalCost; - sumDroppedFrames += droppedFrames; - sumFrame++; + private void collect(int droppedFrames) { if (droppedFrames >= frozenThreshold) { - dropLevel[DropStatus.DROPPED_FROZEN.index]++; - dropSum[DropStatus.DROPPED_FROZEN.index] += droppedFrames; + dropLevel[DropStatus.DROPPED_FROZEN.ordinal()]++; + dropSum[DropStatus.DROPPED_FROZEN.ordinal()] += droppedFrames; } else if (droppedFrames >= highThreshold) { - dropLevel[DropStatus.DROPPED_HIGH.index]++; - dropSum[DropStatus.DROPPED_HIGH.index] += droppedFrames; + dropLevel[DropStatus.DROPPED_HIGH.ordinal()]++; + dropSum[DropStatus.DROPPED_HIGH.ordinal()] += droppedFrames; } else if (droppedFrames >= middleThreshold) { - dropLevel[DropStatus.DROPPED_MIDDLE.index]++; - dropSum[DropStatus.DROPPED_MIDDLE.index] += droppedFrames; + dropLevel[DropStatus.DROPPED_MIDDLE.ordinal()]++; + dropSum[DropStatus.DROPPED_MIDDLE.ordinal()] += droppedFrames; } else if (droppedFrames >= normalThreshold) { - dropLevel[DropStatus.DROPPED_NORMAL.index]++; - dropSum[DropStatus.DROPPED_NORMAL.index] += droppedFrames; + dropLevel[DropStatus.DROPPED_NORMAL.ordinal()]++; + dropSum[DropStatus.DROPPED_NORMAL.ordinal()] += droppedFrames; } else { - dropLevel[DropStatus.DROPPED_BEST.index]++; - dropSum[DropStatus.DROPPED_BEST.index] += Math.max(droppedFrames, 0); + dropLevel[DropStatus.DROPPED_BEST.ordinal()]++; + dropSum[DropStatus.DROPPED_BEST.ordinal()] += Math.max(droppedFrames, 0); } } - void report() { - float fps = Math.min(refreshRate, 1000.f * sumFrame / sumFrameCost); - MatrixLog.i(TAG, "[report] FPS:%s %s", fps, toString()); - try { - TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class); - if (null == plugin) { - return; - } - JSONObject dropLevelObject = new JSONObject(); - dropLevelObject.put(DropStatus.DROPPED_FROZEN.name(), dropLevel[DropStatus.DROPPED_FROZEN.index]); - dropLevelObject.put(DropStatus.DROPPED_HIGH.name(), dropLevel[DropStatus.DROPPED_HIGH.index]); - dropLevelObject.put(DropStatus.DROPPED_MIDDLE.name(), dropLevel[DropStatus.DROPPED_MIDDLE.index]); - dropLevelObject.put(DropStatus.DROPPED_NORMAL.name(), dropLevel[DropStatus.DROPPED_NORMAL.index]); - dropLevelObject.put(DropStatus.DROPPED_BEST.name(), dropLevel[DropStatus.DROPPED_BEST.index]); - - JSONObject dropSumObject = new JSONObject(); - dropSumObject.put(DropStatus.DROPPED_FROZEN.name(), dropSum[DropStatus.DROPPED_FROZEN.index]); - dropSumObject.put(DropStatus.DROPPED_HIGH.name(), dropSum[DropStatus.DROPPED_HIGH.index]); - dropSumObject.put(DropStatus.DROPPED_MIDDLE.name(), dropSum[DropStatus.DROPPED_MIDDLE.index]); - dropSumObject.put(DropStatus.DROPPED_NORMAL.name(), dropSum[DropStatus.DROPPED_NORMAL.index]); - dropSumObject.put(DropStatus.DROPPED_BEST.name(), dropSum[DropStatus.DROPPED_BEST.index]); - - JSONObject resultObject = new JSONObject(); - resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication()); + private void reset() { + dropCount = 0; + refreshRate = 0; + totalDuration = 0; + count = 0; - resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene); - resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject); - resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject); - resultObject.put(SharePluginInfo.ISSUE_FPS, fps); - - Issue issue = new Issue(); - issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS); - issue.setContent(resultObject); - plugin.onDetectIssue(issue); - - } catch (JSONException e) { - MatrixLog.e(TAG, "json error", e); - } finally { - sumFrame = 0; - sumDroppedFrames = 0; - sumFrameCost = 0; - } - } - - - @Override - public String toString() { - return "visibleScene=" + visibleScene - + ", sumFrame=" + sumFrame - + ", sumDroppedFrames=" + sumDroppedFrames - + ", sumFrameCost=" + sumFrameCost - + ", dropLevel=" + Arrays.toString(dropLevel); + Arrays.fill(durations, 0); + Arrays.fill(dropLevel, 0); + Arrays.fill(dropSum, 0); } } - public enum DropStatus { - DROPPED_FROZEN(4), DROPPED_HIGH(3), DROPPED_MIDDLE(2), DROPPED_NORMAL(1), DROPPED_BEST(0); - public int index; - - DropStatus(int index) { - this.index = index; - } - + /** + * This method is reserved for compatibility. Using {@code setDropFrameListener} method + * as alternative for api level greater equal N(24). + */ + @Deprecated + public void addDropFrameListener(int dropFrameListenerThreshold, DropFrameListener dropFrameListener) { + this.oldDropFrameListener = dropFrameListener; + this.dropFrameListenerThreshold = dropFrameListenerThreshold; } - public void addDropFrameListener(int dropFrameListenerThreshold, DropFrameListener dropFrameListener) { + public void setDropFrameListener(int dropFrameListenerThreshold, IDropFrameListener dropFrameListener) { this.dropFrameListener = dropFrameListener; this.dropFrameListenerThreshold = dropFrameListenerThreshold; } public void removeDropFrameListener() { + this.oldDropFrameListener = null; this.dropFrameListener = null; } + @Deprecated public interface DropFrameListener { - void dropFrame(int droppedFrame, long jitter, String scene, long lastResumeTime); + void dropFrame(int droppedFrame, long jitter, String scene); } + @RequiresApi(Build.VERSION_CODES.N) + public static String metricsToString(FrameMetrics frameMetrics) { + StringBuilder sb = new StringBuilder(); + + sb.append("{unknown_delay_duration=").append(frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION)); + sb.append("; input_handling_duration=").append(frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION)); + sb.append("; animation_duration=").append(frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION)); + sb.append("; layout_measure_duration=").append(frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION)); + sb.append("; draw_duration=").append(frameMetrics.getMetric(FrameMetrics.DRAW_DURATION)); + sb.append("; sync_duration=").append(frameMetrics.getMetric(FrameMetrics.SYNC_DURATION)); + sb.append("; command_issue_duration=").append(frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION)); + sb.append("; swap_buffers_duration=").append(frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION)); + sb.append("; total_duration=").append(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)); + sb.append("; first_draw_frame=").append(frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME)); + if (FrameTracer.sdkInt >= Build.VERSION_CODES.S) { + sb.append("; gpu_duration=").append(frameMetrics.getMetric(FrameMetrics.GPU_DURATION)); + } + sb.append("}"); + + return sb.toString(); + } @Override public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { @@ -394,37 +540,85 @@ public void onActivityStarted(Activity activity) { } - @RequiresApi(api = Build.VERSION_CODES.N) + private float getRefreshRate(Window window) { + if (sdkInt >= Build.VERSION_CODES.R) { + return window.getContext().getDisplay().getRefreshRate(); + } + return window.getWindowManager().getDefaultDisplay().getRefreshRate(); + } + + @RequiresApi(Build.VERSION_CODES.N) @Override public void onActivityResumed(Activity activity) { - lastResumeTimeMap.put(activity.getClass().getName(), System.currentTimeMillis()); + if (frameListenerMap.containsKey(activity.hashCode())) { + return; + } - if (useFrameMetrics) { - if (frameListenerMap.containsKey(activity.hashCode())) { - return; + defaultRefreshRate = getRefreshRate(activity.getWindow()); + MatrixLog.i(TAG, "default refresh rate is %dHz", (int) defaultRefreshRate); + + Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() { + private float cachedRefreshRate = defaultRefreshRate; + private float cachedThreshold = dropFrameListenerThreshold / 60f * cachedRefreshRate; + private int lastModeId = -1; + private int lastThreshold = -1; + private WindowManager.LayoutParams attributes = null; + + private void updateRefreshRate(Window window) { + if (attributes == null) { + attributes = window.getAttributes(); + } + if (attributes.preferredDisplayModeId != lastModeId || lastThreshold != dropFrameListenerThreshold) { + lastModeId = attributes.preferredDisplayModeId; + lastThreshold = dropFrameListenerThreshold; + cachedRefreshRate = getRefreshRate(window); + cachedThreshold = dropFrameListenerThreshold / 60f * cachedRefreshRate; + } } - this.refreshRate = (int) activity.getWindowManager().getDefaultDisplay().getRefreshRate(); - this.frameIntervalNs = Constants.TIME_SECOND_TO_NANO / (long) refreshRate; - Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() { - @RequiresApi(api = Build.VERSION_CODES.O) - @Override - public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) { + + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) { + if (isForeground()) { + // skip not available metrics. + for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) { + long v = frameMetrics.getMetric(FrameDuration.indices[i]); + if (v < 0 || v >= HALF_MAX) { + // some devices will produce outliers, especially the Honor series, eg: NTH-AN00, ANY-AN00, etc. + return; + } + } FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics); - long vsynTime = frameMetricsCopy.getMetric(FrameMetrics.VSYNC_TIMESTAMP); - long intendedVsyncTime = frameMetricsCopy.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP); - frameMetricsCopy.getMetric(FrameMetrics.DRAW_DURATION); - notifyListener(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), intendedVsyncTime, vsynTime, true, intendedVsyncTime, 0, 0, 0); + + updateRefreshRate(window); + + long totalDuration = frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION); + float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / cachedRefreshRate; + float droppedFrames = Math.max(0f, (totalDuration - frameIntervalNanos) / frameIntervalNanos); + + droppedSum += droppedFrames; + + if (dropFrameListener != null && droppedFrames >= cachedThreshold) { + dropFrameListener.onFrameMetricsAvailable(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), frameMetricsCopy, droppedFrames, cachedRefreshRate); + } + synchronized (listeners) { + for (IFrameListener observer : listeners) { + observer.onFrameMetricsAvailable(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), frameMetricsCopy, droppedFrames, cachedRefreshRate); + } + } } - }; - this.frameListenerMap.put(activity.hashCode(), onFrameMetricsAvailableListener); - activity.getWindow().addOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener, new Handler()); - MatrixLog.i(TAG, "onActivityResumed addOnFrameMetricsAvailableListener"); - } + } + }; + + this.frameListenerMap.put(activity.hashCode(), onFrameMetricsAvailableListener); + activity.getWindow().addOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener, MatrixHandlerThread.getDefaultHandler()); + MatrixLog.i(TAG, "onActivityResumed addOnFrameMetricsAvailableListener"); } + @RequiresApi(Build.VERSION_CODES.N) @Override public void onActivityPaused(Activity activity) { - + sceneFrameCollector.resetAllAndCallBack(); } @Override @@ -437,15 +631,83 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } - @RequiresApi(api = Build.VERSION_CODES.N) + @RequiresApi(Build.VERSION_CODES.N) @Override public void onActivityDestroyed(Activity activity) { - if (useFrameMetrics) { + try { + activity.getWindow().removeOnFrameMetricsAvailableListener(frameListenerMap.remove(activity.hashCode())); + } catch (Throwable t) { + MatrixLog.e(TAG, "removeOnFrameMetricsAvailableListener error : " + t.getMessage()); + } + } + + @RequiresApi(Build.VERSION_CODES.N) + static class AllSceneFrameListener implements ISceneFrameListener { + private static final String TAG = "AllSceneFrameListener"; + + @Override + public int getIntervalMs() { + return Constants.DEFAULT_FPS_TIME_SLICE_ALIVE_MS; + } + + @Override + public String getName() { + return null; + } + + @Override + public boolean skipFirstFrame() { + return false; + } + + @Override + public int getThreshold() { + return 0; + } + + @Override + public void onFrameMetricsAvailable(@NonNull String sceneName, long[] avgDurations, int[] dropLevel, int[] dropSum, float avgDroppedFrame, float avgRefreshRate, float avgFps) { + MatrixLog.i(TAG, "[report] FPS:%s %s", avgFps, toString()); try { - activity.getWindow().removeOnFrameMetricsAvailableListener(frameListenerMap.remove(activity.hashCode())); - } catch (Throwable t) { - MatrixLog.e(TAG, "removeOnFrameMetricsAvailableListener error : " + t.getMessage()); + TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class); + if (null == plugin) { + return; + } + JSONObject dropLevelObject = new JSONObject(); + JSONObject dropSumObject = new JSONObject(); + for (DropStatus dropStatus : DropStatus.values()) { + dropLevelObject.put(dropStatus.name(), dropLevel[dropStatus.ordinal()]); + dropSumObject.put(dropStatus.name(), dropSum[dropStatus.ordinal()]); + } + + JSONObject resultObject = new JSONObject(); + DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication()); + + resultObject.put(SharePluginInfo.ISSUE_SCENE, sceneName); + resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject); + resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject); + resultObject.put(SharePluginInfo.ISSUE_FPS, avgFps); + + for (FrameDuration frameDuration : FrameDuration.values()) { + resultObject.put(frameDuration.name(), avgDurations[frameDuration.ordinal()]); + if (frameDuration.equals(FrameDuration.TOTAL_DURATION)) { + break; + } + } + if (sdkInt >= Build.VERSION_CODES.S) { + resultObject.put("GPU_DURATION", avgDurations[FrameDuration.GPU_DURATION.ordinal()]); + } + resultObject.put("DROP_COUNT", Math.round(avgDroppedFrame)); + resultObject.put("REFRESH_RATE", (int) (avgRefreshRate)); + + Issue issue = new Issue(); + issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS); + issue.setContent(resultObject); + plugin.onDetectIssue(issue); + + } catch (JSONException e) { + MatrixLog.e(TAG, "json error", e); } } } -} +} \ No newline at end of file diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/IdleHandlerLagTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/IdleHandlerLagTracer.java index a6137be4e..f339874cc 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/IdleHandlerLagTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/IdleHandlerLagTracer.java @@ -86,7 +86,7 @@ public void run() { String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene(); JSONObject jsonObject = new JSONObject(); - jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); + DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.LAG_IDLE_HANDLER); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, stackTrace); diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/LooperAnrTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/LooperAnrTracer.java index 9ffa2b345..ef6ffff79 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/LooperAnrTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/LooperAnrTracer.java @@ -29,8 +29,9 @@ import com.tencent.matrix.trace.config.TraceConfig; import com.tencent.matrix.trace.constants.Constants; import com.tencent.matrix.trace.core.AppMethodBeat; -import com.tencent.matrix.trace.core.UIThreadMonitor; +import com.tencent.matrix.trace.core.LooperMonitor; import com.tencent.matrix.trace.items.MethodItem; +import com.tencent.matrix.trace.listeners.ILooperListener; import com.tencent.matrix.trace.util.TraceDataUtils; import com.tencent.matrix.trace.util.Utils; import com.tencent.matrix.util.DeviceUtil; @@ -44,7 +45,7 @@ import java.util.LinkedList; import java.util.List; -public class LooperAnrTracer extends Tracer { +public class LooperAnrTracer extends Tracer implements ILooperListener { private static final String TAG = "Matrix.AnrTracer"; private Handler anrHandler; @@ -52,7 +53,7 @@ public class LooperAnrTracer extends Tracer { private final TraceConfig traceConfig; private final AnrHandleTask anrTask = new AnrHandleTask(); private final LagHandleTask lagTask = new LagHandleTask(); - private boolean isAnrTraceEnable; + private final boolean isAnrTraceEnable; public LooperAnrTracer(TraceConfig traceConfig) { this.traceConfig = traceConfig; @@ -63,7 +64,7 @@ public LooperAnrTracer(TraceConfig traceConfig) { public void onAlive() { super.onAlive(); if (isAnrTraceEnable) { - UIThreadMonitor.getMonitor().addObserver(this); + LooperMonitor.register(this); this.anrHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper()); this.lagHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper()); } @@ -73,7 +74,7 @@ public void onAlive() { public void onDead() { super.onDead(); if (isAnrTraceEnable) { - UIThreadMonitor.getMonitor().removeObserver(this); + LooperMonitor.unregister(this); anrTask.getBeginRecord().release(); anrHandler.removeCallbacksAndMessages(null); lagHandler.removeCallbacksAndMessages(null); @@ -81,29 +82,26 @@ public void onDead() { } @Override - public void dispatchBegin(long beginNs, long cpuBeginMs, long token) { - super.dispatchBegin(beginNs, cpuBeginMs, token); + public boolean isValid() { + return true; + } + @Override + public void onDispatchBegin(String log) { anrTask.beginRecord = AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin"); - anrTask.token = token; if (traceConfig.isDevEnv()) { - MatrixLog.v(TAG, "* [dispatchBegin] token:%s index:%s", token, anrTask.beginRecord.index); + MatrixLog.v(TAG, "* [dispatchBegin] index:%s", anrTask.beginRecord.index); } - long cost = (System.nanoTime() - token) / Constants.TIME_MILLIS_TO_NANO; - anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR - cost); - lagHandler.postDelayed(lagTask, Constants.DEFAULT_NORMAL_LAG - cost); + anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR); + lagHandler.postDelayed(lagTask, Constants.DEFAULT_NORMAL_LAG); } - @Override - public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isBelongFrame) { - super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isBelongFrame); + public void onDispatchEnd(String log, long beginNs, long endNs) { if (traceConfig.isDevEnv()) { long cost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO; - MatrixLog.v(TAG, "[dispatchEnd] token:%s cost:%sms cpu:%sms usage:%s", - token, cost, - cpuEndMs - cpuBeginMs, Utils.calculateCpuUsage(cpuEndMs - cpuBeginMs, cost)); + MatrixLog.v(TAG, "[dispatchEnd] beginNs:%s endNs:%s cost:%sms", beginNs, endNs, cost); } anrTask.getBeginRecord().release(); anrHandler.removeCallbacks(anrTask); @@ -126,7 +124,7 @@ public void run() { String dumpStack = Utils.getWholeStack(stackTrace); JSONObject jsonObject = new JSONObject(); - jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); + DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.LAG); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, dumpStack); @@ -178,29 +176,27 @@ public void run() { // Thread state Thread.State status = Looper.getMainLooper().getThread().getState(); - StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace(); String dumpStack; - if (traceConfig.getLooperPrinterStackStyle() == TraceConfig.STACK_STYLE_WHOLE) { - dumpStack = Utils.getWholeStack(stackTrace, "|*\t\t"); - } else { - dumpStack = Utils.getStack(stackTrace, "|*\t\t", 12); + switch (traceConfig.getLooperPrinterStackStyle()) { + case TraceConfig.STACK_STYLE_WHOLE: + dumpStack = Utils.getWholeStack(Looper.getMainLooper().getThread().getStackTrace(), "|*\t\t"); + break; + case TraceConfig.STACK_STYLE_RAW: + dumpStack = Utils.getMainThreadJavaStackTrace(); + break; + case TraceConfig.STACK_STYLE_SIMPLE: + default: + dumpStack = Utils.getStack(Looper.getMainLooper().getThread().getStackTrace(), "|*\t\t", 12); } - - // frame - UIThreadMonitor monitor = UIThreadMonitor.getMonitor(); - long inputCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_INPUT, token); - long animationCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_ANIMATION, token); - long traversalCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_TRAVERSAL, token); - // trace - LinkedList stack = new LinkedList(); + LinkedList stack = new LinkedList<>(); if (data.length > 0) { TraceDataUtils.structuredDataToStack(data, stack, true, curTime); TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() { @Override public boolean isFilter(long during, int filterCount) { - return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS; + return during < (long) filterCount * Constants.TIME_UPDATE_CYCLE_MS; } @Override @@ -211,7 +207,7 @@ public int getFilterMaxCount() { @Override public void fallback(List stack, int size) { MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack); - Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK)); + Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK)); while (iterator.hasNext()) { iterator.next(); iterator.remove(); @@ -227,8 +223,8 @@ public void fallback(List stack, int size) { // stackKey String stackKey = TraceDataUtils.getTreeKey(stack, stackCost); MatrixLog.w(TAG, "%s \npostTime:%s curTime:%s", - printAnr(scene, processStat, memoryInfo, status, logcatBuilder, isForeground, stack.size(), - stackKey, dumpStack, inputCost, animationCost, traversalCost, stackCost), + printAnr(scene, processStat, memoryInfo, status, logcatBuilder, + isForeground, stack.size(), stackKey, dumpStack, stackCost), token / Constants.TIME_MILLIS_TO_NANO, curTime); // for logcat if (stackCost >= Constants.DEFAULT_ANR_INVALID) { @@ -243,17 +239,13 @@ public void fallback(List stack, int size) { return; } JSONObject jsonObject = new JSONObject(); - jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); + DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.ANR); jsonObject.put(SharePluginInfo.ISSUE_COST, stackCost); jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, stackKey); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); jsonObject.put(SharePluginInfo.ISSUE_TRACE_STACK, reportBuilder.toString()); - if (traceConfig.getLooperPrinterStackStyle() == TraceConfig.STACK_STYLE_WHOLE) { - jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, Utils.getWholeStack(stackTrace)); - } else { - jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, Utils.getStack(stackTrace)); - } + jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, dumpStack); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_PRIORITY, processStat[0]); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_NICE, processStat[1]); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_FOREGROUND, isForeground); @@ -277,8 +269,8 @@ public void fallback(List stack, int size) { } - private String printAnr(String scene, int[] processStat, long[] memoryInfo, Thread.State state, StringBuilder stack, boolean isForeground, - long stackSize, String stackKey, String dumpStack, long inputCost, long animationCost, long traversalCost, long stackCost) { + private String printAnr(String scene, int[] processStat, long[] memoryInfo, Thread.State state, StringBuilder stack, + boolean isForeground, long stackSize, String stackKey, String dumpStack, long stackCost) { StringBuilder print = new StringBuilder(); print.append(String.format("-\n>>>>>>>>>>>>>>>>>>>>>>> maybe happens ANR(%s ms)! <<<<<<<<<<<<<<<<<<<<<<<\n", stackCost)); print.append("|* [Status]").append("\n"); @@ -291,9 +283,6 @@ private String printAnr(String scene, int[] processStat, long[] memoryInfo, Thre print.append("|*\t\tDalvikHeap: ").append(memoryInfo[0]).append("kb\n"); print.append("|*\t\tNativeHeap: ").append(memoryInfo[1]).append("kb\n"); print.append("|*\t\tVmSize: ").append(memoryInfo[2]).append("kb\n"); - print.append("|* [doFrame]").append("\n"); - print.append("|*\t\tinputCost:animationCost:traversalCost").append("\n"); - print.append("|*\t\t").append(inputCost).append(":").append(animationCost).append(":").append(traversalCost).append("\n"); print.append("|* [Thread]").append("\n"); print.append(String.format("|*\t\tStack(%s): ", state)).append(dumpStack); print.append("|* [Trace]").append("\n"); diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/SignalAnrTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/SignalAnrTracer.java index d172e5a8b..7c398b30c 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/SignalAnrTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/SignalAnrTracer.java @@ -24,8 +24,10 @@ import android.os.Message; import android.os.MessageQueue; import android.os.SystemClock; +import android.util.Pair; import androidx.annotation.Keep; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.tencent.matrix.AppActiveMatrixDelegate; @@ -45,17 +47,28 @@ import org.json.JSONObject; import java.io.BufferedReader; +import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class SignalAnrTracer extends Tracer { private static final String TAG = "SignalAnrTracer"; private static final String CHECK_ANR_STATE_THREAD_NAME = "Check-ANR-State-Thread"; + private static final String ANR_DUMP_THREAD_NAME = "ANR-Dump-Thread"; private static final int CHECK_ERROR_STATE_INTERVAL = 500; private static final int ANR_DUMP_MAX_TIME = 20000; + private static long anrReportTimeout = ANR_DUMP_MAX_TIME; private static final int CHECK_ERROR_STATE_COUNT = ANR_DUMP_MAX_TIME / CHECK_ERROR_STATE_INTERVAL; private static final long FOREGROUND_MSG_THRESHOLD = -2000; @@ -79,6 +92,191 @@ public class SignalAnrTracer extends Tracer { System.loadLibrary("trace-canary"); } + private static class SimpleDeadLockDetector { + static class ThreadNode { + int threadId; + String info; + String lockObjCls; + int peerId = -1; + int visit = 0; // 0 not visited, 1 visiting, 2 visited + } + + private final Pattern threadPattern = Pattern.compile("^\"(.*?)\" .*? tid=(\\d+) \\w+$"); + private final Pattern lockHeldPattern = Pattern.compile("^ - .*?\\(a (.*?)\\) held by thread (\\d+)$"); + private final StringBuilder currentSb = new StringBuilder(); + private final HashMap threadsWaitingForHeldLock = new HashMap<>(); + private LinkedList waitingList = new LinkedList<>(); + private String mainThreadInfo = ""; + private boolean threadInfoBegin = false; + private ThreadNode currentThreadInfo = new ThreadNode(); + + public void parseLine(String line) { + + if (line.isEmpty()) { + // thread info end + threadInfoBegin = false; + + if (currentSb.length() > 0 && currentThreadInfo.peerId >= 0) { + String threadInfo = currentSb.toString(); + if (currentThreadInfo.threadId == 1) { + // "currentThreadId" is a thin lock thread id. This is a small integer used by the + // thin lock implementation. This is not to be confused with the native thread's tid, + // nor is it the value returned by java.lang.Thread.getId --- this is a distinct value, + // used only for locking. usually, 0 is reserved to mean "invalid", 1 for main thread. + mainThreadInfo = threadInfo; + } + + currentThreadInfo.info = threadInfo; + threadsWaitingForHeldLock.put(currentThreadInfo.threadId, currentThreadInfo); + currentThreadInfo = new ThreadNode(); + } + + } else if (!threadInfoBegin) { + Matcher m = threadPattern.matcher(line); + if (m.find()) { + // new thread info begin + threadInfoBegin = true; + + currentSb.setLength(0); + currentSb.append(line).append('\n'); + try { + currentThreadInfo.threadId = Integer.parseInt(Objects.requireNonNull(m.group(2))); + } catch (Exception e) { + MatrixLog.e(TAG, e.toString()); + } + } + } else { + Matcher m = lockHeldPattern.matcher(line); + if (m.find()) { + try { + currentThreadInfo.lockObjCls = m.group(1); + currentThreadInfo.peerId = Integer.parseInt(Objects.requireNonNull(m.group(2))); + } catch (Exception e) { + MatrixLog.e(TAG, e.toString()); + } + } + currentSb.append(line).append('\n'); + } + } + + public boolean hasDeadLock() { + parseLine(""); // ensure thread info parse complete + return checkDeadLock(); + } + + @NonNull + public String getMainThreadInfo() { + return mainThreadInfo; + } + + @NonNull + public String getLockHeldThread1Info() { + if (waitingList == null || waitingList.size() == 0) { + return ""; + } + int threadId = waitingList.get(0).threadId; + ThreadNode node = threadsWaitingForHeldLock.get(threadId); + return node == null ? "" : node.info; + } + + @NonNull + public String getLockHeldThread2Info() { + if (waitingList == null || waitingList.size() == 0) { + return ""; + } + int threadId = waitingList.get(waitingList.size() - 1).threadId; + ThreadNode node = threadsWaitingForHeldLock.get(threadId); + return node == null ? "" : node.info; + } + + private static class Pair implements Map.Entry { + F f; + S s; + + Pair(F f, S s) { + this.f = f; + this.s = s; + } + + @Override + public F getKey() { + return f; + } + + @Override + public S getValue() { + return s; + } + + @Override + public S setValue(S value) { + return s = value; + } + + @Override + public String toString() { + return "Pair{" + "f=" + f + ", s=" + s + '}'; + } + } + + @NonNull + public Map.Entry getWaitingThreadsInfo() { + if (waitingList.size() == 0) { + return new Pair<>(null, null); + } else { + int[] threadsId = new int[waitingList.size()]; + String[] locksType = new String[waitingList.size()]; + int idx = 0; + for (ThreadNode threadNode : waitingList) { + threadsId[idx] = threadNode.threadId; + locksType[idx] = threadNode.lockObjCls; + ++idx; + } + return new Pair<>(threadsId, locksType); + } + } + + private boolean checkDeadLock() { + waitingList.clear(); + for (Map.Entry nodeEntry : threadsWaitingForHeldLock.entrySet()) { + ThreadNode node = nodeEntry.getValue(); + if (node.visit == 0) { + ThreadNode ret; + if ((ret = dfsSearch(node)) != null) { + // retrieve cycle from path and save it in waitingList + while (waitingList.size() > 0 && waitingList.getFirst() != ret) { + waitingList.removeFirst(); + } + return true; + } + } + } + return false; + } + + // Return the entry point if a cycle is found, else return null. + private ThreadNode dfsSearch(ThreadNode node) { + waitingList.addLast(node); + node.visit = 1; + + ThreadNode peerNode = threadsWaitingForHeldLock.get(node.peerId); + if (peerNode != null) { + if (peerNode.visit == 1) { + return peerNode; + } + + ThreadNode ret; + if (peerNode.visit == 0 && (ret = dfsSearch(peerNode)) != null) { + return ret; + } + } + + node.visit = 2; + waitingList.removeLast(); + return null; + } + } + @Override protected void onAlive() { super.onAlive(); @@ -114,6 +312,10 @@ public SignalAnrTracer(Application application, String anrTraceFilePath, String sApplication = application; } + public static void setAnrReportTimeout(long timeout) { + anrReportTimeout = timeout; + } + public void setSignalAnrDetectedListener(SignalAnrDetectedListener listener) { sSignalAnrDetectedListener = listener; } @@ -150,21 +352,52 @@ public void run() { @RequiresApi(api = Build.VERSION_CODES.M) @Keep private synchronized static void onANRDumped() { - onAnrDumpedTimeStamp = System.currentTimeMillis(); - MatrixLog.i(TAG, "onANRDumped"); - stackTrace = Utils.getMainThreadJavaStackTrace(); - MatrixLog.i(TAG, "onANRDumped, stackTrace = %s, duration = %d", stackTrace, (System.currentTimeMillis() - onAnrDumpedTimeStamp)); - cgroup = readCgroup(); - MatrixLog.i(TAG, "onANRDumped, read cgroup duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp)); - currentForeground = AppForegroundUtil.isInterestingToUser(); - MatrixLog.i(TAG, "onANRDumped, isInterestingToUser duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp)); - confirmRealAnr(true); + final CountDownLatch anrDumpLatch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + onAnrDumpedTimeStamp = System.currentTimeMillis(); + MatrixLog.i(TAG, "onANRDumped"); + stackTrace = Utils.getMainThreadJavaStackTrace(); + MatrixLog.i(TAG, "onANRDumped, stackTrace = %s, duration = %d", stackTrace, (System.currentTimeMillis() - onAnrDumpedTimeStamp)); + cgroup = readCgroup(); + MatrixLog.i(TAG, "onANRDumped, read cgroup duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp)); + currentForeground = AppForegroundUtil.isInterestingToUser(); + MatrixLog.i(TAG, "onANRDumped, isInterestingToUser duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp)); + confirmRealAnr(true); + anrDumpLatch.countDown(); + } + }, ANR_DUMP_THREAD_NAME).start(); + + try { + anrDumpLatch.await(anrReportTimeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + //empty here + } } @Keep private static void onANRDumpTrace() { try { - MatrixUtil.printFileByLine(TAG, sAnrTraceFilePath); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(sAnrTraceFilePath)), "UTF-8"))) { + String line; + SimpleDeadLockDetector detector = new SimpleDeadLockDetector(); + while ((line = reader.readLine()) != null) { + detector.parseLine(line); + MatrixLog.i(TAG, line); + } + if (sSignalAnrDetectedListener != null) { + if (detector.hasDeadLock()) { + sSignalAnrDetectedListener.onDeadLockAnrDetected( + detector.getMainThreadInfo(), detector.getLockHeldThread1Info(), + detector.getLockHeldThread2Info(), detector.getWaitingThreadsInfo()); + } else if (detector.getMainThreadInfo().contains("android.os.MessageQueue.nativePollOnce")) { + sSignalAnrDetectedListener.onMainThreadStuckAtNativePollOnce(detector.getMainThreadInfo()); + } + } + } catch (Throwable t) { + MatrixLog.e(TAG, "printFileByLine failed e : " + t.getMessage()); + } } catch (Throwable t) { MatrixLog.e(TAG, "onANRDumpTrace error: %s", t.getMessage()); } @@ -347,6 +580,11 @@ public static void printTrace() { public interface SignalAnrDetectedListener { void onAnrDetected(String stackTrace, String mMessageString, long mMessageWhen, boolean fromProcessErrorState, String cpuset); + void onNativeBacktraceDetected(String backtrace, String mMessageString, long mMessageWhen, boolean fromProcessErrorState); + + void onDeadLockAnrDetected(String mainThreadStackTrace, String lockHeldThread1, String lockHeldThread2, Map.Entry waitingList); + + void onMainThreadStuckAtNativePollOnce(String mainThreadStackTrace); } } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadPriorityTracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadTracer.java similarity index 74% rename from matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadPriorityTracer.java rename to matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadTracer.java index a07041629..5b31de460 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadPriorityTracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/ThreadTracer.java @@ -29,10 +29,13 @@ import org.json.JSONObject; -public class ThreadPriorityTracer extends Tracer { +public class ThreadTracer extends Tracer { private static final String TAG = "ThreadPriorityTracer"; private static MainThreadPriorityModifiedListener sMainThreadPriorityModifiedListener; + private static PthreadKeyCallback sPthreadKeyCallback; + private static boolean enableThreadPriorityTracer = false; + private static boolean enablePthreadKeyTracer = false; static { System.loadLibrary("trace-canary"); @@ -41,7 +44,9 @@ public class ThreadPriorityTracer extends Tracer { @Override protected void onAlive() { super.onAlive(); - nativeInitMainThreadPriorityDetective(); + if (enableThreadPriorityTracer || enablePthreadKeyTracer) { + nativeInitThreadHook(enableThreadPriorityTracer ? 1 : 0, enablePthreadKeyTracer ? 1 : 0); + } } @Override @@ -50,10 +55,21 @@ protected void onDead() { } public void setMainThreadPriorityModifiedListener(MainThreadPriorityModifiedListener mainThreadPriorityModifiedListener) { - this.sMainThreadPriorityModifiedListener = mainThreadPriorityModifiedListener; + enableThreadPriorityTracer = true; + sMainThreadPriorityModifiedListener = mainThreadPriorityModifiedListener; + } + + public void setPthreadKeyCallback(PthreadKeyCallback callback) { + enablePthreadKeyTracer = true; + sPthreadKeyCallback = callback; } - private static native void nativeInitMainThreadPriorityDetective(); + public static int getPthreadKeySeq() { + return nativeGetPthreadKeySeq(); + } + + private static native void nativeInitThreadHook(int priority, int phreadKey); + private static native int nativeGetPthreadKeySeq(); @Keep private static void onMainThreadPriorityModified(int priorityBefore, int priorityAfter) { @@ -115,12 +131,27 @@ private static void onMainThreadTimerSlackModified(long timerSlack) { } catch (Throwable t) { MatrixLog.e(TAG, "onMainThreadPriorityModified error: %s", t.getMessage()); } + } + @Keep + private static void pthreadKeyCallback(int type, int ret, int keySeq, String soPath, String backtrace) { + if (sPthreadKeyCallback != null) { + if (type == 0) { + sPthreadKeyCallback.onPthreadCreate(ret, keySeq, soPath, backtrace); + } else if (type == 1) { + sPthreadKeyCallback.onPthreadDelete(ret, keySeq, soPath, backtrace); + } + + } } public interface MainThreadPriorityModifiedListener { void onMainThreadPriorityModified(int priorityBefore, int priorityAfter); - void onMainThreadTimerSlackModified(long timerSlack); } + + public interface PthreadKeyCallback { + void onPthreadCreate(int ret, int keyIndex, String soPath, String backtrace); + void onPthreadDelete(int ret, int keyIndex, String soPath, String backtrace); + } } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/Tracer.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/Tracer.java index db8ae89eb..3d6e1a8f8 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/Tracer.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/tracer/Tracer.java @@ -19,10 +19,9 @@ import androidx.annotation.CallSuper; import com.tencent.matrix.lifecycle.owners.ProcessUILifecycleOwner; -import com.tencent.matrix.trace.listeners.LooperObserver; import com.tencent.matrix.util.MatrixLog; -public abstract class Tracer extends LooperObserver implements ITracer { +public abstract class Tracer implements ITracer { private volatile boolean isAlive = false; private static final String TAG = "Matrix.Tracer"; diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/TraceDataUtils.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/TraceDataUtils.java index 57cb6aa69..aa34ae16a 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/TraceDataUtils.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/TraceDataUtils.java @@ -58,7 +58,7 @@ public static void structuredDataToStack(long[] buffer, LinkedList r } if (!isBegin) { - MatrixLog.d(TAG, "never begin! pass this method[%s]", getMethodId(trueId)); + // MatrixLog.d(TAG, "never begin! pass this method[%s]", getMethodId(trueId)); continue; } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/Utils.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/Utils.java index 1852d62b8..39f637172 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/Utils.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/util/Utils.java @@ -87,16 +87,12 @@ public static String getMainThreadJavaStackTrace() { return stackTrace.toString(); } - public static String calculateCpuUsage(long threadMs, long ms) { - if (threadMs <= 0) { - return ms > 1000 ? "0%" : "100%"; - } - - if (threadMs >= ms) { - return "100%"; + public static String getJavaStackTrace() { + StringBuilder stackTrace = new StringBuilder(); + for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) { + stackTrace.append(stackTraceElement.toString()).append("\n"); } - - return String.format("%.2f", 1.f * threadMs / ms * 100) + "%"; + return stackTrace.toString(); } public static boolean isEmpty(String str) { diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FloatFrameView.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FloatFrameView.java index 5af2f4dcd..dfac506b1 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FloatFrameView.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FloatFrameView.java @@ -22,6 +22,7 @@ import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; +import android.os.Build; import android.text.TextPaint; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -30,27 +31,40 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.RequiresApi; + import com.tencent.matrix.trace.R; import com.tencent.matrix.trace.constants.Constants; import java.util.LinkedList; +@RequiresApi(Build.VERSION_CODES.N) public class FloatFrameView extends LinearLayout { public TextView fpsView; + public TextView sceneView; public LineChartView chartView; - public TextView levelFrozenView; - public TextView levelHighView; - public TextView levelMiddleView; - public TextView levelNormalView; - public TextView sumLevelFrozenView; - public TextView sumLevelHighView; - public TextView sumLevelMiddleView; - public TextView sumLevelNormalView; + public TextView extraInfoView; - public TextView sceneView; - public TextView qiWangView; - public TextView sumQiWangView; + public TextView unknownDelayDurationView; + public TextView inputHandlingDurationView; + public TextView animationDurationView; + public TextView layoutMeasureDurationView; + public TextView drawDurationView; + public TextView syncDurationView; + public TextView commandIssueDurationView; + public TextView swapBuffersDurationView; + public TextView gpuDurationView; + public TextView totalDurationView; + + public TextView sumNormalView; + public TextView sumMiddleView; + public TextView sumHighView; + public TextView sumFrozenView; + public TextView levelNormalView; + public TextView levelMiddleView; + public TextView levelHighView; + public TextView levelFrozenView; public FloatFrameView(Context context) { super(context); @@ -71,17 +85,26 @@ private void initView(Context context) { sceneView = findViewById(R.id.scene_view); extraInfoView.setText("{other info}"); - qiWangView = findViewById(R.id.qi_wang_tv); - levelFrozenView = findViewById(R.id.level_frozen); - levelHighView = findViewById(R.id.level_high); - levelMiddleView = findViewById(R.id.level_middle); + unknownDelayDurationView = findViewById(R.id.unknown_delay_duration_tv); + inputHandlingDurationView = findViewById(R.id.input_handling_duration_tv); + animationDurationView = findViewById(R.id.animation_duration_tv); + layoutMeasureDurationView = findViewById(R.id.layout_measure_duration_tv); + drawDurationView = findViewById(R.id.draw_duration_tv); + syncDurationView = findViewById(R.id.sync_duration_tv); + commandIssueDurationView = findViewById(R.id.command_issue_duration_tv); + swapBuffersDurationView = findViewById(R.id.swap_buffers_duration_tv); + gpuDurationView = findViewById(R.id.gpu_duration_tv); + totalDurationView = findViewById(R.id.total_duration_tv); + + sumNormalView = findViewById(R.id.sum_normal); + sumMiddleView = findViewById(R.id.sum_middle); + sumHighView = findViewById(R.id.sum_high); + sumFrozenView = findViewById(R.id.sum_frozen); levelNormalView = findViewById(R.id.level_normal); + levelMiddleView = findViewById(R.id.level_middle); + levelHighView = findViewById(R.id.level_high); + levelFrozenView = findViewById(R.id.level_frozen); - sumQiWangView = findViewById(R.id.sum_qi_wang_tv); - sumLevelFrozenView = findViewById(R.id.sum_level_frozen); - sumLevelHighView = findViewById(R.id.sum_level_high); - sumLevelMiddleView = findViewById(R.id.sum_level_middle); - sumLevelNormalView = findViewById(R.id.sum_level_normal); chartView = findViewById(R.id.chart); } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FrameDecorator.java b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FrameDecorator.java index 1b1cbbe49..e587f4d09 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FrameDecorator.java +++ b/matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/view/FrameDecorator.java @@ -33,43 +33,42 @@ import android.view.animation.AccelerateInterpolator; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + import com.tencent.matrix.Matrix; import com.tencent.matrix.lifecycle.IStateObserver; import com.tencent.matrix.lifecycle.owners.ProcessUIResumedStateOwner; import com.tencent.matrix.trace.R; import com.tencent.matrix.trace.TracePlugin; import com.tencent.matrix.trace.constants.Constants; -import com.tencent.matrix.trace.core.UIThreadMonitor; -import com.tencent.matrix.trace.listeners.IDoFrameListener; +import com.tencent.matrix.trace.listeners.ISceneFrameListener; import com.tencent.matrix.trace.tracer.FrameTracer; -import com.tencent.matrix.util.MatrixHandlerThread; import com.tencent.matrix.util.MatrixLog; -import java.util.Objects; -import java.util.concurrent.Executor; +import java.util.Arrays; -public class FrameDecorator extends IDoFrameListener { +@RequiresApi(Build.VERSION_CODES.N) +public class FrameDecorator implements ISceneFrameListener { private static final String TAG = "Matrix.FrameDecorator"; private WindowManager windowManager; private WindowManager.LayoutParams layoutParam; private boolean isShowing; - private FloatFrameView view; - private static Handler mainHandler = new Handler(Looper.getMainLooper()); - private Handler handler; + private final FloatFrameView view; + private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static FrameDecorator instance; private static final Object lock = new Object(); private View.OnClickListener clickListener; - private DisplayMetrics displayMetrics = new DisplayMetrics(); + private final DisplayMetrics displayMetrics = new DisplayMetrics(); private boolean isEnable = true; - private float frameIntervalMs; - private float maxFps; - + private static final int sdkInt = Build.VERSION.SDK_INT; - private int bestColor; - private int normalColor; - private int middleColor; - private int highColor; - private int frozenColor; + private final int bestColor; + private final int normalColor; + private final int middleColor; + private final int highColor; + private final int frozenColor; + private int belongColor; private IStateObserver mProcessForegroundListener = new IStateObserver() { @Override @@ -85,15 +84,13 @@ public void off() { @SuppressLint("ClickableViewAccessibility") private FrameDecorator(Context context, final FloatFrameView view) { - this.frameIntervalMs = 1f * UIThreadMonitor.getMonitor().getFrameIntervalNanos() / Constants.TIME_MILLIS_TO_NANO; - this.maxFps = Math.round(1000f / frameIntervalMs); this.view = view; - view.fpsView.setText(String.format("%.2f FPS", maxFps)); this.bestColor = context.getResources().getColor(R.color.level_best_color); this.normalColor = context.getResources().getColor(R.color.level_normal_color); this.middleColor = context.getResources().getColor(R.color.level_middle_color); this.highColor = context.getResources().getColor(R.color.level_high_color); this.frozenColor = context.getResources().getColor(R.color.level_frozen_color); + belongColor = bestColor; ProcessUIResumedStateOwner.INSTANCE.observeForever(mProcessForegroundListener); @@ -105,7 +102,7 @@ public void onViewAttachedToWindow(View v) { TracePlugin tracePlugin = Matrix.with().getPluginByClass(TracePlugin.class); if (null != tracePlugin) { FrameTracer tracer = tracePlugin.getFrameTracer(); - tracer.addListener(FrameDecorator.this); + tracer.register(FrameDecorator.this); } } } @@ -117,7 +114,7 @@ public void onViewDetachedFromWindow(View v) { TracePlugin tracePlugin = Matrix.with().getPluginByClass(TracePlugin.class); if (null != tracePlugin) { FrameTracer tracer = tracePlugin.getFrameTracer(); - tracer.removeListener(FrameDecorator.this); + tracer.unregister(FrameDecorator.this); } } } @@ -160,8 +157,7 @@ public void onAnimationUpdate(ValueAnimator animation) { if (!isShowing) { return; } - int value = (int) animation.getAnimatedValue("trans"); - layoutParam.x = value; + layoutParam.x = (int) animation.getAnimatedValue("trans"); windowManager.updateViewLayout(v, layoutParam); } }); @@ -197,165 +193,6 @@ public void setExtraInfo(String info) { } - private long sumFrameCost; - private long[] lastCost = new long[1]; - private long sumFrames; - private int belongColor = bestColor; - private long[] lastFrames = new long[1]; - private int[] dropLevel = new int[FrameTracer.DropStatus.values().length]; - private int[] sumDropLevel = new int[FrameTracer.DropStatus.values().length]; - private String lastVisibleScene = "default"; - - private Runnable updateDefaultRunnable = new Runnable() { - @Override - public void run() { - view.fpsView.setText(String.format("%.2f FPS", maxFps)); - view.fpsView.setTextColor(view.getResources().getColor(R.color.level_best_color)); - } - }; - - - @Override - public void doFrameAsync(String focusedActivity, long startNs, long endNs, int dropFrame, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { - super.doFrameAsync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); - - if (!Objects.equals(focusedActivity, lastVisibleScene)) { - dropLevel = new int[FrameTracer.DropStatus.values().length]; - lastVisibleScene = focusedActivity; - lastCost[0] = 0; - lastFrames[0] = 0; - } - - sumFrameCost += (dropFrame + 1) * frameIntervalMs; - sumFrames += 1; - float duration = sumFrameCost - lastCost[0]; - - if (dropFrame >= Constants.DEFAULT_DROPPED_FROZEN) { - dropLevel[FrameTracer.DropStatus.DROPPED_FROZEN.index]++; - sumDropLevel[FrameTracer.DropStatus.DROPPED_FROZEN.index]++; - belongColor = frozenColor; - } else if (dropFrame >= Constants.DEFAULT_DROPPED_HIGH) { - dropLevel[FrameTracer.DropStatus.DROPPED_HIGH.index]++; - sumDropLevel[FrameTracer.DropStatus.DROPPED_HIGH.index]++; - if (belongColor != frozenColor) { - belongColor = highColor; - } - } else if (dropFrame >= Constants.DEFAULT_DROPPED_MIDDLE) { - dropLevel[FrameTracer.DropStatus.DROPPED_MIDDLE.index]++; - sumDropLevel[FrameTracer.DropStatus.DROPPED_MIDDLE.index]++; - if (belongColor != frozenColor && belongColor != highColor) { - belongColor = middleColor; - } - } else if (dropFrame >= Constants.DEFAULT_DROPPED_NORMAL) { - dropLevel[FrameTracer.DropStatus.DROPPED_NORMAL.index]++; - sumDropLevel[FrameTracer.DropStatus.DROPPED_NORMAL.index]++; - if (belongColor != frozenColor && belongColor != highColor && belongColor != middleColor) { - belongColor = normalColor; - } - } else { - dropLevel[FrameTracer.DropStatus.DROPPED_BEST.index]++; - sumDropLevel[FrameTracer.DropStatus.DROPPED_BEST.index]++; - if (belongColor != frozenColor && belongColor != highColor && belongColor != middleColor && belongColor != normalColor) { - belongColor = bestColor; - } - } - - - long collectFrame = sumFrames - lastFrames[0]; - if (duration >= 200) { - final float fps = Math.min(maxFps, 1000.f * collectFrame / duration); - updateView(view, fps, belongColor, - dropLevel[FrameTracer.DropStatus.DROPPED_NORMAL.index], - dropLevel[FrameTracer.DropStatus.DROPPED_MIDDLE.index], - dropLevel[FrameTracer.DropStatus.DROPPED_HIGH.index], - dropLevel[FrameTracer.DropStatus.DROPPED_FROZEN.index], - sumDropLevel[FrameTracer.DropStatus.DROPPED_NORMAL.index], - sumDropLevel[FrameTracer.DropStatus.DROPPED_MIDDLE.index], - sumDropLevel[FrameTracer.DropStatus.DROPPED_HIGH.index], - sumDropLevel[FrameTracer.DropStatus.DROPPED_FROZEN.index]); - belongColor = bestColor; - lastCost[0] = sumFrameCost; - lastFrames[0] = sumFrames; - mainHandler.removeCallbacks(updateDefaultRunnable); - mainHandler.postDelayed(updateDefaultRunnable, 250); - } - } - - private void updateView(final FloatFrameView view, final float fps, final int belongColor, - final int normal, final int middle, final int high, final int frozen, - final int sumNormal, final int sumMiddle, final int sumHigh, final int sumFrozen) { - int all = normal + middle + high + frozen; - float frozenValue = all <= 0 ? 0 : 1.f * frozen / all * 60; - float highValue = all <= 0 ? 0 : 1.f * high / all * 25; - float middleValue = all <= 0 ? 0 : 1.f * middle / all * 14; - float normaValue = all <= 0 ? 0 : 1.f * normal / all * 1; - float qiWang = frozenValue + highValue + middleValue + normaValue; - - int sumAll = sumNormal + sumMiddle + sumHigh + sumFrozen; - float sumFrozenValue = sumAll <= 0 ? 0 : 1.f * sumFrozen / sumAll * 60; - float sumHighValue = sumAll <= 0 ? 0 : 1.f * sumHigh / sumAll * 25; - float sumMiddleValue = sumAll <= 0 ? 0 : 1.f * sumMiddle / sumAll * 14; - float sumNormaValue = sumAll <= 0 ? 0 : 1.f * sumNormal / sumAll * 1; - float sumQiWang = sumFrozenValue + sumHighValue + sumMiddleValue + sumNormaValue; - - final String radioFrozen = String.format("%.1f", frozenValue); - final String radioHigh = String.format("%.1f", highValue); - final String radioMiddle = String.format("%.1f", middleValue); - final String radioNormal = String.format("%.1f", normaValue); - final String qiWangStr = String.format("current: %.1f", qiWang); - - final String sumRadioFrozen = String.format("%.1f", sumFrozenValue); - final String sumRadioHigh = String.format("%.1f", sumHighValue); - final String sumRadioMiddle = String.format("%.1f", sumMiddleValue); - final String sumRadioNormal = String.format("%.1f", sumNormaValue); - final String sumQiWangStr = String.format("sum: %.1f", sumQiWang); - - final String fpsStr = String.format("%.2f FPS", fps); - - mainHandler.post(new Runnable() { - @Override - public void run() { - view.chartView.addFps((int) fps, belongColor); - view.fpsView.setText(fpsStr); - view.fpsView.setTextColor(belongColor); - - view.qiWangView.setText(qiWangStr); - view.levelFrozenView.setText(radioFrozen); - view.levelHighView.setText(radioHigh); - view.levelMiddleView.setText(radioMiddle); - view.levelNormalView.setText(radioNormal); - - view.sumQiWangView.setText(sumQiWangStr); - view.sumLevelFrozenView.setText(sumRadioFrozen); - view.sumLevelHighView.setText(sumRadioHigh); - view.sumLevelMiddleView.setText(sumRadioMiddle); - view.sumLevelNormalView.setText(sumRadioNormal); - - } - }); - } - - @Override - public Executor getExecutor() { - return executor; - } - - private Executor executor = new Executor() { - @Override - public void execute(Runnable command) { - getHandler().post(command); - } - }; - - private Handler getHandler() { - if (handler == null || !handler.getLooper().getThread().isAlive()) { - if (null != MatrixHandlerThread.getDefaultHandlerThread()) { - handler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper()); - } - } - return handler; - } - public static FrameDecorator get() { return instance; } @@ -482,4 +319,86 @@ public void run() { } } + @Override + public int getIntervalMs() { + return 200; + } + + @Override + public String getName() { + return null; + } + + @Override + public boolean skipFirstFrame() { + return false; + } + + @Override + public int getThreshold() { + return 0; + } + + @SuppressLint("DefaultLocale") + @Override + public void onFrameMetricsAvailable(@NonNull String sceneName, long[] avgDurations, int[] dropLevel, int[] dropSum, float avgDroppedFrame, float avgRefreshRate, final float fps) { + final String unknownDelay = String.format("unknown delay: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.UNKNOWN_DELAY_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String inputHandling = String.format("input handling: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.INPUT_HANDLING_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String animation = String.format("animation: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.ANIMATION_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String layoutMeasure = String.format("layout measure: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.LAYOUT_MEASURE_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String draw = String.format("draw: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.DRAW_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String sync = String.format("sync: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.SYNC_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String commandIssue = String.format("command issue: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.COMMAND_ISSUE_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String swapBuffers = String.format("swap buffers: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.SWAP_BUFFERS_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String gpu = String.format("gpu: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.GPU_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + final String total = String.format("total: %.1fms", (double) avgDurations[FrameTracer.FrameDuration.TOTAL_DURATION.ordinal()] / Constants.TIME_MILLIS_TO_NANO); + + if (fps <= avgRefreshRate - Constants.DEFAULT_DROPPED_FROZEN) { + belongColor = frozenColor; + } else if (fps <= avgRefreshRate - Constants.DEFAULT_DROPPED_HIGH) { + belongColor = highColor; + } else if (fps <= avgRefreshRate - Constants.DEFAULT_DROPPED_MIDDLE) { + belongColor = middleColor; + } else if (fps <= avgRefreshRate - Constants.DEFAULT_DROPPED_NORMAL) { + belongColor = normalColor; + } else { + belongColor = bestColor; + } + + final int[] level = Arrays.copyOf(dropLevel, dropLevel.length); + final int[] sum = Arrays.copyOf(dropSum, dropSum.length); + + mainHandler.post(new Runnable() { + @Override + public void run() { + view.chartView.addFps((int) fps, belongColor); + view.fpsView.setText(String.format("%.2f FPS", fps)); + view.fpsView.setTextColor(belongColor); + + view.unknownDelayDurationView.setText(unknownDelay); + view.inputHandlingDurationView.setText(inputHandling); + view.animationDurationView.setText(animation); + view.layoutMeasureDurationView.setText(layoutMeasure); + view.drawDurationView.setText(draw); + view.syncDurationView.setText(sync); + view.commandIssueDurationView.setText(commandIssue); + view.swapBuffersDurationView.setText(swapBuffers); + if (sdkInt >= Build.VERSION_CODES.S) { + view.gpuDurationView.setText(gpu); + } else { + view.gpuDurationView.setText("gpu: unusable"); + } + view.totalDurationView.setText(total); + + view.sumNormalView.setText(String.valueOf(sum[FrameTracer.DropStatus.DROPPED_NORMAL.ordinal()])); + view.sumMiddleView.setText(String.valueOf(sum[FrameTracer.DropStatus.DROPPED_MIDDLE.ordinal()])); + view.sumHighView.setText(String.valueOf(sum[FrameTracer.DropStatus.DROPPED_HIGH.ordinal()])); + view.sumFrozenView.setText(String.valueOf(sum[FrameTracer.DropStatus.DROPPED_FROZEN.ordinal()])); + view.levelNormalView.setText(String.valueOf(level[FrameTracer.DropStatus.DROPPED_NORMAL.ordinal()])); + view.levelMiddleView.setText(String.valueOf(level[FrameTracer.DropStatus.DROPPED_MIDDLE.ordinal()])); + view.levelHighView.setText(String.valueOf(level[FrameTracer.DropStatus.DROPPED_HIGH.ordinal()])); + view.levelFrozenView.setText(String.valueOf(level[FrameTracer.DropStatus.DROPPED_FROZEN.ordinal()])); + } + }); + } } diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/res/layout/float_frame_view.xml b/matrix/matrix-android/matrix-trace-canary/src/main/res/layout/float_frame_view.xml index 3e0c4b570..5dc13d0a2 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/res/layout/float_frame_view.xml +++ b/matrix/matrix-android/matrix-trace-canary/src/main/res/layout/float_frame_view.xml @@ -34,14 +34,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" - android:minWidth="76dp" + android:minWidth="100dp" android:orientation="vertical"> @@ -49,7 +49,7 @@ android:id="@+id/scene_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="left" + android:gravity="start" android:textColor="@color/dark_text" android:textSize="8dp" @@ -68,14 +68,86 @@ + + + + + + + + + + + + + + + + + + + - - @@ -165,9 +223,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:layout_gravity="center" android:background="@color/level_middle_color" - android:gravity="center" - android:maxHeight="20dp" android:text="0" android:textColor="@android:color/white" android:textSize="8dp" /> @@ -177,9 +234,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:layout_gravity="center" android:background="@color/level_high_color" - android:gravity="center" - android:maxHeight="20dp" android:text="0" android:textColor="@android:color/white" android:textSize="8dp" /> @@ -189,9 +245,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:layout_gravity="center" android:background="@color/level_frozen_color" - android:gravity="center" - android:maxHeight="20dp" android:text="0" android:textColor="@android:color/white" android:textSize="8dp" /> diff --git a/matrix/matrix-android/matrix-trace-canary/src/main/res/values/colors.xml b/matrix/matrix-android/matrix-trace-canary/src/main/res/values/colors.xml index 8f80c7293..8521b7122 100644 --- a/matrix/matrix-android/matrix-trace-canary/src/main/res/values/colors.xml +++ b/matrix/matrix-android/matrix-trace-canary/src/main/res/values/colors.xml @@ -1,9 +1,9 @@ #999999 - #0eb83a - #FFD700 - #EE7600 - #DC143C - #99FFFF + #0cf300 + #40bf00 + #708f00 + #bb4400 + #f00f00 \ No newline at end of file diff --git a/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.cc b/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.cc index 694dca159..110c59fea 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.cc +++ b/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.cc @@ -28,9 +28,11 @@ #include #include +#include #include #include #include +#include #include "BacktraceDefine.h" #include "Backtrace.h" @@ -44,9 +46,14 @@ using namespace MatrixTraffic; static bool HOOKED = false; static bool sDumpNativeBackTrace = false; +static map> backtraceMap; +static shared_mutex backtraceMapLock; + static struct StacktraceJNI { jclass TrafficPlugin; jmethodID TrafficPlugin_setFdStackTrace; + jmethodID TrafficPlugin_clearFdInfo; + jmethodID TrafficPlugin_printLog; } gJ; @@ -59,11 +66,6 @@ void makeNativeStack(wechat_backtrace::Backtrace* backtrace, char *&stack) { auto _callback = [&](wechat_backtrace::FrameDetail it) { std::string so_name = it.map_name; - char *demangled_name = nullptr; - int status = 0; - - demangled_name = abi::__cxa_demangle(it.function_name, nullptr, 0, &status); - if (strstr(it.map_name, "libmatrix-traffic.so") || strstr(it.map_name, "libwechatbacktrace.so")) { return; } @@ -72,9 +74,6 @@ void makeNativeStack(wechat_backtrace::Backtrace* backtrace, char *&stack) { << "#" << std::dec << (index++) << " pc " << std::hex << it.rel_pc << " " << it.map_name - << " (" - << (demangled_name ? demangled_name : "null") - << ")" << std::endl; if (last_so_name != it.map_name) { last_so_name = it.map_name; @@ -82,10 +81,6 @@ void makeNativeStack(wechat_backtrace::Backtrace* backtrace, char *&stack) { } brief_stack_builder << std::hex << it.rel_pc << ";"; - - if (demangled_name) { - free(demangled_name); - } }; wechat_backtrace::restore_frame_detail(backtrace->frames.get(), backtrace->frame_size, @@ -95,79 +90,90 @@ void makeNativeStack(wechat_backtrace::Backtrace* backtrace, char *&stack) { strcpy(stack, full_stack_builder.str().c_str()); } - -static char* getNativeBacktrace() { +static void saveNativeBackTrace(const char* key) { wechat_backtrace::Backtrace *backtracePrt; - - wechat_backtrace::Backtrace backtrace_zero = BACKTRACE_INITIALIZER( - 16); - - + int max_frames = 16; backtracePrt = new wechat_backtrace::Backtrace; - backtracePrt->max_frames = backtrace_zero.max_frames; - backtracePrt->frame_size = backtrace_zero.frame_size; - backtracePrt->frames = backtrace_zero.frames; + backtracePrt->max_frames = max_frames; + backtracePrt->frame_size = 0; + backtracePrt->frames = std::shared_ptr( + new wechat_backtrace::Frame[max_frames], std::default_delete()); wechat_backtrace::unwind_adapter(backtracePrt->frames.get(), backtracePrt->max_frames, backtracePrt->frame_size); - char* nativeStack; - makeNativeStack(backtracePrt, nativeStack); - return nativeStack; + string keyString(key); + backtraceMapLock.lock(); + backtraceMap[keyString] = static_cast>(backtracePrt); + backtraceMapLock.unlock(); } - -int (*original_connect)(int fd, const struct sockaddr* addr, socklen_t addr_length); -int my_connect(int fd, sockaddr *addr, socklen_t addr_length) { - TrafficCollector::enQueueConnect(fd, addr, addr_length); - return original_connect(fd, addr, addr_length); +static char* getNativeBacktrace(string keyString) { + if (backtraceMap.count(keyString) > 0) { + backtraceMapLock.lock_shared(); + wechat_backtrace::Backtrace *backtracePrt = backtraceMap[keyString].get(); + backtraceMapLock.unlock_shared(); + char* nativeStack; + makeNativeStack(backtracePrt, nativeStack); + return nativeStack; + } else { + return new char(0); + } } ssize_t (*original_read)(int fd, void *buf, size_t count); ssize_t my_read(int fd, void *buf, size_t count) { ssize_t ret = original_read(fd, buf, count); - TrafficCollector::enQueueRx(MSG_TYPE_READ, fd, ret); + if (ret > 0) { + TrafficCollector::enQueueRx(MSG_TYPE_READ, fd, ret); + } return ret; } - ssize_t (*original_recv)(int sockfd, void *buf, size_t len, int flags); ssize_t my_recv(int sockfd, void *buf, size_t len, int flags) { ssize_t ret = original_recv(sockfd, buf, len, flags); - TrafficCollector::enQueueRx(MSG_TYPE_RECV, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueRx(MSG_TYPE_RECV, sockfd, ret); + } return ret; } - ssize_t (*original_recvfrom)(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t my_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { ssize_t ret = original_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); - TrafficCollector::enQueueRx(MSG_TYPE_RECVFROM, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueRx(MSG_TYPE_RECVFROM, sockfd, ret); + } return ret; } - ssize_t (*original_recvmsg)(int sockfd, struct msghdr *msg, int flags); ssize_t my_recvmsg(int sockfd, struct msghdr *msg, int flags) { ssize_t ret = original_recvmsg(sockfd, msg, flags); - TrafficCollector::enQueueRx(MSG_TYPE_RECVMSG, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueRx(MSG_TYPE_RECVMSG, sockfd, ret); + } return ret; } - ssize_t (*original_write)(int fd, const void *buf, size_t count); ssize_t my_write(int fd, const void *buf, size_t count) { ssize_t ret = original_write(fd, buf, count); - TrafficCollector::enQueueTx(MSG_TYPE_WRITE, fd, ret); + if (ret > 0) { + TrafficCollector::enQueueTx(MSG_TYPE_WRITE, fd, ret); + } return ret; } ssize_t (*original_send)(int sockfd, const void *buf, size_t len, int flags); ssize_t my_send(int sockfd, const void *buf, size_t len, int flags) { ssize_t ret = original_send(sockfd, buf, len, flags); - TrafficCollector::enQueueTx(MSG_TYPE_SEND, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueTx(MSG_TYPE_SEND, sockfd, ret); + } return ret; } @@ -176,25 +182,42 @@ ssize_t (*original_sendto)(int sockfd, const void *buf, size_t len, int flags, ssize_t my_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { ssize_t ret = original_sendto(sockfd, buf, len, flags, dest_addr, addrlen); - TrafficCollector::enQueueTx(MSG_TYPE_SENDTO, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueTx(MSG_TYPE_SENDTO, sockfd, ret); + } return ret; } ssize_t (*original_sendmsg)(int sockfd, const struct msghdr *msg, int flags); ssize_t my_sendmsg(int sockfd, const struct msghdr *msg, int flags) { ssize_t ret = original_sendmsg(sockfd, msg, flags); - TrafficCollector::enQueueTx(MSG_TYPE_SENDMSG, sockfd, ret); + if (ret > 0) { + TrafficCollector::enQueueTx(MSG_TYPE_SENDMSG, sockfd, ret); + } + return ret; +} + +int (*original_fclose)(FILE *stream); +int my_fclose(FILE *stream) { + int fd = fileno(stream); + int ret = original_fclose(stream); + if (ret == 0) { + TrafficCollector::enQueueClose(fd); + } return ret; } int (*original_close)(int fd); int my_close(int fd) { - TrafficCollector::enQueueClose(fd); - return original_close(fd); + int ret = original_close(fd); + if (ret == 0) { + TrafficCollector::enQueueClose(fd); + } + return ret; } static jobject nativeGetTrafficInfoMap(JNIEnv *env, jclass, jint type) { - return TrafficCollector::getTrafficInfoMap(type); + return TrafficCollector::getFdTrafficInfoMap(type); } static void nativeReleaseMatrixTraffic(JNIEnv *env, jclass) { @@ -202,42 +225,52 @@ static void nativeReleaseMatrixTraffic(JNIEnv *env, jclass) { TrafficCollector::clearTrafficInfo(); } +static jstring nativeGetNativeBackTraceByKey(JNIEnv *env, jclass, jstring key) { + const char* cKey = env->GetStringUTFChars(key, JNI_FALSE); + string keyString(cKey); + char* ret = getNativeBacktrace(keyString); + jstring jRet = env->NewStringUTF(ret); + delete[] ret; + return jRet; +} + static void nativeClearTrafficInfo(JNIEnv *env, jclass) { + backtraceMapLock.lock(); + backtraceMap.clear(); + backtraceMapLock.unlock(); TrafficCollector::clearTrafficInfo(); } -void setStackTrace(char* threadName) { +void setFdStackTraceCall(const char* key) { JNIEnv *env = JniInvocation::getEnv(); if (!env) return; - - jstring jThreadName = env->NewStringUTF(threadName); - jstring nativeBacktrace; + jstring jKey = env->NewStringUTF(key); if (sDumpNativeBackTrace) { - nativeBacktrace = env->NewStringUTF(getNativeBacktrace()); - } else { - nativeBacktrace = env->NewStringUTF(""); + saveNativeBackTrace(key); } - - env->CallStaticVoidMethod(gJ.TrafficPlugin, gJ.TrafficPlugin_setFdStackTrace, jThreadName, nativeBacktrace); - env->DeleteLocalRef(jThreadName); - env->DeleteLocalRef(nativeBacktrace); + env->CallStaticVoidMethod(gJ.TrafficPlugin, gJ.TrafficPlugin_setFdStackTrace, jKey); + env->DeleteLocalRef(jKey); } -static void hookSocket(bool rxHook, bool txHook) { +static void hookSocket(bool rxHook, bool txHook, bool willHookAllSoReadWrite) { if (HOOKED) { return; } - xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "connect", - (void *) my_connect, (void **) (&original_connect)); - xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "close", (void *) my_close, (void **) (&original_close)); + xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "fclose", + (void *) my_fclose, (void **) (&original_fclose)); if (rxHook) { - xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "read", - (void *) my_read, (void **) (&original_read)); + if (willHookAllSoReadWrite) { + xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "read", + (void *) my_read, (void **) (&original_read)); + } else { + xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, "/data/.*\\.so$", "read", + (void *) my_read, (void **) (&original_read)); + } xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "recv", (void *) my_recv, (void **) (&original_recv)); xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "recvfrom", @@ -247,8 +280,13 @@ static void hookSocket(bool rxHook, bool txHook) { } if (txHook) { - xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "write", - (void *) my_write, (void **) (&original_write)); + if (willHookAllSoReadWrite) { + xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "write", + (void *) my_write, (void **) (&original_write)); + } else { + xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, "/data/.*\\.so$", "write", + (void *) my_write, (void **) (&original_write)); + } xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "send", (void *) my_send, (void **) (&original_send)); xhook_grouped_register(HOOK_REQUEST_GROUPID_TRAFFIC, ".*\\.so$", "sendto", @@ -257,18 +295,30 @@ static void hookSocket(bool rxHook, bool txHook) { (void *) my_sendmsg, (void **) (&original_sendmsg)); } + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "read"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "recv"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "recvfrom"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "recvmsg"); + + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "write"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "send"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "sendto"); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", "sendmsg"); + + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, "/vendor/lib.*", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libinput\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libmeminfo\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libgui\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libsensor\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libutils\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libcutils\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libtrace-canary\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libgsl\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libadbconnection_client\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libadbconnection\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libandroid_runtime\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libnetd_client\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libstatssocket\\.so$", nullptr); - xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libc\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libprofile\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libbinder\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libGLES_mali\\.so$", nullptr); @@ -277,7 +327,16 @@ static void hookSocket(bool rxHook, bool txHook) { xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libsqlite\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libbase\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libartbase\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libart\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libtombstoned_client\\.so$", nullptr); xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*liblog\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libcodec2_vndk\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libandroidfw\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libaudioclient\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libjavacrypto\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libwechatbacktrace\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libmatrix-memoryhook\\.so$", nullptr); + xhook_grouped_ignore(HOOK_REQUEST_GROUPID_TRAFFIC, ".*libmatrix-traffic\\.so$", nullptr); xhook_refresh(true); HOOKED = true; @@ -291,11 +350,11 @@ static void ignoreSo(JNIEnv *env, jobjectArray ignoreSoFiles) { } } -static void nativeInitMatrixTraffic(JNIEnv *env, jclass, jboolean rxEnable, jboolean txEnable, jboolean dumpStackTrace, jboolean dumpNativeBackTrace, jboolean lookupIpAddress, jobjectArray ignoreSoFiles) { - TrafficCollector::startLoop(dumpStackTrace == JNI_TRUE, lookupIpAddress == JNI_TRUE); +static void nativeInitMatrixTraffic(JNIEnv *env, jclass, jboolean rxEnable, jboolean txEnable, jboolean dumpStackTrace, jboolean dumpNativeBackTrace, jboolean willHookAllSoReadWrite, jobjectArray ignoreSoFiles) { + TrafficCollector::startLoop(dumpStackTrace == JNI_TRUE); sDumpNativeBackTrace = (dumpNativeBackTrace == JNI_TRUE); ignoreSo(env, ignoreSoFiles); - hookSocket(rxEnable == JNI_TRUE, txEnable == JNI_TRUE); + hookSocket(rxEnable == JNI_TRUE, txEnable == JNI_TRUE, willHookAllSoReadWrite == JNI_TRUE); } template @@ -306,6 +365,7 @@ static const JNINativeMethod TRAFFIC_METHODS[] = { {"nativeGetTrafficInfoMap", "(I)Ljava/util/HashMap;", (void *) nativeGetTrafficInfoMap}, {"nativeClearTrafficInfo", "()V", (void *) nativeClearTrafficInfo}, {"nativeReleaseMatrixTraffic", "()V", (void *) nativeReleaseMatrixTraffic}, + {"nativeGetNativeBackTraceByKey", "(Ljava/lang/String;)Ljava/lang/String;", (void *) nativeGetNativeBackTraceByKey}, }; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { @@ -319,8 +379,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { if (!trafficCollectorCls) return -1; gJ.TrafficPlugin = static_cast(env->NewGlobalRef(trafficCollectorCls)); + gJ.TrafficPlugin_setFdStackTrace = - env->GetStaticMethodID(trafficCollectorCls, "setStackTrace", "(Ljava/lang/String;Ljava/lang/String;)V"); + env->GetStaticMethodID(trafficCollectorCls, "setFdStackTrace", "(Ljava/lang/String;)V"); if (env->RegisterNatives( trafficCollectorCls, TRAFFIC_METHODS, static_cast(NELEM(TRAFFIC_METHODS))) != 0) @@ -328,4 +389,4 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { env->DeleteLocalRef(trafficCollectorCls); return JNI_VERSION_1_6; -} // namespace MatrixTraffic +} // namespace MatrixTraffic \ No newline at end of file diff --git a/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.h b/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.h index dd83748c2..c87fc2c7a 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.h +++ b/matrix/matrix-android/matrix-traffic/src/main/cpp/MatrixTraffic.h @@ -21,6 +21,8 @@ #ifndef MATRIX_ANDROID_MATRIXTRAFFIC_H #define MATRIX_ANDROID_MATRIXTRAFFIC_H -void setStackTrace(char* threadName); +void setFdStackTraceCall(const char* key); +void clearFdInfoCall(int fd); +void printLog(const char* log); #endif //MATRIX_ANDROID_MATRIXTRAFFIC_H diff --git a/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.cc b/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.cc index d26d8a9b0..c691746c6 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.cc +++ b/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.cc @@ -29,124 +29,81 @@ #include #include "MatrixTraffic.h" +#include #include #include +#include using namespace std; namespace MatrixTraffic { static mutex queueMutex; -static lock_guard lock(queueMutex); +static condition_variable cv; + static bool loopRunning = false; static bool sDumpStackTrace = false; -static bool sLookupIpAddress = false; -static map fdFamilyMap; + +static unordered_set activeFdSet; +static shared_mutex activeFdSetMutex; + +static unordered_set invalidFdSet; + static blocking_queue> msgQueue; -static map rxTrafficInfoMap; -static map txTrafficInfoMap; -static mutex rxTrafficInfoMapLock; -static mutex txTrafficInfoMapLock; + +static map rxFdTrafficInfoMap; +static map txFdTrafficInfoMap; +static shared_mutex rxTrafficInfoMapLock; +static shared_mutex txTrafficInfoMapLock; static map fdThreadNameMap; -static map fdIpAddressMap; -static mutex fdThreadNameMapLock; -static mutex fdIpAddressMapLock; - -string getIpAddressFromAddr(sockaddr* _addr) { - string ipAddress; - if (_addr != nullptr) { - if ((int)_addr->sa_family == AF_LOCAL) { - ipAddress = _addr->sa_data; - ipAddress.append(":LOCAL"); - } else if ((int)_addr->sa_family == AF_INET) { - auto *sin4 = reinterpret_cast(_addr); - char ipv4str[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &(sin4->sin_addr), ipv4str, INET6_ADDRSTRLEN) != nullptr) { - ipAddress = ipv4str; - int port = ntohs(sin4->sin_port); - if (port != -1) { - ipAddress.append(":"); - ipAddress.append(to_string(port)); - } - } - } else if ((int)_addr->sa_family == AF_INET6) { - auto *sin6 = reinterpret_cast(_addr); - char ipv6str[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, &(sin6->sin6_addr), ipv6str, INET6_ADDRSTRLEN) != nullptr) { - ipAddress = ipv6str; - int port = ntohs(sin6->sin6_port); - if (port != -1) { - ipAddress.append(":"); - ipAddress.append(to_string(port)); - } - } +static shared_mutex fdThreadNameMapSharedLock; + +int isNetworkSocketFd(int fd) { + struct sockaddr c; + socklen_t cLen = sizeof(c); + int getSockNameRet = getsockname(fd, (struct sockaddr*) &c, &cLen); + if (getSockNameRet == 0) { + if (c.sa_family != AF_LOCAL && c.sa_family != AF_NETLINK) { + return 1; } } - return ipAddress; -} - -void saveIpAddress(int fd, sockaddr* addr) { - fdIpAddressMapLock.lock(); - if (fdIpAddressMap.count(fd) == 0) { - fdIpAddressMap[fd] = getIpAddressFromAddr(addr); - } - fdIpAddressMapLock.unlock(); + return 0; } -string getIpAddress(int fd) { - fdIpAddressMapLock.lock(); - string ipAddress = fdIpAddressMap[fd]; - fdIpAddressMapLock.unlock(); - return ipAddress; -} - - -string getKeyAndSaveStack(int fd) { - fdThreadNameMapLock.lock(); +string saveFdInfo(int fd) { + fdThreadNameMapSharedLock.lock_shared(); if (fdThreadNameMap.count(fd) == 0) { - string key; - if (sLookupIpAddress) { - key = getIpAddress(fd); - key.append(";"); - } + fdThreadNameMapSharedLock.unlock_shared(); - auto threadName = new char[15]; + char threadName[15]; prctl(PR_GET_NAME, threadName); - key.append(threadName); - fdThreadNameMap[fd] = key; - fdThreadNameMapLock.unlock(); + char key[32]; + snprintf(key, sizeof(key), "%d-%s", fd, threadName); + if (sDumpStackTrace) { - setStackTrace(key.data()); + setFdStackTraceCall(key); } + + fdThreadNameMapSharedLock.lock(); + fdThreadNameMap[fd] = key; + fdThreadNameMapSharedLock.unlock(); + return key; } else { auto key = fdThreadNameMap[fd]; - fdThreadNameMapLock.unlock(); + fdThreadNameMapSharedLock.unlock_shared(); return key; } } -void TrafficCollector::enQueueConnect(int fd, sockaddr *addr, socklen_t addr_length) { - if (!loopRunning) { - return; - } - if (sLookupIpAddress) { - saveIpAddress(fd, addr); - } - shared_ptr msg = make_shared(MSG_TYPE_CONNECT, fd, addr->sa_family, getKeyAndSaveStack(fd), 0); - - msgQueue.push(msg); - queueMutex.unlock(); -} - void TrafficCollector::enQueueClose(int fd) { if (!loopRunning) { return; } - shared_ptr msg = make_shared(MSG_TYPE_CLOSE, fd, 0, "", 0); + shared_ptr msg = make_shared(MSG_TYPE_CLOSE, fd, 0); msgQueue.push(msg); - queueMutex.unlock(); + cv.notify_one(); } void enQueueMsg(int type, int fd, size_t len) { @@ -154,9 +111,18 @@ void enQueueMsg(int type, int fd, size_t len) { return; } - shared_ptr msg = make_shared(type, fd, 0, getKeyAndSaveStack(fd), len); + activeFdSetMutex.lock_shared(); + if (activeFdSet.count(fd) > 0) { + activeFdSetMutex.unlock_shared(); + saveFdInfo(fd); + } else { + activeFdSetMutex.unlock_shared(); + } + + shared_ptr msg = make_shared(type, fd, len); msgQueue.push(msg); - queueMutex.unlock(); + + cv.notify_one(); } void TrafficCollector::enQueueTx(int type, int fd, size_t len) { @@ -173,58 +139,72 @@ void TrafficCollector::enQueueRx(int type, int fd, size_t len) { enQueueMsg(type, fd, len); } -void appendRxTraffic(const string& threadName, long len) { +void appendRxTraffic(int fd, long len) { rxTrafficInfoMapLock.lock(); - rxTrafficInfoMap[threadName] += len; + rxFdTrafficInfoMap[fd] += len; rxTrafficInfoMapLock.unlock(); } -void appendTxTraffic(const string& threadName, long len) { +void appendTxTraffic(int fd, long len) { txTrafficInfoMapLock.lock(); - txTrafficInfoMap[threadName] += len; + txFdTrafficInfoMap[fd] += len; txTrafficInfoMapLock.unlock(); } void loop() { + unique_lock lk(queueMutex); while (loopRunning) { if (msgQueue.empty()) { - queueMutex.lock(); + cv.wait(lk); } else { shared_ptr msg = msgQueue.front(); - if (msg->type == MSG_TYPE_CONNECT) { - fdFamilyMap[msg->fd] = msg->sa_family; - } else if (msg->type == MSG_TYPE_READ) { - if (fdFamilyMap.count(msg->fd) > 0) { - appendRxTraffic(msg->threadName, msg->len); - } - } else if (msg->type >= MSG_TYPE_RECV && msg->type <= MSG_TYPE_RECVMSG) { - if (fdFamilyMap[msg->fd] != AF_LOCAL) { - appendRxTraffic(msg->threadName, msg->len); + int fd = msg->fd; + int type = msg->type; + if (type == MSG_TYPE_CLOSE) { + if (activeFdSet.count(fd) > 0) { + fdThreadNameMapSharedLock.lock(); + fdThreadNameMap.erase(fd); + fdThreadNameMapSharedLock.unlock(); + + activeFdSetMutex.lock(); + activeFdSet.erase(fd); + activeFdSetMutex.unlock(); } - } else if (msg->type == MSG_TYPE_WRITE) { - if (fdFamilyMap.count(msg->fd) > 0) { - appendTxTraffic(msg->threadName, msg->len); + invalidFdSet.erase(fd); + } else { + if (activeFdSet.count(fd) == 0 && invalidFdSet.count(fd) == 0) { + if (!isNetworkSocketFd(fd)) { + invalidFdSet.insert(fd); + fdThreadNameMapSharedLock.lock(); + fdThreadNameMap.erase(fd); + fdThreadNameMapSharedLock.unlock(); + } else { + activeFdSetMutex.lock(); + activeFdSet.insert(fd); + activeFdSetMutex.unlock(); + } } - } else if (msg->type >= MSG_TYPE_SEND && msg->type <= MSG_TYPE_SENDMSG) { - if (fdFamilyMap[msg->fd] != AF_LOCAL) { - appendTxTraffic(msg->threadName, msg->len); + if (type >= MSG_TYPE_READ && type <= MSG_TYPE_RECVMSG) { + if (activeFdSet.count(fd) > 0 && invalidFdSet.count(fd) == 0) { + appendRxTraffic(fd, msg->len); + } + } else if (type >= MSG_TYPE_WRITE && type <= MSG_TYPE_SENDMSG) { + if (activeFdSet.count(fd) > 0 && invalidFdSet.count(fd) == 0) { + appendTxTraffic(fd, msg->len); + } } - } else if (msg->type == MSG_TYPE_CLOSE) { - fdThreadNameMapLock.lock(); - fdThreadNameMap.erase(msg->fd); - fdThreadNameMapLock.unlock(); - fdFamilyMap.erase(msg->fd); } msgQueue.pop(); } } } - -void TrafficCollector::startLoop(bool dumpStackTrace, bool lookupIpAddress) { - sDumpStackTrace = dumpStackTrace; - sLookupIpAddress = lookupIpAddress; +void TrafficCollector::startLoop(bool dumpStackTrace) { + if (loopRunning) { + return; + } loopRunning = true; + sDumpStackTrace = dumpStackTrace; thread loopThread(loop); loopThread.detach(); } @@ -233,7 +213,7 @@ void TrafficCollector::stopLoop() { loopRunning = false; } -jobject TrafficCollector::getTrafficInfoMap(int type) { +jobject TrafficCollector::getFdTrafficInfoMap(int type) { JNIEnv *env = JniInvocation::getEnv(); jclass mapClass = env->FindClass("java/util/HashMap"); if(mapClass == nullptr) { @@ -245,25 +225,54 @@ jobject TrafficCollector::getTrafficInfoMap(int type) { "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); if (type == TYPE_GET_TRAFFIC_RX) { - rxTrafficInfoMapLock.lock(); - for (auto & it : rxTrafficInfoMap) { - jstring threadName = env->NewStringUTF(it.first.c_str()); + rxTrafficInfoMapLock.lock_shared(); + for (auto & it : rxFdTrafficInfoMap) { + int fd = it.first; + if (fdThreadNameMap.count(fd) == 0) { + continue; + } + fdThreadNameMapSharedLock.lock_shared(); + const char* key = fdThreadNameMap[fd].c_str(); + fdThreadNameMapSharedLock.unlock_shared(); + if (key == nullptr || strlen(key) == 0) { + continue; + } + + if (it.second <= 0) { + continue; + } + jstring jKey = env->NewStringUTF(key); jstring traffic = env->NewStringUTF(to_string(it.second).c_str()); - env->CallObjectMethod(jHashMap, mapPut, threadName, traffic); - env->DeleteLocalRef(threadName); + env->CallObjectMethod(jHashMap, mapPut, jKey, traffic); + env->DeleteLocalRef(jKey); env->DeleteLocalRef(traffic); + } - rxTrafficInfoMapLock.unlock(); + rxTrafficInfoMapLock.unlock_shared(); } else if (type == TYPE_GET_TRAFFIC_TX) { - txTrafficInfoMapLock.lock(); - for (auto & it : txTrafficInfoMap) { - jstring threadName = env->NewStringUTF(it.first.c_str()); + txTrafficInfoMapLock.lock_shared(); + for (auto & it : txFdTrafficInfoMap) { + int fd = it.first; + if (fdThreadNameMap.count(fd) == 0) { + continue; + } + fdThreadNameMapSharedLock.lock_shared(); + const char* key = fdThreadNameMap[fd].c_str(); + fdThreadNameMapSharedLock.unlock_shared(); + if (key == nullptr || strlen(key) == 0) { + continue; + } + + if (it.second <= 0) { + continue; + } + jstring jKey = env->NewStringUTF(key); jstring traffic = env->NewStringUTF(to_string(it.second).c_str()); - env->CallObjectMethod(jHashMap, mapPut, threadName, traffic); - env->DeleteLocalRef(threadName); + env->CallObjectMethod(jHashMap, mapPut, jKey, traffic); + env->DeleteLocalRef(jKey); env->DeleteLocalRef(traffic); } - txTrafficInfoMapLock.unlock(); + txTrafficInfoMapLock.unlock_shared(); } env->DeleteLocalRef(mapClass); return jHashMap; @@ -271,16 +280,18 @@ jobject TrafficCollector::getTrafficInfoMap(int type) { void TrafficCollector::clearTrafficInfo() { rxTrafficInfoMapLock.lock(); - rxTrafficInfoMap.clear(); + rxFdTrafficInfoMap.clear(); rxTrafficInfoMapLock.unlock(); txTrafficInfoMapLock.lock(); - txTrafficInfoMap.clear(); + txFdTrafficInfoMap.clear(); txTrafficInfoMapLock.unlock(); - fdThreadNameMapLock.lock(); + fdThreadNameMapSharedLock.lock(); fdThreadNameMap.clear(); - fdThreadNameMapLock.unlock(); + fdThreadNameMapSharedLock.unlock(); + + } TrafficCollector::~TrafficCollector() { diff --git a/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.h b/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.h index 276cf0572..e94dd7797 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.h +++ b/matrix/matrix-android/matrix-traffic/src/main/cpp/TrafficCollector.h @@ -31,8 +31,6 @@ #include #include -#define MSG_TYPE_CONNECT 1 - #define MSG_TYPE_READ 10 #define MSG_TYPE_RECV 11 #define MSG_TYPE_RECVFROM 12 @@ -53,25 +51,23 @@ using namespace std; namespace MatrixTraffic { class TrafficMsg { public: - TrafficMsg(const int _type, const int _fd, sa_family_t _sa_family, string _threadName, long _len) - : type(_type), fd(_fd), sa_family(_sa_family), threadName(_threadName), len(_len) { + TrafficMsg(const int _type, const int _fd, long _len) + : type(_type), fd(_fd), len(_len) { } int type; int fd; - sa_family_t sa_family; - string threadName; +// sa_family_t sa_family; +// string threadName; long len; }; class TrafficCollector { public : - static void startLoop(bool dumpStackTrace, bool lookupIpAddress); + static void startLoop(bool dumpStackTrace); static void stopLoop(); - static void enQueueConnect(int fd, sockaddr *addr, socklen_t __addr_length); - static void enQueueClose(int fd); static void enQueueTx(int type, int fd, size_t len); @@ -80,7 +76,7 @@ public : static void clearTrafficInfo(); - static jobject getTrafficInfoMap(int type); + static jobject getFdTrafficInfoMap(int type); virtual ~TrafficCollector(); }; diff --git a/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficConfig.java b/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficConfig.java index fcf375d7c..5f5c5960f 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficConfig.java +++ b/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficConfig.java @@ -4,11 +4,18 @@ import java.util.List; public class TrafficConfig { + public static final int STACK_TRACE_FILTER_MODE_FULL = 0; + public static final int STACK_TRACE_FILTER_MODE_STARTS_WITH = 1; + public static final int STACK_TRACE_FILTER_MODE_PATTERN = 2; private boolean rxCollectorEnable; private boolean txCollectorEnable; private boolean dumpStackTraceEnable; private boolean dumpNativeBackTraceEnable; - private boolean lookupIpAddressEnable; + private boolean hookAllSoReadWrite = true; + //TODO + //private boolean lookupIpAddressEnable; + private int stackTraceFilterMode = 0; + private String stackTraceFilterCore; private List ignoreSoList = new ArrayList<>(); public TrafficConfig() { @@ -20,15 +27,13 @@ public TrafficConfig(boolean rxCollectorEnable, boolean txCollectorEnable, boole this.txCollectorEnable = txCollectorEnable; this.dumpStackTraceEnable = dumpStackTraceEnable; this.dumpNativeBackTraceEnable = false; - this.lookupIpAddressEnable = false; } - public TrafficConfig(boolean rxCollectorEnable, boolean txCollectorEnable, boolean dumpStackTraceEnable, boolean dumpNativeBackTraceEnable, boolean lookupIpAddressEnable) { + public TrafficConfig(boolean rxCollectorEnable, boolean txCollectorEnable, boolean dumpStackTraceEnable, boolean dumpNativeBackTraceEnable) { this.rxCollectorEnable = rxCollectorEnable; this.txCollectorEnable = txCollectorEnable; this.dumpStackTraceEnable = dumpStackTraceEnable; this.dumpNativeBackTraceEnable = dumpNativeBackTraceEnable; - this.lookupIpAddressEnable = lookupIpAddressEnable; } public boolean isRxCollectorEnable() { @@ -61,14 +66,6 @@ public void setDumpNativeBackTrace(boolean dumpNativeBackTraceEnable) { this.dumpNativeBackTraceEnable = dumpNativeBackTraceEnable; } - public boolean willLookupIpAddress() { - return lookupIpAddressEnable; - } - - public void setLookupIpAddressEnable(boolean lookupIpAddressEnable) { - this.lookupIpAddressEnable = lookupIpAddressEnable; - } - public void addIgnoreSoFile(String soName) { ignoreSoList.add(soName); } @@ -76,4 +73,26 @@ public void addIgnoreSoFile(String soName) { public String[] getIgnoreSoFiles() { return ignoreSoList.toArray(new String[ignoreSoList.size()]); } + + public void setStackTraceFilterMode(int mode, String filterCore) { + this.stackTraceFilterMode = mode; + this.stackTraceFilterCore = filterCore; + } + + public int getStackTraceFilterMode() { + return this.stackTraceFilterMode; + } + + public String getStackTraceFilterCore() { + return this.stackTraceFilterCore; + } + + public boolean willHookAllSoReadWrite() { + return hookAllSoReadWrite; + } + + public void setHookAllSoReadWrite(boolean hookAllSoReadWrite) { + this.hookAllSoReadWrite = hookAllSoReadWrite; + } + } diff --git a/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficPlugin.java b/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficPlugin.java index c6926159f..9791731ef 100644 --- a/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficPlugin.java +++ b/matrix/matrix-android/matrix-traffic/src/main/java/com/tencent/matrix/traffic/TrafficPlugin.java @@ -4,8 +4,10 @@ import com.tencent.matrix.plugin.Plugin; import com.tencent.matrix.util.MatrixLog; +import com.tencent.matrix.util.MatrixUtil; import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class TrafficPlugin extends Plugin { @@ -15,9 +17,12 @@ public class TrafficPlugin extends Plugin { public static final int TYPE_GET_TRAFFIC_RX = 0; public static final int TYPE_GET_TRAFFIC_TX = 1; - private static final ConcurrentHashMap stackTraceMap = new ConcurrentHashMap<>(); + private static int stackTraceFilterMode = 0; + private static String stackTraceFilterCore = ""; + private static final Map hashStackTraceMap = new ConcurrentHashMap<>(); + private static final Map keyHashMap = new ConcurrentHashMap<>(); - //TODO will be done in next upgrade + //TODO //public static final int TYPE_GET_TRAFFIC_ALL = 2; static { @@ -36,7 +41,9 @@ public void start() { super.start(); MatrixLog.i(TAG, "start"); String[] ignoreSoFiles = trafficConfig.getIgnoreSoFiles(); - nativeInitMatrixTraffic(trafficConfig.isRxCollectorEnable(), trafficConfig.isTxCollectorEnable(), trafficConfig.willDumpStackTrace(), trafficConfig.willDumpNativeBackTrace(), trafficConfig.willLookupIpAddress(), ignoreSoFiles); + stackTraceFilterMode = trafficConfig.getStackTraceFilterMode(); + stackTraceFilterCore = trafficConfig.getStackTraceFilterCore(); + nativeInitMatrixTraffic(trafficConfig.isRxCollectorEnable(), trafficConfig.isTxCollectorEnable(), trafficConfig.willDumpStackTrace(), trafficConfig.willDumpNativeBackTrace(), trafficConfig.willHookAllSoReadWrite(), ignoreSoFiles); } @@ -54,35 +61,68 @@ public HashMap getTrafficInfoMap(int type) { return nativeGetTrafficInfoMap(type); } - public ConcurrentHashMap getStackTraceMap() { - return stackTraceMap; + public String getStackTraceByMd5(String md5) { + return hashStackTraceMap.get(md5); + } + + public String getJavaStackTraceByKey(String key) { + if (!trafficConfig.willDumpStackTrace()) { + return ""; + } + String md5 = keyHashMap.get(key); + if (md5 == null || md5.isEmpty()) { + return ""; + } + return hashStackTraceMap.get(md5); + } + public String getNativeBackTraceByKey(String key) { + if (!trafficConfig.willDumpNativeBackTrace()) { + return ""; + } + return nativeGetNativeBackTraceByKey(key); } public void clearTrafficInfo() { - stackTraceMap.clear(); + keyHashMap.clear(); + hashStackTraceMap.clear(); nativeClearTrafficInfo(); } - private static native void nativeInitMatrixTraffic(boolean rxEnable, boolean txEnable, boolean dumpStackTrace, boolean dumpNativeBackTrace, boolean lookupIpAddress, String[] ignoreSoFiles); + private static native void nativeInitMatrixTraffic(boolean rxEnable, boolean txEnable, boolean dumpStackTrace, boolean dumpNativeBackTrace, boolean willHookAllSoReadWrite, String[] ignoreSoFiles); private static native String nativeGetTrafficInfo(); private static native String nativeGetAllStackTraceTrafficInfo(); private static native void nativeReleaseMatrixTraffic(); private static native void nativeClearTrafficInfo(); private static native HashMap nativeGetTrafficInfoMap(int type); + private static native String nativeGetNativeBackTraceByKey(String key); @Keep - private static void setStackTrace(String threadName, String nativeBackTrace) { - MatrixLog.i(TAG, "setStackTrace, threadName = " + threadName, "nativeBackTrace = " + nativeBackTrace); - StringBuilder stackTrace = new StringBuilder(nativeBackTrace); + private static void setFdStackTrace(String key) { + StringBuilder stackTrace = new StringBuilder(); StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for (int line = 3; line < stackTraceElements.length; line++) { - stackTrace.append(stackTraceElements[line]).append("\n"); - } - - stackTraceMap.put(threadName, stackTrace.toString()); - } + for (int line = 0; line < stackTraceElements.length; line++) { + String stackTraceLine = stackTraceElements[line].toString(); + boolean willAppend = false; + if (stackTraceFilterMode == TrafficConfig.STACK_TRACE_FILTER_MODE_FULL) { + willAppend = true; + } else if (stackTraceFilterMode == TrafficConfig.STACK_TRACE_FILTER_MODE_STARTS_WITH) { + if (stackTraceLine.startsWith(stackTraceFilterCore)) { + willAppend = true; + } + } else if (stackTraceFilterMode == TrafficConfig.STACK_TRACE_FILTER_MODE_PATTERN) { + if (stackTraceLine.matches(stackTraceFilterCore)) { + willAppend = true; + } + } + if (willAppend) { + stackTrace.append(stackTraceLine).append("\n"); + } - public void clearStackTrace() { - stackTraceMap.clear(); + } + String md5 = MatrixUtil.getMD5String(stackTrace.toString()); + if (!hashStackTraceMap.containsKey(md5)) { + hashStackTraceMap.put(md5, stackTrace.toString()); + } + keyHashMap.put(key, md5); } } diff --git a/matrix/matrix-android/settings.gradle b/matrix/matrix-android/settings.gradle index 3204b7166..f4e73e4fc 100644 --- a/matrix/matrix-android/settings.gradle +++ b/matrix/matrix-android/settings.gradle @@ -24,7 +24,7 @@ include ':matrix-traffic' include ':matrix-backtrace' include ':matrix-hooks' include ':matrix-memguard' -include ':matrix-jectl' +include ':matrix-mallctl' include ':matrix-fd' // Benchmark diff --git a/matrix/matrix-android/test/test-openglhook/src/main/java/com/example/openglhook/OpenglHookTestActivity.java b/matrix/matrix-android/test/test-openglhook/src/main/java/com/example/openglhook/OpenglHookTestActivity.java index 01e56a94f..6a003cce0 100644 --- a/matrix/matrix-android/test/test-openglhook/src/main/java/com/example/openglhook/OpenglHookTestActivity.java +++ b/matrix/matrix-android/test/test-openglhook/src/main/java/com/example/openglhook/OpenglHookTestActivity.java @@ -9,6 +9,7 @@ import android.app.Application; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.opengl.EGLContext; import android.opengl.GLES20; import android.opengl.GLUtils; import android.os.Bundle; @@ -26,9 +27,13 @@ import com.tencent.matrix.openglleak.statistics.resource.OpenGLInfo; import com.tencent.matrix.openglleak.statistics.resource.ResRecordManager; import com.tencent.matrix.openglleak.utils.EGLHelper; +import com.tencent.matrix.util.MatrixLog; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -334,4 +339,89 @@ public void onLeak(OpenGLInfo info) { // }); } + EGLContext initContext = null; + + public void testGLSharedContext(View view) { + + HandlerThread thread1 = new HandlerThread("thread1"); + thread1.start(); + Handler handler1 = new Handler(thread1.getLooper()); + + HandlerThread thread2 = new HandlerThread("thread2"); + thread2.start(); + final Handler handler2 = new Handler(thread2.getLooper()); + + + final int totalCount = 1; + final int[] textures = new int[totalCount]; + final int[] buffers = new int[totalCount]; + final int[] renderBuffers = new int[totalCount]; + final int[] frameBuffers = new int[totalCount]; + + handler1.post(new Runnable() { + @Override + public void run() { + initContext = EGLHelper.initOpenGL(); + MatrixLog.i(TAG, "init Context = %s", initContext.getNativeHandle()); + for (int i = 0; i < totalCount; i++) { + GLES20.glGenRenderbuffers(1, renderBuffers, i); + GLES20.glGenTextures(1, textures, i); + GLES20.glGenBuffers(1, buffers, i); + GLES20.glGenFramebuffers(1, frameBuffers, i); + } + + handler2.post(new Runnable() { + @Override + public void run() { + EGLContext sharedContext = EGLHelper.initOpenGLSharedContext(initContext); + MatrixLog.i(TAG, "shared Context = %s", sharedContext.getNativeHandle()); + for (int i = 0; i < totalCount; i++) { + GLES20.glDeleteRenderbuffers(1, renderBuffers, i); + GLES20.glDeleteTextures(1, textures, i); + GLES20.glDeleteBuffers(1, buffers, i); + GLES20.glDeleteFramebuffers(1, frameBuffers, i); + } + } + }); + } + }); + + handler1.postDelayed(new Runnable() { + @Override + public void run() { + MatrixLog.i(TAG, "dump..."); + dump(); + } + }, 5000); + } + + private void dump() { + final String dirPath = getApplication().getExternalCacheDir() + "/OpenGLHook"; + final String filePath = dirPath + "/" + Process.myPid() + "_opengl_dump.txt"; + File dirFile = new File(dirPath); + if (!dirFile.exists()) { + dirFile.mkdirs(); + } + File dumpFile = new File(filePath); + if (dumpFile.exists()) { + dumpFile.delete(); + } + try { + dumpFile.createNewFile(); + } catch (IOException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + + ResRecordManager.getInstance().dumpGLToFile(filePath); + + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)))){ + String line; + while ((line = br.readLine()) != null) { + MatrixLog.i(TAG, line); + } + } catch (IOException e) { + MatrixLog.printErrStackTrace(TAG, e, ""); + } + } + } \ No newline at end of file diff --git a/matrix/matrix-android/test/test-openglhook/src/main/res/layout/activity_opengl_leak_test.xml b/matrix/matrix-android/test/test-openglhook/src/main/res/layout/activity_opengl_leak_test.xml index 18ec6f950..6cba47ea4 100644 --- a/matrix/matrix-android/test/test-openglhook/src/main/res/layout/activity_opengl_leak_test.xml +++ b/matrix/matrix-android/test/test-openglhook/src/main/res/layout/activity_opengl_leak_test.xml @@ -63,5 +63,13 @@ android:layout_marginBottom="10dp" android:layout_height="50dp" /> +