Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7cba1d46 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

UsageStats: Fix time change handling

When the time changed, we would end up creating a new stats file, even if we could
reuse an old one. On systems where the time toggled a lot, this would generate
hundreds of files and slow down other IO operations on this set.

Now we properly reuse stats files after time changes. The time at which a particular
file began recording was always clamped to the start of the interval (day, month, year).
However, this makes no sense when the time changes, and clamping to the start of the interval
would always cause a new file to be created, since the current time no longer fell into that
time interval. So now the time is just offset from the last file's end time.

Bug:22716352
Change-Id: I6cec156e2e93b4402116600fa09c1018f3b870fe
parent f8def4d4
Loading
Loading
Loading
Loading
+4 −46
Original line number Diff line number Diff line
@@ -15,40 +15,22 @@
 */
package com.android.server.usage;

import android.app.usage.UsageStatsManager;

/**
 * A handy calendar object that knows nothing of Locale's or TimeZones. This simplifies
 * interval book-keeping. It is *NOT* meant to be used as a user-facing calendar, as it has
 * no concept of Locale or TimeZone.
 */
public class UnixCalendar {
    private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000;
    private static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
    private static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
    private static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
    public static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000;
    public static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
    public static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
    public static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
    private long mTime;

    public UnixCalendar(long time) {
        mTime = time;
    }

    public void truncateToDay() {
        mTime -= mTime % DAY_IN_MILLIS;
    }

    public void truncateToWeek() {
        mTime -= mTime % WEEK_IN_MILLIS;
    }

    public void truncateToMonth() {
        mTime -= mTime % MONTH_IN_MILLIS;
    }

    public void truncateToYear() {
        mTime -= mTime % YEAR_IN_MILLIS;
    }

    public void addDays(int val) {
        mTime += val * DAY_IN_MILLIS;
    }
@@ -72,28 +54,4 @@ public class UnixCalendar {
    public long getTimeInMillis() {
        return mTime;
    }

    public static void truncateTo(UnixCalendar calendar, int intervalType) {
        switch (intervalType) {
            case UsageStatsManager.INTERVAL_YEARLY:
                calendar.truncateToYear();
                break;

            case UsageStatsManager.INTERVAL_MONTHLY:
                calendar.truncateToMonth();
                break;

            case UsageStatsManager.INTERVAL_WEEKLY:
                calendar.truncateToWeek();
                break;

            case UsageStatsManager.INTERVAL_DAILY:
                calendar.truncateToDay();
                break;

            default:
                throw new UnsupportedOperationException("Can't truncate date to interval " +
                        intervalType);
        }
    }
}
+0 −17
Original line number Diff line number Diff line
@@ -349,23 +349,6 @@ class UsageStatsDatabase {
        return null;
    }

    /**
     * Get the time at which the latest stats begin for this interval type.
     */
    public long getLatestUsageStatsBeginTime(int intervalType) {
        synchronized (mLock) {
            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
                throw new IllegalArgumentException("Bad interval type " + intervalType);
            }

            final int statsFileCount = mSortedStatFiles[intervalType].size();
            if (statsFileCount > 0) {
                return mSortedStatFiles[intervalType].keyAt(statsFileCount - 1);
            }
            return -1;
        }
    }

    /**
     * Figures out what to extract from the given IntervalStats object.
     */
+30 −44
Original line number Diff line number Diff line
@@ -65,6 +65,11 @@ class UserUsageStatsService {
    private final String mLogPrefix;
    private final int mUserId;

    private static final long[] INTERVAL_LENGTH = new long[] {
            UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
            UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
    };

    interface StatsUpdatedListener {
        void onStatsUpdated();
    }
@@ -104,18 +109,12 @@ class UserUsageStatsService {

            // By calling loadActiveStats, we will
            // generate new stats for each bucket.
            loadActiveStats(currentTimeMillis,/*force=*/ false, /*resetBeginIdleTime=*/ false);
            loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
        } else {
            // Set up the expiry date to be one day from the latest daily stat.
            // This may actually be today and we will rollover on the first event
            // that is reported.
            mDailyExpiryDate.setTimeInMillis(
                    mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
            mDailyExpiryDate.addDays(1);
            mDailyExpiryDate.truncateToDay();
            Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
                    sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
                    "(" + mDailyExpiryDate.getTimeInMillis() + ")");
            updateRolloverDeadline();
        }

        // Now close off any events that were open at the time this was saved.
@@ -170,7 +169,7 @@ class UserUsageStatsService {
    void onTimeChanged(long oldTime, long newTime, boolean resetBeginIdleTime) {
        persistActiveStats();
        mDatabase.onTimeChanged(newTime - oldTime);
        loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime);
        loadActiveStats(newTime, resetBeginIdleTime);
    }

    void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
@@ -448,7 +447,7 @@ class UserUsageStatsService {

        persistActiveStats();
        mDatabase.prune(currentTimeMillis);
        loadActiveStats(currentTimeMillis, /*force=*/ false, /*resetBeginIdleTime=*/ false);
        loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);

        final int continueCount = continuePreviousDay.size();
        for (int i = 0; i < continueCount; i++) {
@@ -474,45 +473,28 @@ class UserUsageStatsService {
        }
    }

    /**
     * @param force To force all in-memory stats to be reloaded.
     */
    private void loadActiveStats(final long currentTimeMillis, boolean force,
            boolean resetBeginIdleTime) {
        final UnixCalendar tempCal = mDailyExpiryDate;
    private void loadActiveStats(final long currentTimeMillis, boolean resetBeginIdleTime) {
        for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
            tempCal.setTimeInMillis(currentTimeMillis);
            UnixCalendar.truncateTo(tempCal, intervalType);

            if (!force && mCurrentStats[intervalType] != null &&
                    mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
                // These are the same, no need to load them (in memory stats are always newer
                // than persisted stats).
                continue;
            }

            final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
            if (lastBeginTime >= tempCal.getTimeInMillis()) {
            final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
            if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
                    currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
                if (DEBUG) {
                    Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
                            sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
                            sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
                            ") for interval " + intervalType);
                }
                mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
                mCurrentStats[intervalType] = stats;
            } else {
                mCurrentStats[intervalType] = null;
            }

            if (mCurrentStats[intervalType] == null) {
                // No good fit remains.
                if (DEBUG) {
                    Slog.d(TAG, "Creating new stats @ " +
                            sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
                            tempCal.getTimeInMillis() + ") for interval " + intervalType);

                            sDateFormat.format(currentTimeMillis) + "(" +
                            currentTimeMillis + ") for interval " + intervalType);
                }

                mCurrentStats[intervalType] = new IntervalStats();
                mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
                mCurrentStats[intervalType].endTime = currentTimeMillis;
                mCurrentStats[intervalType].beginTime = currentTimeMillis;
                mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
            }

            if (resetBeginIdleTime) {
@@ -522,12 +504,16 @@ class UserUsageStatsService {
            }
        }
        mStatsChanged = false;
        mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
        updateRolloverDeadline();
    }

    private void updateRolloverDeadline() {
        mDailyExpiryDate.setTimeInMillis(
                mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
        mDailyExpiryDate.addDays(1);
        mDailyExpiryDate.truncateToDay();
        Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
                sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
                tempCal.getTimeInMillis() + ")");
                mDailyExpiryDate.getTimeInMillis() + ")");
    }

    //