1 /* 2 * Copyright (C) 2022 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.compatibility.common.util; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.fail; 20 21 import android.app.Instrumentation; 22 import android.os.ParcelFileDescriptor; 23 import android.os.SystemClock; 24 import android.util.Log; 25 26 import java.io.BufferedReader; 27 import java.io.FileInputStream; 28 import java.io.InputStreamReader; 29 import java.util.ArrayDeque; 30 import java.util.Queue; 31 32 import javax.annotation.concurrent.GuardedBy; 33 34 /** 35 * Detects an ANR using `am monitor`. 36 * 37 * Use {@link #start} to create an instance and start monitoring. 38 * Use {@link #waitForAnrAndReturnUptime} to wait for an "early ANR", and get the uptime of it. 39 * ANR monitoring is single-shot, so once an ANR is detected, 40 * the target process will be killed and the AnrMonitor instance will be unusable. 41 */ 42 public class AnrMonitor implements AutoCloseable { 43 private static final String TAG = "AnrMonitor"; 44 45 public static final long NO_ANR = -1L; 46 47 private final Instrumentation mInstrumentation; 48 private final String mTargetProcess; 49 private final Thread mThread; 50 private volatile boolean mStop = false; 51 52 /** Queue of detected "early ANR" uptime */ 53 @GuardedBy("mEventQueue") 54 private final Queue<Long> mEventQueue = new ArrayDeque<>(0); 55 AnrMonitor(Instrumentation instrumentation, String targetProcess)56 private AnrMonitor(Instrumentation instrumentation, String targetProcess) { 57 mInstrumentation = instrumentation; 58 mTargetProcess = targetProcess; 59 mThread = new Thread(this::threadMain, "AnrMonitor"); 60 mThread.setDaemon(true); 61 } 62 63 /** 64 * Start monitoring a process for an "early ANR". 65 */ start(Instrumentation instrumentation, String targetProcess)66 public static AnrMonitor start(Instrumentation instrumentation, String targetProcess) { 67 final AnrMonitor instance = new AnrMonitor(instrumentation, targetProcess); 68 69 instance.run(); 70 71 return instance; 72 } 73 run()74 private void run() { 75 mThread.start(); 76 } 77 78 @Override close()79 public void close() { 80 mStop = true; 81 synchronized (mEventQueue) { 82 mEventQueue.notifyAll(); 83 } 84 if (mThread.isAlive()) { 85 mThread.interrupt(); 86 } 87 } 88 89 /** 90 * Return for an early-ANR event from the target process, and return the uptime of it. 91 * Fails if an ANR doesn't happen before the timeout. 92 */ waitForAnrAndReturnUptime(long timeoutMillis)93 public long waitForAnrAndReturnUptime(long timeoutMillis) { 94 return waitForAnrAndReturnUptime(true, timeoutMillis); 95 } 96 97 /** 98 * Asserts that no ANR occurs within the specified timeframe. 99 */ assertNoAnr(long timeoutMillis)100 public void assertNoAnr(long timeoutMillis) { 101 assertEquals(NO_ANR, waitForAnrAndReturnUptime(false, timeoutMillis)); 102 } 103 104 /** 105 * Waits for an ANR for the target process. 106 * If an ANR is expected, the uptime when an ANR event occurred will be returned, or the 107 * method will fail. 108 * If no ANR is expected, the method will fail if an ANR is seen, or it will return 109 * {@link #NO_ANR} after the timeout. 110 */ waitForAnrAndReturnUptime(boolean expectAnr, long timeoutMillis)111 private long waitForAnrAndReturnUptime(boolean expectAnr, long timeoutMillis) { 112 if (timeoutMillis <= 0) { 113 fail("Timeout must be positive"); 114 } 115 if (mStop) { 116 fail("Monitor has been closed"); 117 } 118 try { 119 final long timeoutUptime = SystemClock.uptimeMillis() + timeoutMillis; 120 synchronized (mEventQueue) { 121 for (;;) { 122 if (mEventQueue.size() == 0) { 123 final long waitTime = timeoutUptime - SystemClock.uptimeMillis(); 124 if (waitTime <= 0) { 125 // Timed out 126 if (expectAnr) { 127 fail("Timeout waiting for an ANR event from `am monitor`"); 128 } else { 129 return NO_ANR; 130 } 131 } 132 try { 133 mEventQueue.wait(waitTime); 134 } catch (InterruptedException e) { 135 continue; 136 } 137 } 138 final Long uptime = mEventQueue.poll(); 139 if (uptime == null) { 140 // Empty 141 continue; 142 } 143 if (!expectAnr) { 144 fail("App had unexpected ANR"); 145 } 146 return uptime; 147 } 148 } 149 } finally { 150 close(); 151 } 152 } 153 threadMain()154 private void threadMain() { 155 ParcelFileDescriptor[] pfds = null; 156 try { 157 pfds = mInstrumentation.getUiAutomation() 158 .executeShellCommandRw("am monitor -s -k -p " + mTargetProcess); 159 final ParcelFileDescriptor rfd = pfds[0]; 160 final FileInputStream rfs = new ParcelFileDescriptor.AutoCloseInputStream(rfd); 161 final BufferedReader reader = new BufferedReader(new InputStreamReader(rfs)); 162 163 Log.i(TAG, "am monitor started"); 164 165 // We don't use the writer. 166 // We don't need to send "q" for finish. Closing the FD will finish the command. 167 168 // Read from the `am monitor` command output... 169 for (;;) { 170 if (mStop) { 171 return; 172 } 173 final String line = reader.readLine(); 174 if (line == null || mStop) { 175 // command finished, or stopping 176 return; 177 } 178 if (!line.startsWith("** EARLY PROCESS NOT RESPONDING:")) { 179 Log.i(TAG, "Ignoring unrelated am monitor output: " + line); 180 // ignore 181 continue; 182 } 183 184 Log.i(TAG, "Early ANR detected: " + line); 185 synchronized (mEventQueue) { 186 mEventQueue.add(SystemClock.uptimeMillis()); 187 mEventQueue.notifyAll(); 188 } 189 } 190 } catch (Throwable th) { 191 if (!mStop) { 192 Log.e(TAG, "BG thread dying unexpectedly", th); 193 fail("Unexpected exception detected: " + th.getMessage() + "\n" 194 + Log.getStackTraceString(th)); 195 } 196 } finally { 197 if (pfds != null) { 198 closeQuietly(pfds[0]); 199 closeQuietly(pfds[1]); 200 } 201 Log.i(TAG, "am monitor finished"); 202 } 203 } 204 closeQuietly(AutoCloseable c)205 private static void closeQuietly(AutoCloseable c) { 206 if (c != null) { 207 try { 208 c.close(); 209 } catch (Exception e) { 210 } 211 } 212 } 213 } 214