Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,15 +31,14 @@ 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
import org.maplibre.android.testapp.utils.BenchmarkRunResult
import org.maplibre.android.testapp.utils.FrameTimeStore
import org.maplibre.android.testapp.utils.jsonPayload
import java.io.File
import java.util.ArrayList
import kotlin.collections.flatMap
import kotlin.collections.toTypedArray
import kotlin.coroutines.resume
Expand Down Expand Up @@ -88,6 +89,7 @@ suspend fun MapView.setStyleSuspend(styleUrl: String): Unit =
*/
class BenchmarkActivity : AppCompatActivity() {
private val TAG = "BenchmarkActivity"
private val useAdvancedMetrics = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any downside to enabling it by default?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting some error logs when calling Debug.getMemoryInfo that caused the fps to take a hit. I'm not sure if it's throttling the access, but it wasn't consistent on all test devices/emulators.


private lateinit var mapView: MapView
private var handler: Handler? = null
Expand Down Expand Up @@ -248,6 +250,9 @@ class BenchmarkActivity : AppCompatActivity() {

val encodingTimeStore = FrameTimeStore()
val renderingTimeStore = FrameTimeStore()
val metrics = if (useAdvancedMetrics) BenchmarkAdvancedMetrics() else null

metrics?.start()

maplibreMap.setSwapBehaviorFlush(benchmarkRun.syncRendering)

Expand All @@ -256,6 +261,7 @@ class BenchmarkActivity : AppCompatActivity() {
renderingTimeStore.add(stats.renderingTime * 1e3)
numFrames++;
}

mapView.addOnDidFinishRenderingFrameListener(listener)
mapView.setStyleSuspend(benchmarkRun.styleURL)
numFrames = 0
Expand All @@ -272,8 +278,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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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>,
Expand All @@ -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 (
Expand All @@ -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))
}
}
}
}
Expand Down Expand Up @@ -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()
}
}
}
2 changes: 2 additions & 0 deletions platform/ios/benchmark/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("//platform/ios/bazel:macros.bzl", "info_plist")
filegroup(
name = "ios_benchmark_srcs",
srcs = [
"BenchmarkAdvancedMetrics.mm",
"MBXBenchAppDelegate.mm",
"MBXBenchViewController.mm",
"locations.cpp",
Expand All @@ -14,6 +15,7 @@ filegroup(
filegroup(
name = "ios_benchmark_hdrs",
srcs = [
"BenchmarkAdvancedMetrics.h",
"MBXBenchAppDelegate.h",
"MBXBenchViewController.h",
"locations.hpp",
Expand Down
45 changes: 45 additions & 0 deletions platform/ios/benchmark/BenchmarkAdvancedMetrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#import <Foundation/Foundation.h>

struct BenchmarkSnapshot {
float cpu = 0.0f;
unsigned long long memory = 0;

void min(BenchmarkSnapshot s) {
if (cpu > s.cpu) {
cpu = s.cpu;
}

if (memory > s.memory) {
memory = s.memory;
}
}

void max(BenchmarkSnapshot s) {
if (cpu < s.cpu) {
cpu = s.cpu;
}

if (memory < s.memory) {
memory = s.memory;
}
}

void add(BenchmarkSnapshot s) {
cpu += s.cpu;
memory += s.memory;
}
};

@interface BenchmarkAdvancedMetrics : NSObject

@property (nonatomic, readonly) BenchmarkSnapshot min;
@property (nonatomic, readonly) BenchmarkSnapshot max;
@property (nonatomic, readonly) BenchmarkSnapshot avg;

- (void)start:(NSTimeInterval)collectInterval;
- (void)stop;

+ (float)getCpuUsage;
+ (unsigned long long)getMemoryUsage;

@end
Loading
Loading