blob: cffb0ad9fdb9995174751f727bb73dd530615be3 [file] [log] [blame]
/*
* 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.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.SystemClock;
import android.system.suspend.ISuspendControlService;
import android.system.suspend.WakeLockInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
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 String sSysClassWakeupDir = "/sys/class/wakeup";
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];
private ISuspendControlService mSuspendControlService = null;
private byte[] mKernelWakelockBuffer = new byte[32 * 1024];
/**
* 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) {
boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists();
if (useSystemSuspend) {
// Get both kernel and native wakelock stats from SystemSuspend
updateVersion(staleStats);
if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
Slog.w(TAG, "Failed to get wakelock stats from SystemSuspend");
return null;
}
return removeOldStats(staleStats);
} else {
Arrays.fill(mKernelWakelockBuffer, (byte) 0);
int len = 0;
boolean wakeup_sources;
final long startTime = SystemClock.uptimeMillis();
final int oldMask = StrictMode.allowThreadDiskReadsMask();
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;
}
}
int cnt;
while ((cnt = is.read(mKernelWakelockBuffer, len,
mKernelWakelockBuffer.length - len)) > 0) {
len += cnt;
}
is.close();
} catch (java.io.IOException e) {
Slog.wtf(TAG, "failed to read kernel wakelocks", e);
return null;
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
final long readTime = SystemClock.uptimeMillis() - startTime;
if (readTime > 100) {
Slog.w(TAG, "Reading wakelock stats took " + readTime + "ms");
}
if (len > 0) {
if (len >= mKernelWakelockBuffer.length) {
Slog.wtf(TAG, "Kernel wake locks exceeded mKernelWakelockBuffer size "
+ mKernelWakelockBuffer.length);
}
int i;
for (i=0; i<len; i++) {
if (mKernelWakelockBuffer[i] == '\0') {
len = i;
break;
}
}
}
updateVersion(staleStats);
// Get native wakelock stats from SystemSuspend
if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
Slog.w(TAG, "Failed to get Native wakelock stats from SystemSuspend");
}
// Get kernel wakelock stats
parseProcWakelocks(mKernelWakelockBuffer, len, wakeup_sources, staleStats);
return removeOldStats(staleStats);
}
}
/**
* On success, returns the updated stats from SystemSupend, else returns null.
*/
private KernelWakelockStats getWakelockStatsFromSystemSuspend(
final KernelWakelockStats staleStats) {
WakeLockInfo[] wlStats = null;
if (mSuspendControlService == null) {
try {
mSuspendControlService = ISuspendControlService.Stub.asInterface(
ServiceManager.getServiceOrThrow("suspend_control"));
} catch (ServiceNotFoundException e) {
Slog.wtf(TAG, "Required service suspend_control not available", e);
return null;
}
}
try {
wlStats = mSuspendControlService.getWakeLockStats();
updateWakelockStats(wlStats, staleStats);
} catch (RemoteException e) {
Slog.wtf(TAG, "Failed to obtain wakelock stats from ISuspendControlService", e);
return null;
}
return staleStats;
}
/**
* Updates statleStats with stats from SystemSuspend.
* @param staleStats Existing object to update.
* @return the updated stats.
*/
@VisibleForTesting
public KernelWakelockStats updateWakelockStats(WakeLockInfo[] wlStats,
final KernelWakelockStats staleStats) {
for (WakeLockInfo info : wlStats) {
if (!staleStats.containsKey(info.name)) {
staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
} else {
KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
kwlStats.mCount = (int) info.activeCount;
// Convert milliseconds to microseconds
kwlStats.mTotalTime = info.totalTime * 1000;
kwlStats.mVersion = sKernelWakelockUpdateVersion;
}
}
return staleStats;
}
/**
* Reads the wakelocks and updates the staleStats with the new information.
*/
@VisibleForTesting
public KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
final KernelWakelockStats staleStats) {
String name;
int count;
long totalTime;
int startIndex;
int endIndex;
// 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) {
while (endIndex < len) {
for (endIndex=startIndex;
endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
endIndex++);
// Don't go over the end of the buffer, Process.parseProcLine might
// write to wlBuffer[endIndex]
if (endIndex > (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].trim();
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;
}
return staleStats;
}
}
/**
* Increments sKernelWakelockUpdateVersion and updates the version in staleStats.
* @param staleStats Existing object to update.
* @return the updated stats.
*/
@VisibleForTesting
public KernelWakelockStats updateVersion(KernelWakelockStats staleStats) {
sKernelWakelockUpdateVersion++;
staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
return staleStats;
}
/**
* Removes old stats from staleStats.
* @param staleStats Existing object to update.
* @return the updated stats.
*/
@VisibleForTesting
public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
while (itr.hasNext()) {
if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
itr.remove();
}
}
return staleStats;
}
}