Merge "Cache earliest event results." into tm-dev
This commit is contained in:
@@ -179,4 +179,177 @@ public class UserUsageStatsServiceTest {
|
|||||||
assertTrue(hasTestEvent);
|
assertTrue(hasTestEvent);
|
||||||
assertEquals(2, count);
|
assertEquals(2, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tests that the API works as expected even with the caching system. */
|
||||||
|
@Test
|
||||||
|
public void testQueryEarliestEventsForPackage_Caching() throws Exception {
|
||||||
|
final long forcedDiff = 5000;
|
||||||
|
Event event1 = new Event(NOTIFICATION_SEEN, SystemClock.elapsedRealtime());
|
||||||
|
event1.mPackage = TEST_PACKAGE_NAME;
|
||||||
|
mService.reportEvent(event1);
|
||||||
|
final long event1ReportTime = System.currentTimeMillis();
|
||||||
|
Thread.sleep(forcedDiff);
|
||||||
|
Event event2 = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
|
||||||
|
event2.mPackage = TEST_PACKAGE_NAME;
|
||||||
|
mService.reportEvent(event2);
|
||||||
|
final long event2ReportTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Force persist the events instead of waiting for them to be processed on the handler.
|
||||||
|
mService.persistActiveStats();
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long startTime = now - forcedDiff * 2;
|
||||||
|
UsageEvents events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
boolean hasTestEvent = false;
|
||||||
|
int count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(2, count);
|
||||||
|
|
||||||
|
// Query again
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(2, count);
|
||||||
|
|
||||||
|
// Query around just the first event
|
||||||
|
now = event1ReportTime;
|
||||||
|
startTime = now - forcedDiff * 2;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(hasTestEvent);
|
||||||
|
assertEquals(1, count);
|
||||||
|
|
||||||
|
// Shift query around the first event, still exclude the second
|
||||||
|
now = event1ReportTime + forcedDiff / 2;
|
||||||
|
startTime = event1ReportTime - forcedDiff / 2;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(hasTestEvent);
|
||||||
|
assertEquals(1, count);
|
||||||
|
|
||||||
|
// Shift query around the second event only
|
||||||
|
now = event2ReportTime + 1;
|
||||||
|
startTime = event1ReportTime + forcedDiff / 4;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(1, count);
|
||||||
|
|
||||||
|
// Shift query around both events
|
||||||
|
now = event2ReportTime + 1;
|
||||||
|
startTime = now - forcedDiff * 2;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(2, count);
|
||||||
|
|
||||||
|
// Query around just the first event and then shift end time to include second event
|
||||||
|
now = event1ReportTime;
|
||||||
|
startTime = now - forcedDiff * 2;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(hasTestEvent);
|
||||||
|
assertEquals(1, count);
|
||||||
|
|
||||||
|
now = event2ReportTime + 1;
|
||||||
|
events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
hasTestEvent = false;
|
||||||
|
count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(2, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1453,7 +1453,7 @@ public class UsageStatsService extends SystemService implements
|
|||||||
@NonNull String packageName, int eventType) {
|
@NonNull String packageName, int eventType) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (!mUserUnlockedStates.contains(userId)) {
|
if (!mUserUnlockedStates.contains(userId)) {
|
||||||
Slog.w(TAG, "Failed to query earliset package events for locked user " + userId);
|
Slog.w(TAG, "Failed to query earliest package events for locked user " + userId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import android.util.ArrayMap;
|
|||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.AtomicFile;
|
import android.util.AtomicFile;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
import android.util.SparseArrayMap;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
@@ -104,6 +105,23 @@ class UserUsageStatsService {
|
|||||||
void onNewUpdate(int mUserId);
|
void onNewUpdate(int mUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class CachedEarlyEvents {
|
||||||
|
public long searchBeginTime;
|
||||||
|
|
||||||
|
public long eventTime;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<UsageEvents.Event> events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of {@link UsageEvents.Event} event value to packageName-cached early usage event.
|
||||||
|
* This is used to reduce how much we need to interact with the underlying database to get the
|
||||||
|
* earliest event for a specific package.
|
||||||
|
*/
|
||||||
|
private final SparseArrayMap<String, CachedEarlyEvents> mCachedEarlyEvents =
|
||||||
|
new SparseArrayMap<>();
|
||||||
|
|
||||||
UserUsageStatsService(Context context, int userId, File usageStatsDir,
|
UserUsageStatsService(Context context, int userId, File usageStatsDir,
|
||||||
StatsUpdatedListener listener) {
|
StatsUpdatedListener listener) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -177,9 +195,14 @@ class UserUsageStatsService {
|
|||||||
void userStopped() {
|
void userStopped() {
|
||||||
// Flush events to disk immediately to guarantee persistence.
|
// Flush events to disk immediately to guarantee persistence.
|
||||||
persistActiveStats();
|
persistActiveStats();
|
||||||
|
mCachedEarlyEvents.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int onPackageRemoved(String packageName, long timeRemoved) {
|
int onPackageRemoved(String packageName, long timeRemoved) {
|
||||||
|
for (int i = mCachedEarlyEvents.numMaps() - 1; i >= 0; --i) {
|
||||||
|
final int eventType = mCachedEarlyEvents.keyAt(i);
|
||||||
|
mCachedEarlyEvents.delete(eventType, packageName);
|
||||||
|
}
|
||||||
return mDatabase.onPackageRemoved(packageName, timeRemoved);
|
return mDatabase.onPackageRemoved(packageName, timeRemoved);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +263,7 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onTimeChanged(long oldTime, long newTime) {
|
private void onTimeChanged(long oldTime, long newTime) {
|
||||||
|
mCachedEarlyEvents.clear();
|
||||||
persistActiveStats();
|
persistActiveStats();
|
||||||
mDatabase.onTimeChanged(newTime - oldTime);
|
mDatabase.onTimeChanged(newTime - oldTime);
|
||||||
loadActiveStats(newTime);
|
loadActiveStats(newTime);
|
||||||
@@ -675,14 +699,56 @@ class UserUsageStatsService {
|
|||||||
* for the package as well as the earliest event of {@code eventType} seen for the package.
|
* for the package as well as the earliest event of {@code eventType} seen for the package.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
UsageEvents queryEarliestEventsForPackage(final long beginTime, final long endTime,
|
UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime,
|
||||||
@NonNull final String packageName, final int eventType) {
|
@NonNull final String packageName, final int eventType) {
|
||||||
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
|
final long currentTime = checkAndGetTimeLocked();
|
||||||
|
if (!validRange(currentTime, beginTime, endTime)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName);
|
||||||
|
if (cachedEvents != null) {
|
||||||
|
// We can use this cached event if the previous search time was the exact same
|
||||||
|
// or earlier AND the event we previously found was at this current time or
|
||||||
|
// afterwards. Since no new events will be added before the cached event,
|
||||||
|
// redoing the search will yield the same event.
|
||||||
|
if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) {
|
||||||
|
final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size();
|
||||||
|
if ((numEvents == 0
|
||||||
|
|| cachedEvents.events.get(numEvents - 1).getEventType() != eventType)
|
||||||
|
&& cachedEvents.eventTime < endTime) {
|
||||||
|
// We didn't find a match in the earlier range but this new request is allowing
|
||||||
|
// us to look at events after the previous request's end time, so we may find
|
||||||
|
// something new.
|
||||||
|
beginTime = Math.min(currentTime, cachedEvents.eventTime);
|
||||||
|
// Leave the cachedEvents's searchBeginTime as the earlier begin time to
|
||||||
|
// cache/show that we searched the entire range (the union of the two queries):
|
||||||
|
// [previous query's begin time, current query's end time].
|
||||||
|
} else if (cachedEvents.eventTime <= endTime) {
|
||||||
|
if (cachedEvents.events == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new UsageEvents(cachedEvents.events, new String[]{packageName}, false);
|
||||||
|
} else {
|
||||||
|
// Any event we previously found is after the end of this query's range, but
|
||||||
|
// this query starts at the same time (or after) the previous query's begin time
|
||||||
|
// so there is no event to return.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The previous query isn't helpful in any way for this query. Reset the event data.
|
||||||
|
cachedEvents.searchBeginTime = beginTime;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cachedEvents = new CachedEarlyEvents();
|
||||||
|
cachedEvents.searchBeginTime = beginTime;
|
||||||
|
mCachedEarlyEvents.add(eventType, packageName, cachedEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
final long finalBeginTime = beginTime;
|
||||||
final List<Event> results = queryStats(INTERVAL_DAILY,
|
final List<Event> results = queryStats(INTERVAL_DAILY,
|
||||||
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
|
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
|
||||||
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime);
|
||||||
final int size = stats.events.size();
|
final int size = stats.events.size();
|
||||||
for (int i = startIndex; i < size; i++) {
|
for (int i = startIndex; i < size; i++) {
|
||||||
final Event event = stats.events.get(i);
|
final Event event = stats.events.get(i);
|
||||||
@@ -706,9 +772,15 @@ class UserUsageStatsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (results == null || results.isEmpty()) {
|
if (results == null || results.isEmpty()) {
|
||||||
|
// There won't be any new events added earlier than endTime, so we can use endTime to
|
||||||
|
// avoid querying for events earlier than it.
|
||||||
|
cachedEvents.eventTime = Math.min(currentTime, endTime);
|
||||||
|
cachedEvents.events = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp();
|
||||||
|
cachedEvents.events = results;
|
||||||
return new UsageEvents(results, new String[]{packageName}, false);
|
return new UsageEvents(results, new String[]{packageName}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user