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.server.power.stats;
17 
18 import android.os.Process;
19 import android.os.RemoteException;
20 import android.os.ServiceManager;
21 import android.os.ServiceManager.ServiceNotFoundException;
22 import android.os.StrictMode;
23 import android.os.SystemClock;
24 import android.system.suspend.internal.ISuspendControlServiceInternal;
25 import android.system.suspend.internal.WakeLockInfo;
26 import android.util.Slog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.util.Arrays;
33 import java.util.Iterator;
34 
35 /**
36  * Reads and parses wakelock stats from the kernel (/proc/wakelocks).
37  */
38 public class KernelWakelockReader {
39     private static final String TAG = "KernelWakelockReader";
40     private static int sKernelWakelockUpdateVersion = 0;
41     private static final String sWakelockFile = "/proc/wakelocks";
42     private static final String sWakeupSourceFile = "/d/wakeup_sources";
43     private static final String sSysClassWakeupDir = "/sys/class/wakeup";
44 
45     private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
46         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
47                               Process.PROC_QUOTES,
48         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
49         Process.PROC_TAB_TERM,
50         Process.PROC_TAB_TERM,
51         Process.PROC_TAB_TERM,
52         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
53     };
54 
55     private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
56         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
57         Process.PROC_TAB_TERM|Process.PROC_COMBINE|
58                               Process.PROC_OUT_LONG,                  // 1: count
59         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
60         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
61         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
62         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
63         Process.PROC_TAB_TERM|Process.PROC_COMBINE
64                              |Process.PROC_OUT_LONG,                  // 6: totalTime
65     };
66 
67     private final String[] mProcWakelocksName = new String[3];
68     private final long[] mProcWakelocksData = new long[3];
69     private ISuspendControlServiceInternal mSuspendControlService = null;
70     private byte[] mKernelWakelockBuffer = new byte[32 * 1024];
71 
72     /**
73      * Reads kernel wakelock stats and updates the staleStats with the new information.
74      * @param staleStats Existing object to update.
75      * @return the updated data.
76      */
readKernelWakelockStats(KernelWakelockStats staleStats)77     public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
78         boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists();
79 
80         if (useSystemSuspend) {
81             // static read/write lock protection for sKernelWakelockUpdateVersion
82             synchronized (KernelWakelockReader.class) {
83                 // Get both kernel and native wakelock stats from SystemSuspend
84                 updateVersion(staleStats);
85                 if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
86                     Slog.w(TAG, "Failed to get wakelock stats from SystemSuspend");
87                     return null;
88                 }
89                 return removeOldStats(staleStats);
90             }
91         } else {
92             Arrays.fill(mKernelWakelockBuffer, (byte) 0);
93             int len = 0;
94             boolean wakeup_sources;
95             final long startTime = SystemClock.uptimeMillis();
96 
97             final int oldMask = StrictMode.allowThreadDiskReadsMask();
98             try {
99                 FileInputStream is;
100                 try {
101                     is = new FileInputStream(sWakelockFile);
102                     wakeup_sources = false;
103                 } catch (java.io.FileNotFoundException e) {
104                     try {
105                         is = new FileInputStream(sWakeupSourceFile);
106                         wakeup_sources = true;
107                     } catch (java.io.FileNotFoundException e2) {
108                         Slog.wtf(TAG, "neither " + sWakelockFile + " nor " +
109                                 sWakeupSourceFile + " exists");
110                         return null;
111                     }
112                 }
113 
114                 int cnt;
115                 while ((cnt = is.read(mKernelWakelockBuffer, len,
116                                 mKernelWakelockBuffer.length - len)) > 0) {
117                     len += cnt;
118                 }
119 
120                 is.close();
121             } catch (java.io.IOException e) {
122                 Slog.wtf(TAG, "failed to read kernel wakelocks", e);
123                 return null;
124             } finally {
125                 StrictMode.setThreadPolicyMask(oldMask);
126             }
127 
128             final long readTime = SystemClock.uptimeMillis() - startTime;
129             if (readTime > 100) {
130                 Slog.w(TAG, "Reading wakelock stats took " + readTime + "ms");
131             }
132 
133             if (len > 0) {
134                 if (len >= mKernelWakelockBuffer.length) {
135                     Slog.wtf(TAG, "Kernel wake locks exceeded mKernelWakelockBuffer size "
136                             + mKernelWakelockBuffer.length);
137                 }
138                 int i;
139                 for (i=0; i<len; i++) {
140                     if (mKernelWakelockBuffer[i] == '\0') {
141                         len = i;
142                         break;
143                     }
144                 }
145             }
146 
147             // static read/write lock protection for sKernelWakelockUpdateVersion
148             synchronized (KernelWakelockReader.class) {
149                 updateVersion(staleStats);
150                 // Get native wakelock stats from SystemSuspend
151                 if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
152                     Slog.w(TAG, "Failed to get Native wakelock stats from SystemSuspend");
153                 }
154                 // Get kernel wakelock stats
155                 parseProcWakelocks(mKernelWakelockBuffer, len, wakeup_sources, staleStats);
156                 return removeOldStats(staleStats);
157             }
158         }
159     }
160 
161     /**
162      * Attempt to wait for suspend_control service if not immediately available.
163      */
waitForSuspendControlService()164     private ISuspendControlServiceInternal waitForSuspendControlService()
165             throws ServiceNotFoundException {
166         final String name = "suspend_control_internal";
167         final int numRetries = 5;
168         for (int i = 0; i < numRetries; i++) {
169             mSuspendControlService = ISuspendControlServiceInternal.Stub.asInterface(
170                                         ServiceManager.getService(name));
171             if (mSuspendControlService != null) {
172                 return mSuspendControlService;
173             }
174         }
175         throw new ServiceNotFoundException(name);
176     }
177 
178     /**
179      * On success, returns the updated stats from SystemSupend, else returns null.
180      */
getWakelockStatsFromSystemSuspend( final KernelWakelockStats staleStats)181     private KernelWakelockStats getWakelockStatsFromSystemSuspend(
182             final KernelWakelockStats staleStats) {
183         WakeLockInfo[] wlStats = null;
184         try {
185             mSuspendControlService = waitForSuspendControlService();
186         } catch (ServiceNotFoundException e) {
187             Slog.wtf(TAG, "Required service suspend_control not available", e);
188             return null;
189         }
190 
191         try {
192             wlStats = mSuspendControlService.getWakeLockStats();
193             updateWakelockStats(wlStats, staleStats);
194         } catch (RemoteException e) {
195             Slog.wtf(TAG, "Failed to obtain wakelock stats from ISuspendControlService", e);
196             return null;
197         }
198 
199         return staleStats;
200     }
201 
202     /**
203      * Updates statleStats with stats from  SystemSuspend.
204      * @param staleStats Existing object to update.
205      * @return the updated stats.
206      */
207     @VisibleForTesting
updateWakelockStats(WakeLockInfo[] wlStats, final KernelWakelockStats staleStats)208     public KernelWakelockStats updateWakelockStats(WakeLockInfo[] wlStats,
209                                                       final KernelWakelockStats staleStats) {
210         for (WakeLockInfo info : wlStats) {
211             if (!staleStats.containsKey(info.name)) {
212                 staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
213                         info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
214             } else {
215                 KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
216                 kwlStats.mCount = (int) info.activeCount;
217                 // Convert milliseconds to microseconds
218                 kwlStats.mTotalTime = info.totalTime * 1000;
219                 kwlStats.mVersion = sKernelWakelockUpdateVersion;
220             }
221         }
222 
223         return staleStats;
224     }
225 
226     /**
227      * Reads the wakelocks and updates the staleStats with the new information.
228      */
229     @VisibleForTesting
parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, final KernelWakelockStats staleStats)230     public KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
231                                                   final KernelWakelockStats staleStats) {
232         String name;
233         int count;
234         long totalTime;
235         int startIndex;
236         int endIndex;
237 
238         // Advance past the first line.
239         int i;
240         for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
241         startIndex = endIndex = i + 1;
242 
243         synchronized(this) {
244             while (endIndex < len) {
245                 for (endIndex=startIndex;
246                         endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
247                         endIndex++);
248                 // Don't go over the end of the buffer, Process.parseProcLine might
249                 // write to wlBuffer[endIndex]
250                 if (endIndex > (len - 1) ) {
251                     break;
252                 }
253 
254                 String[] nameStringArray = mProcWakelocksName;
255                 long[] wlData = mProcWakelocksData;
256                 // Stomp out any bad characters since this is from a circular buffer
257                 // A corruption is seen sometimes that results in the vm crashing
258                 // This should prevent crashes and the line will probably fail to parse
259                 for (int j = startIndex; j < endIndex; j++) {
260                     if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
261                 }
262                 boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
263                         wakeup_sources ? WAKEUP_SOURCES_FORMAT :
264                                          PROC_WAKELOCKS_FORMAT,
265                         nameStringArray, wlData, null);
266 
267                 name = nameStringArray[0].trim();
268                 count = (int) wlData[1];
269 
270                 if (wakeup_sources) {
271                         // convert milliseconds to microseconds
272                         totalTime = wlData[2] * 1000;
273                 } else {
274                         // convert nanoseconds to microseconds with rounding.
275                         totalTime = (wlData[2] + 500) / 1000;
276                 }
277 
278                 if (parsed && name.length() > 0) {
279                     if (!staleStats.containsKey(name)) {
280                         staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
281                                 sKernelWakelockUpdateVersion));
282                     } else {
283                         KernelWakelockStats.Entry kwlStats = staleStats.get(name);
284                         if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
285                             kwlStats.mCount += count;
286                             kwlStats.mTotalTime += totalTime;
287                         } else {
288                             kwlStats.mCount = count;
289                             kwlStats.mTotalTime = totalTime;
290                             kwlStats.mVersion = sKernelWakelockUpdateVersion;
291                         }
292                     }
293                 } else if (!parsed) {
294                     try {
295                         Slog.wtf(TAG, "Failed to parse proc line: " +
296                                 new String(wlBuffer, startIndex, endIndex - startIndex));
297                     } catch (Exception e) {
298                         Slog.wtf(TAG, "Failed to parse proc line!");
299                     }
300                 }
301                 startIndex = endIndex + 1;
302             }
303 
304             return staleStats;
305         }
306     }
307 
308     /**
309      * Increments sKernelWakelockUpdateVersion and updates the version in staleStats.
310      * @param staleStats Existing object to update.
311      * @return the updated stats.
312      */
313     @VisibleForTesting
updateVersion(KernelWakelockStats staleStats)314     public KernelWakelockStats updateVersion(KernelWakelockStats staleStats) {
315         sKernelWakelockUpdateVersion++;
316         staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
317         return staleStats;
318     }
319 
320     /**
321      * Removes old stats from staleStats.
322      * @param staleStats Existing object to update.
323      * @return the updated stats.
324      */
325     @VisibleForTesting
removeOldStats(final KernelWakelockStats staleStats)326     public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
327         Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
328         while (itr.hasNext()) {
329             if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
330                 itr.remove();
331             }
332         }
333         return staleStats;
334     }
335 }
336