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