1 /* 2 * Copyright (C) 2016 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.tradefed.util; 18 19 import com.android.loganalysis.item.LogcatItem; 20 import com.android.loganalysis.item.MiscLogcatItem; 21 import com.android.loganalysis.parser.LogcatParser; 22 import com.android.tradefed.device.ILogcatReceiver; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.result.InputStreamSource; 25 26 import java.io.BufferedReader; 27 import java.io.Closeable; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.InputStreamReader; 31 import java.util.AbstractMap.SimpleEntry; 32 import java.util.List; 33 import java.util.Map; 34 35 /** 36 * Parse logcat input for system updater related events. 37 * 38 * <p>In any system with A/B updates, the updater will log its progress to logcat. This class 39 * interprets updater-related logcat messages and can inform listeners of events in both a blocking 40 * and non-blocking fashion. 41 */ 42 public class LogcatUpdaterEventParser implements Closeable { 43 44 // define a custom logcat parser category for events of our interests 45 private static final String UPDATER_CUSTOM_CATEGORY = "updaterEvent"; 46 // wait time before trying to reobtain logcat stream 47 private static final long LOGCAT_REFRESH_WAIT_TIME_MS = 50L; 48 49 private EventTriggerMap mEventTriggerMap; 50 private ILogcatReceiver mLogcatReceiver; 51 private LogcatParser mInternalParser; 52 private BufferedReader mStreamReader = null; 53 private InputStreamSource mCurrentLogcatData = null; 54 private InputStream mCurrentInputStream = null; 55 private InputStreamReader mCurrentStreamReader = null; 56 57 /** 58 * Helper class to store (logcat tag, message) with the corresponding {@link UpdaterEventType}. 59 * 60 * <p>The message registered with {@link #put(String, String, UpdaterEventType)} may be a 61 * substring of the actual logcat message. 62 */ 63 private class EventTriggerMap { 64 // a map that each tag corresponds to multiple entry of (partial message, response) 65 private MultiMap<String, SimpleEntry<String, UpdaterEventType>> mResponses = 66 new MultiMap<>(); 67 68 /** Register a {@link UpdaterEventType} with a specific logcat tag and message. */ 69 public void put(String tag, String partialMessage, UpdaterEventType response) { 70 mResponses.put(tag, new SimpleEntry<>(partialMessage, response)); 71 } 72 73 /** 74 * Return an {@link UpdaterEventType} with exactly matching tag and partially matching 75 * message. 76 */ 77 public UpdaterEventType get(String tag, String message) { 78 for (Map.Entry<String, UpdaterEventType> entry : mResponses.get(tag)) { 79 if (message.contains(entry.getKey())) { 80 return entry.getValue(); 81 } 82 } 83 return null; 84 } 85 } 86 87 /** 88 * A monitor object which allows callers to receive events asynchronously. 89 */ 90 public class AsyncUpdaterEvent { 91 92 private boolean mIsCompleted; 93 private UpdaterEventType mResult; 94 95 public AsyncUpdaterEvent() { 96 mIsCompleted = false; 97 } 98 99 public synchronized boolean isCompleted() { 100 return mIsCompleted; 101 } 102 103 public synchronized void setCompleted(UpdaterEventType result) { 104 if (isCompleted()) { 105 throw new IllegalStateException("Wait thread already completed!"); 106 } 107 mIsCompleted = true; 108 mResult = result; 109 } 110 111 public synchronized void waitUntilCompleted(long timeoutMs) { 112 try { 113 while (!isCompleted()) { 114 wait(timeoutMs); 115 } 116 } catch (InterruptedException e) { 117 throw new RuntimeException(e); 118 } 119 } 120 121 public synchronized UpdaterEventType getResult() { 122 if (!isCompleted()) { 123 throw new IllegalStateException("Wait thread not yet completed."); 124 } 125 return mResult; 126 } 127 } 128 129 /** Create a new {@link LogcatUpdaterEventParser} */ 130 public LogcatUpdaterEventParser(ILogcatReceiver logcatReceiver) { 131 mEventTriggerMap = new EventTriggerMap(); 132 mLogcatReceiver = logcatReceiver; 133 mInternalParser = new LogcatParser(); 134 135 // events registered first are matched first 136 registerEventTrigger( 137 "update_engine", "Using this install plan:", UpdaterEventType.UPDATE_START); 138 registerEventTrigger( 139 "update_engine", 140 "ActionProcessor: finished DownloadAction with code ErrorCode::kSuccess", 141 UpdaterEventType.DOWNLOAD_COMPLETE); 142 registerEventTrigger( 143 "update_engine", 144 "ActionProcessor: finished DownloadAction with code ErrorCode", 145 UpdaterEventType.ERROR); 146 registerEventTrigger( 147 "update_engine", 148 "ActionProcessor: finished FilesystemVerifierAction with code ErrorCode::kSuccess", 149 UpdaterEventType.PATCH_COMPLETE); 150 registerEventTrigger( 151 "update_engine", 152 "ActionProcessor: finished FilesystemVerifierAction with code ErrorCode", 153 UpdaterEventType.ERROR); 154 registerEventTrigger( 155 "update_engine", 156 "All post-install commands succeeded", 157 UpdaterEventType.D2O_COMPLETE); 158 registerEventTrigger( 159 "update_engine", 160 "Update successfully applied", 161 UpdaterEventType.UPDATE_COMPLETE); 162 registerEventTrigger( 163 "update_engine_client", 164 "onPayloadApplicationComplete(ErrorCode::kSuccess (0))", 165 UpdaterEventType.UPDATE_COMPLETE); 166 // kNewRootfsVerificationError is often caused by flaky flashing - b/66996067 (#6) 167 registerEventTrigger( 168 "update_engine_client", 169 "onPayloadApplicationComplete(ErrorCode::kNewRootfsVerificationError (15))", 170 UpdaterEventType.ERROR_FLAKY); 171 registerEventTrigger( 172 "update_engine_client", 173 "onPayloadApplicationComplete(ErrorCode", 174 UpdaterEventType.ERROR); 175 registerEventTrigger( 176 "update_verifier", 177 "Leaving update_verifier.", 178 UpdaterEventType.UPDATE_VERIFIER_COMPLETE); 179 180 mCurrentLogcatData = mLogcatReceiver.getLogcatData(); 181 mCurrentInputStream = mCurrentLogcatData.createInputStream(); 182 mCurrentStreamReader = new InputStreamReader(mCurrentInputStream); 183 mStreamReader = new BufferedReader(mCurrentStreamReader); 184 } 185 186 /** 187 * Register an event of given logcat tag and message with the desired response. Message may be 188 * partial. 189 */ 190 protected void registerEventTrigger(String tag, String msg, UpdaterEventType response) { 191 mEventTriggerMap.put(tag, msg, response); 192 mInternalParser.addPattern(null, "I", tag, UPDATER_CUSTOM_CATEGORY); 193 } 194 195 /** 196 * Internal call that blacks until the specific event is encountered or the timeout is reached. 197 * Return the expected event or an error code. 198 */ 199 private UpdaterEventType internalWaitForEvent( 200 final UpdaterEventType expectedEvent, long timeoutMs) throws IOException { 201 // Parse only a single line at a time, otherwise the LogcatParser won't return until 202 // the logcat stream is over 203 long startTime = System.currentTimeMillis(); 204 while (System.currentTimeMillis() - startTime < timeoutMs) { 205 String lastLine; 206 while ((lastLine = mStreamReader.readLine()) != null) { 207 UpdaterEventType parsedEvent = parseEventType(lastLine); 208 if (parsedEvent == UpdaterEventType.ERROR 209 || parsedEvent == UpdaterEventType.ERROR_FLAKY 210 || expectedEvent.equals(parsedEvent)) { 211 return parsedEvent; 212 } 213 } 214 // if the stream returns null, we lost our logcat. Wait for a small amount 215 // of time, then try to reobtain it. 216 CLog.v("Failed to read logcat stream. Attempt to reconnect."); 217 RunUtil.getDefault().sleep(LOGCAT_REFRESH_WAIT_TIME_MS); 218 refreshLogcatStream(); 219 } 220 CLog.e( 221 "waitForEvent():" 222 + expectedEvent.toString() 223 + " timed out after " 224 + TimeUtil.formatElapsedTime(timeoutMs)); 225 return UpdaterEventType.INFRA_TIMEOUT; 226 } 227 228 /** Block until the specified event is encountered or the timeout is reached. */ 229 public UpdaterEventType waitForEvent(UpdaterEventType expectedEvent, long timeoutMs) { 230 try { 231 return internalWaitForEvent(expectedEvent, timeoutMs); 232 } catch (IOException e) { 233 throw new RuntimeException(e); 234 } 235 } 236 237 /** 238 * Wait for an event but do not block. Return an {@link AsyncUpdaterEvent} monitor which the 239 * caller may wait on and query. 240 */ 241 public AsyncUpdaterEvent waitForEventAsync( 242 final UpdaterEventType expectedEvent, long timeoutMs) { 243 final AsyncUpdaterEvent event = new AsyncUpdaterEvent(); 244 final Thread callingThread = Thread.currentThread(); 245 246 Thread waitThread = 247 new Thread( 248 () -> { 249 UpdaterEventType result = UpdaterEventType.INFRA_TIMEOUT; 250 try { 251 result = internalWaitForEvent(expectedEvent, timeoutMs); 252 } catch (IOException e) { 253 // Unblock calling thread 254 callingThread.interrupt(); 255 throw new RuntimeException(e); 256 } finally { 257 // ensure that the waiting thread drops out of the wait loop 258 synchronized (event) { 259 event.setCompleted(result); 260 event.notifyAll(); 261 } 262 } 263 }); 264 waitThread.setName(getClass().getCanonicalName()); 265 waitThread.setDaemon(true); 266 waitThread.start(); 267 return event; 268 } 269 270 /** 271 * Parse a logcat line and return the captured event (that were registered with {@link 272 * #registerEventTrigger(String, String, UpdaterEventType)}) or null. 273 */ 274 protected UpdaterEventType parseEventType(String lastLine) { 275 LogcatItem item = mInternalParser.parse(ArrayUtil.list(lastLine)); 276 if (item == null) { 277 return null; 278 } 279 List<MiscLogcatItem> miscItems = item.getMiscEvents(UPDATER_CUSTOM_CATEGORY); 280 if (miscItems.size() == 0) { 281 return null; 282 } 283 MiscLogcatItem mi = miscItems.get(0); 284 mInternalParser.clear(); 285 return mEventTriggerMap.get(mi.getTag(), mi.getStack()); 286 } 287 288 /** Reobtain logcat stream when it's lost */ 289 private void refreshLogcatStream() throws IOException { 290 mStreamReader.close(); 291 StreamUtil.cancel(mCurrentLogcatData); 292 mCurrentLogcatData = mLogcatReceiver.getLogcatData(); 293 mCurrentInputStream = mCurrentLogcatData.createInputStream(); 294 mCurrentStreamReader = new InputStreamReader(mCurrentInputStream); 295 mStreamReader = new BufferedReader(mCurrentStreamReader); 296 } 297 298 /** {@inheritDoc} */ 299 @Override 300 public void close() throws IOException { 301 StreamUtil.close(mStreamReader); 302 StreamUtil.cancel(mCurrentLogcatData); 303 StreamUtil.close(mCurrentInputStream); 304 StreamUtil.close(mCurrentStreamReader); 305 } 306 } 307 308