1 /* 2 * Copyright (C) 2017 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 package com.android.server.cts; 17 18 import android.service.NetworkIdentityProto; 19 import android.service.NetworkInterfaceProto; 20 import android.service.NetworkStatsCollectionKeyProto; 21 import android.service.NetworkStatsCollectionStatsProto; 22 import android.service.NetworkStatsHistoryBucketProto; 23 import android.service.NetworkStatsHistoryProto; 24 import android.service.NetworkStatsRecorderProto; 25 import android.service.NetworkStatsServiceDumpProto; 26 27 import com.android.tradefed.log.LogUtil.CLog; 28 29 import java.util.List; 30 import java.util.function.Function; 31 import java.util.function.Predicate; 32 33 /** 34 * Test for "dumpsys netstats --proto" 35 * 36 * Note most of the logic here is just heuristics. 37 * 38 * Usage: 39 40 cts-tradefed run cts --skip-device-info --skip-preconditions \ 41 --skip-system-status-check \ 42 com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker \ 43 -a armeabi-v7a -m CtsIncidentHostTestCases -t com.android.server.cts.NetstatsIncidentTest 44 45 */ 46 public class NetstatsIncidentTest extends ProtoDumpTestCase { 47 private static final String DEVICE_SIDE_TEST_APK = "CtsNetStatsApp.apk"; 48 private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.netstats"; 49 private static final String FEATURE_WIFI = "android.hardware.wifi"; 50 51 @Override 52 protected void tearDown() throws Exception { 53 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 54 55 super.tearDown(); 56 } 57 58 59 private void assertPositive(String name, long value) { 60 if (value > 0) return; 61 fail(name + " expected to be positive, but was: " + value); 62 } 63 64 private void assertNotNegative(String name, long value) { 65 if (value >= 0) return; 66 fail(name + " expected to be zero or positive, but was: " + value); 67 } 68 69 private void assertGreaterOrEqual(long greater, long lesser) { 70 assertTrue("" + greater + " expected to be greater than or equal to " + lesser, 71 greater >= lesser); 72 } 73 74 /** 75 * Parse the output of "dumpsys netstats --proto" and make sure all the values are probable. 76 */ 77 public void testSanityCheck() throws Exception { 78 79 final long st = System.currentTimeMillis(); 80 81 installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true); 82 83 // Find the package UID. 84 final int uid = Integer.parseInt(execCommandAndGetFirstGroup( 85 "dumpsys package " + DEVICE_SIDE_TEST_PACKAGE, "userId=(\\d+)")); 86 87 CLog.i("Start time: " + st); 88 CLog.i("App UID: " + uid); 89 90 // Run the device side test which makes some network requests. 91 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, null, null); 92 93 // Make some more activity. 94 getDevice().executeShellCommand("ping -s 100 -c 10 -i 0 www.android.com"); 95 96 // Force refresh the output. 97 getDevice().executeShellCommand("dumpsys netstats --poll"); 98 99 NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(), 100 "dumpsys netstats --proto"); 101 102 CLog.d("First dump:\n" + dump.toString()); 103 104 // Basic sanity check. 105 checkInterfaces(dump.getActiveInterfacesList()); 106 checkInterfaces(dump.getActiveUidInterfacesList()); 107 108 checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false); 109 checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false); 110 checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false); 111 checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true); 112 113 // Remember the original values. 114 final Predicate<NetworkStatsCollectionKeyProto> uidFilt = key -> key.getUid() == uid; 115 final Predicate<NetworkStatsCollectionKeyProto> tagFilt = 116 key -> (key.getTag() == 123123123) && (key.getUid() == uid); 117 118 final long devRxPackets = sum(dump.getDevStats(), st, b -> b.getRxPackets()); 119 final long devRxBytes = sum(dump.getDevStats(), st, b -> b.getRxBytes()); 120 final long devTxPackets = sum(dump.getDevStats(), st, b -> b.getTxPackets()); 121 final long devTxBytes = sum(dump.getDevStats(), st, b -> b.getTxBytes()); 122 123 final long xtRxPackets = sum(dump.getXtStats(), st, b -> b.getRxPackets()); 124 final long xtRxBytes = sum(dump.getXtStats(), st, b -> b.getRxBytes()); 125 final long xtTxPackets = sum(dump.getXtStats(), st, b -> b.getTxPackets()); 126 final long xtTxBytes = sum(dump.getXtStats(), st, b -> b.getTxBytes()); 127 128 final long uidRxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets()); 129 final long uidRxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes()); 130 final long uidTxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets()); 131 final long uidTxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes()); 132 133 final long tagRxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets()); 134 final long tagRxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes()); 135 final long tagTxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets()); 136 final long tagTxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes()); 137 138 // Run again to make some more activity. 139 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, 140 "com.android.server.cts.netstats.NetstatsDeviceTest", 141 "testDoNetworkWithoutTagging"); 142 143 getDevice().executeShellCommand("dumpsys netstats --poll"); 144 dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto"); 145 146 CLog.d("Second dump:\n" + dump.toString()); 147 148 final long devRxPackets2 = sum(dump.getDevStats(), st, b -> b.getRxPackets()); 149 final long devRxBytes2 = sum(dump.getDevStats(), st, b -> b.getRxBytes()); 150 final long devTxPackets2 = sum(dump.getDevStats(), st, b -> b.getTxPackets()); 151 final long devTxBytes2 = sum(dump.getDevStats(), st, b -> b.getTxBytes()); 152 153 final long xtRxPackets2 = sum(dump.getXtStats(), st, b -> b.getRxPackets()); 154 final long xtRxBytes2 = sum(dump.getXtStats(), st, b -> b.getRxBytes()); 155 final long xtTxPackets2 = sum(dump.getXtStats(), st, b -> b.getTxPackets()); 156 final long xtTxBytes2 = sum(dump.getXtStats(), st, b -> b.getTxBytes()); 157 158 final long uidRxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets()); 159 final long uidRxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes()); 160 final long uidTxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets()); 161 final long uidTxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes()); 162 163 final long tagRxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets()); 164 final long tagRxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes()); 165 final long tagTxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets()); 166 final long tagTxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes()); 167 168 // At least 1 packet, 100 bytes sent. 169 assertGreaterOrEqual(uidTxPackets2, uidTxPackets + 1); 170 assertGreaterOrEqual(uidTxBytes2, uidTxBytes + 100); 171 172 // assertGreaterOrEqual(tagTxPackets2, tagTxPackets + 1); 173 // assertGreaterOrEqual(tagTxBytes2, tagTxBytes + 100); 174 175 // At least 2 packets, 100 bytes sent. 176 assertGreaterOrEqual(uidRxPackets2, uidRxPackets + 2); 177 assertGreaterOrEqual(uidRxBytes2, uidRxBytes + 100); 178 179 // assertGreaterOrEqual(tagRxPackets2, tagRxPackets + 2); 180 // assertGreaterOrEqual(tagRxBytes2, tagRxBytes + 100); 181 182 // Run again to make some more activity. 183 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, 184 "com.android.server.cts.netstats.NetstatsDeviceTest", 185 "testDoNetworkWithTagging"); 186 187 getDevice().executeShellCommand("dumpsys netstats --poll"); 188 dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto"); 189 190 CLog.d("Second dump:\n" + dump.toString()); 191 192 final long devRxPackets3 = sum(dump.getDevStats(), st, b -> b.getRxPackets()); 193 final long devRxBytes3 = sum(dump.getDevStats(), st, b -> b.getRxBytes()); 194 final long devTxPackets3 = sum(dump.getDevStats(), st, b -> b.getTxPackets()); 195 final long devTxBytes3 = sum(dump.getDevStats(), st, b -> b.getTxBytes()); 196 197 final long xtRxPackets3 = sum(dump.getXtStats(), st, b -> b.getRxPackets()); 198 final long xtRxBytes3 = sum(dump.getXtStats(), st, b -> b.getRxBytes()); 199 final long xtTxPackets3 = sum(dump.getXtStats(), st, b -> b.getTxPackets()); 200 final long xtTxBytes3 = sum(dump.getXtStats(), st, b -> b.getTxBytes()); 201 202 final long uidRxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets()); 203 final long uidRxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes()); 204 final long uidTxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets()); 205 final long uidTxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes()); 206 207 final long tagRxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets()); 208 final long tagRxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes()); 209 final long tagTxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets()); 210 final long tagTxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes()); 211 212 // At least 1 packet, 100 bytes sent. 213 assertGreaterOrEqual(uidTxPackets3, uidTxPackets2 + 1); 214 assertGreaterOrEqual(uidTxBytes3, uidTxBytes2 + 100); 215 216 assertGreaterOrEqual(tagTxPackets3, tagTxPackets2 + 1); 217 assertGreaterOrEqual(tagTxBytes3, tagTxBytes2 + 100); 218 219 // At least 2 packets, 100 bytes sent. 220 assertGreaterOrEqual(uidRxPackets3, uidRxPackets2 + 2); 221 assertGreaterOrEqual(uidRxBytes3, uidRxBytes2 + 100); 222 223 assertGreaterOrEqual(tagRxPackets3, tagRxPackets2 + 2); 224 assertGreaterOrEqual(tagRxBytes3, tagRxBytes2 + 100); 225 } 226 227 private long sum(NetworkStatsRecorderProto recorder, 228 long startTime, 229 Function<NetworkStatsHistoryBucketProto, Long> func) { 230 return sum(recorder, startTime, key -> true, func); 231 } 232 233 private long sum(NetworkStatsRecorderProto recorder, 234 long startTime, 235 Predicate<NetworkStatsCollectionKeyProto> filter, 236 Function<NetworkStatsHistoryBucketProto, Long> func) { 237 238 long total = 0; 239 for (NetworkStatsCollectionStatsProto stats 240 : recorder.getCompleteHistory().getStatsList()) { 241 if (!filter.test(stats.getKey())) { 242 continue; 243 } 244 for (NetworkStatsHistoryBucketProto bucket : stats.getHistory().getBucketsList()) { 245 if (startTime < bucket.getBucketStartMs()) { 246 continue; 247 } 248 total += func.apply(bucket); 249 } 250 } 251 return total; 252 } 253 254 private void checkInterfaces(List<NetworkInterfaceProto> interfaces) throws Exception{ 255 /* Example: 256 active_interfaces=[ 257 NetworkInterfaceProto { 258 interface=wlan0 259 identities=NetworkIdentitySetProto { 260 identities=[ 261 NetworkIdentityProto { 262 type=1 263 subscriber_id= 264 network_id="wifiap" 265 roaming=false 266 metered=false 267 } 268 ] 269 } 270 } 271 ] 272 */ 273 assertTrue("There must be at least one network device", 274 interfaces.size() > 0); 275 276 boolean allRoaming = true; 277 boolean allMetered = true; 278 279 for (NetworkInterfaceProto iface : interfaces) { 280 assertTrue("Missing interface name", !iface.getInterface().isEmpty()); 281 282 assertPositive("# identities", iface.getIdentities().getIdentitiesList().size()); 283 284 for (NetworkIdentityProto iden : iface.getIdentities().getIdentitiesList()) { 285 allRoaming &= iden.getRoaming(); 286 allMetered &= iden.getMetered(); 287 288 // TODO Can we check the other fields too? type, subscriber_id, and network_id. 289 } 290 } 291 assertFalse("There must be at least one non-roaming interface during CTS", allRoaming); 292 if (hasWiFiFeature()) { 293 assertFalse("There must be at least one non-metered interface during CTS", allMetered); 294 } 295 } 296 297 private void checkStats(NetworkStatsRecorderProto recorder, boolean withUid, boolean withTag) { 298 /* 299 * Example: 300 dev_stats=NetworkStatsRecorderProto { 301 pending_total_bytes=136 302 complete_history=NetworkStatsCollectionProto { 303 stats=[ 304 NetworkStatsCollectionStatsProto { 305 key=NetworkStatsCollectionKeyProto { 306 identity=NetworkIdentitySetProto { 307 identities=[ 308 NetworkIdentityProto { 309 type=1 310 subscriber_id= 311 network_id="wifiap" 312 roaming=false 313 metered=false 314 } 315 ] 316 } 317 uid=-1 318 set=-1 319 tag=0 320 } 321 history=NetworkStatsHistoryProto { 322 bucket_duration_ms=3600000 323 buckets=[ 324 NetworkStatsHistoryBucketProto { 325 bucket_start_ms=2273694336 326 rx_bytes=2142 327 rx_packets=10 328 tx_bytes=1568 329 tx_packets=12 330 operations=0 331 } 332 NetworkStatsHistoryBucketProto { 333 bucket_start_ms=3196682880 334 rx_bytes=2092039 335 rx_packets=1987 336 tx_bytes=236735 337 tx_packets=1750 338 operations=0 339 } 340 */ 341 342 assertNotNegative("Pending bytes", recorder.getPendingTotalBytes()); 343 344 for (NetworkStatsCollectionStatsProto stats : recorder.getCompleteHistory().getStatsList()) { 345 346 final NetworkStatsCollectionKeyProto key = stats.getKey(); 347 348 // TODO Check the key. 349 350 final NetworkStatsHistoryProto hist = stats.getHistory(); 351 352 assertPositive("duration", hist.getBucketDurationMs()); 353 354 // Subtract one hour from duration to compensate for possible DTS. 355 final long minInterval = hist.getBucketDurationMs() - (60 * 60 * 1000); 356 357 NetworkStatsHistoryBucketProto prev = null; 358 for (NetworkStatsHistoryBucketProto bucket : hist.getBucketsList()) { 359 360 // Make sure the start time is increasing by at least the "duration", 361 // except we subtract duration from one our to compensate possible DTS. 362 363 if (prev != null) { 364 assertTrue( 365 String.format("Last start=%d, current start=%d, diff=%d, duration=%d", 366 prev.getBucketStartMs(), bucket.getBucketStartMs(), 367 (bucket.getBucketStartMs() - prev.getBucketStartMs()), 368 minInterval), 369 (bucket.getBucketStartMs() - prev.getBucketStartMs()) >= 370 minInterval); 371 } 372 assertNotNegative("RX bytes", bucket.getRxBytes()); 373 assertNotNegative("RX packets", bucket.getRxPackets()); 374 assertNotNegative("TX bytes", bucket.getTxBytes()); 375 assertNotNegative("TX packets", bucket.getTxPackets()); 376 377 assertTrue( 378 String.format("# of bytes %d too small for # of packets %d", 379 bucket.getRxBytes(), bucket.getRxPackets()), 380 bucket.getRxBytes() >= bucket.getRxPackets()); 381 assertTrue( 382 String.format("# of bytes %d too small for # of packets %d", 383 bucket.getTxBytes(), bucket.getTxPackets()), 384 bucket.getTxBytes() >= bucket.getTxPackets()); 385 } 386 } 387 388 // TODO Make sure test app's UID actually shows up. 389 } 390 391 private boolean hasWiFiFeature() throws Exception { 392 final String commandOutput = getDevice().executeShellCommand("pm list features"); 393 return commandOutput.contains(FEATURE_WIFI); 394 } 395 396 // Currently only verifies that privacy filtering is done properly. 397 static void verifyNetworkStatsServiceDumpProto(NetworkStatsServiceDumpProto dump, final int filterLevel) throws Exception { 398 if (filterLevel == PRIVACY_AUTO) { 399 for (NetworkInterfaceProto nip : dump.getActiveInterfacesList()) { 400 verifyNetworkInterfaceProto(nip, filterLevel); 401 } 402 for (NetworkInterfaceProto nip : dump.getActiveUidInterfacesList()) { 403 verifyNetworkInterfaceProto(nip, filterLevel); 404 } 405 } 406 } 407 408 private static void verifyNetworkInterfaceProto(NetworkInterfaceProto nip, final int filterLevel) throws Exception { 409 for (NetworkIdentityProto ni : nip.getIdentities().getIdentitiesList()) { 410 if (filterLevel == PRIVACY_AUTO) { 411 assertTrue(ni.getSubscriberId().isEmpty()); 412 assertTrue(ni.getNetworkId().isEmpty()); 413 } 414 } 415 } 416 } 417