diff --git a/README.md b/README.md
index 48329f31a..37488b0f5 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@

[](https://github.com/Tencent/matrix/blob/master/LICENSE)
[](https://github.com/Tencent/matrix/pulls)
-[](https://github.com/Tencent/matrix/wiki)
+[](https://github.com/Tencent/matrix/wiki)
[](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

-[](https://github.com/Tencent/matrix/blob/master/LICENSE)[](https://github.com/Tencent/matrix/pulls) [](https://github.com/Tencent/matrix/wiki)
+[](https://github.com/Tencent/matrix/blob/master/LICENSE)[](https://github.com/Tencent/matrix/pulls) [](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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends Snapshot>> 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 extends AbsTaskMonitorFeature> featClass) {
- AbsTaskMonitorFeature taskFeat = getFeature(featClass);
- if (taskFeat != null) {
- List> deltas = taskFeat.currentJiffies();
- taskFeat.clearFinishedJiffies();
- putTaskDeltas(featClass, deltas);
+ protected void configureTaskDeltas(final Class extends AbsTaskMonitorFeature> 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 extends AbsTaskMonitorFeature> 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 extends AbsTaskMonitorFeature> key, List> deltas) {
+ public void putTaskDeltas(Class extends AbsTaskMonitorFeature> key, List> deltas) {
mTaskDeltas.put(key, deltas);
}
- public List> getTaskDeltas(Class extends AbsTaskMonitorFeature> key) {
- List> deltas = mTaskDeltas.get(key);
+ public List> getTaskDeltas(Class extends AbsTaskMonitorFeature> key) {
+ List> deltas = mTaskDeltas.get(key);
if (deltas == null) {
return Collections.emptyList();
}
return deltas;
}
- public void getTaskDeltas(Class extends AbsTaskMonitorFeature> key, Consumer>> block) {
- List> deltas = mTaskDeltas.get(key);
+ public void getTaskDeltas(Class extends AbsTaskMonitorFeature> 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