OSDN Git Service

DO NOT MERGE ANYWHERE: UsageStatsService: Fix app idle issue at rollover time
authorAdam Lesinski <adamlesinski@google.com>
Fri, 8 Jan 2016 02:24:53 +0000 (18:24 -0800)
committerThe Android Automerger <android-build@google.com>
Wed, 24 Feb 2016 21:21:09 +0000 (13:21 -0800)
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

services/usage/java/com/android/server/usage/UserUsageStatsService.java

index b07b815..d8f26ed 100644 (file)
@@ -59,6 +59,7 @@ class UserUsageStatsService {
     private final Context mContext;
     private final UsageStatsDatabase mDatabase;
     private final IntervalStats[] mCurrentStats;
+    private IntervalStats mAppIdleRollingWindow;
     private boolean mStatsChanged = false;
     private final UnixCalendar mDailyExpiryDate;
     private final StatsUpdatedListener mListener;
@@ -138,6 +139,8 @@ class UserUsageStatsService {
             initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
                     mDatabase.isFirstUpdate());
         }
+
+        refreshAppIdleRollingWindow(currentTimeMillis);
     }
 
     /**
@@ -171,6 +174,7 @@ class UserUsageStatsService {
         persistActiveStats();
         mDatabase.onTimeChanged(newTime - oldTime);
         loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime);
+        refreshAppIdleRollingWindow(newTime);
     }
 
     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();
     }
 
@@ -223,6 +232,7 @@ class UserUsageStatsService {
         for (IntervalStats stats : mCurrentStats) {
             stats.updateBeginIdleTime(packageName, beginIdleTime);
         }
+        mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime);
         notifyStatsChanged();
     }
 
@@ -230,6 +240,7 @@ class UserUsageStatsService {
         for (IntervalStats stats : mCurrentStats) {
             stats.updateSystemLastUsedTime(packageName, lastUsedTime);
         }
+        mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime);
         notifyStatsChanged();
     }
 
@@ -388,9 +399,8 @@ class UserUsageStatsService {
     }
 
     long getBeginIdleTime(String packageName) {
-        final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
         UsageStats packageUsage;
-        if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
+        if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
             return -1;
         } else {
             return packageUsage.getBeginIdleTime();
@@ -398,9 +408,8 @@ class UserUsageStatsService {
     }
 
     long getSystemLastUsedTime(String packageName) {
-        final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
         UsageStats packageUsage;
-        if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
+        if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
             return -1;
         } else {
             return packageUsage.getLastTimeSystemUsed();
@@ -462,6 +471,8 @@ class UserUsageStatsService {
         }
         persistActiveStats();
 
+        refreshAppIdleRollingWindow(currentTimeMillis);
+
         final long totalTime = SystemClock.elapsedRealtime() - startTime;
         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
                 + " milliseconds");
@@ -521,6 +532,7 @@ class UserUsageStatsService {
                 }
             }
         }
+
         mStatsChanged = false;
         mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
         mDailyExpiryDate.addDays(1);
@@ -530,6 +542,68 @@ class UserUsageStatsService {
                 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 --
     //
@@ -552,6 +626,9 @@ class UserUsageStatsService {
             pw.println(" stats");
             printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
         }
+
+        pw.println("AppIdleRollingWindow cache");
+        printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true);
     }
 
     private String formatDateTime(long dateTime, boolean pretty) {