Home | History | Annotate | Download | only in aupt
      1 /*
      2  * Copyright (C) 2014 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.support.test.aupt;
     18 
     19 import android.app.UiAutomation;
     20 import android.app.ActivityManager.RunningAppProcessInfo;
     21 import android.os.ParcelFileDescriptor;
     22 import android.util.Log;
     23 
     24 import java.io.BufferedReader;
     25 import java.io.InputStreamReader;
     26 import java.io.IOException;
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 import java.util.HashSet;
     30 import java.util.List;
     31 import java.util.Map;
     32 import java.util.regex.Pattern;
     33 import java.util.regex.Matcher;
     34 import java.util.Set;
     35 
     36 public class ProcessStatusTracker implements IProcessStatusTracker {
     37     private static final String TAG = "ProcessStatusTracker";
     38 
     39     // Example return line from "adb shell ps" for the pattern below
     40     // USER     PID     PPID    VSIZE   RSS     WCHAN       PC          NAME
     41     // root     1       0       2216    804     sys_epoll_  00000000 S  com.android.chrome
     42     private final String PS_PATTERN =
     43             "\\w+\\s+(\\d+)\\s+\\w+\\s+\\d+\\s+\\d+\\s+\\w+\\s+[0-9a-f]+\\s+\\w+\\s+(.+)";
     44     private final Pattern PS_PATTERN_MATCH = Pattern.compile(PS_PATTERN);
     45     private final int PS_PATTERN_PID_GROUP = 1;
     46     private final int PS_PATTERN_PKG_GROUP = 2;
     47 
     48     private Map<String, Integer> mPidTracker;
     49     private Set<String> mPidExclusions;
     50 
     51     public ProcessStatusTracker(String[] processes) {
     52         mPidTracker = new HashMap<String, Integer>();
     53         mPidExclusions = new HashSet<String>();
     54         if (processes == null) return;
     55         for (String proc : processes) {
     56             addMonitoredProcess(proc);
     57         }
     58     }
     59 
     60     @Override
     61     public void addMonitoredProcess(String processName) {
     62         if (mPidTracker.containsKey(processName)) {
     63             throw new IllegalArgumentException("Process already being monitored: " + processName);
     64         }
     65         mPidTracker.put(processName, -1);
     66         // don't track right away, until told to
     67         mPidExclusions.add(processName);
     68     }
     69 
     70     @Override
     71     public List<ProcessDetails> getProcessDetails() {
     72         if (mPidTracker == null || mPidTracker.isEmpty()) {
     73             // nothing to track
     74             Log.v(TAG, "getProcessDetails - No pids to track, not doing anything");
     75             return null;
     76         }
     77         List<ProcessDetails> ret = new ArrayList<ProcessDetails>();
     78         List<RunningAppProcessInfo> runningApps = null;
     79         // Refactored to use shell commands, not ActivityManager
     80         runningApps = getRunningAppProcesses();
     81         if (runningApps == null || runningApps.isEmpty()) {
     82             Log.e(TAG, "Failed to retrieve list of running apps");
     83             return ret;
     84         }
     85         // used for keeping track of which process has died
     86         Set<String> deadProcesses = new HashSet<String>(mPidTracker.keySet());
     87         Log.i(TAG, "Got running processes");
     88         for (RunningAppProcessInfo info : runningApps) {
     89             if (mPidTracker.containsKey(info.processName)
     90                     && !mPidExclusions.contains(info.processName)) {
     91                 ProcessDetails detail = new ProcessDetails();
     92                 detail.processName = info.processName;
     93                 detail.pid0 = info.pid;
     94                 // this is a process that we are monitoring
     95                 int pid = mPidTracker.get(info.processName);
     96                 if (pid == -1) {
     97                     mPidTracker.put(info.processName, info.pid);
     98                     Log.d(TAG, String.format(
     99                             "pid detected - %s : %d", info.processName, info.pid));
    100                     // all good with this process
    101                     deadProcesses.remove(info.processName);
    102                     detail.processStatus = ProcessStatus.PROC_STARTED;
    103                 } else if (pid == info.pid) {
    104                     // pid hasn't changed, all good
    105                     deadProcesses.remove(info.processName);
    106                     detail.processStatus = ProcessStatus.PROC_OK;
    107                 } else {
    108                     //pid changed
    109                     deadProcesses.remove(info.processName);
    110                     detail.processStatus = ProcessStatus.PROC_RESTARTED;
    111                     detail.pid1 = pid;
    112                 }
    113                 ret.add(detail);
    114             }
    115         }
    116         for (String proc : deadProcesses) {
    117             ProcessDetails detail = new ProcessDetails();
    118             detail.processName = proc;
    119             // check the remaining ones are dead, or not started
    120             int pid = mPidTracker.get(proc);
    121             if (pid != -1) {
    122                 detail.processStatus = ProcessStatus.PROC_DIED;
    123             } else {
    124                 detail.processStatus = ProcessStatus.PROC_NOT_STARTED;
    125             }
    126             ret.add(detail);
    127         }
    128         return ret;
    129     }
    130 
    131     @Override
    132     public void setAllowProcessTracking(String processName) {
    133         if (mPidTracker == null || mPidTracker.isEmpty()) {
    134             // nothing to track
    135             return;
    136         }
    137         // ignore those not under monitoring
    138         if (mPidExclusions.contains(processName)) {
    139             mPidExclusions.remove(processName);
    140             Log.v(TAG, "Started tracking pid changes: " + processName);
    141         }
    142         verifyRunningProcess();
    143     }
    144 
    145     @Override
    146     public void verifyRunningProcess() {
    147         Log.i(TAG, "Getting process details");
    148         List<ProcessDetails> details = getProcessDetails();
    149         if (details == null) {
    150             return;
    151         }
    152         for (ProcessDetails d :  details) {
    153             Log.v(TAG, String.format("verifyRunningProcess - proc: %s, state: %s",
    154                     d.processName, d.processStatus.toString()));
    155             switch (d.processStatus) {
    156                 case PROC_OK:
    157                 case PROC_NOT_STARTED:
    158                     // no op
    159                     break;
    160                 case PROC_STARTED:
    161                     Log.v(TAG, String.format("Process started: %s - %d", d.processName, d.pid0));
    162                     break;
    163                 case PROC_DIED: {
    164                     String msg = String.format("Process %s has died.", d.processName);
    165                     throw new AuptTerminator(msg);
    166                 }
    167                 case PROC_RESTARTED: {
    168                     String msg = String.format("Process %s restarted: %d -> %d", d.processName,
    169                             d.pid1, d.pid0);
    170                     throw new AuptTerminator(msg);
    171                 }
    172                 default:
    173                     break;
    174             }
    175         }
    176     }
    177 
    178     /**
    179      * Enumerates the list of processes to be tracked and returns a list of RunningAppProcessInfo
    180      * objects with pkg name and pid's set to the process's current information
    181      * @result a List of RunningAppProcessInfo objects with pkg and pid set
    182      */
    183     public List<RunningAppProcessInfo> getRunningAppProcesses() {
    184         List<RunningAppProcessInfo> results = new ArrayList<RunningAppProcessInfo>();
    185 
    186         Set<String> procSet = mPidTracker.keySet();
    187         // Enumerate status for all currently tracked processes
    188         for (String proc : procSet) {
    189             // Execute shell command and parse results
    190             BufferedReader stream = executeShellCommand("ps");
    191             try {
    192                 String line;
    193                 while ((line = stream.readLine()) != null) {
    194                     Matcher matcher = PS_PATTERN_MATCH.matcher(line);
    195                     // Find a line that matches the process name exactly
    196                     if (matcher.matches() && matcher.group(PS_PATTERN_PKG_GROUP).equals(proc)) {
    197                         int pid = Integer.valueOf(matcher.group(PS_PATTERN_PID_GROUP));
    198                         results.add(new RunningAppProcessInfo(proc, pid, null));
    199                     }
    200                 }
    201             } catch (IOException exception) {
    202                 Log.e(TAG, "Error with buffered reader", exception);
    203                 return null;
    204             } finally {
    205                 try {
    206                     if (stream != null) {
    207                         stream.close();
    208                     }
    209                 } catch (IOException exception) {
    210                     Log.e(TAG, "Error with closing the stream", exception);
    211                 }
    212             }
    213         }
    214 
    215         return results;
    216     }
    217 
    218     // TODO: Create subclass for shell commands used by this and GraphicsStatsMonitor
    219 
    220     /**
    221      * UiAutomation is included solely for the purpose of executing shell commands
    222      */
    223     private UiAutomation mUiAutomation;
    224 
    225     /**
    226      * Executes a shell command through UiAutomation and puts the results in an
    227      * InputStreamReader that is returned inside a BufferedReader.
    228      * @param command the command to be executed in the adb shell
    229      * @result a BufferedReader that reads the command output
    230      */
    231     public BufferedReader executeShellCommand (String command) {
    232         ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
    233 
    234         BufferedReader stream = new BufferedReader(new InputStreamReader(
    235                 new ParcelFileDescriptor.AutoCloseInputStream(stdout)));
    236         return stream;
    237     }
    238 
    239     /**
    240      * Sets the UiAutomation member for shell execution
    241      */
    242     public void setUiAutomation (UiAutomation uiAutomation) {
    243         mUiAutomation = uiAutomation;
    244     }
    245 
    246     /**
    247      * @return UiAutomation instance from Aupt instrumentation
    248      */
    249     public UiAutomation getUiAutomation () {
    250         return mUiAutomation;
    251     }
    252 }
    253