summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2014-10-29 23:11:52 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-10-29 23:11:54 +0000
commit720a5c51beb5beab442832ea8d169aa5b4edb43f (patch)
treed33e4f00d6323f4b24aee11ae3ff3f3629760cf0 /services
parenta232953ea3f7eedfdd4dd7b58b63886433dbdd56 (diff)
parent1bb18c435dbf967f3a9bc9d680411471b8bab4ac (diff)
downloadframeworks_base-720a5c51beb5beab442832ea8d169aa5b4edb43f.zip
frameworks_base-720a5c51beb5beab442832ea8d169aa5b4edb43f.tar.gz
frameworks_base-720a5c51beb5beab442832ea8d169aa5b4edb43f.tar.bz2
Merge "Add dumpsys output to UsageStatsService, along with --checkin support" into lmp-mr1-dev
Diffstat (limited to 'services')
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java69
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java42
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsXml.java19
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java124
4 files changed, 240 insertions, 14 deletions
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index cfa4436..11da380 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -39,6 +39,7 @@ class UsageStatsDatabase {
private static final String TAG = "UsageStatsDatabase";
private static final boolean DEBUG = UsageStatsService.DEBUG;
+ private static final String BAK_SUFFIX = ".bak";
private final Object mLock = new Object();
private final File[] mIntervalDirs;
@@ -95,11 +96,71 @@ class UsageStatsDatabase {
}
}
+ public interface CheckinAction {
+ boolean checkin(IntervalStats stats);
+ }
+
+ /**
+ * Calls {@link CheckinAction#checkin(IntervalStats)} on the given {@link CheckinAction}
+ * for all {@link IntervalStats} that haven't been checked-in.
+ * If any of the calls to {@link CheckinAction#checkin(IntervalStats)} returns false or throws
+ * an exception, the check-in will be aborted.
+ *
+ * @param checkinAction The callback to run when checking-in {@link IntervalStats}.
+ * @return true if the check-in succeeded.
+ */
+ public boolean checkinDailyFiles(CheckinAction checkinAction) {
+ synchronized (mLock) {
+ final TimeSparseArray<AtomicFile> files =
+ mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY];
+ final int fileCount = files.size();
+ int start = 0;
+ while (start < fileCount - 1) {
+ if (!files.valueAt(start).getBaseFile().getName().endsWith("-c")) {
+ break;
+ }
+ }
+
+ if (start == fileCount - 1) {
+ return true;
+ }
+
+ try {
+ IntervalStats stats = new IntervalStats();
+ for (int i = start; i < fileCount - 1; i++) {
+ UsageStatsXml.read(files.valueAt(i), stats);
+ if (!checkinAction.checkin(stats)) {
+ return false;
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to check-in", e);
+ return false;
+ }
+
+ // We have successfully checked-in the stats, so rename the files so that they
+ // are marked as checked-in.
+ for (int i = start; i < fileCount - 1; i++) {
+ final AtomicFile file = files.valueAt(i);
+ final File checkedInFile = new File(file.getBaseFile().getParent(),
+ file.getBaseFile().getName() + "-c");
+ if (!file.getBaseFile().renameTo(checkedInFile)) {
+ // We must return success, as we've already marked some files as checked-in.
+ // It's better to repeat ourselves than to lose data.
+ Slog.e(TAG, "Failed to mark file " + file.getBaseFile().getPath()
+ + " as checked-in");
+ return true;
+ }
+ }
+ }
+ return true;
+ }
+
private void indexFilesLocked() {
final FilenameFilter backupFileFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
- return !name.endsWith(".bak");
+ return !name.endsWith(BAK_SUFFIX);
}
};
@@ -383,10 +444,10 @@ class UsageStatsDatabase {
if (files != null) {
for (File f : files) {
String path = f.getPath();
- if (path.endsWith(".bak")) {
- f = new File(path.substring(0, path.length() - 4));
+ if (path.endsWith(BAK_SUFFIX)) {
+ f = new File(path.substring(0, path.length() - BAK_SUFFIX.length()));
}
- long beginTime = Long.parseLong(f.getName());
+ long beginTime = UsageStatsXml.parseBeginTime(f);
if (beginTime < expiryTime) {
new AtomicFile(f).delete();
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7ff246a..485b2a2 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -33,7 +33,6 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Binder;
-import android.os.Debug;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
@@ -48,9 +47,12 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
@@ -177,7 +179,7 @@ public class UsageStatsService extends SystemService implements
long currentTimeMillis) {
UserUsageStatsService service = mUserState.get(userId);
if (service == null) {
- service = new UserUsageStatsService(userId,
+ service = new UserUsageStatsService(getContext(), userId,
new File(mUsageStatsDir, Integer.toString(userId)), this);
service.init(currentTimeMillis);
mUserState.put(userId, service);
@@ -320,6 +322,30 @@ public class UsageStatsService extends SystemService implements
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
+ /**
+ * Called by the Binder stub.
+ */
+ void dump(String[] args, PrintWriter pw) {
+ synchronized (mLock) {
+ IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
+ ArraySet<String> argSet = new ArraySet<>();
+ argSet.addAll(Arrays.asList(args));
+
+ final int userCount = mUserState.size();
+ for (int i = 0; i < userCount; i++) {
+ idpw.printPair("user", mUserState.keyAt(i));
+ idpw.println();
+ idpw.increaseIndent();
+ if (argSet.contains("--checkin")) {
+ mUserState.valueAt(i).checkin(idpw);
+ } else {
+ mUserState.valueAt(i).dump(idpw);
+ }
+ idpw.decreaseIndent();
+ }
+ }
+ }
+
class H extends Handler {
public H(Looper looper) {
super(looper);
@@ -422,6 +448,18 @@ public class UsageStatsService extends SystemService implements
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump UsageStats from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+ UsageStatsService.this.dump(args, pw);
+ }
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
index 9ce6d63..26148ce 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXml.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -24,21 +24,26 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
public class UsageStatsXml {
private static final String TAG = "UsageStatsXml";
private static final int CURRENT_VERSION = 1;
private static final String USAGESTATS_TAG = "usagestats";
private static final String VERSION_ATTR = "version";
+ private static final String CHECKED_IN_SUFFIX = "-c";
public static long parseBeginTime(AtomicFile file) {
- return Long.parseLong(file.getBaseFile().getName());
+ return parseBeginTime(file.getBaseFile());
+ }
+
+ public static long parseBeginTime(File file) {
+ final String name = file.getName();
+ if (name.endsWith(CHECKED_IN_SUFFIX)) {
+ return Long.parseLong(
+ name.substring(0, name.length() - CHECKED_IN_SUFFIX.length()));
+ }
+ return Long.parseLong(name);
}
public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 4916ec2..6596781 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -23,9 +23,13 @@ import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.res.Configuration;
import android.os.SystemClock;
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.usage.UsageStatsDatabase.StatCombiner;
import java.io.File;
@@ -43,7 +47,13 @@ class UserUsageStatsService {
private static final String TAG = "UsageStatsService";
private static final boolean DEBUG = UsageStatsService.DEBUG;
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private static final int sDateFormatFlags =
+ DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_SHOW_YEAR
+ | DateUtils.FORMAT_NUMERIC_DATE;
+ private final Context mContext;
private final UsageStatsDatabase mDatabase;
private final IntervalStats[] mCurrentStats;
private boolean mStatsChanged = false;
@@ -55,7 +65,8 @@ class UserUsageStatsService {
void onStatsUpdated();
}
- UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
+ UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) {
+ mContext = context;
mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
@@ -433,6 +444,117 @@ class UserUsageStatsService {
tempCal.getTimeInMillis() + ")");
}
+ //
+ // -- DUMP related methods --
+ //
+
+ void checkin(final IndentingPrintWriter pw) {
+ mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
+ @Override
+ public boolean checkin(IntervalStats stats) {
+ printIntervalStats(pw, stats, false);
+ return true;
+ }
+ });
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ // This is not a check-in, only dump in-memory stats.
+ for (int interval = 0; interval < mCurrentStats.length; interval++) {
+ pw.print("In-memory ");
+ pw.print(intervalToString(interval));
+ pw.println(" stats");
+ printIntervalStats(pw, mCurrentStats[interval], true);
+ }
+ }
+
+ private String formatDateTime(long dateTime, boolean pretty) {
+ if (pretty) {
+ return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
+ }
+ return Long.toString(dateTime);
+ }
+
+ private String formatElapsedTime(long elapsedTime, boolean pretty) {
+ if (pretty) {
+ return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
+ }
+ return Long.toString(elapsedTime);
+ }
+
+ void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates) {
+ if (prettyDates) {
+ pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
+ stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
+ } else {
+ pw.printPair("beginTime", stats.beginTime);
+ pw.printPair("endTime", stats.endTime);
+ }
+ pw.println();
+ pw.increaseIndent();
+ pw.println("packages");
+ pw.increaseIndent();
+ final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
+ final int pkgCount = pkgStats.size();
+ for (int i = 0; i < pkgCount; i++) {
+ final UsageStats usageStats = pkgStats.valueAt(i);
+ pw.printPair("package", usageStats.mPackageName);
+ pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
+ pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
+ pw.println();
+ }
+ pw.decreaseIndent();
+
+ pw.println("configurations");
+ pw.increaseIndent();
+ final ArrayMap<Configuration, ConfigurationStats> configStats =
+ stats.configurations;
+ final int configCount = configStats.size();
+ for (int i = 0; i < configCount; i++) {
+ final ConfigurationStats config = configStats.valueAt(i);
+ pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
+ pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
+ pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
+ pw.printPair("count", config.mActivationCount);
+ pw.println();
+ }
+ pw.decreaseIndent();
+
+ pw.println("events");
+ pw.increaseIndent();
+ final TimeSparseArray<UsageEvents.Event> events = stats.events;
+ final int eventCount = events != null ? events.size() : 0;
+ for (int i = 0; i < eventCount; i++) {
+ final UsageEvents.Event event = events.valueAt(i);
+ pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
+ pw.printPair("type", eventToString(event.mEventType));
+ pw.printPair("package", event.mPackage);
+ if (event.mClass != null) {
+ pw.printPair("class", event.mClass);
+ }
+ if (event.mConfiguration != null) {
+ pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+
+ private static String intervalToString(int interval) {
+ switch (interval) {
+ case UsageStatsManager.INTERVAL_DAILY:
+ return "daily";
+ case UsageStatsManager.INTERVAL_WEEKLY:
+ return "weekly";
+ case UsageStatsManager.INTERVAL_MONTHLY:
+ return "monthly";
+ case UsageStatsManager.INTERVAL_YEARLY:
+ return "yearly";
+ default:
+ return "?";
+ }
+ }
private static String eventToString(int eventType) {
switch (eventType) {