summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Hui Yu <huiyu@google.com> 2019-01-28 16:33:32 -0800
committer Hui Yu <huiyu@google.com> 2019-02-20 21:15:39 +0000
commitb21d59f283f72c8bf6bafdf3ac6cc1c2a1f17e5c (patch)
tree8f421c2e735abe16cb8b44515abdf255f2b71394
parentffd44b35193a52e4548b2914ff31aa8d9b2308c1 (diff)
UsageStats creates too many daily/weekly/monthly/yearly files.
A wrong condition causes new sets of daily/weekly/monthly/yearly files been created before it reaches the rollover time. Currently database files are pruned by timestamp, it allows 3 years yearly files, 6 months monthly files, 4 weeks weekly files, 10 days daily files. Because the bug there could be unlimited number of files are created as long as files' timestamp falls in these allowed time spans. Add a logic to prune files by number of files. Only allow up to 10 yearly files, 12 monthly files, 50 weekly files, 100 daily files. Change-Id: Iac42f70ae19edb48885a123dfd9988021de6c88d Fix: b/122725555 Test: NA.
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java44
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java40
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java4
3 files changed, 77 insertions, 11 deletions
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 8d9b3cfcff35..fe4825c1241c 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -23,12 +23,14 @@ import static junit.framework.TestCase.fail;
import static org.testng.Assert.assertEquals;
import android.app.usage.EventList;
+import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.res.Configuration;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
@@ -448,4 +451,45 @@ public class UsageStatsDatabaseTest {
runBackupRestoreTest(0);
runBackupRestoreTest(99999);
}
+
+ /**
+ * Test the pruning in indexFilesLocked() that only allow up to 100 daily files, 50 weekly files
+ * , 12 monthly files, 10 yearly files.
+ */
+ @Test
+ public void testMaxFiles() throws IOException {
+ final File[] intervalDirs = new File[]{
+ new File(mTestDir, "daily"),
+ new File(mTestDir, "weekly"),
+ new File(mTestDir, "monthly"),
+ new File(mTestDir, "yearly"),
+ };
+ // Create 10 extra files under each interval dir.
+ final int extra = 10;
+ final int length = intervalDirs.length;
+ for (int i = 0; i < length; i++) {
+ final int numFiles = UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra;
+ for (int f = 0; f < numFiles; f++) {
+ final AtomicFile file = new AtomicFile(new File(intervalDirs[i], Long.toString(f)));
+ FileOutputStream fos = file.startWrite();
+ fos.write(1);
+ file.finishWrite(fos);
+ }
+ }
+ // indexFilesLocked() list files under each interval dir, if number of files are more than
+ // the max allowed files for each interval type, it deletes the lowest numbered files.
+ mUsageStatsDatabase.forceIndexFiles();
+ final int len = mUsageStatsDatabase.mSortedStatFiles.length;
+ for (int i = 0; i < len; i++) {
+ final TimeSparseArray<AtomicFile> files = mUsageStatsDatabase.mSortedStatFiles[i];
+ // The stats file for each interval type equals to max allowed.
+ assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i],
+ files.size());
+ // The highest numbered file,
+ assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra - 1,
+ files.keyAt(files.size() - 1));
+ // The lowest numbered file:
+ assertEquals(extra, files.keyAt(0));
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 0e15947abf52..dff680944525 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -43,8 +43,8 @@ import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -84,6 +84,9 @@ public class UsageStatsDatabase {
@VisibleForTesting
public static final int BACKUP_VERSION = 4;
+ @VisibleForTesting
+ static final int[] MAX_FILES_PER_INTERVAL_TYPE = new int[]{100, 50, 12, 10};
+
// Key under which the payload blob is stored
// same as UsageStatsBackupHelper.KEY_USAGE_STATS
static final String KEY_USAGE_STATS = "usage_stats";
@@ -104,7 +107,8 @@ public class UsageStatsDatabase {
private final Object mLock = new Object();
private final File[] mIntervalDirs;
- private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
+ @VisibleForTesting
+ final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
private final UnixCalendar mCal;
private final File mVersionFile;
private final File mBackupsDir;
@@ -126,10 +130,10 @@ public class UsageStatsDatabase {
@VisibleForTesting
public UsageStatsDatabase(File dir, int version) {
mIntervalDirs = new File[]{
- new File(dir, "daily"),
- new File(dir, "weekly"),
- new File(dir, "monthly"),
- new File(dir, "yearly"),
+ new File(dir, "daily"),
+ new File(dir, "weekly"),
+ new File(dir, "monthly"),
+ new File(dir, "yearly"),
};
mCurrentVersion = version;
mVersionFile = new File(dir, "version");
@@ -248,6 +252,14 @@ public class UsageStatsDatabase {
return true;
}
+ /** @hide */
+ @VisibleForTesting
+ void forceIndexFiles() {
+ synchronized (mLock) {
+ indexFilesLocked();
+ }
+ }
+
private void indexFilesLocked() {
final FilenameFilter backupFileFilter = new FilenameFilter() {
@Override
@@ -255,7 +267,6 @@ public class UsageStatsDatabase {
return !name.endsWith(BAK_SUFFIX);
}
};
-
// Index the available usage stat files on disk.
for (int i = 0; i < mSortedStatFiles.length; i++) {
if (mSortedStatFiles[i] == null) {
@@ -268,8 +279,9 @@ public class UsageStatsDatabase {
if (DEBUG) {
Slog.d(TAG, "Found " + files.length + " stat files for interval " + i);
}
-
- for (File f : files) {
+ final int len = files.length;
+ for (int j = 0; j < len; j++) {
+ final File f = files[j];
final AtomicFile af = new AtomicFile(f);
try {
mSortedStatFiles[i].put(parseBeginTime(af), af);
@@ -277,6 +289,16 @@ public class UsageStatsDatabase {
Slog.e(TAG, "failed to index file: " + f, e);
}
}
+
+ // only keep the max allowed number of files for each interval type.
+ final int toDelete = mSortedStatFiles[i].size() - MAX_FILES_PER_INTERVAL_TYPE[i];
+ if (toDelete > 0) {
+ for (int j = 0; j < toDelete; j++) {
+ mSortedStatFiles[i].valueAt(0).delete();
+ mSortedStatFiles[i].removeAt(0);
+ }
+ Slog.d(TAG, "Deleted " + toDelete + " stat files for interval " + i);
+ }
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 3cb22162e36c..ebd8e36aa07d 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -595,8 +595,8 @@ class UserUsageStatsService {
private void loadActiveStats(final long currentTimeMillis) {
for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
- if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
- currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
+ if (stats != null
+ && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +