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