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.util.Log;
20 
21 import androidx.annotation.VisibleForTesting;
22 import androidx.test.InstrumentationRegistry;
23 import androidx.test.uiautomator.UiDevice;
24 
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 
34 /**
35  * PwrStatsUtilHelper runs and collects power stats HAL metrics from the Pixel-specific
36  * pwrstats_util tool that is already included on the device in userdebug builds.
37  */
38 public class PwrStatsUtilHelper implements ICollectorHelper<Long> {
39     private static final String LOG_TAG = PwrStatsUtilHelper.class.getSimpleName();
40 
41     private UiDevice mDevice = null;
42 
43     @VisibleForTesting protected int mUtilPid = 0;
44     @VisibleForTesting protected File mLogFile;
45 
46     @Override
startCollecting()47     public boolean startCollecting() {
48         Log.i(LOG_TAG, "Starting pwrstats_util collection...");
49         // Create temp log file for pwrstats_util
50         try {
51             mLogFile = File.createTempFile("pwrstats_output", ".txt");
52         } catch (IOException e) {
53             Log.e(LOG_TAG, e.toString());
54             return false;
55         }
56         mLogFile.delete();
57 
58         // Kick off pwrstats_util and get its PID
59         final String pid_regex = "^pid = (\\d+)$";
60         final Pattern pid_pattern = Pattern.compile(pid_regex);
61         String output;
62         try {
63             output = executeShellCommand("pwrstats_util -d " + mLogFile.getAbsolutePath());
64         } catch (IOException e) {
65             Log.e(LOG_TAG, e.toString());
66             return false;
67         }
68         Matcher m = pid_pattern.matcher(output);
69         if (m.find()) {
70             mUtilPid = Integer.parseInt(m.group(1));
71         }
72         return true;
73     }
74 
75     @Override
stopCollecting()76     public boolean stopCollecting() {
77         Log.i(LOG_TAG, "Ending pwrstats_util collection...");
78         try {
79             executeShellCommand("kill -INT " + mUtilPid);
80         } catch (IOException e) {
81             Log.e(LOG_TAG, e.toString());
82             return false;
83         }
84         try {
85             Thread.sleep(1000);
86         } catch (InterruptedException e) {
87             Log.e(LOG_TAG, e.toString());
88         }
89         return true;
90     }
91 
92     @Override
getMetrics()93     public Map<String, Long> getMetrics() {
94         stopCollecting();
95 
96         Log.i(LOG_TAG, "Getting metrics...");
97         return processMetricsFromLogFile();
98     }
99 
100     @VisibleForTesting
101     /* Execute a shell command and return its output. */
executeShellCommand(String command)102     protected String executeShellCommand(String command) throws IOException {
103         if (mDevice == null) {
104             mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
105         }
106 
107         Log.i(LOG_TAG, "Running '" + command + "'");
108         return mDevice.executeShellCommand(command);
109     }
110 
111     /* Parse each of the metrics into a key value pair and delete the log file. */
processMetricsFromLogFile()112     private Map<String, Long> processMetricsFromLogFile() {
113         final String PATTERN = "^(.*)=(\\d+)$";
114         final Pattern r = Pattern.compile(PATTERN);
115         Map<String, Long> metrics = new HashMap<>();
116         BufferedReader br = null;
117         try {
118             br = new BufferedReader(new FileReader(mLogFile));
119             // First line is elapsed time
120             String line = br.readLine();
121             Matcher m;
122             while ((line = br.readLine()) != null) {
123                 m = r.matcher(line);
124                 if (m.find()) {
125                     Log.i(LOG_TAG, m.group(1) + "=" + m.group(2));
126                     metrics.put(m.group(1), Long.parseLong(m.group(2)));
127                 }
128             }
129             mLogFile.delete();
130         } catch (IOException e) {
131             Log.e(LOG_TAG, e.toString());
132         }
133 
134         try {
135             if (br != null) br.close();
136         } catch (IOException e) {
137             Log.e(LOG_TAG, e.toString());
138         }
139         return metrics;
140     }
141 }
142