1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.net.benchmarktests 18 19 import android.net.NetworkStats.NonMonotonicObserver 20 import android.net.NetworkStatsCollection 21 import android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID 22 import android.os.DropBoxManager 23 import androidx.test.platform.app.InstrumentationRegistry 24 import com.android.internal.util.FileRotator 25 import com.android.internal.util.FileRotator.Reader 26 import com.android.server.net.NetworkStatsRecorder 27 import java.io.BufferedInputStream 28 import java.io.DataInputStream 29 import java.io.File 30 import java.io.FileOutputStream 31 import java.nio.file.Files 32 import java.util.concurrent.TimeUnit 33 import java.util.zip.ZipInputStream 34 import kotlin.test.assertTrue 35 import org.junit.BeforeClass 36 import org.junit.Test 37 import org.junit.runner.RunWith 38 import org.junit.runners.JUnit4 39 import org.mockito.Mockito.mock 40 41 @RunWith(JUnit4::class) 42 class NetworkStatsTest { 43 companion object { 44 private val DEFAULT_BUFFER_SIZE = 8192 45 private val FILE_CACHE_WARM_UP_REPEAT_COUNT = 10 46 private val UID_COLLECTION_BUCKET_DURATION_MS = TimeUnit.HOURS.toMillis(2) 47 private val UID_RECORDER_ROTATE_AGE_MS = TimeUnit.DAYS.toMillis(15) 48 private val UID_RECORDER_DELETE_AGE_MS = TimeUnit.DAYS.toMillis(90) 49 private val TEST_DATASET_SUBFOLDER = "dataset/" 50 51 // These files are generated by using real user dataset which has many uid records 52 // and agreed to share the dataset for testing purpose. These dataset can be 53 // extracted from rooted devices by using 54 // "adb pull /data/misc/apexdata/com.android.tethering/netstats" command. 55 private val testFilesAssets by lazy { 56 val zipFiles = context.assets.list(TEST_DATASET_SUBFOLDER)!!.asList() 57 zipFiles.map { 58 val zipInputStream = 59 ZipInputStream((TEST_DATASET_SUBFOLDER + it).toAssetInputStream()) 60 File(unzipToTempDir(zipInputStream), "netstats") 61 } 62 } 63 64 // Test results shows the test cases who read the file first will take longer time to 65 // execute, and reading time getting shorter each time due to file caching mechanism. 66 // Read files several times prior to tests to minimize the impact. 67 // This cannot live in setUp() since the time spent on the file reading will be 68 // attributed to the time spent on the individual test case. 69 @JvmStatic 70 @BeforeClass 71 fun setUpOnce() { 72 repeat(FILE_CACHE_WARM_UP_REPEAT_COUNT) { 73 testFilesAssets.forEach { 74 val uidTestFiles = getSortedListForPrefix(it, "uid") 75 val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS) 76 for (file in uidTestFiles) { 77 readFile(file, collection) 78 } 79 } 80 } 81 } 82 83 val context get() = InstrumentationRegistry.getInstrumentation().getContext() 84 private fun String.toAssetInputStream() = DataInputStream(context.assets.open(this)) 85 86 private fun unzipToTempDir(zis: ZipInputStream): File { 87 val statsDir = 88 Files.createTempDirectory(NetworkStatsTest::class.simpleName).toFile() 89 generateSequence { zis.nextEntry }.forEach { entry -> 90 val entryFile = File(statsDir, entry.name) 91 if (entry.isDirectory) { 92 entryFile.mkdirs() 93 return@forEach 94 } 95 96 // Make sure all folders exists. There is no guarantee anywhere. 97 entryFile.parentFile!!.mkdirs() 98 99 // If the entry is a file extract it. 100 FileOutputStream(entryFile).use { 101 zis.copyTo(it, DEFAULT_BUFFER_SIZE) 102 } 103 } 104 return statsDir 105 } 106 107 // List [xt|uid|uid_tag].<start>-<end> files under the given directory. 108 private fun getSortedListForPrefix(statsDir: File, prefix: String): List<File> { 109 assertTrue(statsDir.exists()) 110 return statsDir.list { _, name -> name.startsWith("$prefix.") } 111 .orEmpty() 112 .map { it -> File(statsDir, it) } 113 .sorted() 114 } 115 116 private fun readFile(file: File, reader: Reader) = 117 BufferedInputStream(file.inputStream()).use { 118 reader.read(it) 119 } 120 } 121 122 @Test 123 fun testReadCollection_manyUids() { 124 // The file cache is warmed up by the @BeforeClass method, so now the test can repeat 125 // this a number of time to have a stable number. 126 testFilesAssets.forEach { 127 val uidTestFiles = getSortedListForPrefix(it, "uid") 128 val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS) 129 for (file in uidTestFiles) { 130 readFile(file, collection) 131 } 132 } 133 } 134 135 @Test 136 fun testReadFromRecorder_manyUids_useDataInput() { 137 doTestReadFromRecorder_manyUids(useFastDataInput = false) 138 } 139 140 @Test 141 fun testReadFromRecorder_manyUids_useFastDataInput() { 142 doTestReadFromRecorder_manyUids(useFastDataInput = true) 143 } 144 145 fun doTestReadFromRecorder_manyUids(useFastDataInput: Boolean) { 146 val mockObserver = mock<NonMonotonicObserver<String>>() 147 val mockDropBox = mock<DropBoxManager>() 148 testFilesAssets.forEach { 149 val recorder = NetworkStatsRecorder( 150 FileRotator( 151 it, PREFIX_UID, UID_RECORDER_ROTATE_AGE_MS, UID_RECORDER_DELETE_AGE_MS 152 ), 153 mockObserver, 154 mockDropBox, 155 PREFIX_UID, 156 UID_COLLECTION_BUCKET_DURATION_MS, 157 false /* includeTags */, 158 false /* wipeOnError */, 159 useFastDataInput /* useFastDataInput */, 160 it 161 ) 162 recorder.orLoadCompleteLocked 163 } 164 } 165 166 inline fun <reified T> mock(): T = mock(T::class.java) 167 } 168