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