Benchmark cpu/memory usage (#3697)

This commit is contained in:
Adrian Cojocaru
2026-04-22 19:14:46 +03:00
committed by GitHub
parent edf6cd36e0
commit ae818ca6f2
6 changed files with 373 additions and 5 deletions

View File

@@ -2,9 +2,11 @@ package org.maplibre.android.testapp.activity.benchmark
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.os.Debug
import android.os.Environment
import android.os.Handler
import android.os.PowerManager
@@ -29,7 +31,7 @@ import org.maplibre.android.maps.MapLibreMap.CancelableCallback
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.RenderingStats
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
import org.maplibre.android.testapp.utils.BenchmarkAdvancedMetrics
import org.maplibre.android.testapp.utils.BenchmarkInputData
import org.maplibre.android.testapp.utils.BenchmarkResult
import org.maplibre.android.testapp.utils.BenchmarkRun
@@ -39,7 +41,6 @@ import org.maplibre.android.testapp.utils.animateCameraSuspend
import org.maplibre.android.testapp.utils.jsonPayload
import org.maplibre.android.testapp.utils.setStyleSuspend
import java.io.File
import java.util.ArrayList
import kotlin.collections.flatMap
import kotlin.collections.toTypedArray
import kotlin.coroutines.resume
@@ -49,6 +50,7 @@ import kotlin.coroutines.resume
*/
class BenchmarkActivity : AppCompatActivity() {
private val TAG = "BenchmarkActivity"
private val useAdvancedMetrics = false
private lateinit var mapView: MapView
private var handler: Handler? = null
@@ -209,6 +211,9 @@ class BenchmarkActivity : AppCompatActivity() {
val encodingTimeStore = FrameTimeStore()
val renderingTimeStore = FrameTimeStore()
val metrics = if (useAdvancedMetrics) BenchmarkAdvancedMetrics() else null
metrics?.start()
maplibreMap.setSwapBehaviorFlush(benchmarkRun.syncRendering)
@@ -217,6 +222,7 @@ class BenchmarkActivity : AppCompatActivity() {
renderingTimeStore.add(stats.renderingTime * 1e3)
numFrames++;
}
mapView.addOnDidFinishRenderingFrameListener(listener)
mapView.setStyleSuspend(benchmarkRun.styleURL)
numFrames = 0
@@ -233,8 +239,9 @@ class BenchmarkActivity : AppCompatActivity() {
val fps = (numFrames * 1E9) / (endTime - startTime)
mapView.removeOnDidFinishRenderingFrameListener(listener)
metrics?.stop()
return BenchmarkRunResult(fps, encodingTimeStore, renderingTimeStore, getThermalStatus())
return BenchmarkRunResult(fps, encodingTimeStore, renderingTimeStore, getThermalStatus(), metrics)
}
override fun onStart() {

View File

@@ -1,15 +1,23 @@
package org.maplibre.android.testapp.utils
import android.app.ActivityManager
import android.content.Context
import android.net.TrafficStats
import android.os.Build
import android.os.Debug
import android.os.StrictMode
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.putJsonArray
import org.maplibre.android.BuildConfig.GIT_REVISION
import org.maplibre.android.testapp.BuildConfig
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.ArrayList
import java.util.Timer
import kotlin.concurrent.fixedRateTimer
data class BenchmarkInputData(
val styleNames: List<String>,
@@ -32,7 +40,8 @@ data class BenchmarkRunResult(
val fps: Double,
val encodingTimeStore: FrameTimeStore,
val renderingTimeStore: FrameTimeStore,
val thermalState: Int
val thermalState: Int,
val advancedMetrics: BenchmarkAdvancedMetrics?
)
data class BenchmarkResult (
@@ -53,6 +62,24 @@ fun jsonPayload(benchmarkResult: BenchmarkResult): JsonObject {
put("avgRenderingTime", JsonPrimitive(run.second.renderingTimeStore.average()))
put("low1pEncodingTime", JsonPrimitive(run.second.encodingTimeStore.low1p()))
put("low1pRenderingTime", JsonPrimitive(run.second.renderingTimeStore.low1p()))
run.second.advancedMetrics?.let { metrics ->
put("cpu", buildJsonObject {
put("min", JsonPrimitive(metrics.min.cpu.value))
put("max", JsonPrimitive(metrics.max.cpu.value))
put("avg", JsonPrimitive(metrics.avg.cpu.value))
})
put("memory", buildJsonObject {
// convert to MB
put("min", JsonPrimitive(metrics.min.memory.value / 1024) )
put("max", JsonPrimitive(metrics.max.memory.value / 1024))
put("avg", JsonPrimitive(metrics.avg.memory.value / 1024))
})
// convert to MB
put("traffic", JsonPrimitive(metrics.traffic / 1024))
}
}
}
}
@@ -85,3 +112,173 @@ class FrameTimeStore {
return timeValues.average()
}
}
class BenchmarkAdvancedMetrics {
class Metric<T : Comparable<T>>(var value: T) {
fun min(newValue: Metric<T>) {
if (value > newValue.value) {
value = newValue.value
}
}
fun max(newValue: Metric<T>) {
if (value < newValue.value) {
value = newValue.value
}
}
@Suppress("UNCHECKED_CAST")
operator fun plusAssign(newValue: Metric<T>) {
when (value) {
is Int -> value = ((value as Int) + (newValue.value as Int)) as T
is Long -> value = ((value as Long) + (newValue.value as Long)) as T
is Float -> value = ((value as Float) + (newValue.value as Float)) as T
is Double -> value = ((value as Double) + (newValue.value as Double)) as T
}
}
@Suppress("UNCHECKED_CAST")
operator fun div(newValue: Int): Metric<T> {
return when (value) {
is Int -> Metric(((value as Int) / newValue) as T)
is Long -> Metric(((value as Long) / newValue) as T)
is Float -> Metric(((value as Float) / newValue) as T)
is Double -> Metric(((value as Double) / newValue) as T)
else -> this
}
}
}
class Snapshot (
var cpu: Metric<Float>,
var memory: Metric<Long>,
) {
fun min(snapshot: Snapshot) {
cpu.min(snapshot.cpu)
memory.min(snapshot.memory)
}
fun max(snapshot: Snapshot) {
cpu.max(snapshot.cpu)
memory.max(snapshot.memory)
}
operator fun plusAssign(snapshot: Snapshot) {
cpu += snapshot.cpu
memory += snapshot.memory
}
operator fun div(value: Int): Snapshot {
return Snapshot(
cpu / value,
memory / value,
)
}
}
private var startTime = 0L
private var stopTime = 0L
private var trafficStart = 0L
private var trafficStop = 0L
private var timer: Timer? = null
public var min = Snapshot(Metric(Float.MAX_VALUE), Metric(Long.MAX_VALUE))
public var max = Snapshot(Metric(Float.MIN_VALUE), Metric(Long.MIN_VALUE))
// track total/avg or keep values for median/low/high?
public var total = Snapshot(Metric(0.0f), Metric(0))
public var frameCount = 0
public val avg: Snapshot get() { return total / frameCount }
public val time: Long get() { return stopTime - startTime }
public val traffic: Long get() { return trafficStop - trafficStart }
public val enabled: Boolean get() { return time > 0.0 }
@Synchronized public fun start(collectInterval: Long = 1000L) {
reset()
startTime = System.currentTimeMillis()
trafficStart = TrafficStats.getTotalRxBytes()
timer = fixedRateTimer(
name = "BenchmarkAdvancedMetrics",
period = collectInterval
) {
collect()
}
}
@Synchronized public fun stop() {
timer?.cancel()
timer = null
stopTime = System.currentTimeMillis()
trafficStop = TrafficStats.getTotalRxBytes()
}
private fun collect() {
val snapshot = Snapshot(Metric(getCPU()), Metric(getMemory()))
min.min(snapshot)
max.max(snapshot)
total += snapshot
++frameCount
}
private fun reset() {
min = Snapshot(Metric(Float.MAX_VALUE), Metric(Long.MAX_VALUE))
max = Snapshot(Metric(Float.MIN_VALUE), Metric(Long.MIN_VALUE))
total = Snapshot(Metric(0.0f), Metric(0))
frameCount = 0
}
public fun toJson(): JsonObject {
return buildJsonObject {
put("cpu", buildJsonObject {
put("min", JsonPrimitive(min.cpu.value))
put("max", JsonPrimitive(max.cpu.value))
put("avg", JsonPrimitive(avg.cpu.value))
})
put("memory", buildJsonObject {
put("min", JsonPrimitive(min.memory.value))
put("max", JsonPrimitive(max.memory.value))
put("avg", JsonPrimitive(avg.memory.value))
})
put("traffic", JsonPrimitive(traffic))
}
}
companion object {
public fun getCPU(): Float {
val currentPolicy = StrictMode.allowThreadDiskReads()
try {
val pid = android.os.Process.myPid().toString()
val cores = Runtime.getRuntime().availableProcessors()
val process = Runtime.getRuntime().exec("top -n 1 -o PID,%CPU")
val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
var line = bufferedReader.readLine()
while (line != null) {
if (line.contains(pid)) {
val rawCpu = line.split(" ").last().toFloat()
return rawCpu / cores
}
line = bufferedReader.readLine()
}
} catch (e: Exception) {
return 0.0f
} finally {
StrictMode.setThreadPolicy(currentPolicy)
}
return 0.0f
}
public fun getMemory(): Long {
val debugMemInfo = Debug.MemoryInfo()
Debug.getMemoryInfo(debugMemInfo)
return debugMemInfo.totalPss.toLong()
}
}
}