Home | History | Annotate | Download | only in device
      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 com.android.tradefed.device;
     18 
     19 import com.android.tradefed.command.remote.DeviceDescriptor;
     20 import com.android.tradefed.config.GlobalConfiguration;
     21 import com.android.tradefed.config.Option;
     22 import com.android.tradefed.log.LogUtil.CLog;
     23 import com.android.tradefed.util.CircularByteArray;
     24 
     25 import java.util.HashMap;
     26 import java.util.Hashtable;
     27 import java.util.Map;
     28 import java.util.Timer;
     29 import java.util.TimerTask;
     30 
     31 /**
     32  * A {@link IDeviceMonitor} that calculates device utilization stats.
     33  * <p/>
     34  * Currently measures simple moving average of allocation time % over a 24 hour window.
     35  */
     36 public class DeviceUtilStatsMonitor implements IDeviceMonitor {
     37 
     38     private static final int INITIAL_DELAY_MS = 5000;
     39 
     40     /**
     41      * Enum for configuring treatment of stub devices when calculating average host utilization
     42      */
     43     public enum StubDeviceUtil {
     44         /** never include stub device data */
     45         IGNORE,
     46         /**
     47          * include stub device data only if any stub device of same type is allocated at least
     48          * once
     49          */
     50         INCLUDE_IF_USED,
     51         /** always include stub device data */
     52         ALWAYS_INCLUDE
     53     }
     54 
     55     @Option(name = "collect-null-device", description =
     56             "controls if null device data should be used when calculating avg host utilization")
     57     private StubDeviceUtil mCollectNullDevice = StubDeviceUtil.INCLUDE_IF_USED;
     58 
     59     @Option(name = "collect-emulator", description =
     60             "controls if emulator data should be used when calculating avg host utilization")
     61     private StubDeviceUtil mCollectEmulator = StubDeviceUtil.INCLUDE_IF_USED;
     62 
     63     @Option(name = "sample-window-hours", description =
     64             "the moving average window size to use, in hours")
     65     private int mSampleWindowHours = 8;
     66 
     67     @Option(name = "sample-interval-secs", description =
     68             "the time period between samples, in seconds")
     69     private int mSamplingIntervalSec = 60;
     70 
     71     private boolean mNullDeviceAllocated = false;
     72     private boolean mEmulatorAllocated = false;
     73 
     74     /**
     75      * Container for utilization stats.
     76      */
     77     public static class UtilizationDesc {
     78         final int mTotalUtil;
     79         final Map<String, Integer> mDeviceUtil;
     80 
     81         public UtilizationDesc(int totalUtil, Map<String, Integer> deviceUtil) {
     82             mTotalUtil = totalUtil;
     83             mDeviceUtil = deviceUtil;
     84         }
     85 
     86         /**
     87          * Return the total utilization for all devices in TF process, measured as total allocation
     88          * time for all devices vs total available time.
     89          *
     90          * @return percentage utilization
     91          */
     92         public int getTotalUtil() {
     93             return mTotalUtil;
     94         }
     95 
     96         /**
     97          * Helper method to return percent utilization for a device. Returns 0 if no utilization
     98          * data exists for device
     99          */
    100         public Integer getUtilForDevice(String serial) {
    101             Integer util = mDeviceUtil.get(serial);
    102             if (util == null) {
    103                 return 0;
    104             }
    105             return util;
    106         }
    107     }
    108 
    109     private class DeviceUtilRecord {
    110         // store samples of device util, where 0 = avail, 1 = allocated
    111         // TODO: save memory by using CircularBitArray
    112         private CircularByteArray mData;
    113         private int mConsecutiveMissedSamples = 0;
    114 
    115         DeviceUtilRecord() {
    116             mData = new CircularByteArray(mMaxSamples);
    117         }
    118 
    119         public void addSample(DeviceAllocationState state) {
    120             if (DeviceAllocationState.Allocated.equals(state)) {
    121                 mData.add((byte)1);
    122             } else {
    123                 mData.add((byte)0);
    124             }
    125             mConsecutiveMissedSamples = 0;
    126         }
    127 
    128         public long getNumAllocations() {
    129             return mData.getSum();
    130         }
    131 
    132         public long getTotalSamples() {
    133             return mData.size();
    134         }
    135 
    136         /**
    137          * Record sample for missing device.
    138          *
    139          * @param serial device serial number
    140          * @return true if sample was added, false if device has been missing for more than max
    141          * samples
    142          */
    143         public boolean addMissingSample(String serial) {
    144             mConsecutiveMissedSamples++;
    145             if (mConsecutiveMissedSamples > mMaxSamples) {
    146                 return false;
    147             }
    148             mData.add((byte)0);
    149             return true;
    150         }
    151     }
    152 
    153     private class SamplingTask extends TimerTask {
    154         @Override
    155         public void run() {
    156             CLog.d("Collecting utilization");
    157             // track devices that we have records for, but are not reported by device lister
    158             Map<String, DeviceUtilRecord> goneDevices = new HashMap<>(mDeviceUtilMap);
    159 
    160             for (DeviceDescriptor deviceDesc : mDeviceLister.listDevices()) {
    161                 DeviceUtilRecord record = getDeviceRecord(deviceDesc.getSerial());
    162                 record.addSample(deviceDesc.getState());
    163                 goneDevices.remove(deviceDesc.getSerial());
    164             }
    165 
    166             // now record samples for gone devices
    167             for (Map.Entry<String, DeviceUtilRecord> goneSerialEntry : goneDevices.entrySet()) {
    168                 String serial = goneSerialEntry.getKey();
    169                 if (!goneSerialEntry.getValue().addMissingSample(serial)) {
    170                     CLog.d("Forgetting device %s", serial);
    171                     mDeviceUtilMap.remove(serial);
    172                 }
    173             }
    174         }
    175     }
    176 
    177     private int mMaxSamples;
    178 
    179     /** a map of device serial to device records */
    180     private Map<String, DeviceUtilRecord> mDeviceUtilMap = new Hashtable<>();
    181 
    182     private DeviceLister mDeviceLister;
    183 
    184     private Timer mTimer;
    185     private SamplingTask mSamplingTask = new SamplingTask();
    186 
    187     /**
    188      * Get the device utilization up to the last 24 hours
    189      */
    190     public synchronized UtilizationDesc getUtilizationStats() {
    191         CLog.d("Calculating device util");
    192 
    193         long totalAllocSamples = 0;
    194         long totalSamples = 0;
    195         Map<String, Integer> deviceUtilMap = new HashMap<>();
    196         for (Map.Entry<String, DeviceUtilRecord> deviceRecordEntry : mDeviceUtilMap.entrySet()) {
    197             if (shouldTrackDevice(deviceRecordEntry.getKey())) {
    198                 long allocSamples = deviceRecordEntry.getValue().getNumAllocations();
    199                 long numSamples = deviceRecordEntry.getValue().getTotalSamples();
    200                 totalAllocSamples += allocSamples;
    201                 totalSamples += numSamples;
    202                 deviceUtilMap.put(deviceRecordEntry.getKey(), getUtil(allocSamples, numSamples));
    203             }
    204         }
    205         return new UtilizationDesc(getUtil(totalAllocSamples, totalSamples), deviceUtilMap);
    206     }
    207 
    208     /**
    209      * Get device utilization as a percent
    210      */
    211     private static int getUtil(long allocSamples, long numSamples) {
    212         if (numSamples <= 0) {
    213             return 0;
    214         }
    215         return (int)((allocSamples * 100) / numSamples);
    216     }
    217 
    218     @Override
    219     public void run() {
    220         calculateMaxSamples();
    221         mTimer  = new Timer();
    222         mTimer.scheduleAtFixedRate(mSamplingTask, INITIAL_DELAY_MS, mSamplingIntervalSec * 1000);
    223     }
    224 
    225     @Override
    226     public void stop() {
    227         if (mTimer != null) {
    228             mTimer.cancel();
    229             mTimer.purge();
    230         }
    231     }
    232 
    233     @Override
    234     public void setDeviceLister(DeviceLister lister) {
    235         mDeviceLister = lister;
    236     }
    237 
    238     /**
    239      * Listens to device state changes and records time that device transitions from or to
    240      * available or allocated state.
    241      */
    242     @Override
    243     public synchronized void notifyDeviceStateChange(String serial, DeviceAllocationState oldState,
    244             DeviceAllocationState newState) {
    245         if (mNullDeviceAllocated && mEmulatorAllocated) {
    246             // optimization, don't enter calculation below unless needed
    247             return;
    248         }
    249         if (DeviceAllocationState.Allocated.equals(newState)) {
    250             IDeviceManager dvcMgr = getDeviceManager();
    251             if (dvcMgr.isNullDevice(serial)) {
    252                 mNullDeviceAllocated = true;
    253             } else if (dvcMgr.isEmulator(serial)) {
    254                 mEmulatorAllocated = true;
    255             }
    256         }
    257     }
    258 
    259     /**
    260      * Get the device util records for given serial, creating if necessary.
    261      */
    262     private DeviceUtilRecord getDeviceRecord(String serial) {
    263         DeviceUtilRecord r = mDeviceUtilMap.get(serial);
    264         if (r == null) {
    265             r = new DeviceUtilRecord();
    266             mDeviceUtilMap.put(serial, r);
    267         }
    268         return r;
    269     }
    270 
    271     private boolean shouldTrackDevice(String serial) {
    272         IDeviceManager dvcMgr = getDeviceManager();
    273         if (dvcMgr.isNullDevice(serial)) {
    274             switch (mCollectNullDevice) {
    275                 case ALWAYS_INCLUDE:
    276                     return true;
    277                 case IGNORE:
    278                     return false;
    279                 case INCLUDE_IF_USED:
    280                     return mNullDeviceAllocated;
    281             }
    282         } else if (dvcMgr.isEmulator(serial)) {
    283             switch (mCollectEmulator) {
    284                 case ALWAYS_INCLUDE:
    285                     return true;
    286                 case IGNORE:
    287                     return false;
    288                 case INCLUDE_IF_USED:
    289                     return mEmulatorAllocated;
    290             }
    291         }
    292         return true;
    293     }
    294 
    295     IDeviceManager getDeviceManager() {
    296         return GlobalConfiguration.getDeviceManagerInstance();
    297     }
    298 
    299     TimerTask getSamplingTask() {
    300         return mSamplingTask;
    301     }
    302 
    303     // @VisibleForTesting
    304     void calculateMaxSamples() {
    305         // find max samples to collect by converting sample window to seconds then divide by
    306         // sampling interval
    307         mMaxSamples = mSampleWindowHours * 60 * 60 / mSamplingIntervalSec;
    308         assert(mMaxSamples > 0);
    309     }
    310 
    311     // @VisibleForTesting
    312     void setMaxSamples(int maxSamples) {
    313         mMaxSamples = maxSamples;
    314     }
    315 
    316     // @VisibleForTesting
    317     int getMaxSamples() {
    318         return mMaxSamples;
    319     }
    320 }
    321