/* * Copyright (C) 2006 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.server.am; import com.android.internal.app.IBatteryStats; import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Config; import android.util.Log; import android.util.SparseArray; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * All information we are collecting about things that can happen that impact * battery life. */ public final class BatteryStats extends IBatteryStats.Stub { public static final int WAKE_TYPE_PARTIAL = 0; public static final int WAKE_TYPE_FULL = 1; public static final int WAKE_TYPE_WINDOW = 2; /** * Include all of the loaded data in the stats. */ public static final int STATS_LOADED = 0; /** * Include only the last run in the stats. */ public static final int STATS_LAST = 1; /** * Include only the current run in the stats. */ public static final int STATS_CURRENT = 2; static final int VERSION = 11; static IBatteryStats sService; final File mFile; final File mBackupFile; Context mContext; /** * The statistics we have collected organized by uids. */ final SparseArray uidStats = new SparseArray(); int mStartCount; long mBatteryUptime; long mBatteryUptimeStart; long mBatteryLastUptime; long mBatteryRealtime; long mBatteryRealtimeStart; long mBatteryLastRealtime; long mUptime; long mUptimeStart; long mLastUptime; long mRealtime; long mRealtimeStart; long mLastRealtime; /** * These provide time bases that discount the time the device is plugged * in to power. */ boolean mOnBattery = true; long mTrackBatteryPastUptime = 0; long mTrackBatteryUptimeStart = 0; long mTrackBatteryPastRealtime = 0; long mTrackBatteryRealtimeStart = 0; /** * State for keeping track of timing information. */ final static class Timer { long totalTime; long startTime; int nesting; int count; long loadedTotalTime; int loadedCount; long lastTotalTime; int lastCount; void startRunningLocked(BatteryStats stats) { if (nesting == 0) { nesting = 1; startTime = stats.getBatteryUptimeLocked(); count++; } else { nesting++; } } void stopRunningLocked(BatteryStats stats) { if (nesting == 1) { nesting = 0; long heldTime = stats.getBatteryUptimeLocked() - startTime; if (heldTime != 0) { totalTime += heldTime; } else { count--; } } else { nesting--; } } long computeRunTimeLocked(long curTime) { return totalTime + (nesting > 0 ? (curTime-startTime) : 0); } void writeLocked(Parcel out, long curTime) throws java.io.IOException { long runTime = computeRunTimeLocked(curTime); out.writeLong(runTime); out.writeLong(runTime - loadedTotalTime); out.writeInt(count); out.writeInt(count - loadedCount); } void readLocked(Parcel in) throws java.io.IOException { totalTime = loadedTotalTime = in.readLong(); lastTotalTime = in.readLong(); count = loadedCount = in.readInt(); lastCount = in.readInt(); nesting = 0; } } /** * The statistics associated with a particular uid. */ final class Uid { /** * The statics we have collected for this uid's wake locks. */ final HashMap wakelockStats = new HashMap(); /** * The statics we have collected for this uid's processes. */ final HashMap processStats = new HashMap(); /** * The statics we have collected for this uid's processes. */ final HashMap packageStats = new HashMap(); /** * The statistics associated with a particular wake lock. */ final class Wakelock { /** * How long (in ms) this uid has been keeping the device partially awake. */ Timer wakeTimePartial; /** * How long (in ms) this uid has been keeping the device fully awake. */ Timer wakeTimeFull; /** * How long (in ms) this uid has had a window keeping the device awake. */ Timer wakeTimeWindow; } /** * The statistics associated with a particular process. */ final class Proc { /** * Total time (in 1/100 sec) spent executing in user code. */ long userTime; /** * Total time (in 1/100 sec) spent executing in kernel code. */ long systemTime; /** * Number of times the process has been started. */ int starts; /** * The amount of user time loaded from a previous save. */ long loadedUserTime; /** * The amount of system time loaded from a previous save. */ long loadedSystemTime; /** * The number of times the process has started from a previous save. */ int loadedStarts; /** * The amount of user time loaded from the previous run. */ long lastUserTime; /** * The amount of system time loaded from the previous run. */ long lastSystemTime; /** * The number of times the process has started from the previous run. */ int lastStarts; BatteryStats getBatteryStats() { return BatteryStats.this; } } /** * The statistics associated with a particular package. */ final class Pkg { /** * Number of times this package has done something that could wake up the * device from sleep. */ int wakeups; /** * Number of things that could wake up the device loaded from a * previous save. */ int loadedWakeups; /** * Number of things that could wake up the device as of the * last run. */ int lastWakeups; /** * The statics we have collected for this package's services. */ final HashMap serviceStats = new HashMap(); /** * The statistics associated with a particular service. */ final class Serv { /** * Total time (ms) the service has been left started. */ long startTime; /** * If service has been started and not yet stopped, this is * when it was started. */ long runningSince; /** * True if we are currently running. */ boolean running; /** * Total number of times startService() has been called. */ int starts; /** * Total time (ms) the service has been left launched. */ long launchedTime; /** * If service has been launched and not yet exited, this is * when it was launched. */ long launchedSince; /** * True if we are currently launched. */ boolean launched; /** * Total number times the service has been launched. */ int launches; /** * The amount of time spent started loaded from a previous save. */ long loadedStartTime; /** * The number of starts loaded from a previous save. */ int loadedStarts; /** * The number of launches loaded from a previous save. */ int loadedLaunches; /** * The amount of time spent started as of the last run. */ long lastStartTime; /** * The number of starts as of the last run. */ int lastStarts; /** * The number of launches as of the last run. */ int lastLaunches; long getLaunchTimeToNowLocked(long now) { if (!launched) return launchedTime; return launchedTime + now - launchedSince; } long getStartTimeToNowLocked(long now) { if (!running) return startTime; return startTime + now - runningSince; } void startLaunchedLocked() { if (!launched) { launches++; launchedSince = getBatteryUptimeLocked(); launched = true; } } void stopLaunchedLocked() { if (launched) { long time = getBatteryUptimeLocked() - launchedSince; if (time > 0) { launchedTime += time; } else { launches--; } launched = false; } } void startRunningLocked() { if (!running) { starts++; runningSince = getBatteryUptimeLocked(); running = true; } } void stopRunningLocked() { if (running) { long time = getBatteryUptimeLocked() - runningSince; if (time > 0) { startTime += time; } else { starts--; } running = false; } } BatteryStats getBatteryStats() { return BatteryStats.this; } } BatteryStats getBatteryStats() { return BatteryStats.this; } private final Serv newServiceStatsLocked() { return new Serv(); } } /** * Retrieve the statistics object for a particular process, creating * if needed. */ Proc getProcessStatsLocked(String name) { Proc ps = processStats.get(name); if (ps == null) { ps = new Proc(); processStats.put(name, ps); } return ps; } /** * Retrieve the statistics object for a particular service, creating * if needed. */ Pkg getPackageStatsLocked(String name) { Pkg ps = packageStats.get(name); if (ps == null) { ps = new Pkg(); packageStats.put(name, ps); } return ps; } /** * Retrieve the statistics object for a particular service, creating * if needed. */ Pkg.Serv getServiceStatsLocked(String pkg, String serv) { Pkg ps = getPackageStatsLocked(pkg); Pkg.Serv ss = ps.serviceStats.get(serv); if (ss == null) { ss = ps.newServiceStatsLocked(); ps.serviceStats.put(serv, ss); } return ss; } Timer getWakeTimerLocked(String name, int type) { Wakelock wl = wakelockStats.get(name); if (wl == null) { wl = new Wakelock(); wakelockStats.put(name, wl); } Timer t = null; switch (type) { case WAKE_TYPE_PARTIAL: t = wl.wakeTimePartial; if (t == null) { t = new Timer(); wl.wakeTimePartial = t; } return t; case WAKE_TYPE_FULL: t = wl.wakeTimeFull; if (t == null) { t = new Timer(); wl.wakeTimeFull = t; } return t; case WAKE_TYPE_WINDOW: t = wl.wakeTimeWindow; if (t == null) { t = new Timer(); wl.wakeTimeWindow = t; } return t; } return t; } void noteStartWakeLocked(String name, int type) { Timer t = getWakeTimerLocked(name, type); if (t != null) { t.startRunningLocked(BatteryStats.this); } } void noteStopWakeLocked(String name, int type) { Timer t = getWakeTimerLocked(name, type); if (t != null) { t.stopRunningLocked(BatteryStats.this); } } BatteryStats getBatteryStats() { return BatteryStats.this; } } BatteryStats(String filename) { mFile = new File(filename); mBackupFile = new File(filename + ".bak"); mStartCount++; mUptimeStart = SystemClock.uptimeMillis(); mRealtimeStart = SystemClock.elapsedRealtime(); } public void publish(Context context) { mContext = context; ServiceManager.addService("batteryinfo", asBinder()); } public static IBatteryStats getService() { if (sService != null) { return sService; } IBinder b = ServiceManager.getService("batteryinfo"); sService = asInterface(b); return sService; } public void noteStartWakelock(int uid, String name, int type) { enforceCallingPermission(); synchronized(this) { getUidStatsLocked(uid).noteStartWakeLocked(name, type); } } public void noteStopWakelock(int uid, String name, int type) { enforceCallingPermission(); synchronized(this) { getUidStatsLocked(uid).noteStopWakeLocked(name, type); } } public boolean isOnBattery() { return mOnBattery; } public void setOnBattery(boolean onBattery) { enforceCallingPermission(); synchronized(this) { if (mOnBattery != onBattery) { if (onBattery) { mTrackBatteryUptimeStart = SystemClock.uptimeMillis(); mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime(); } else { mTrackBatteryPastUptime += SystemClock.uptimeMillis() - mTrackBatteryUptimeStart; mTrackBatteryPastRealtime += SystemClock.elapsedRealtime() - mTrackBatteryRealtimeStart; } mOnBattery = onBattery; } } } public long getAwakeTimeBattery() { return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT); } public long getAwakeTimePlugged() { return SystemClock.uptimeMillis() - getAwakeTimeBattery(); } long getBatteryUptimeLocked() { long time = mTrackBatteryPastUptime; if (mOnBattery) { time += SystemClock.uptimeMillis() - mTrackBatteryUptimeStart; } return time; } long getBatteryRealtimeLocked() { long time = mTrackBatteryPastRealtime; if (mOnBattery) { time += SystemClock.elapsedRealtime() - mTrackBatteryRealtimeStart; } return time; } long computeUptime(long curTime, int which) { switch (which) { case STATS_LOADED: return mUptime + (curTime-mUptimeStart); case STATS_LAST: return mLastUptime; case STATS_CURRENT: return (curTime-mUptimeStart); } return 0; } long computeRealtime(long curTime, int which) { switch (which) { case STATS_LOADED: return mRealtime + (curTime-mRealtimeStart); case STATS_LAST: return mLastRealtime; case STATS_CURRENT: return (curTime-mRealtimeStart); } return 0; } long computeBatteryUptime(long curTime, int which) { switch (which) { case STATS_LOADED: return mBatteryUptime + (curTime-mBatteryUptimeStart); case STATS_LAST: return mBatteryLastUptime; case STATS_CURRENT: return (curTime-mBatteryUptimeStart); } return 0; } long computeBatteryRealtime(long curTime, int which) { switch (which) { case STATS_LOADED: return mBatteryRealtime + (curTime-mBatteryRealtimeStart); case STATS_LAST: return mBatteryLastRealtime; case STATS_CURRENT: return (curTime-mBatteryRealtimeStart); } return 0; } public void enforceCallingPermission() { if (Binder.getCallingPid() == Process.myPid()) { return; } mContext.enforcePermission(android.Manifest.permission.BATTERY_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null); } /** * Retrieve the statistics object for a particular uid, creating if needed. */ Uid getUidStatsLocked(int uid) { Uid u = uidStats.get(uid); if (u == null) { u = new Uid(); uidStats.put(uid, u); } return u; } /** * Retrieve the statistics object for a particular process, creating * if needed. */ Uid.Proc getProcessStatsLocked(int uid, String name) { Uid u = getUidStatsLocked(uid); return u.getProcessStatsLocked(name); } /** * Retrieve the statistics object for a particular process, creating * if needed. */ Uid.Pkg getPackageStatsLocked(int uid, String pkg) { Uid u = getUidStatsLocked(uid); return u.getPackageStatsLocked(pkg); } /** * Retrieve the statistics object for a particular service, creating * if needed. */ Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) { Uid u = getUidStatsLocked(uid); return u.getServiceStatsLocked(pkg, name); } private final static String formatTime(long time) { long sec = time/100; StringBuilder sb = new StringBuilder(); sb.append(sec); if (time != 0) { sb.append('.'); sb.append((char)(((time/10)%10)+'0')); sb.append((char)((time%10)+'0')); } sb.append(" sec"); return sb.toString(); } private final static String formatTimeMs(long time) { long sec = time/1000; StringBuilder sb = new StringBuilder(); sb.append(sec); if (time != 0) { sb.append('.'); sb.append((char)(((time/100)%10)+'0')); sb.append((char)(((time/10)%10)+'0')); sb.append((char)((time%10)+'0')); } sb.append(" sec"); return sb.toString(); } final String printWakeLock(StringBuilder sb, Timer timer, long now, String name, int which, String linePrefix) { if (timer != null) { long totalTime; int count; if (which == STATS_LAST) { totalTime = timer.lastTotalTime; count = timer.lastCount; } else { totalTime = timer.computeRunTimeLocked(now); count = timer.count; if (which == STATS_CURRENT) { totalTime -= timer.loadedTotalTime; count -= timer.loadedCount; } } if (totalTime != 0) { sb.append(linePrefix); sb.append(formatTimeMs(totalTime)); sb.append(' '); sb.append(name); sb.append(' '); sb.append('('); sb.append(count); sb.append(" times)"); return ", "; } } return linePrefix; } final void dumpLocked(FileDescriptor fd, PrintWriter pw, String prefix, int which) { final long NOW = getBatteryUptimeLocked(); StringBuilder sb = new StringBuilder(); if (which == STATS_LOADED) { pw.println(prefix + "Current and Historic Battery Usage Statistics:"); pw.println(prefix + " System starts: " + mStartCount); } else if (which == STATS_LAST) { pw.println(prefix + "Last Battery Usage Statistics:"); } else { pw.println(prefix + "Current Battery Usage Statistics:"); } pw.println(prefix + " On battery: " + formatTimeMs(computeBatteryUptime(NOW, which)) + " uptime, " + formatTimeMs(computeBatteryRealtime(getBatteryRealtimeLocked(), which)) + " realtime"); pw.println(prefix + " Total: " + formatTimeMs(computeUptime(SystemClock.uptimeMillis(), which)) + " uptime, " + formatTimeMs(computeRealtime(SystemClock.elapsedRealtime(), which)) + " realtime"); pw.println(" "); final int NU = uidStats.size(); for (int iu=0; iu 0) { for (Map.Entry ent : u.wakelockStats.entrySet()) { Uid.Wakelock wl = ent.getValue(); String linePrefix = ": "; sb.setLength(0); sb.append(prefix); sb.append(" Wake lock "); sb.append(ent.getKey()); linePrefix = printWakeLock(sb, wl.wakeTimeFull, NOW, "full", which, linePrefix); linePrefix = printWakeLock(sb, wl.wakeTimePartial, NOW, "partial", which, linePrefix); linePrefix = printWakeLock(sb, wl.wakeTimeWindow, NOW, "window", which, linePrefix); if (linePrefix.equals(": ")) { sb.append(": (nothing executed)"); } pw.println(sb.toString()); uidActivity = true; } } if (u.processStats.size() > 0) { for (Map.Entry ent : u.processStats.entrySet()) { BatteryStats.Uid.Proc ps = ent.getValue(); long userTime; long systemTime; int starts; if (which == STATS_LAST) { userTime = ps.lastUserTime; systemTime = ps.lastSystemTime; starts = ps.lastStarts; } else { userTime = ps.userTime; systemTime = ps.systemTime; starts = ps.starts; if (which == STATS_CURRENT) { userTime -= ps.loadedUserTime; systemTime -= ps.loadedSystemTime; starts -= ps.loadedStarts; } } if (userTime != 0 || systemTime != 0 || starts != 0) { pw.println(prefix + " Proc " + ent.getKey() + ":"); pw.println(prefix + " CPU: " + formatTime(userTime) + " user + " + formatTime(systemTime) + " kernel"); pw.println(prefix + " " + starts + " process starts"); uidActivity = true; } } } if (u.packageStats.size() > 0) { for (Map.Entry ent : u.packageStats.entrySet()) { pw.println(prefix + " Apk " + ent.getKey() + ":"); boolean apkActivity = false; BatteryStats.Uid.Pkg ps = ent.getValue(); int wakeups; if (which == STATS_LAST) { wakeups = ps.lastWakeups; } else { wakeups = ps.wakeups; if (which == STATS_CURRENT) { wakeups -= ps.loadedWakeups; } } if (wakeups != 0) { pw.println(prefix + " " + wakeups + " wakeup alarms"); apkActivity = true; } if (ps.serviceStats.size() > 0) { for (Map.Entry sent : ps.serviceStats.entrySet()) { BatteryStats.Uid.Pkg.Serv ss = sent.getValue(); long time; int starts; int launches; if (which == STATS_LAST) { time = ss.lastStartTime; starts = ss.lastStarts; launches = ss.lastLaunches; } else { time = ss.getStartTimeToNowLocked(NOW); starts = ss.starts; launches = ss.launches; if (which == STATS_CURRENT) { time -= ss.loadedStartTime; starts -= ss.loadedStarts; launches -= ss.loadedLaunches; } } if (time != 0 || starts != 0 || launches != 0) { pw.println(prefix + " Service " + sent.getKey() + ":"); pw.println(prefix + " Time spent started: " + formatTimeMs(time)); pw.println(prefix + " Starts: " + starts + ", launches: " + launches); apkActivity = true; } } } if (!apkActivity) { pw.println(prefix + " (nothing executed)"); } uidActivity = true; } } if (!uidActivity) { pw.println(prefix + " (nothing executed)"); } } } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (this) { dumpLocked(fd, pw, "", STATS_LOADED); pw.println(""); dumpLocked(fd, pw, "", STATS_LAST); pw.println(""); dumpLocked(fd, pw, "", STATS_CURRENT); } } void writeLocked() { // Keep the old file around until we know the new one has // been successfully written. if (mFile.exists()) { if (mBackupFile.exists()) { mBackupFile.delete(); } mFile.renameTo(mBackupFile); } try { FileOutputStream stream = new FileOutputStream(mFile); final long NOW = getBatteryUptimeLocked(); final long NOWREAL = getBatteryRealtimeLocked(); final long NOW_SYS = SystemClock.uptimeMillis(); final long NOWREAL_SYS = SystemClock.elapsedRealtime(); Parcel out = Parcel.obtain(); out.writeInt(VERSION); out.writeInt(mStartCount); out.writeLong(computeBatteryUptime(NOW, STATS_LOADED)); out.writeLong(computeBatteryUptime(NOW, STATS_CURRENT)); out.writeLong(computeBatteryRealtime(NOWREAL, STATS_LOADED)); out.writeLong(computeBatteryRealtime(NOWREAL, STATS_CURRENT)); out.writeLong(computeUptime(NOW_SYS, STATS_LOADED)); out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_LOADED)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT)); final int NU = uidStats.size(); out.writeInt(NU); for (int iu=0; iu 0) { for (Map.Entry ent : u.wakelockStats.entrySet()) { out.writeString(ent.getKey()); Uid.Wakelock wl = ent.getValue(); if (wl.wakeTimeFull != null) { out.writeInt(1); wl.wakeTimeFull.writeLocked(out, NOW); } else { out.writeInt(0); } if (wl.wakeTimePartial != null) { out.writeInt(1); wl.wakeTimePartial.writeLocked(out, NOW); } else { out.writeInt(0); } if (wl.wakeTimeWindow != null) { out.writeInt(1); wl.wakeTimeWindow.writeLocked(out, NOW); } else { out.writeInt(0); } } } int NP = u.processStats.size(); out.writeInt(NP); if (NP > 0) { for (Map.Entry ent : u.processStats.entrySet()) { out.writeString(ent.getKey()); BatteryStats.Uid.Proc ps = ent.getValue(); out.writeLong(ps.userTime); out.writeLong(ps.userTime - ps.loadedUserTime); out.writeLong(ps.systemTime); out.writeLong(ps.systemTime - ps.loadedSystemTime); out.writeInt(ps.starts); out.writeInt(ps.starts - ps.loadedStarts); } } NP = u.packageStats.size(); out.writeInt(NP); if (NP > 0) { for (Map.Entry ent : u.packageStats.entrySet()) { out.writeString(ent.getKey()); BatteryStats.Uid.Pkg ps = ent.getValue(); out.writeInt(ps.wakeups); out.writeInt(ps.wakeups - ps.loadedWakeups); final int NS = ps.serviceStats.size(); out.writeInt(NS); if (NS > 0) { for (Map.Entry sent : ps.serviceStats.entrySet()) { out.writeString(sent.getKey()); BatteryStats.Uid.Pkg.Serv ss = sent.getValue(); long time = ss.getStartTimeToNowLocked(NOW); out.writeLong(time); out.writeLong(time - ss.loadedStartTime); out.writeInt(ss.starts); out.writeInt(ss.starts - ss.loadedStarts); out.writeInt(ss.launches); out.writeInt(ss.launches - ss.loadedLaunches); } } } } } stream.write(out.marshall()); out.recycle(); stream.flush(); stream.close(); mBackupFile.delete(); } catch(java.io.IOException e) { Log.e("BatteryStats", "Error writing battery statistics", e); } } static byte[] readFully(FileInputStream stream) throws java.io.IOException { int pos = 0; int avail = stream.available(); byte[] data = new byte[avail]; while (true) { int amt = stream.read(data, pos, data.length-pos); //Log.i("foo", "Read " + amt + " bytes at " + pos // + " of avail " + data.length); if (amt <= 0) { //Log.i("foo", "**** FINISHED READING: pos=" + pos // + " len=" + data.length); return data; } pos += amt; avail = stream.available(); if (avail > data.length-pos) { byte[] newData = new byte[pos+avail]; System.arraycopy(data, 0, newData, 0, pos); data = newData; } } } void readLocked() { uidStats.clear(); FileInputStream stream = null; if (mBackupFile.exists()) { try { stream = new FileInputStream(mBackupFile); } catch (java.io.IOException e) { // We'll try for the normal settings file. } } try { if (stream == null) { if (!mFile.exists()) { return; } stream = new FileInputStream(mFile); } byte[] raw = readFully(stream); Parcel in = Parcel.obtain(); in.unmarshall(raw, 0, raw.length); in.setDataPosition(0); stream.close(); final int version = in.readInt(); //Log.i("foo", "Read version: got " + version + ", expecting " + VERSION); if (version != VERSION) { return; } mStartCount = in.readInt(); mBatteryUptime = in.readLong(); mBatteryLastUptime = in.readLong(); mBatteryRealtime = in.readLong(); mBatteryLastRealtime = in.readLong(); mUptime = in.readLong(); mLastUptime = in.readLong(); mRealtime = in.readLong(); mLastRealtime = in.readLong(); //Log.i("foo", "Start count: " + mStartCount); mStartCount++; final int NU = in.readInt(); //Log.i("foo", "Number uids: " + NU); for (int iu=0; iu