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