DO NOT MERGE ANYWHERE: UsageStatsService: Fix app idle issue at rollover time
App Idle queries are very frequent and so they only check in memory stats. However, in memory stats can be missing some entries, especially after a rollover, but also due to a larger bug fixed in master (too risky to take now). The fix is to do a deep query (reading older files from disk) and maintain a parallel cache of stats for app idle. That way the rolling window of data required to serve app idle queries stays in memory. Bug:26355386 Change-Id: I6a29bbc25214f6a3c2f24c8c079936e66f99e42e
This commit is contained in:
@@ -59,6 +59,7 @@ class UserUsageStatsService {
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final UsageStatsDatabase mDatabase;
|
private final UsageStatsDatabase mDatabase;
|
||||||
private final IntervalStats[] mCurrentStats;
|
private final IntervalStats[] mCurrentStats;
|
||||||
|
private IntervalStats mAppIdleRollingWindow;
|
||||||
private boolean mStatsChanged = false;
|
private boolean mStatsChanged = false;
|
||||||
private final UnixCalendar mDailyExpiryDate;
|
private final UnixCalendar mDailyExpiryDate;
|
||||||
private final StatsUpdatedListener mListener;
|
private final StatsUpdatedListener mListener;
|
||||||
@@ -138,6 +139,8 @@ class UserUsageStatsService {
|
|||||||
initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
|
initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
|
||||||
mDatabase.isFirstUpdate());
|
mDatabase.isFirstUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshAppIdleRollingWindow(currentTimeMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,6 +174,7 @@ class UserUsageStatsService {
|
|||||||
persistActiveStats();
|
persistActiveStats();
|
||||||
mDatabase.onTimeChanged(newTime - oldTime);
|
mDatabase.onTimeChanged(newTime - oldTime);
|
||||||
loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime);
|
loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime);
|
||||||
|
refreshAppIdleRollingWindow(newTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
|
void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
|
||||||
@@ -212,6 +216,11 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.mEventType != Event.CONFIGURATION_CHANGE) {
|
||||||
|
mAppIdleRollingWindow.update(event.mPackage, event.mTimeStamp, event.mEventType);
|
||||||
|
mAppIdleRollingWindow.updateBeginIdleTime(event.mPackage, deviceUsageTime);
|
||||||
|
}
|
||||||
|
|
||||||
notifyStatsChanged();
|
notifyStatsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +232,7 @@ class UserUsageStatsService {
|
|||||||
for (IntervalStats stats : mCurrentStats) {
|
for (IntervalStats stats : mCurrentStats) {
|
||||||
stats.updateBeginIdleTime(packageName, beginIdleTime);
|
stats.updateBeginIdleTime(packageName, beginIdleTime);
|
||||||
}
|
}
|
||||||
|
mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime);
|
||||||
notifyStatsChanged();
|
notifyStatsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +240,7 @@ class UserUsageStatsService {
|
|||||||
for (IntervalStats stats : mCurrentStats) {
|
for (IntervalStats stats : mCurrentStats) {
|
||||||
stats.updateSystemLastUsedTime(packageName, lastUsedTime);
|
stats.updateSystemLastUsedTime(packageName, lastUsedTime);
|
||||||
}
|
}
|
||||||
|
mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime);
|
||||||
notifyStatsChanged();
|
notifyStatsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,9 +399,8 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long getBeginIdleTime(String packageName) {
|
long getBeginIdleTime(String packageName) {
|
||||||
final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
|
|
||||||
UsageStats packageUsage;
|
UsageStats packageUsage;
|
||||||
if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
|
if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return packageUsage.getBeginIdleTime();
|
return packageUsage.getBeginIdleTime();
|
||||||
@@ -398,9 +408,8 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long getSystemLastUsedTime(String packageName) {
|
long getSystemLastUsedTime(String packageName) {
|
||||||
final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
|
|
||||||
UsageStats packageUsage;
|
UsageStats packageUsage;
|
||||||
if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
|
if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return packageUsage.getLastTimeSystemUsed();
|
return packageUsage.getLastTimeSystemUsed();
|
||||||
@@ -462,6 +471,8 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
persistActiveStats();
|
persistActiveStats();
|
||||||
|
|
||||||
|
refreshAppIdleRollingWindow(currentTimeMillis);
|
||||||
|
|
||||||
final long totalTime = SystemClock.elapsedRealtime() - startTime;
|
final long totalTime = SystemClock.elapsedRealtime() - startTime;
|
||||||
Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
|
Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
|
||||||
+ " milliseconds");
|
+ " milliseconds");
|
||||||
@@ -521,6 +532,7 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mStatsChanged = false;
|
mStatsChanged = false;
|
||||||
mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
|
mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
|
||||||
mDailyExpiryDate.addDays(1);
|
mDailyExpiryDate.addDays(1);
|
||||||
@@ -530,6 +542,68 @@ class UserUsageStatsService {
|
|||||||
tempCal.getTimeInMillis() + ")");
|
tempCal.getTimeInMillis() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void mergePackageStats(IntervalStats dst, IntervalStats src) {
|
||||||
|
dst.endTime = Math.max(dst.endTime, src.endTime);
|
||||||
|
|
||||||
|
final int srcPackageCount = src.packageStats.size();
|
||||||
|
for (int i = 0; i < srcPackageCount; i++) {
|
||||||
|
final String packageName = src.packageStats.keyAt(i);
|
||||||
|
final UsageStats srcStats = src.packageStats.valueAt(i);
|
||||||
|
final UsageStats dstStats = dst.packageStats.get(packageName);
|
||||||
|
if (dstStats == null) {
|
||||||
|
dst.packageStats.put(packageName, new UsageStats(srcStats));
|
||||||
|
} else {
|
||||||
|
dstStats.add(src.packageStats.valueAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all the stats into the first element of the resulting list.
|
||||||
|
*/
|
||||||
|
private static final StatCombiner<IntervalStats> sPackageStatsMerger =
|
||||||
|
new StatCombiner<IntervalStats>() {
|
||||||
|
@Override
|
||||||
|
public void combine(IntervalStats stats, boolean mutable,
|
||||||
|
List<IntervalStats> accumulatedResult) {
|
||||||
|
IntervalStats accum;
|
||||||
|
if (accumulatedResult.isEmpty()) {
|
||||||
|
accum = new IntervalStats();
|
||||||
|
accum.beginTime = stats.beginTime;
|
||||||
|
accumulatedResult.add(accum);
|
||||||
|
} else {
|
||||||
|
accum = accumulatedResult.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergePackageStats(accum, stats);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App idle operates on a rolling window of time. When we roll over time, we end up with a
|
||||||
|
* period of time where in-memory stats are empty and we don't hit the disk for older stats
|
||||||
|
* for performance reasons. Suddenly all apps will become idle.
|
||||||
|
*
|
||||||
|
* Instead, at times we do a deep query to find all the apps that have run in the past few
|
||||||
|
* days and keep the cached data up to date.
|
||||||
|
*
|
||||||
|
* @param currentTimeMillis
|
||||||
|
*/
|
||||||
|
void refreshAppIdleRollingWindow(long currentTimeMillis) {
|
||||||
|
// Start the rolling window for AppIdle requests.
|
||||||
|
List<IntervalStats> stats = mDatabase.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
|
||||||
|
currentTimeMillis - (1000 * 60 * 60 * 24 * 2), currentTimeMillis,
|
||||||
|
sPackageStatsMerger);
|
||||||
|
|
||||||
|
if (stats == null || stats.isEmpty()) {
|
||||||
|
mAppIdleRollingWindow = new IntervalStats();
|
||||||
|
mergePackageStats(mAppIdleRollingWindow,
|
||||||
|
mCurrentStats[UsageStatsManager.INTERVAL_YEARLY]);
|
||||||
|
} else {
|
||||||
|
mAppIdleRollingWindow = stats.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// -- DUMP related methods --
|
// -- DUMP related methods --
|
||||||
//
|
//
|
||||||
@@ -552,6 +626,9 @@ class UserUsageStatsService {
|
|||||||
pw.println(" stats");
|
pw.println(" stats");
|
||||||
printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
|
printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pw.println("AppIdleRollingWindow cache");
|
||||||
|
printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatDateTime(long dateTime, boolean pretty) {
|
private String formatDateTime(long dateTime, boolean pretty) {
|
||||||
|
|||||||
Reference in New Issue
Block a user