blob: 0c43a3349d6866659e368002e3afec8f296ac7c3 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.am;
import android.annotation.NonNull;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Iterator;
import java.util.LinkedList;
/**
* Base class to track certain individual event of app states, it groups the events into time-based
* slots, thus we could only track the total number of events in a slot, eliminating
* the needs to track the timestamps for each individual event. This will be much more memory
* efficient for the case of massive amount of events.
*/
class BaseAppStateTimeSlotEvents extends BaseAppStateEvents<Integer> {
static final boolean DEBUG_BASE_APP_TIME_SLOT_EVENTS = false;
/**
* The size (in ms) of the timeslot, should be greater than 0 always.
*/
final long mTimeSlotSize;
/**
* The start timestamp of current timeslot.
*/
long[] mCurSlotStartTime;
BaseAppStateTimeSlotEvents(int uid, @NonNull String packageName, int numOfEventTypes,
long timeslotSize, @NonNull String tag,
@NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
mTimeSlotSize = timeslotSize;
mCurSlotStartTime = new long[numOfEventTypes];
}
BaseAppStateTimeSlotEvents(@NonNull BaseAppStateTimeSlotEvents other) {
super(other);
mTimeSlotSize = other.mTimeSlotSize;
mCurSlotStartTime = new long[other.mCurSlotStartTime.length];
for (int i = 0; i < mCurSlotStartTime.length; i++) {
mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
}
}
@Override
LinkedList<Integer> add(LinkedList<Integer> events, LinkedList<Integer> otherEvents) {
if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
Slog.wtf(mTag, "Called into BaseAppStateTimeSlotEvents#add unexpected.");
}
// This function is invalid semantically here without the information of time-bases.
return null;
}
@Override
void add(BaseAppStateEvents otherObj) {
if (otherObj == null || !(otherObj instanceof BaseAppStateTimeSlotEvents)) {
return;
}
final BaseAppStateTimeSlotEvents other = (BaseAppStateTimeSlotEvents) otherObj;
if (mEvents.length != other.mEvents.length) {
if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + other);
}
return;
}
for (int i = 0; i < mEvents.length; i++) {
final LinkedList<Integer> otherEvents = other.mEvents[i];
if (otherEvents == null || otherEvents.size() == 0) {
continue;
}
LinkedList<Integer> events = mEvents[i];
if (events == null || events.size() == 0) {
mEvents[i] = new LinkedList<Integer>(otherEvents);
mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
continue;
}
final LinkedList<Integer> dest = new LinkedList<>();
final Iterator<Integer> itl = events.iterator();
final Iterator<Integer> itr = otherEvents.iterator();
final long maxl = mCurSlotStartTime[i];
final long maxr = other.mCurSlotStartTime[i];
final long minl = maxl - mTimeSlotSize * (events.size() - 1);
final long minr = maxr - mTimeSlotSize * (otherEvents.size() - 1);
final long latest = Math.max(maxl, maxr);
final long earliest = Math.min(minl, minr);
for (long start = earliest; start <= latest; start += mTimeSlotSize) {
dest.add((start >= minl && start <= maxl ? itl.next() : 0)
+ (start >= minr && start <= maxr ? itr.next() : 0));
}
mEvents[i] = dest;
if (maxl < maxr) {
mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
}
trimEvents(getEarliest(mCurSlotStartTime[i]), i);
}
}
@Override
int getTotalEventsSince(long since, long now, int index) {
final LinkedList<Integer> events = mEvents[index];
if (events == null || events.size() == 0) {
return 0;
}
final long start = getSlotStartTime(since);
if (start > mCurSlotStartTime[index]) {
return 0;
}
final long end = Math.min(getSlotStartTime(now), mCurSlotStartTime[index]);
final Iterator<Integer> it = events.descendingIterator();
int count = 0;
for (long time = mCurSlotStartTime[index]; time >= start && it.hasNext();
time -= mTimeSlotSize) {
final int val = it.next();
if (time <= end) {
count += val;
}
}
return count;
}
void addEvent(long now, int index) {
final long slot = getSlotStartTime(now);
if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
Slog.i(mTag, "Adding event to slot " + slot);
}
LinkedList<Integer> events = mEvents[index];
if (events == null) {
events = new LinkedList<Integer>();
mEvents[index] = events;
}
if (events.size() == 0) {
events.add(1);
} else {
for (long start = mCurSlotStartTime[index]; start < slot; start += mTimeSlotSize) {
events.add(0);
}
events.offerLast(events.pollLast() + 1);
}
mCurSlotStartTime[index] = slot;
trimEvents(getEarliest(now), index);
}
@Override
void trimEvents(long earliest, int index) {
final LinkedList<Integer> events = mEvents[index];
if (events == null || events.size() == 0) {
return;
}
final long slot = getSlotStartTime(earliest);
for (long time = mCurSlotStartTime[index] - mTimeSlotSize * (events.size() - 1);
time < slot && events.size() > 0; time += mTimeSlotSize) {
events.pop();
}
}
long getSlotStartTime(long timestamp) {
return timestamp - timestamp % mTimeSlotSize;
}
@VisibleForTesting
long getCurrentSlotStartTime(int index) {
return mCurSlotStartTime[index];
}
}