Home | History | Annotate | Download | only in util
      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