mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Improve logging around memory usage.
This commit is contained in:
committed by
Clark Chen
parent
500ae0c72e
commit
5ca025544e
@@ -30,6 +30,7 @@ import com.google.android.gms.security.ProviderInstaller;
|
|||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
|
import org.signal.core.util.MemoryTracker;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.AndroidLogger;
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
@@ -237,6 +238,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
ApplicationDependencies.getShakeToReport().enable();
|
ApplicationDependencies.getShakeToReport().enable();
|
||||||
checkBuildExpiration();
|
checkBuildExpiration();
|
||||||
|
MemoryTracker.start();
|
||||||
|
|
||||||
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
|
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
@@ -260,6 +262,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
ApplicationDependencies.getFrameRateTracker().stop();
|
ApplicationDependencies.getFrameRateTracker().stop();
|
||||||
ApplicationDependencies.getShakeToReport().disable();
|
ApplicationDependencies.getShakeToReport().disable();
|
||||||
ApplicationDependencies.getDeadlockDetector().stop();
|
ApplicationDependencies.getDeadlockDetector().stop();
|
||||||
|
MemoryTracker.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersistentLogger getPersistentLogger() {
|
public PersistentLogger getPersistentLogger() {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.thoughtcrime.securesms.logsubmit
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import org.signal.core.util.MemoryTracker
|
||||||
|
import org.signal.core.util.bytes
|
||||||
|
import org.signal.core.util.kibiBytes
|
||||||
|
import org.signal.core.util.roundedString
|
||||||
|
|
||||||
|
class LogSectionMemory : LogSection {
|
||||||
|
override fun getTitle(): String = "MEMORY"
|
||||||
|
|
||||||
|
override fun getContent(context: Context): CharSequence {
|
||||||
|
val appMemory = MemoryTracker.getAppJvmHeapUsage()
|
||||||
|
val nativeMemory = MemoryTracker.getSystemNativeMemoryUsage(context)
|
||||||
|
var base = """
|
||||||
|
-- App JVM Heap
|
||||||
|
Used : ${appMemory.usedBytes.byteDisplay()}
|
||||||
|
Free : ${appMemory.freeBytes.byteDisplay()}
|
||||||
|
Current Total: ${appMemory.currentTotalBytes.byteDisplay()}
|
||||||
|
Max Possible : ${appMemory.maxPossibleBytes.byteDisplay()}
|
||||||
|
|
||||||
|
-- System Native Memory
|
||||||
|
Used : ${nativeMemory.usedBytes.byteDisplay()}
|
||||||
|
Free : ${nativeMemory.freeBytes.byteDisplay()}
|
||||||
|
Total : ${nativeMemory.totalBytes.byteDisplay()}
|
||||||
|
Low Memory Threshold: ${nativeMemory.lowMemoryThreshold.byteDisplay()}
|
||||||
|
Low Memory? : ${nativeMemory.lowMemory}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
val detailedMemory = MemoryTracker.getDetailedMemoryStats()
|
||||||
|
|
||||||
|
base += "\n\n"
|
||||||
|
base += """
|
||||||
|
-- Detailed Memory (API 23+)
|
||||||
|
App JVM Heap Usage : ${detailedMemory.appJavaHeapUsageKb?.kbDisplay()}
|
||||||
|
App Native Heap Usage: ${detailedMemory.appNativeHeapUsageKb?.kbDisplay()}
|
||||||
|
Code Usage : ${detailedMemory.codeUsageKb?.kbDisplay()}
|
||||||
|
Graphics Usage : ${detailedMemory.graphicsUsageKb?.kbDisplay()}
|
||||||
|
Stack Usage : ${detailedMemory.stackUsageKb?.kbDisplay()}
|
||||||
|
Other Usage : ${detailedMemory.appOtherUsageKb?.kbDisplay()}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Long.byteDisplay(): String {
|
||||||
|
return "$this bytes (${bytes.inMebiBytes.roundedString(2)} MiB)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Long.kbDisplay(): String {
|
||||||
|
return "$this KiB (${kibiBytes.inMebiBytes.roundedString(2)} MiB)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,7 @@ public class SubmitDebugLogRepository {
|
|||||||
add(new LogSectionJobs());
|
add(new LogSectionJobs());
|
||||||
add(new LogSectionConstraints());
|
add(new LogSectionConstraints());
|
||||||
add(new LogSectionCapabilities());
|
add(new LogSectionCapabilities());
|
||||||
|
add(new LogSectionMemory());
|
||||||
add(new LogSectionLocalMetrics());
|
add(new LogSectionLocalMetrics());
|
||||||
add(new LogSectionFeatureFlags());
|
add(new LogSectionFeatureFlags());
|
||||||
add(new LogSectionPin());
|
add(new LogSectionPin());
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.core.util
|
||||||
|
|
||||||
|
inline val Long.bytes: ByteSize
|
||||||
|
get() = ByteSize(this)
|
||||||
|
|
||||||
|
inline val Long.kibiBytes: ByteSize
|
||||||
|
get() = (this * 1024).bytes
|
||||||
|
|
||||||
|
inline val Long.mebiBytes: ByteSize
|
||||||
|
get() = (this * 1024).kibiBytes
|
||||||
|
|
||||||
|
inline val Long.gibiBytes: ByteSize
|
||||||
|
get() = (this * 1024).mebiBytes
|
||||||
|
|
||||||
|
class ByteSize(val bytes: Long) {
|
||||||
|
val inWholeBytes: Long
|
||||||
|
get() = bytes
|
||||||
|
|
||||||
|
val inWholeKibiBytes: Long
|
||||||
|
get() = bytes / 1024
|
||||||
|
|
||||||
|
val inWholeMebiBytes: Long
|
||||||
|
get() = inWholeKibiBytes / 1024
|
||||||
|
|
||||||
|
val inWholeGibiBytes: Long
|
||||||
|
get() = inWholeMebiBytes / 1024
|
||||||
|
|
||||||
|
val inKibiBytes: Float
|
||||||
|
get() = bytes / 1024f
|
||||||
|
|
||||||
|
val inMebiBytes: Float
|
||||||
|
get() = inKibiBytes / 1024f
|
||||||
|
|
||||||
|
val inGibiBytes: Float
|
||||||
|
get() = inMebiBytes / 1024f
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.core.util
|
||||||
|
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds a number to the specififed number of places. e.g.
|
||||||
|
*
|
||||||
|
* 1.123456f.roundedString(2) = 1.12
|
||||||
|
* 1.123456f.roundedString(5) = 1.12346
|
||||||
|
*/
|
||||||
|
fun Float.roundedString(places: Int): String {
|
||||||
|
val powerMultiplier = 10f.pow(places)
|
||||||
|
return (round(this * powerMultiplier) / powerMultiplier).toString()
|
||||||
|
}
|
||||||
153
core-util/src/main/java/org/signal/core/util/MemoryTracker.kt
Normal file
153
core-util/src/main/java/org/signal/core/util/MemoryTracker.kt
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.core.util
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Debug
|
||||||
|
import android.os.Handler
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
object MemoryTracker {
|
||||||
|
|
||||||
|
private val TAG = Log.tag(MemoryTracker::class.java)
|
||||||
|
|
||||||
|
private val runtime: Runtime = Runtime.getRuntime()
|
||||||
|
private val activityMemoryInfo: ActivityManager.MemoryInfo = ActivityManager.MemoryInfo()
|
||||||
|
private val debugMemoryInfo: Debug.MemoryInfo = Debug.MemoryInfo()
|
||||||
|
private val handler: Handler = Handler(SignalExecutors.getAndStartHandlerThread("MemoryTracker", ThreadUtil.PRIORITY_BACKGROUND_THREAD).looper)
|
||||||
|
private val POLLING_INTERVAL = 5.seconds.inWholeMilliseconds
|
||||||
|
|
||||||
|
private var running = false
|
||||||
|
private lateinit var previousAppHeadUsage: AppHeapUsage
|
||||||
|
private var increaseMemoryCount = 0
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun start() {
|
||||||
|
Log.d(TAG, "Beginning memory monitoring.")
|
||||||
|
running = true
|
||||||
|
previousAppHeadUsage = getAppJvmHeapUsage()
|
||||||
|
increaseMemoryCount = 0
|
||||||
|
handler.postDelayed(this::poll, POLLING_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun stop() {
|
||||||
|
Log.d(TAG, "Ending memory monitoring.")
|
||||||
|
running = false
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun poll() {
|
||||||
|
val currentHeapUsage = getAppJvmHeapUsage()
|
||||||
|
|
||||||
|
if (currentHeapUsage.currentTotalBytes != previousAppHeadUsage.currentTotalBytes) {
|
||||||
|
if (currentHeapUsage.currentTotalBytes > previousAppHeadUsage.currentTotalBytes) {
|
||||||
|
Log.d(TAG, "The system increased our app JVM heap from ${previousAppHeadUsage.currentTotalBytes.byteDisplay()} to ${currentHeapUsage.currentTotalBytes.byteDisplay()}")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "The system decreased our app JVM heap from ${previousAppHeadUsage.currentTotalBytes.byteDisplay()} to ${currentHeapUsage.currentTotalBytes.byteDisplay()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHeapUsage.usedBytes >= previousAppHeadUsage.usedBytes) {
|
||||||
|
increaseMemoryCount++
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Used memory has decreased from ${previousAppHeadUsage.usedBytes.byteDisplay()} to ${currentHeapUsage.usedBytes.byteDisplay()}")
|
||||||
|
increaseMemoryCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (increaseMemoryCount > 0 && increaseMemoryCount % 5 == 0) {
|
||||||
|
Log.d(TAG, "Used memory has increased or stayed the same for the last $increaseMemoryCount intervals (${increaseMemoryCount * POLLING_INTERVAL.milliseconds.inWholeSeconds} seconds). Using: ${currentHeapUsage.usedBytes.byteDisplay()}, Free: ${currentHeapUsage.freeBytes.byteDisplay()}, CurrentTotal: ${currentHeapUsage.currentTotalBytes.byteDisplay()}, MaxPossible: ${currentHeapUsage.maxPossibleBytes.byteDisplay()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
previousAppHeadUsage = currentHeapUsage
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
handler.postDelayed(this::poll, POLLING_INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives us basic memory usage data for our app JVM heap usage. Very fast, ~10 micros on an emulator.
|
||||||
|
*/
|
||||||
|
fun getAppJvmHeapUsage(): AppHeapUsage {
|
||||||
|
return AppHeapUsage(
|
||||||
|
freeBytes = runtime.freeMemory(),
|
||||||
|
currentTotalBytes = runtime.totalMemory(),
|
||||||
|
maxPossibleBytes = runtime.maxMemory()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This gives us details stats, but it takes an appreciable amount of time. On an emulator, it can take ~30ms.
|
||||||
|
* As a result, we don't want to be calling this regularly for most users.
|
||||||
|
*/
|
||||||
|
@RequiresApi(23)
|
||||||
|
fun getDetailedMemoryStats(): DetailedMemoryStats {
|
||||||
|
Debug.getMemoryInfo(debugMemoryInfo)
|
||||||
|
|
||||||
|
return DetailedMemoryStats(
|
||||||
|
appJavaHeapUsageKb = debugMemoryInfo.getMemoryStat("summary.java-heap")?.toLongOrNull(),
|
||||||
|
appNativeHeapUsageKb = debugMemoryInfo.getMemoryStat("summary.native-heap")?.toLongOrNull(),
|
||||||
|
codeUsageKb = debugMemoryInfo.getMemoryStat("summary.code")?.toLongOrNull(),
|
||||||
|
stackUsageKb = debugMemoryInfo.getMemoryStat("summary.stack")?.toLongOrNull(),
|
||||||
|
graphicsUsageKb = debugMemoryInfo.getMemoryStat("summary.graphics")?.toLongOrNull(),
|
||||||
|
appOtherUsageKb = debugMemoryInfo.getMemoryStat("summary.private-other")?.toLongOrNull()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSystemNativeMemoryUsage(context: Context): NativeMemoryUsage {
|
||||||
|
val activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||||
|
activityManager.getMemoryInfo(activityMemoryInfo)
|
||||||
|
|
||||||
|
return NativeMemoryUsage(
|
||||||
|
freeBytes = activityMemoryInfo.availMem,
|
||||||
|
totalBytes = activityMemoryInfo.totalMem,
|
||||||
|
lowMemory = activityMemoryInfo.lowMemory,
|
||||||
|
lowMemoryThreshold = activityMemoryInfo.threshold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Long.byteDisplay(): String {
|
||||||
|
return "$this (${this.bytes.inMebiBytes.roundedString(2)} MiB)"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AppHeapUsage(
|
||||||
|
/** The number of bytes that are free to use. */
|
||||||
|
val freeBytes: Long,
|
||||||
|
/** The current total number of bytes our app could use. This can increase over time as the system increases our allocation. */
|
||||||
|
val currentTotalBytes: Long,
|
||||||
|
/** The maximum number of bytes that our app could ever be given. */
|
||||||
|
val maxPossibleBytes: Long
|
||||||
|
) {
|
||||||
|
/** The number of bytes that our app is currently using. */
|
||||||
|
val usedBytes: Long
|
||||||
|
get() = currentTotalBytes - freeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
data class NativeMemoryUsage(
|
||||||
|
val freeBytes: Long,
|
||||||
|
val totalBytes: Long,
|
||||||
|
val lowMemory: Boolean,
|
||||||
|
val lowMemoryThreshold: Long
|
||||||
|
) {
|
||||||
|
val usedBytes: Long
|
||||||
|
get() = totalBytes - freeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DetailedMemoryStats(
|
||||||
|
val appJavaHeapUsageKb: Long?,
|
||||||
|
val appNativeHeapUsageKb: Long?,
|
||||||
|
val codeUsageKb: Long?,
|
||||||
|
val graphicsUsageKb: Long?,
|
||||||
|
val stackUsageKb: Long?,
|
||||||
|
val appOtherUsageKb: Long?
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user