1 /* 2 * Copyright (C) 2019 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.helpers; 18 19 import android.os.SystemClock; 20 import android.util.Log; 21 22 import androidx.test.InstrumentationRegistry; 23 import androidx.test.uiautomator.UiDevice; 24 25 import java.io.IOException; 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.regex.Matcher; 29 import java.util.regex.Pattern; 30 31 /** An {@link ProcLoadHelper} to check for cpu load in last minute is lesser or equal 32 * to given threshold for a given timeout and collect the cpu load as metric. 33 */ 34 public class ProcLoadHelper implements ICollectorHelper<Double> { 35 36 private static final String LOG_TAG = ProcLoadHelper.class.getSimpleName(); 37 private static final String LOAD_CMD = "cat /proc/loadavg"; 38 public static final String LAST_MINUTE_LOAD_METRIC_KEY = "proc_loadavg_last_minute"; 39 public static final String PROC_LOAD_WAIT_TIME_METRIC_KEY = "proc_load_wait_time_msecs"; 40 41 private static final Pattern LOAD_OUTPUT_PATTERN = Pattern.compile( 42 "(?<LASTMINUTELOAD>.*)\\s.*\\s.*\\s.*\\s.*"); 43 44 private double mProcLoadThreshold = 0; 45 private long mProcLoadWaitTimeInMs = 0; 46 // Default to 500 msecs timeout. 47 private long mProcLoadIntervalInMs = 500; 48 private double mRecentLoad = 0; 49 private UiDevice mDevice; 50 private double mTotalWaitTime = 0; 51 52 /** Wait untill the proc/load reaches below the threshold or timeout expires */ 53 @Override startCollecting()54 public boolean startCollecting() { 55 mRecentLoad = 0; 56 long remainingWaitTime = mProcLoadWaitTimeInMs; 57 while (true) { 58 mRecentLoad = getProcLoadInLastMinute(); 59 Log.i(LOG_TAG, String.format("Average cpu load in last minute is : %s", mRecentLoad)); 60 if (mRecentLoad <= mProcLoadThreshold) { 61 break; 62 } else { 63 if (remainingWaitTime <= 0) { 64 Log.i(LOG_TAG, "Timeout because proc/loadavg never went below the threshold."); 65 return false; 66 } 67 long currWaitTime = (mProcLoadIntervalInMs < remainingWaitTime) 68 ? mProcLoadIntervalInMs : remainingWaitTime; 69 Log.d(LOG_TAG, String.format("Waiting for %s msecs", currWaitTime)); 70 SystemClock.sleep(currWaitTime); 71 mTotalWaitTime += currWaitTime; 72 Log.d(LOG_TAG, String.format("Waited for %s msecs", mTotalWaitTime)); 73 remainingWaitTime = remainingWaitTime - mProcLoadIntervalInMs; 74 } 75 } 76 return true; 77 } 78 79 /** Collect the proc/load_avg last minute cpu load average metric. */ 80 @Override getMetrics()81 public Map<String, Double> getMetrics() { 82 // Adding the last recorded load in the metric that will be reported. 83 Map<String, Double> result = new HashMap<>(); 84 Log.i(LOG_TAG, String.format("proc/loadavg in last minute before test is : %s", 85 mRecentLoad)); 86 result.put(LAST_MINUTE_LOAD_METRIC_KEY, mRecentLoad); 87 result.put(PROC_LOAD_WAIT_TIME_METRIC_KEY, mTotalWaitTime); 88 return result; 89 } 90 91 /** Do nothing, because nothing is needed to disable cpuy load avg. */ 92 @Override stopCollecting()93 public boolean stopCollecting() { 94 return true; 95 } 96 97 /** 98 * Parse the last minute cpu load from proc/loadavg 99 * 100 * @return cpu load in last minute. Returns -1 in case if it is failed to parse. 101 */ getProcLoadInLastMinute()102 private double getProcLoadInLastMinute() { 103 try { 104 String output = getDevice().executeShellCommand(LOAD_CMD); 105 Log.i(LOG_TAG, String.format("Output of proc_loadavg is : %s", output)); 106 // Output of the load command 107 // 1.39 1.10 1.21 2/2679 6380 108 // 1.39 is the proc load in the last minute. 109 110 Matcher match = null; 111 if ((match = matches(LOAD_OUTPUT_PATTERN, output.trim())) != null) { 112 Log.i(LOG_TAG, String.format("Current load is : %s", 113 match.group("LASTMINUTELOAD"))); 114 return Double.parseDouble(match.group("LASTMINUTELOAD")); 115 } else { 116 Log.w(LOG_TAG, "Not able to parse the proc/loadavg"); 117 } 118 } catch (IOException e) { 119 throw new RuntimeException("Failed to get proc/loadavg.", e); 120 } 121 122 return -1; 123 } 124 125 /** Returns the {@link UiDevice} under test. */ getDevice()126 private UiDevice getDevice() { 127 if (mDevice == null) { 128 mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 129 } 130 return mDevice; 131 } 132 133 /** 134 * Checks whether {@code line} matches the given {@link Pattern}. 135 * 136 * @return The resulting {@link Matcher} obtained by matching the {@code line} against 137 * {@code pattern}, or null if the {@code line} does not match. 138 */ matches(Pattern pattern, String line)139 private static Matcher matches(Pattern pattern, String line) { 140 Matcher ret = pattern.matcher(line); 141 return ret.matches() ? ret : null; 142 } 143 144 /** 145 * Sets the threshold value which the device cpu load average should be lesser than or equal. 146 */ setProcLoadThreshold(double procLoadThreshold)147 public void setProcLoadThreshold(double procLoadThreshold) { 148 mProcLoadThreshold = procLoadThreshold; 149 } 150 151 /** 152 * Sets the timeout in msecs checking for threshold before proceeding with the testing. 153 */ setProcLoadWaitTimeInMs(long procLoadWaitTimeInMs)154 public void setProcLoadWaitTimeInMs(long procLoadWaitTimeInMs) { 155 mProcLoadWaitTimeInMs = procLoadWaitTimeInMs; 156 } 157 158 /** 159 * Sets the interval time in msecs to check continuosly untill the timeout expires. 160 */ setProcLoadIntervalInMs(long procLoadIntervalInMs)161 public void setProcLoadIntervalInMs(long procLoadIntervalInMs) { 162 mProcLoadIntervalInMs = procLoadIntervalInMs; 163 } 164 } 165