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.util.Slog;
20 
21 import java.io.FileInputStream;
22 import java.util.Iterator;
23 
24 /**
25  * Reads and parses wakelock stats from the kernel (/proc/wakelocks).
26  */
27 public class KernelWakelockReader {
28     private static final String TAG = "KernelWakelockReader";
29     private static int sKernelWakelockUpdateVersion = 0;
30     private static final String sWakelockFile = "/proc/wakelocks";
31     private static final String sWakeupSourceFile = "/d/wakeup_sources";
32 
33     private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
34         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
35                               Process.PROC_QUOTES,
36         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
37         Process.PROC_TAB_TERM,
38         Process.PROC_TAB_TERM,
39         Process.PROC_TAB_TERM,
40         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
41     };
42 
43     private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
44         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
45         Process.PROC_TAB_TERM|Process.PROC_COMBINE|
46                               Process.PROC_OUT_LONG,                  // 1: count
47         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
48         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
49         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
50         Process.PROC_TAB_TERM|Process.PROC_COMBINE,
51         Process.PROC_TAB_TERM|Process.PROC_COMBINE
52                              |Process.PROC_OUT_LONG,                  // 6: totalTime
53     };
54 
55     private final String[] mProcWakelocksName = new String[3];
56     private final long[] mProcWakelocksData = new long[3];
57 
58     /**
59      * Reads kernel wakelock stats and updates the staleStats with the new information.
60      * @param staleStats Existing object to update.
61      * @return the updated data.
62      */
readKernelWakelockStats(KernelWakelockStats staleStats)63     public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
64         byte[] buffer = new byte[32*1024];
65         int len;
66         boolean wakeup_sources;
67 
68         try {
69             FileInputStream is;
70             try {
71                 is = new FileInputStream(sWakelockFile);
72                 wakeup_sources = false;
73             } catch (java.io.FileNotFoundException e) {
74                 try {
75                     is = new FileInputStream(sWakeupSourceFile);
76                     wakeup_sources = true;
77                 } catch (java.io.FileNotFoundException e2) {
78                     Slog.wtf(TAG, "neither " + sWakelockFile + " nor " +
79                             sWakeupSourceFile + " exists");
80                     return null;
81                 }
82             }
83 
84             len = is.read(buffer);
85             is.close();
86         } catch (java.io.IOException e) {
87             Slog.wtf(TAG, "failed to read kernel wakelocks", e);
88             return null;
89         }
90 
91         if (len > 0) {
92             if (len >= buffer.length) {
93                 Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
94             }
95             int i;
96             for (i=0; i<len; i++) {
97                 if (buffer[i] == '\0') {
98                     len = i;
99                     break;
100                 }
101             }
102         }
103         return parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
104     }
105 
106     /**
107      * Reads the wakelocks and updates the staleStats with the new information.
108      */
parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, final KernelWakelockStats staleStats)109     private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
110                                                    final KernelWakelockStats staleStats) {
111         String name;
112         int count;
113         long totalTime;
114         int startIndex;
115         int endIndex;
116         int numUpdatedWlNames = 0;
117 
118         // Advance past the first line.
119         int i;
120         for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
121         startIndex = endIndex = i + 1;
122 
123         synchronized(this) {
124             sKernelWakelockUpdateVersion++;
125             while (endIndex < len) {
126                 for (endIndex=startIndex;
127                         endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
128                         endIndex++);
129                 endIndex++; // endIndex is an exclusive upper bound.
130                 // Don't go over the end of the buffer, Process.parseProcLine might
131                 // write to wlBuffer[endIndex]
132                 if (endIndex >= (len - 1) ) {
133                     return staleStats;
134                 }
135 
136                 String[] nameStringArray = mProcWakelocksName;
137                 long[] wlData = mProcWakelocksData;
138                 // Stomp out any bad characters since this is from a circular buffer
139                 // A corruption is seen sometimes that results in the vm crashing
140                 // This should prevent crashes and the line will probably fail to parse
141                 for (int j = startIndex; j < endIndex; j++) {
142                     if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
143                 }
144                 boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
145                         wakeup_sources ? WAKEUP_SOURCES_FORMAT :
146                                          PROC_WAKELOCKS_FORMAT,
147                         nameStringArray, wlData, null);
148 
149                 name = nameStringArray[0];
150                 count = (int) wlData[1];
151 
152                 if (wakeup_sources) {
153                         // convert milliseconds to microseconds
154                         totalTime = wlData[2] * 1000;
155                 } else {
156                         // convert nanoseconds to microseconds with rounding.
157                         totalTime = (wlData[2] + 500) / 1000;
158                 }
159 
160                 if (parsed && name.length() > 0) {
161                     if (!staleStats.containsKey(name)) {
162                         staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
163                                 sKernelWakelockUpdateVersion));
164                         numUpdatedWlNames++;
165                     } else {
166                         KernelWakelockStats.Entry kwlStats = staleStats.get(name);
167                         if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
168                             kwlStats.mCount += count;
169                             kwlStats.mTotalTime += totalTime;
170                         } else {
171                             kwlStats.mCount = count;
172                             kwlStats.mTotalTime = totalTime;
173                             kwlStats.mVersion = sKernelWakelockUpdateVersion;
174                             numUpdatedWlNames++;
175                         }
176                     }
177                 } else if (!parsed) {
178                     try {
179                         Slog.wtf(TAG, "Failed to parse proc line: " +
180                                 new String(wlBuffer, startIndex, endIndex - startIndex));
181                     } catch (Exception e) {
182                         Slog.wtf(TAG, "Failed to parse proc line!");
183                     }
184                 }
185                 startIndex = endIndex;
186             }
187 
188             if (staleStats.size() != numUpdatedWlNames) {
189                 // Don't report old data.
190                 Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
191                 while (itr.hasNext()) {
192                     if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
193                         itr.remove();
194                     }
195                 }
196             }
197 
198             staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
199             return staleStats;
200         }
201     }
202 }
203