1 /*
2  * Copyright (C) 2015 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 package com.android.tradefed.targetprep;
17 
18 import com.android.tradefed.config.Option;
19 import com.android.tradefed.config.OptionClass;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.invoker.TestInformation;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.util.RunUtil;
25 
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 
32 /**
33  * An {@link ITargetPreparer} that waits until max frequency on all cores are restored to highest
34  * level available
35  */
36 @OptionClass(alias = "cpu-throttle-waiter")
37 public class CpuThrottlingWaiter extends BaseTargetPreparer {
38 
39     @Option(name = "poll-interval",
40             description = "Interval in seconds, to poll for core frequencies; defaults to 5s")
41     private long mPollIntervalSecs = 5;
42 
43     @Option(name = "max-wait", description = "Max wait time in seconds, for all cores to be"
44             + " free of throttling; defaults to 240s")
45     private long mMaxWaitSecs = 240;
46 
47     @Option(name = "post-idle-wait", description = "Additional time to wait in seconds, after cores"
48             + " are no longer subject to throttling; defaults to 120s")
49     private long mPostIdleWaitSecs = 120;
50 
51     @Option(name = "abort-on-timeout", description = "If test should be aborted if cores are still"
52             + " subject to throttling after timeout has reached; defaults to false")
53     private boolean mAbortOnTimeout = false;
54 
55     /** {@inheritDoc} */
56     @Override
setUp(TestInformation testInfo)57     public void setUp(TestInformation testInfo)
58             throws TargetSetupError, BuildError, DeviceNotAvailableException {
59         ITestDevice device = testInfo.getDevice();
60         // first figure out number of CPU cores available and their corresponding max frequencies
61         // map: path/to/core : max frequency
62         Map<String, String> cpuMaxFreqs = getCpuMaxFreqs(device);
63         if (cpuMaxFreqs.isEmpty()) {
64             CLog.i("Unable to determine cores available, falling back to max wait time");
65             RunUtil.getDefault().sleep(mMaxWaitSecs * 1000);
66             return;
67         }
68         // poll CPU frequencies
69         long start = System.currentTimeMillis();
70         long maxWaitMs = mMaxWaitSecs * 1000;
71         long intervalMs = mPollIntervalSecs * 1000;
72         while (true) {
73             boolean ready = true;
74             for (Entry<String, String> e : cpuMaxFreqs.entrySet()) {
75                 // check current frequency of each CPU
76                 String freq = device.executeShellCommand(
77                         String.format("cat %s/cpuinfo_max_freq", e.getKey())).trim();
78                 if (!e.getValue().equals(freq)) {
79                     // not ready
80                     CLog.d("CPU %s not ready: %s/%s", e.getKey(), freq, e.getValue());
81                     ready = false;
82                     break; // for loop
83                 }
84             }
85             if (ready) {
86                 break; // while loop
87             }
88             if ((System.currentTimeMillis() - start) > maxWaitMs) {
89                 CLog.w("cores still throttled after %ds", maxWaitMs);
90                 String result = device.executeShellCommand(
91                         "cat /sys/devices/system/cpu/*/cpufreq/cpuinfo_max_freq");
92                 CLog.w("Current CPU frequencies:\n%s", result);
93                 if (mAbortOnTimeout) {
94                     throw new TargetSetupError("cores are still throttled after wait timeout",
95                             device.getDeviceDescriptor());
96                 }
97                 break; // while loop
98             }
99             RunUtil.getDefault().sleep(intervalMs);
100         }
101         // extra idle time so that in case of thermal related throttling, allow heat to dissipate
102         RunUtil.getDefault().sleep(mPostIdleWaitSecs * 1000);
103         CLog.i("Done waiting, total time elapsed: %ds",
104                 (System.currentTimeMillis() - start) / 1000);
105     }
106 
107     /**
108      * Reads info under /sys/devices/system/cpu to determine cores available, and max frequencies
109      * possible for each core
110      * @param device device under test
111      * @return a {@link Map} with paths to sysfs cpuinfo as key, and corresponding max frequency as
112      *         value
113      * @throws DeviceNotAvailableException
114      */
getCpuMaxFreqs(ITestDevice device)115     protected Map<String, String> getCpuMaxFreqs(ITestDevice device)
116             throws DeviceNotAvailableException {
117         Map<String, String> ret = new HashMap<>();
118         String result = device.executeShellCommand("ls -1 -d /sys/devices/system/cpu/cpu*/cpufreq");
119         String[] lines = result.split("\r?\n");
120         List<String> cpuPaths = new ArrayList<>();
121         for (String line : lines) {
122             cpuPaths.add(line.trim());
123         }
124         for (String cpu : cpuPaths) {
125             result = device.executeShellCommand(
126                     String.format("cat %s/scaling_available_frequencies", cpu)).trim();
127             // return should be something like:
128             // 300000 422400 652800 729600 883200 960000 1036800 1190400 1267200 1497600 1574400
129             String[] freqs = result.split("\\s+");
130             String maxFreq = freqs[freqs.length - 1]; // highest available frequency is last
131             // check if the string is a number
132             if (!maxFreq.matches("^\\d+$")) {
133                 CLog.w("Unable to determine max frequency available for CPU: %s", cpu);
134             } else {
135                 CLog.d("CPU: %s  MaxFreq: %s", cpu, maxFreq);
136                 ret.put(cpu, maxFreq);
137             }
138         }
139         return ret;
140     }
141 }
142