1 /* 2 * Copyright (C) 2021 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 android.cts.statsdatom.perfetto; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.cts.statsdatom.lib.AtomTestUtils; 22 import android.cts.statsdatom.lib.ConfigUtils; 23 import android.cts.statsdatom.lib.DeviceUtils; 24 import android.cts.statsdatom.lib.ReportUtils; 25 26 import com.android.internal.os.StatsdConfigProto; 27 import com.android.internal.os.StatsdConfigProto.Alert; 28 import com.android.internal.os.StatsdConfigProto.FieldMatcher; 29 import com.android.internal.os.StatsdConfigProto.PerfettoDetails; 30 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 31 import com.android.internal.os.StatsdConfigProto.Subscription; 32 import com.android.internal.os.StatsdConfigProto.TimeUnit; 33 import com.android.internal.os.StatsdConfigProto.ValueMetric; 34 import com.android.os.AtomsProto; 35 import com.android.os.AtomsProto.AppBreadcrumbReported; 36 import com.android.os.AtomsProto.Atom; 37 import com.android.os.AtomsProto.PerfettoTrigger; 38 import com.android.os.AtomsProto.PerfettoUploaded; 39 import com.android.os.AtomsProto.TracingServiceReportEvent; 40 import com.android.os.StatsLog.EventMetricData; 41 import com.android.tradefed.build.IBuildInfo; 42 import com.android.tradefed.log.LogUtil; 43 import com.android.tradefed.log.LogUtil.CLog; 44 import com.android.tradefed.testtype.DeviceTestCase; 45 import com.android.tradefed.testtype.IBuildReceiver; 46 import com.android.tradefed.util.CommandResult; 47 import com.android.tradefed.util.CommandStatus; 48 import com.android.tradefed.util.RunUtil; 49 50 import com.google.protobuf.ByteString; 51 52 import perfetto.protos.PerfettoConfig.DataSourceConfig; 53 import perfetto.protos.PerfettoConfig.FtraceConfig; 54 import perfetto.protos.PerfettoConfig.TraceConfig; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 import java.util.Random; 59 60 public class PerfettoTests extends DeviceTestCase implements IBuildReceiver { 61 62 private static final int WAIT_AFTER_START_PERFETTO_MS = 3000; 63 64 // Config constants 65 // These were chosen to match the statsd <-> Perfetto CTS integration 66 // test. 67 private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1; 68 private static final int METRIC_ID = 8; 69 private static final int ALERT_ID = 11; 70 private static final int SUBSCRIPTION_ID_PERFETTO = 42; 71 72 private boolean defaultSystemTracingConfigurationHasChanged = false; 73 74 @Override setUp()75 protected void setUp() throws Exception { 76 super.setUp(); 77 78 // Default Android configuration can only change for device type that doesn't require SystemTracingEnabled 79 // by default in CDD. 80 String chars = getDevice().getProperty("ro.build.characteristics"); 81 if (!isSystemTracingEnabled() && chars.contains("automotive")) { 82 enableSystemTracing(); 83 defaultSystemTracingConfigurationHasChanged = true; 84 } 85 86 ConfigUtils.removeConfig(getDevice()); 87 ReportUtils.clearReports(getDevice()); 88 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 89 } 90 91 @Override tearDown()92 protected void tearDown() throws Exception { 93 // Disable SystemTracing if previously enabled at test setUp() 94 if (defaultSystemTracingConfigurationHasChanged) { 95 disableSystemTracing(); 96 defaultSystemTracingConfigurationHasChanged = false; 97 } 98 // Deadline to finish trace collection 99 final long deadLine = System.currentTimeMillis() + 10000; 100 while (isSystemTracingEnabled()) { 101 if (System.currentTimeMillis() > deadLine) { 102 CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled()); 103 break; 104 } 105 CLog.d("Waiting to finish collecting traces. "); 106 Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT); 107 } 108 109 ConfigUtils.removeConfig(getDevice()); 110 ReportUtils.clearReports(getDevice()); 111 super.tearDown(); 112 } 113 114 @Override setBuild(IBuildInfo buildInfo)115 public void setBuild(IBuildInfo buildInfo) { 116 } 117 testPerfettoUploadedIncidentdAtoms()118 public void testPerfettoUploadedIncidentdAtoms() throws Exception { 119 if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return; 120 resetPerfettoGuardrails(); 121 122 StatsdConfig.Builder config = getStatsdConfig(getPerfettoIncidentConfig()); 123 ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER); 124 ConfigUtils.uploadConfig(getDevice(), config); 125 126 startPerfettoTrace(); 127 RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS); 128 129 // While the trace would not have finished in this time, we expect at least 130 // the trace to have been started. 131 List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 132 assertThat(extractPerfettoUploadedEvents(data)) 133 .containsAtLeast( 134 PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN, 135 PerfettoUploaded.Event.PERFETTO_ON_CONNECT, 136 PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING, 137 PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING); 138 } 139 testSkipReportAtoms()140 public void testSkipReportAtoms() throws Exception { 141 if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return; 142 resetPerfettoGuardrails(); 143 144 StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(true)); 145 ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER); 146 ConfigUtils.uploadConfig(getDevice(), config); 147 148 startPerfettoTrace(); 149 RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS); 150 151 List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 152 assertThat(extractPerfettoUploadedEvents(data)) 153 .containsAtLeast( 154 PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN, 155 PerfettoUploaded.Event.PERFETTO_ON_CONNECT, 156 PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING, 157 PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING); 158 } 159 testReportAtoms()160 public void testReportAtoms() throws Exception { 161 if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return; 162 resetPerfettoGuardrails(); 163 164 StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(false)); 165 ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER); 166 ConfigUtils.addEventMetric(config, Atom.TRACING_SERVICE_REPORT_EVENT_FIELD_NUMBER); 167 ConfigUtils.uploadConfig(getDevice(), config); 168 169 startPerfettoTrace(); 170 RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS); 171 172 List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 173 assertThat(extractPerfettoUploadedEvents(data)) 174 .containsAtLeast( 175 PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_BEGIN, 176 PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_HANDOFF); 177 assertThat(extractReportEvents(data)) 178 .containsExactly( 179 TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BEGIN, 180 TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT); 181 } 182 testPerfettoTriggerAtoms()183 public void testPerfettoTriggerAtoms() throws Exception { 184 if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return; 185 186 StatsdConfig.Builder config = ConfigUtils.createConfigBuilder("AID_NOBODY"); 187 ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_TRIGGER_FIELD_NUMBER); 188 ConfigUtils.uploadConfig(getDevice(), config); 189 190 runTriggerPerfetto(); 191 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 192 193 List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 194 assertThat(data).hasSize(1); 195 assertThat(extractPerfettoTriggerEvents(data)) 196 .containsExactly( 197 PerfettoTrigger.Event.PERFETTO_TRACED_TRIGGER); 198 } 199 getPerfettoIncidentConfig()200 private ByteString getPerfettoIncidentConfig() { 201 TraceConfig.IncidentReportConfig incident = 202 TraceConfig.IncidentReportConfig.newBuilder() 203 .setSkipIncidentd(true) 204 .build(); 205 return getBasePerfettoConfigBuilder() 206 .setIncidentReportConfig(incident) 207 .build() 208 .toByteString(); 209 } 210 getPerfettoReportConfig(boolean skipReport)211 private ByteString getPerfettoReportConfig(boolean skipReport) { 212 TraceConfig.AndroidReportConfig config = TraceConfig.AndroidReportConfig.newBuilder() 213 .setSkipReport(skipReport) 214 .setReporterServicePackage("android.cts") 215 .setReporterServiceClass("android.cts.class") 216 .setUsePipeInFrameworkForTesting(true) 217 .build(); 218 return getBasePerfettoConfigBuilder() 219 .setAndroidReportConfig(config) 220 .build() 221 .toByteString(); 222 } 223 getBasePerfettoConfigBuilder()224 private TraceConfig.Builder getBasePerfettoConfigBuilder() { 225 TraceConfig.Builder builder = TraceConfig.newBuilder(); 226 227 TraceConfig.BufferConfig buffer = 228 TraceConfig.BufferConfig.newBuilder().setSizeKb(128).build(); 229 builder.addBuffers(buffer); 230 231 FtraceConfig ftraceConfig = 232 FtraceConfig.newBuilder().addFtraceEvents("sched/sched_switch").build(); 233 DataSourceConfig dataSourceConfig = 234 DataSourceConfig.newBuilder() 235 .setName("linux.ftrace") 236 .setTargetBuffer(0) 237 .setFtraceConfig(ftraceConfig) 238 .build(); 239 TraceConfig.DataSource dataSource = 240 TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build(); 241 builder.addDataSources(dataSource); 242 243 builder.setDurationMs(500); 244 builder.setAllowUserBuildTracing(true); 245 246 // To avoid being hit with guardrails firing in multiple test runs back 247 // to back, we set a unique session key for each config. 248 Random random = new Random(); 249 StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-atom-"); 250 sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE); 251 builder.setUniqueSessionName(sessionNameBuilder.toString()); 252 253 return builder; 254 } 255 extractPerfettoUploadedEvents( List<EventMetricData> input)256 private List<PerfettoUploaded.Event> extractPerfettoUploadedEvents( 257 List<EventMetricData> input) { 258 List<PerfettoUploaded.Event> output = new ArrayList<>(); 259 for (EventMetricData data : input) { 260 if (data.getAtom().hasPerfettoUploaded()) { 261 output.add(data.getAtom().getPerfettoUploaded().getEvent()); 262 } 263 } 264 return output; 265 } 266 extractPerfettoTriggerEvents( List<EventMetricData> input)267 private List<PerfettoTrigger.Event> extractPerfettoTriggerEvents( 268 List<EventMetricData> input) { 269 List<PerfettoTrigger.Event> output = new ArrayList<>(); 270 for (EventMetricData data : input) { 271 if (data.getAtom().hasPerfettoTrigger()) { 272 output.add(data.getAtom().getPerfettoTrigger().getEvent()); 273 } 274 } 275 return output; 276 } 277 extractReportEvents( List<EventMetricData> input)278 private List<TracingServiceReportEvent.Event> extractReportEvents( 279 List<EventMetricData> input) { 280 List<TracingServiceReportEvent.Event> output = new ArrayList<>(); 281 for (EventMetricData data : input) { 282 if (data.getAtom().hasTracingServiceReportEvent()) { 283 output.add(data.getAtom().getTracingServiceReportEvent().getEvent()); 284 } 285 } 286 return output; 287 } 288 289 /** 290 * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too 291 * close of for too many times and hits the upload limit. 292 */ runTriggerPerfetto()293 private void runTriggerPerfetto() throws Exception { 294 final String cmd = "trigger_perfetto cts.test.trigger"; 295 CommandResult cr = getDevice().executeShellV2Command(cmd); 296 if (cr.getStatus() != CommandStatus.SUCCESS) { 297 throw new Exception( 298 String.format( 299 "Error while executing %s: %s %s", 300 cmd, cr.getStdout(), cr.getStderr())); 301 } 302 } 303 304 /** 305 * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too 306 * close of for too many times and hits the upload limit. 307 */ resetPerfettoGuardrails()308 private void resetPerfettoGuardrails() throws Exception { 309 final String cmd = "perfetto --reset-guardrails"; 310 CommandResult cr = getDevice().executeShellV2Command(cmd); 311 if (cr.getStatus() != CommandStatus.SUCCESS) { 312 throw new Exception( 313 String.format( 314 "Error while executing %s: %s %s", 315 cmd, cr.getStdout(), cr.getStderr())); 316 } 317 } 318 startPerfettoTrace()319 private void startPerfettoTrace() throws Exception { 320 getDevice() 321 .executeShellCommand( 322 String.format( 323 "cmd stats log-app-breadcrumb %d %d", 324 1, AppBreadcrumbReported.State.START.getNumber())); 325 } 326 getStatsdConfig(ByteString config)327 private final StatsdConfig.Builder getStatsdConfig(ByteString config) throws Exception { 328 return ConfigUtils.createConfigBuilder("AID_NOBODY") 329 .addSubscription( 330 Subscription.newBuilder() 331 .setId(SUBSCRIPTION_ID_PERFETTO) 332 .setRuleType(Subscription.RuleType.ALERT) 333 .setRuleId(ALERT_ID) 334 .setPerfettoDetails( 335 PerfettoDetails.newBuilder() 336 .setTraceConfig(config))) 337 .addValueMetric( 338 ValueMetric.newBuilder() 339 .setId(METRIC_ID) 340 .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID) 341 .setBucket(TimeUnit.ONE_MINUTE) 342 // Get the label field's value: 343 .setValueField( 344 FieldMatcher.newBuilder() 345 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) 346 .addChild( 347 FieldMatcher.newBuilder() 348 .setField( 349 AppBreadcrumbReported 350 .LABEL_FIELD_NUMBER)))) 351 .addAtomMatcher( 352 StatsdConfigProto.AtomMatcher.newBuilder() 353 .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID) 354 .setSimpleAtomMatcher( 355 StatsdConfigProto.SimpleAtomMatcher.newBuilder() 356 .setAtomId( 357 Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) 358 .addFieldValueMatcher( 359 ConfigUtils.createFvm( 360 AppBreadcrumbReported 361 .STATE_FIELD_NUMBER) 362 .setEqInt( 363 AppBreadcrumbReported.State 364 .START 365 .getNumber())))) 366 .addAlert( 367 Alert.newBuilder() 368 .setId(ALERT_ID) 369 .setMetricId(METRIC_ID) 370 .setNumBuckets(4) 371 .setRefractoryPeriodSecs(0) 372 .setTriggerIfSumGt(0)) 373 .addNoReportMetric(METRIC_ID); 374 } 375 probe(String path)376 private String probe(String path) throws Exception { 377 return getDevice().executeShellCommand("if [ -e " + path + " ] ; then" 378 + " cat " + path + " ; else echo -1 ; fi"); 379 } 380 enableSystemTracing()381 private void enableSystemTracing() throws Exception { 382 getDevice().executeShellCommand("setprop persist.traced.enable 1"); 383 } 384 disableSystemTracing()385 private void disableSystemTracing() throws Exception { 386 getDevice().executeShellCommand("setprop persist.traced.enable 0"); 387 } 388 389 /** 390 * Determines whether perfetto enabled the kernel ftrace tracer. 391 */ isSystemTracingEnabled()392 protected boolean isSystemTracingEnabled() throws Exception { 393 final String traceFsPath = "/sys/kernel/tracing/tracing_on"; 394 String tracing_on = probe(traceFsPath); 395 if (tracing_on.startsWith("0")) return false; 396 if (tracing_on.startsWith("1")) return true; 397 398 // fallback to debugfs 399 LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath, 400 tracing_on); 401 402 final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on"; 403 tracing_on = probe(debugFsPath); 404 if (tracing_on.startsWith("0")) return false; 405 if (tracing_on.startsWith("1")) return true; 406 throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on)); 407 } 408 } 409