Improve AppDataUsageDetailsRepository

Reduce latency by reduce the number of system calls.

Bug: 295260929
Test: manual - on AppDataUsage page
Test: unit test
Change-Id: If233e223db744cd15d3a769416fd4c5957085417
This commit is contained in:
Chaohui Wang
2023-10-14 20:42:51 +08:00
parent 2b045784b9
commit 342256475d
8 changed files with 86 additions and 72 deletions

View File

@@ -26,6 +26,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.core.BasePreferenceController import com.android.settings.core.BasePreferenceController
import com.android.settings.datausage.lib.INetworkCycleDataRepository import com.android.settings.datausage.lib.INetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkCycleChartData
import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkCycleDataRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -72,6 +73,7 @@ open class ChartDataUsagePreferenceController(context: Context, preferenceKey: S
fun update(startTime: Long, endTime: Long) { fun update(startTime: Long, endTime: Long) {
preference.setTime(startTime, endTime) preference.setTime(startTime, endTime)
preference.setNetworkCycleData(NetworkCycleChartData.AllZero)
lifecycleScope.launch { lifecycleScope.launch {
val chartData = withContext(Dispatchers.Default) { val chartData = withContext(Dispatchers.Default) {
repository.queryChartData(startTime, endTime) repository.queryChartData(startTime, endTime)

View File

@@ -20,11 +20,8 @@ import android.app.usage.NetworkStats
import android.content.Context import android.content.Context
import android.net.NetworkTemplate import android.net.NetworkTemplate
import android.util.Range import android.util.Range
import androidx.annotation.VisibleForTesting
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids
import kotlinx.coroutines.async import com.android.settingslib.spa.framework.util.asyncMap
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
interface IAppDataUsageDetailsRepository { interface IAppDataUsageDetailsRepository {
suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData>
@@ -37,32 +34,24 @@ class AppDataUsageDetailsRepository @JvmOverloads constructor(
uids: List<Int>, uids: List<Int>,
private val networkCycleDataRepository: INetworkCycleDataRepository = private val networkCycleDataRepository: INetworkCycleDataRepository =
NetworkCycleDataRepository(context, template), NetworkCycleDataRepository(context, template),
private val networkStatsRepository: NetworkStatsRepository =
NetworkStatsRepository(context, template),
) : IAppDataUsageDetailsRepository { ) : IAppDataUsageDetailsRepository {
private val withSdkSandboxUids = withSdkSandboxUids(uids) private val withSdkSandboxUids = withSdkSandboxUids(uids)
private val networkStatsRepository = NetworkStatsRepository(context, template)
override suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> = coroutineScope { override suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> =
getCycles().map { getCycles().asyncMap { queryDetails(it) }.filter { it.totalUsage > 0 }
async {
queryDetails(it)
}
}.awaitAll().filter { it.totalUsage > 0 }
}
private fun getCycles(): List<Range<Long>> = private fun getCycles(): List<Range<Long>> =
cycles?.zipWithNext { endTime, startTime -> Range(startTime, endTime) } cycles?.zipWithNext { endTime, startTime -> Range(startTime, endTime) }
?: networkCycleDataRepository.getCycles() ?: networkCycleDataRepository.getCycles()
private fun queryDetails(range: Range<Long>): NetworkUsageDetailsData { private fun queryDetails(range: Range<Long>): NetworkUsageDetailsData {
var totalUsage = 0L val buckets = networkStatsRepository.queryBuckets(range.lower, range.upper)
var foregroundUsage = 0L .filter { it.uid in withSdkSandboxUids }
for (uid in withSdkSandboxUids) { val totalUsage = buckets.sumOf { it.bytes }
val usage = getUsage(range, uid, NetworkStats.Bucket.STATE_ALL) val foregroundUsage =
if (usage > 0L) { buckets.filter { it.state == NetworkStats.Bucket.STATE_FOREGROUND }.sumOf { it.bytes }
totalUsage += usage
foregroundUsage += getUsage(range, uid, NetworkStats.Bucket.STATE_FOREGROUND)
}
}
return NetworkUsageDetailsData( return NetworkUsageDetailsData(
range = range, range = range,
totalUsage = totalUsage, totalUsage = totalUsage,
@@ -70,8 +59,4 @@ class AppDataUsageDetailsRepository @JvmOverloads constructor(
backgroundUsage = totalUsage - foregroundUsage, backgroundUsage = totalUsage - foregroundUsage,
) )
} }
@VisibleForTesting
fun getUsage(range: Range<Long>, uid: Int, state: Int): Long =
networkStatsRepository.queryAggregateForUid(range, uid, state)?.usage ?: 0
} }

View File

@@ -41,7 +41,7 @@ class AppDataUsageRepository(
private val networkStatsRepository = NetworkStatsRepository(context, template) private val networkStatsRepository = NetworkStatsRepository(context, template)
fun getAppPercent(carrierId: Int?, startTime: Long, endTime: Long): List<Pair<AppItem, Int>> { fun getAppPercent(carrierId: Int?, startTime: Long, endTime: Long): List<Pair<AppItem, Int>> {
val buckets = networkStatsRepository.querySummary(startTime, endTime) val buckets = networkStatsRepository.queryBuckets(startTime, endTime)
return getAppPercent(carrierId, buckets) return getAppPercent(carrierId, buckets)
} }

View File

@@ -20,9 +20,7 @@ import android.content.Context
import android.net.NetworkTemplate import android.net.NetworkTemplate
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange
import kotlinx.coroutines.async import com.android.settingslib.spa.framework.util.asyncMap
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
interface IAppDataUsageSummaryRepository { interface IAppDataUsageSummaryRepository {
suspend fun querySummary(uid: Int): NetworkUsageData? suspend fun querySummary(uid: Int): NetworkUsageData?
@@ -35,11 +33,8 @@ class AppDataUsageSummaryRepository(
NetworkStatsRepository(context, template), NetworkStatsRepository(context, template),
) : IAppDataUsageSummaryRepository { ) : IAppDataUsageSummaryRepository {
override suspend fun querySummary(uid: Int): NetworkUsageData? = coroutineScope { override suspend fun querySummary(uid: Int): NetworkUsageData? =
withSdkSandboxUids(listOf(uid)).map { uid -> withSdkSandboxUids(listOf(uid)).asyncMap {
async { networkStatsRepository.queryAggregateForUid(range = AllTimeRange, uid = it)
networkStatsRepository.queryAggregateForUid(range = AllTimeRange, uid = uid) }.filterNotNull().aggregate()
}
}.awaitAll().filterNotNull().aggregate()
}
} }

View File

@@ -26,6 +26,11 @@ data class NetworkCycleChartData(
val dailyUsage: List<NetworkUsageData>, val dailyUsage: List<NetworkUsageData>,
) { ) {
companion object { companion object {
val AllZero = NetworkCycleChartData(
total = NetworkUsageData.AllZero,
dailyUsage = emptyList(),
)
val BUCKET_DURATION = 1.days val BUCKET_DURATION = 1.days
} }
} }

View File

@@ -54,7 +54,7 @@ class NetworkStatsRepository(context: Context, private val template: NetworkTemp
0 0
} }
fun querySummary(startTime: Long, endTime: Long): List<Bucket> = try { fun queryBuckets(startTime: Long, endTime: Long): List<Bucket> = try {
networkStatsManager.querySummary(template, startTime, endTime).convertToBuckets() networkStatsManager.querySummary(template, startTime, endTime).convertToBuckets()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Exception querySummary", e) Log.e(TAG, "Exception querySummary", e)
@@ -69,13 +69,14 @@ class NetworkStatsRepository(context: Context, private val template: NetworkTemp
data class Bucket( data class Bucket(
val uid: Int, val uid: Int,
val bytes: Long, val bytes: Long,
val state: Int = NetworkStats.Bucket.STATE_ALL,
) )
private fun NetworkStats.convertToBuckets(): List<Bucket> = use { private fun NetworkStats.convertToBuckets(): List<Bucket> = use {
val buckets = mutableListOf<Bucket>() val buckets = mutableListOf<Bucket>()
val bucket = NetworkStats.Bucket() val bucket = NetworkStats.Bucket()
while (getNextBucket(bucket)) { while (getNextBucket(bucket)) {
buckets += Bucket(uid = bucket.uid, bytes = bucket.bytes) buckets += Bucket(uid = bucket.uid, bytes = bucket.bytes, state = bucket.state)
} }
buckets buckets
} }

View File

@@ -43,8 +43,14 @@ data class NetworkUsageData(
fun getDataUsedString(context: Context): String = fun getDataUsedString(context: Context): String =
context.getString(R.string.data_used_template, formatUsage(context)) context.getString(R.string.data_used_template, formatUsage(context))
private companion object { companion object {
const val DATE_FORMAT = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_ABBREV_MONTH val AllZero = NetworkUsageData(
startTime = 0L,
endTime = 0L,
usage = 0L,
)
private const val DATE_FORMAT = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_ABBREV_MONTH
} }
} }

View File

@@ -16,7 +16,7 @@
package com.android.settings.datausage.lib package com.android.settings.datausage.lib
import android.app.usage.NetworkStats.Bucket import android.app.usage.NetworkStats
import android.content.Context import android.content.Context
import android.net.NetworkTemplate import android.net.NetworkTemplate
import android.util.Range import android.util.Range
@@ -28,8 +28,8 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.spy import org.mockito.kotlin.stub
import org.mockito.kotlin.whenever import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AppDataUsageDetailsRepositoryTest { class AppDataUsageDetailsRepositoryTest {
@@ -41,58 +41,78 @@ class AppDataUsageDetailsRepositoryTest {
on { getCycles() } doReturn listOf(Range(CYCLE1_END_TIME, CYCLE2_END_TIME)) on { getCycles() } doReturn listOf(Range(CYCLE1_END_TIME, CYCLE2_END_TIME))
} }
private val networkStatsRepository = mock<NetworkStatsRepository>()
@Test @Test
fun queryDetailsForCycles_hasCycles(): Unit = runBlocking { fun queryDetailsForCycles_hasCycles(): Unit = runBlocking {
val range = Range(CYCLE1_START_TIME, CYCLE1_END_TIME) networkStatsRepository.stub {
val repository = spy( on { queryBuckets(CYCLE1_START_TIME, CYCLE1_END_TIME) } doReturn listOf(
AppDataUsageDetailsRepository( Bucket(
uid = UID,
state = NetworkStats.Bucket.STATE_DEFAULT,
bytes = BACKGROUND_USAGE,
),
Bucket(
uid = UID,
state = NetworkStats.Bucket.STATE_FOREGROUND,
bytes = FOREGROUND_USAGE,
),
)
}
val repository = AppDataUsageDetailsRepository(
context = context, context = context,
cycles = listOf(CYCLE1_END_TIME, CYCLE1_START_TIME), cycles = listOf(CYCLE1_END_TIME, CYCLE1_START_TIME),
template = template, template = template,
uids = listOf(UID), uids = listOf(UID),
networkCycleDataRepository = networkCycleDataRepository, networkCycleDataRepository = networkCycleDataRepository,
networkStatsRepository = networkStatsRepository,
) )
) {
doReturn(ALL_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_ALL)
doReturn(FOREGROUND_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_FOREGROUND)
}
val detailsForCycles = repository.queryDetailsForCycles() val detailsForCycles = repository.queryDetailsForCycles()
assertThat(detailsForCycles).containsExactly( assertThat(detailsForCycles).containsExactly(
NetworkUsageDetailsData( NetworkUsageDetailsData(
range = range, range = Range(CYCLE1_START_TIME, CYCLE1_END_TIME),
totalUsage = ALL_USAGE, totalUsage = BACKGROUND_USAGE + FOREGROUND_USAGE,
foregroundUsage = FOREGROUND_USAGE, foregroundUsage = FOREGROUND_USAGE,
backgroundUsage = ALL_USAGE - FOREGROUND_USAGE, backgroundUsage = BACKGROUND_USAGE,
) )
) )
} }
@Test @Test
fun queryDetailsForCycles_defaultCycles(): Unit = runBlocking { fun queryDetailsForCycles_defaultCycles(): Unit = runBlocking {
val range = Range(CYCLE1_END_TIME, CYCLE2_END_TIME) networkStatsRepository.stub {
val repository = spy( on { queryBuckets(CYCLE1_END_TIME, CYCLE2_END_TIME) } doReturn listOf(
AppDataUsageDetailsRepository( Bucket(
uid = UID,
state = NetworkStats.Bucket.STATE_DEFAULT,
bytes = BACKGROUND_USAGE,
),
Bucket(
uid = UID,
state = NetworkStats.Bucket.STATE_FOREGROUND,
bytes = FOREGROUND_USAGE,
),
)
}
val repository = AppDataUsageDetailsRepository(
context = context, context = context,
cycles = null, cycles = null,
template = template, template = template,
uids = listOf(UID), uids = listOf(UID),
networkCycleDataRepository = networkCycleDataRepository, networkCycleDataRepository = networkCycleDataRepository,
networkStatsRepository = networkStatsRepository,
) )
) {
doReturn(ALL_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_ALL)
doReturn(FOREGROUND_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_FOREGROUND)
}
val detailsForCycles = repository.queryDetailsForCycles() val detailsForCycles = repository.queryDetailsForCycles()
assertThat(detailsForCycles).containsExactly( assertThat(detailsForCycles).containsExactly(
NetworkUsageDetailsData( NetworkUsageDetailsData(
range = range, range = Range(CYCLE1_END_TIME, CYCLE2_END_TIME),
totalUsage = ALL_USAGE, totalUsage = BACKGROUND_USAGE + FOREGROUND_USAGE,
foregroundUsage = FOREGROUND_USAGE, foregroundUsage = FOREGROUND_USAGE,
backgroundUsage = ALL_USAGE - FOREGROUND_USAGE, backgroundUsage = BACKGROUND_USAGE,
) )
) )
} }
@@ -103,7 +123,7 @@ class AppDataUsageDetailsRepositoryTest {
const val CYCLE2_END_TIME = 1695566666000L const val CYCLE2_END_TIME = 1695566666000L
const val UID = 10000 const val UID = 10000
const val ALL_USAGE = 10L const val BACKGROUND_USAGE = 8L
const val FOREGROUND_USAGE = 2L const val FOREGROUND_USAGE = 2L
} }
} }