/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.Process; import android.util.Slog; import java.io.FileInputStream; import java.util.Iterator; /** * Reads and parses wakelock stats from the kernel (/proc/wakelocks). */ public class KernelWakelockReader { private static final String TAG = "KernelWakelockReader"; private static int sKernelWakelockUpdateVersion = 0; private static final String sWakelockFile = "/proc/wakelocks"; private static final String sWakeupSourceFile = "/d/wakeup_sources"; private static final int[] PROC_WAKELOCKS_FORMAT = new int[] { Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name Process.PROC_QUOTES, Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count Process.PROC_TAB_TERM, Process.PROC_TAB_TERM, Process.PROC_TAB_TERM, Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime }; private static final int[] WAKEUP_SOURCES_FORMAT = new int[] { Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name Process.PROC_TAB_TERM|Process.PROC_COMBINE| Process.PROC_OUT_LONG, // 1: count Process.PROC_TAB_TERM|Process.PROC_COMBINE, Process.PROC_TAB_TERM|Process.PROC_COMBINE, Process.PROC_TAB_TERM|Process.PROC_COMBINE, Process.PROC_TAB_TERM|Process.PROC_COMBINE, Process.PROC_TAB_TERM|Process.PROC_COMBINE |Process.PROC_OUT_LONG, // 6: totalTime }; private final String[] mProcWakelocksName = new String[3]; private final long[] mProcWakelocksData = new long[3]; /** * Reads kernel wakelock stats and updates the staleStats with the new information. * @param staleStats Existing object to update. * @return the updated data. */ public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) { byte[] buffer = new byte[32*1024]; int len; boolean wakeup_sources; try { FileInputStream is; try { is = new FileInputStream(sWakelockFile); wakeup_sources = false; } catch (java.io.FileNotFoundException e) { try { is = new FileInputStream(sWakeupSourceFile); wakeup_sources = true; } catch (java.io.FileNotFoundException e2) { Slog.wtf(TAG, "neither " + sWakelockFile + " nor " + sWakeupSourceFile + " exists"); return null; } } len = is.read(buffer); is.close(); } catch (java.io.IOException e) { Slog.wtf(TAG, "failed to read kernel wakelocks", e); return null; } if (len > 0) { if (len >= buffer.length) { Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length); } int i; for (i=0; i<len; i++) { if (buffer[i] == '\0') { len = i; break; } } } return parseProcWakelocks(buffer, len, wakeup_sources, staleStats); } /** * Reads the wakelocks and updates the staleStats with the new information. */ private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, final KernelWakelockStats staleStats) { String name; int count; long totalTime; int startIndex; int endIndex; int numUpdatedWlNames = 0; // Advance past the first line. int i; for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++); startIndex = endIndex = i + 1; synchronized(this) { sKernelWakelockUpdateVersion++; while (endIndex < len) { for (endIndex=startIndex; endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0'; endIndex++); endIndex++; // endIndex is an exclusive upper bound. // Don't go over the end of the buffer, Process.parseProcLine might // write to wlBuffer[endIndex] if (endIndex >= (len - 1) ) { return staleStats; } String[] nameStringArray = mProcWakelocksName; long[] wlData = mProcWakelocksData; // Stomp out any bad characters since this is from a circular buffer // A corruption is seen sometimes that results in the vm crashing // This should prevent crashes and the line will probably fail to parse for (int j = startIndex; j < endIndex; j++) { if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?'; } boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex, wakeup_sources ? WAKEUP_SOURCES_FORMAT : PROC_WAKELOCKS_FORMAT, nameStringArray, wlData, null); name = nameStringArray[0]; count = (int) wlData[1]; if (wakeup_sources) { // convert milliseconds to microseconds totalTime = wlData[2] * 1000; } else { // convert nanoseconds to microseconds with rounding. totalTime = (wlData[2] + 500) / 1000; } if (parsed && name.length() > 0) { if (!staleStats.containsKey(name)) { staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime, sKernelWakelockUpdateVersion)); numUpdatedWlNames++; } else { KernelWakelockStats.Entry kwlStats = staleStats.get(name); if (kwlStats.mVersion == sKernelWakelockUpdateVersion) { kwlStats.mCount += count; kwlStats.mTotalTime += totalTime; } else { kwlStats.mCount = count; kwlStats.mTotalTime = totalTime; kwlStats.mVersion = sKernelWakelockUpdateVersion; numUpdatedWlNames++; } } } else if (!parsed) { try { Slog.wtf(TAG, "Failed to parse proc line: " + new String(wlBuffer, startIndex, endIndex - startIndex)); } catch (Exception e) { Slog.wtf(TAG, "Failed to parse proc line!"); } } startIndex = endIndex; } if (staleStats.size() != numUpdatedWlNames) { // Don't report old data. Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator(); while (itr.hasNext()) { if (itr.next().mVersion != sKernelWakelockUpdateVersion) { itr.remove(); } } } staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion; return staleStats; } } }