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      * On success, returns the updated stats from SystemSupend, else returns null.
157      */
getWakelockStatsFromSystemSuspend( final KernelWakelockStats staleStats)158     private KernelWakelockStats getWakelockStatsFromSystemSuspend(
159             final KernelWakelockStats staleStats) {
160         WakeLockInfo[] wlStats = null;
161         if (mSuspendControlService == null) {
162             try {
163                 mSuspendControlService = ISuspendControlService.Stub.asInterface(
164                     ServiceManager.getServiceOrThrow("suspend_control"));
165             } catch (ServiceNotFoundException e) {
166                 Slog.wtf(TAG, "Required service suspend_control not available", e);
167                 return null;
168             }
169         }
170 
171         try {
172             wlStats = mSuspendControlService.getWakeLockStats();
173             updateWakelockStats(wlStats, staleStats);
174         } catch (RemoteException e) {
175             Slog.wtf(TAG, "Failed to obtain wakelock stats from ISuspendControlService", e);
176             return null;
177         }
178 
179         return staleStats;
180     }
181 
182     /**
183      * Updates statleStats with stats from  SystemSuspend.
184      * @param staleStats Existing object to update.
185      * @return the updated stats.
186      */
187     @VisibleForTesting
updateWakelockStats(WakeLockInfo[] wlStats, final KernelWakelockStats staleStats)188     public KernelWakelockStats updateWakelockStats(WakeLockInfo[] wlStats,
189                                                       final KernelWakelockStats staleStats) {
190         for (WakeLockInfo info : wlStats) {
191             if (!staleStats.containsKey(info.name)) {
192                 staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
193                         info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
194             } else {
195                 KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
196                 kwlStats.mCount = (int) info.activeCount;
197                 // Convert milliseconds to microseconds
198                 kwlStats.mTotalTime = info.totalTime * 1000;
199                 kwlStats.mVersion = sKernelWakelockUpdateVersion;
200             }
201         }
202 
203         return staleStats;
204     }
205 
206     /**
207      * Reads the wakelocks and updates the staleStats with the new information.
208      */
209     @VisibleForTesting
parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, final KernelWakelockStats staleStats)210     public KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
211                                                   final KernelWakelockStats staleStats) {
212         String name;
213         int count;
214         long totalTime;
215         int startIndex;
216         int endIndex;
217 
218         // Advance past the first line.
219         int i;
220         for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
221         startIndex = endIndex = i + 1;
222 
223         synchronized(this) {
224             while (endIndex < len) {
225                 for (endIndex=startIndex;
226                         endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
227                         endIndex++);
228                 // Don't go over the end of the buffer, Process.parseProcLine might
229                 // write to wlBuffer[endIndex]
230                 if (endIndex > (len - 1) ) {
231                     break;
232                 }
233 
234                 String[] nameStringArray = mProcWakelocksName;
235                 long[] wlData = mProcWakelocksData;
236                 // Stomp out any bad characters since this is from a circular buffer
237                 // A corruption is seen sometimes that results in the vm crashing
238                 // This should prevent crashes and the line will probably fail to parse
239                 for (int j = startIndex; j < endIndex; j++) {
240                     if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
241                 }
242                 boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
243                         wakeup_sources ? WAKEUP_SOURCES_FORMAT :
244                                          PROC_WAKELOCKS_FORMAT,
245                         nameStringArray, wlData, null);
246 
247                 name = nameStringArray[0].trim();
248                 count = (int) wlData[1];
249 
250                 if (wakeup_sources) {
251                         // convert milliseconds to microseconds
252                         totalTime = wlData[2] * 1000;
253                 } else {
254                         // convert nanoseconds to microseconds with rounding.
255                         totalTime = (wlData[2] + 500) / 1000;
256                 }
257 
258                 if (parsed && name.length() > 0) {
259                     if (!staleStats.containsKey(name)) {
260                         staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
261                                 sKernelWakelockUpdateVersion));
262                     } else {
263                         KernelWakelockStats.Entry kwlStats = staleStats.get(name);
264                         if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
265                             kwlStats.mCount += count;
266                             kwlStats.mTotalTime += totalTime;
267                         } else {
268                             kwlStats.mCount = count;
269                             kwlStats.mTotalTime = totalTime;
270                             kwlStats.mVersion = sKernelWakelockUpdateVersion;
271                         }
272                     }
273                 } else if (!parsed) {
274                     try {
275                         Slog.wtf(TAG, "Failed to parse proc line: " +
276                                 new String(wlBuffer, startIndex, endIndex - startIndex));
277                     } catch (Exception e) {
278                         Slog.wtf(TAG, "Failed to parse proc line!");
279                     }
280                 }
281                 startIndex = endIndex + 1;
282             }
283 
284             return staleStats;
285         }
286     }
287 
288     /**
289      * Increments sKernelWakelockUpdateVersion and updates the version in staleStats.
290      * @param staleStats Existing object to update.
291      * @return the updated stats.
292      */
293     @VisibleForTesting
updateVersion(KernelWakelockStats staleStats)294     public KernelWakelockStats updateVersion(KernelWakelockStats staleStats) {
295         sKernelWakelockUpdateVersion++;
296         staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
297         return staleStats;
298     }
299 
300     /**
301      * Removes old stats from staleStats.
302      * @param staleStats Existing object to update.
303      * @return the updated stats.
304      */
305     @VisibleForTesting
removeOldStats(final KernelWakelockStats staleStats)306     public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
307         Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
308         while (itr.hasNext()) {
309             if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
310                 itr.remove();
311             }
312         }
313         return staleStats;
314     }
315 }
316