/* * 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 com.android.internal.annotations.VisibleForTesting; 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 - 1) ) { break; } 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)); } 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; } } } 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 + 1; } // Don't report old data. Iterator itr = staleStats.values().iterator(); while (itr.hasNext()) { if (itr.next().mVersion != sKernelWakelockUpdateVersion) { itr.remove(); } } staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion; return staleStats; } } }