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