1 /* 2 * Copyright (C) 2019 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 android.cts.statsd.subscriber; 17 18 import com.android.compatibility.common.util.CpuFeatures; 19 import com.android.internal.os.StatsdConfigProto; 20 import com.android.os.AtomsProto; 21 import com.android.os.ShellConfig; 22 import com.android.os.statsd.ShellDataProto; 23 import com.android.tradefed.device.CollectingByteOutputReceiver; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.log.LogUtil; 27 import com.android.tradefed.testtype.DeviceTestCase; 28 import com.google.common.io.Files; 29 import com.google.protobuf.InvalidProtocolBufferException; 30 31 import java.io.File; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.util.Arrays; 35 36 /** 37 * Statsd shell data subscription test. 38 */ 39 public class ShellSubscriberTest extends DeviceTestCase { 40 private int sizetBytes; 41 42 @Override 43 protected void setUp() throws Exception { 44 super.setUp(); 45 sizetBytes = getSizetBytes(); 46 } 47 48 private int getSizetBytes() { 49 try { 50 ITestDevice device = getDevice(); 51 if (CpuFeatures.isArm64(device)) { 52 return 8; 53 } 54 if (CpuFeatures.isArm32(device)) { 55 return 4; 56 } 57 return -1; 58 } catch (DeviceNotAvailableException e) { 59 return -1; 60 } 61 } 62 63 // Tests that anomaly detection for count works. 64 // Also tests that anomaly detection works when spanning multiple buckets. 65 public void testShellSubscription() { 66 if (sizetBytes < 0) { 67 return; 68 } 69 // choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME). 70 // Testing pushed atom is a little trickier, because the executeShellCommand() is blocking 71 // and we cannot push a breadcrumb event at the same time when the shell subscription is 72 // running. So test pulled atom instead. 73 ShellConfig.ShellSubscription config = ShellConfig.ShellSubscription.newBuilder() 74 .addPulled(ShellConfig.PulledAtomSubscription.newBuilder().setMatcher( 75 StatsdConfigProto.SimpleAtomMatcher.newBuilder() 76 .setAtomId(AtomsProto.Atom.SYSTEM_UPTIME_FIELD_NUMBER).build()) 77 .setFreqMillis(2000).build()).build(); 78 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 79 startSubscription(config, receiver, 10); 80 byte[] output = receiver.getOutput(); 81 // There should be at lease some data returned. 82 assertTrue(output.length > sizetBytes); 83 84 int atomCount = 0; 85 int i = 0; 86 while (output.length > i + sizetBytes) { 87 int len = 0; 88 for (int j = 0; j < sizetBytes; j++) { 89 len += ((int) output[i + j] & 0xffL) << (sizetBytes * j); 90 } 91 LogUtil.CLog.d("received : " + output.length + " bytes, size : " + len); 92 93 if (output.length < i + sizetBytes + len) { 94 fail("Bad data received."); 95 } 96 97 try { 98 ShellDataProto.ShellData data = 99 ShellDataProto.ShellData.parseFrom( 100 Arrays.copyOfRange(output, i + sizetBytes, i + sizetBytes + len)); 101 assertTrue(data.getAtomCount() > 0); 102 assertTrue(data.getAtom(0).hasSystemUptime()); 103 atomCount++; 104 LogUtil.CLog.d("Received " + data.toString()); 105 } catch (InvalidProtocolBufferException e) { 106 fail("Failed to parse proto"); 107 } 108 i += (sizetBytes + len); 109 } 110 111 assertTrue(atomCount > 0); 112 } 113 114 private void startSubscription(ShellConfig.ShellSubscription config, 115 CollectingByteOutputReceiver receiver, int waitTimeSec) { 116 LogUtil.CLog.d("Uploading the following config:\n" + config.toString()); 117 try { 118 File configFile = File.createTempFile("shellconfig", ".config"); 119 configFile.deleteOnExit(); 120 int length = config.toByteArray().length; 121 byte[] combined = new byte[sizetBytes + config.toByteArray().length]; 122 123 System.arraycopy(IntToByteArrayLittleEndian(length), 0, combined, 0, sizetBytes); 124 System.arraycopy(config.toByteArray(), 0, combined, sizetBytes, length); 125 126 Files.write(combined, configFile); 127 String remotePath = "/data/local/tmp/" + configFile.getName(); 128 getDevice().pushFile(configFile, remotePath); 129 LogUtil.CLog.d("waiting...................."); 130 131 getDevice().executeShellCommand( 132 String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe ", 133 String.valueOf(waitTimeSec)), receiver); 134 getDevice().executeShellCommand("rm " + remotePath); 135 } catch (Exception e) { 136 fail(e.getMessage()); 137 } 138 } 139 140 byte[] IntToByteArrayLittleEndian(int length) { 141 ByteBuffer b = ByteBuffer.allocate(sizetBytes); 142 b.order(ByteOrder.LITTLE_ENDIAN); 143 b.putInt(length); 144 return b.array(); 145 } 146 } 147