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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -14,6 +15,7 @@ filegroup(
|
||||
filegroup(
|
||||
name = "ios_benchmark_hdrs",
|
||||
srcs = [
|
||||
"BenchmarkAdvancedMetrics.h",
|
||||
"MBXBenchAppDelegate.h",
|
||||
"MBXBenchViewController.h",
|
||||
"locations.hpp",
|
||||
|
||||
45
platform/ios/benchmark/BenchmarkAdvancedMetrics.h
Normal file
45
platform/ios/benchmark/BenchmarkAdvancedMetrics.h
Normal file
@@ -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
|
||||
100
platform/ios/benchmark/BenchmarkAdvancedMetrics.mm
Normal file
100
platform/ios/benchmark/BenchmarkAdvancedMetrics.mm
Normal file
@@ -0,0 +1,100 @@
|
||||
#import "BenchmarkAdvancedMetrics.h"
|
||||
#import <mach/mach.h>
|
||||
|
||||
@implementation BenchmarkAdvancedMetrics {
|
||||
NSTimer* timer;
|
||||
unsigned int snapshotCount;
|
||||
BenchmarkSnapshot total;
|
||||
}
|
||||
|
||||
- (BenchmarkSnapshot)avg {
|
||||
BenchmarkSnapshot avg;
|
||||
|
||||
avg.cpu = total.cpu / snapshotCount;
|
||||
avg.memory = total.memory / snapshotCount;
|
||||
|
||||
return avg;
|
||||
}
|
||||
|
||||
- (void)start:(NSTimeInterval)collectInterval {
|
||||
if (timer) {
|
||||
[self stop];
|
||||
}
|
||||
|
||||
[self reset];
|
||||
|
||||
timer = [NSTimer scheduledTimerWithTimeInterval:collectInterval
|
||||
repeats:YES
|
||||
block:^(NSTimer* _Nonnull timer) {
|
||||
[self collect];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (timer) {
|
||||
[timer invalidate];
|
||||
timer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collect {
|
||||
BenchmarkSnapshot snapshot{[BenchmarkAdvancedMetrics getCpuUsage],
|
||||
[BenchmarkAdvancedMetrics getMemoryUsage]};
|
||||
|
||||
_min.min(snapshot);
|
||||
_max.max(snapshot);
|
||||
total.add(snapshot);
|
||||
|
||||
++snapshotCount;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_min = {FLT_MAX, ULLONG_MAX};
|
||||
_max = {-FLT_MAX, 0ull};
|
||||
total = {0.0f, 0ull};
|
||||
|
||||
snapshotCount = 0;
|
||||
}
|
||||
|
||||
+ (unsigned long long)getMemoryUsage {
|
||||
mach_task_basic_info_data_t info;
|
||||
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
|
||||
|
||||
kern_return_t result =
|
||||
task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count);
|
||||
return result == KERN_SUCCESS ? info.resident_size : 0ull;
|
||||
}
|
||||
|
||||
+ (float)getCpuUsage {
|
||||
mach_port_t* threadList = NULL;
|
||||
mach_msg_type_number_t threadCount = 0;
|
||||
thread_basic_info_data_t threadInfo;
|
||||
|
||||
kern_return_t result = task_threads(mach_task_self(), &threadList, &threadCount);
|
||||
if (result != KERN_SUCCESS) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float cpuUsage = 0.0f;
|
||||
|
||||
for (unsigned int i = 0; i < threadCount; ++i) {
|
||||
thread_info_data_t threadInfoData;
|
||||
mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
|
||||
|
||||
result = thread_info(threadList[i], THREAD_BASIC_INFO, threadInfoData, &threadInfoCount);
|
||||
if (result != KERN_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
thread_basic_info_t basicInfo = (thread_basic_info_t)threadInfoData;
|
||||
if (basicInfo->flags & TH_FLAGS_IDLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cpuUsage += ((float)basicInfo->cpu_usage / TH_USAGE_SCALE) * 100.0f;
|
||||
}
|
||||
|
||||
return cpuUsage;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -4,6 +4,7 @@
|
||||
#import "MLNOfflineStorage_Private.h"
|
||||
#import "MLNSettings_Private.h"
|
||||
|
||||
#include "BenchmarkAdvancedMetrics.h"
|
||||
#include "locations.hpp"
|
||||
|
||||
#include <chrono>
|
||||
@@ -183,6 +184,7 @@ enum class State { None, WaitingForAssets, Benchmarking } state = State::None;
|
||||
int frames = 0;
|
||||
double totalFrameEncodingTime = 0;
|
||||
double totalFrameRenderingTime = 0;
|
||||
BenchmarkAdvancedMetrics *advancedMetrics = nil;
|
||||
std::chrono::steady_clock::time_point started;
|
||||
std::vector<std::pair<std::string, std::pair<double, double>>> result;
|
||||
|
||||
@@ -213,6 +215,11 @@ extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploa
|
||||
|
||||
- (void)startBenchmarkIteration {
|
||||
if (mbgl::bench::locations.size() > idx) {
|
||||
if (advancedMetrics == nil) {
|
||||
advancedMetrics = [[BenchmarkAdvancedMetrics alloc] init];
|
||||
[advancedMetrics start:1];
|
||||
}
|
||||
|
||||
const auto &location = mbgl::bench::locations[idx];
|
||||
|
||||
mbgl::CameraOptions cameraOptions;
|
||||
@@ -258,6 +265,16 @@ extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploa
|
||||
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
|
||||
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
|
||||
|
||||
[advancedMetrics stop];
|
||||
|
||||
NSLog(@"Cpu usage: (min %.2f%%) (max %.2f%%) (avg %.2f%%)", advancedMetrics.min.cpu,
|
||||
advancedMetrics.max.cpu, advancedMetrics.avg.cpu);
|
||||
NSLog(@"Memory usage: (min %llu MB) (max %llu MB) (avg %llu MB)",
|
||||
advancedMetrics.min.memory / 0x100000, advancedMetrics.max.memory / 0x100000,
|
||||
advancedMetrics.avg.memory / 0x100000);
|
||||
|
||||
advancedMetrics = nil;
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
|
||||
// TODO: This doesn't clean up everything, what are we missing?
|
||||
|
||||
Reference in New Issue
Block a user