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