Benchmark cpu/memory usage (#3697)
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user