From b1d71f88f51db379f42a9255181d969963afcb66 Mon Sep 17 00:00:00 2001 From: Stanislav Zholnin Date: Fri, 12 Mar 2021 20:50:02 +0000 Subject: [PATCH] Introduce caching to DiscreteRegistry. Cached representation of state on disk is kept in the memory after API query until next time data is dump to disk. Second and following API queries will not hit the disk. Other improvements and bug fixes. Bug: 176965672 Test: in development Change-Id: I583cba4c74487c79f023efd4b3ce2ee844351719 --- .../server/appop/DiscreteRegistry.java | 347 ++++++++++++------ .../server/appop/HistoricalRegistry.java | 2 +- 2 files changed, 243 insertions(+), 106 deletions(-) diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index ed62abc7d7731..2b0157c88136d 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -49,8 +49,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; -import libcore.util.EmptyArray; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -85,6 +83,8 @@ final class DiscreteRegistry { private static final String TAG = DiscreteRegistry.class.getSimpleName(); private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis(); + private static final long TIMELINE_QUANTIZATION = Duration.ofMinutes(1).toMillis(); + private static final String TAG_HISTORY = "h"; private static final String ATTR_VERSION = "v"; private static final int CURRENT_VERSION = 1; @@ -107,6 +107,8 @@ final class DiscreteRegistry { private static final String ATTR_UID_STATE = "us"; private static final String ATTR_FLAGS = "f"; + private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED; + // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -119,6 +121,9 @@ final class DiscreteRegistry { @GuardedBy("mInMemoryLock") private DiscreteOps mDiscreteOps; + @GuardedBy("mOnDiskLock") + private DiscreteOps mCachedOps = null; + DiscreteRegistry(Object inMemoryLock) { mInMemoryLock = inMemoryLock; mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"), @@ -173,23 +178,25 @@ final class DiscreteRegistry { } } } - } - DiscreteOps discreteOps; - synchronized (mInMemoryLock) { - discreteOps = mDiscreteOps; - mDiscreteOps = new DiscreteOps(); - } - if (discreteOps.isEmpty()) { - return; - } - long currentTimeStamp = Instant.now().toEpochMilli(); - try { - final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX); - discreteOps.writeToFile(file); - } catch (Throwable t) { - Slog.e(TAG, - "Error writing timeline state: " + t.getMessage() + " " - + Arrays.toString(t.getStackTrace())); + DiscreteOps discreteOps; + synchronized (mInMemoryLock) { + discreteOps = mDiscreteOps; + mDiscreteOps = new DiscreteOps(); + mCachedOps = null; + } + if (discreteOps.isEmpty()) { + return; + } + long currentTimeStamp = Instant.now().toEpochMilli(); + try { + final File file = new File(mDiscreteAccessDir, + currentTimeStamp + TIMELINE_FILE_SUFFIX); + discreteOps.writeToFile(file); + } catch (Throwable t) { + Slog.e(TAG, + "Error writing timeline state: " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } } } @@ -197,25 +204,33 @@ final class DiscreteRegistry { long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { - writeAndClearAccessHistory(); - DiscreteOps discreteOps = new DiscreteOps(); - readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter, - packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + DiscreteOps discreteOps = getAndCacheDiscreteOps(); + discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter, + opNamesFilter, attributionTagFilter, flagsFilter); discreteOps.applyToHistoricalOps(result); return; } - private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis, - long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, - @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, - @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + private DiscreteOps getAndCacheDiscreteOps() { + DiscreteOps discreteOps = new DiscreteOps(); + synchronized (mOnDiskLock) { - long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, - ChronoUnit.MILLIS).toEpochMilli(); - if (historyBeginTimeMillis > endTimeMillis) { - return; + synchronized (mInMemoryLock) { + discreteOps.merge(mDiscreteOps); } - beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis); + if (mCachedOps == null) { + mCachedOps = new DiscreteOps(); + readDiscreteOpsFromDisk(mCachedOps); + } + discreteOps.merge(mCachedOps); + } + return discreteOps; + } + + private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) { + synchronized (mOnDiskLock) { + long beginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli(); final File[] files = mDiscreteAccessDir.listFiles(); if (files != null && files.length > 0) { @@ -229,8 +244,7 @@ final class DiscreteRegistry { if (timestamp < beginTimeMillis) { continue; } - discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter, - packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + discreteOps.readFromFile(f, beginTimeMillis); } } } @@ -251,15 +265,11 @@ final class DiscreteRegistry { @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { - DiscreteOps discreteOps = new DiscreteOps(); - synchronized (mOnDiskLock) { - writeAndClearAccessHistory(); - String[] opNamesFilter = dumpOp == OP_NONE ? EmptyArray.STRING - : new String[]{AppOpsManager.opToPublicName(dumpOp)}; - readDiscreteOpsFromDisk(discreteOps, 0, Instant.now().toEpochMilli(), filter, - uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, - OP_FLAGS_ALL); - } + DiscreteOps discreteOps = getAndCacheDiscreteOps(); + String[] opNamesFilter = dumpOp == OP_NONE ? null + : new String[]{AppOpsManager.opToPublicName(dumpOp)}; + discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter, + opNamesFilter, attributionTagFilter, OP_FLAGS_ALL); discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps); } @@ -270,7 +280,7 @@ final class DiscreteRegistry { if (!isDiscreteUid(uid)) { return false; } - if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) { + if ((flags & (OP_FLAGS_DISCRETE)) == 0) { return false; } return true; @@ -298,6 +308,19 @@ final class DiscreteRegistry { mUids = new ArrayMap<>(); } + boolean isEmpty() { + return mUids.isEmpty(); + } + + void merge(DiscreteOps other) { + int nUids = other.mUids.size(); + for (int i = 0; i < nUids; i++) { + int uid = other.mUids.keyAt(i); + DiscreteUidOps uidOps = other.mUids.valueAt(i); + getOrCreateDiscreteUidOps(uid).merge(uidOps); + } + } + void addDiscreteAccess(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -305,6 +328,25 @@ final class DiscreteRegistry { uidState, accessTime, accessDuration); } + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_UID) != 0) { + ArrayMap uids = new ArrayMap<>(); + uids.put(uidFilter, getOrCreateDiscreteUidOps(uidFilter)); + mUids = uids; + } + int nUids = mUids.size(); + for (int i = nUids - 1; i >= 0; i--) { + mUids.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, packageNameFilter, + opNamesFilter, attributionTagFilter, flagsFilter); + if (mUids.valueAt(i).isEmpty()) { + mUids.removeAt(i); + } + } + } + private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) { int nUids = mUids.size(); for (int i = 0; i < nUids; i++) { @@ -353,14 +395,7 @@ final class DiscreteRegistry { return result; } - boolean isEmpty() { - return mUids.isEmpty(); - } - - private void readFromFile(File f, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, - @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + private void readFromFile(File f, long beginTimeMillis) { try { FileInputStream stream = new FileInputStream(f); TypedXmlPullParser parser = Xml.resolvePullParser(stream); @@ -377,12 +412,7 @@ final class DiscreteRegistry { while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_UID.equals(parser.getName())) { int uid = parser.getAttributeInt(null, ATTR_UID, -1); - if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) { - continue; - } - getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis, - endTimeMillis, filter, packageNameFilter, opNamesFilter, - attributionTagFilter, flagsFilter); + getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis); } } } catch (Throwable t) { @@ -400,6 +430,38 @@ final class DiscreteRegistry { mPackages = new ArrayMap<>(); } + boolean isEmpty() { + return mPackages.isEmpty(); + } + + void merge(DiscreteUidOps other) { + int nPackages = other.mPackages.size(); + for (int i = 0; i < nPackages; i++) { + String packageName = other.mPackages.keyAt(i); + DiscretePackageOps p = other.mPackages.valueAt(i); + getOrCreateDiscretePackageOps(packageName).merge(p); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { + ArrayMap packages = new ArrayMap<>(); + packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter)); + mPackages = packages; + } + int nPackages = mPackages.size(); + for (int i = nPackages - 1; i >= 0; i--) { + mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter, + attributionTagFilter, flagsFilter); + if (mPackages.valueAt(i).isEmpty()) { + mPackages.removeAt(i); + } + } + } + void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -445,22 +507,12 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, - long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String packageNameFilter, - @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_PACKAGE.equals(parser.getName())) { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); - if ((filter & FILTER_BY_PACKAGE_NAME) != 0 - && !packageName.equals(packageNameFilter)) { - continue; - } - getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis, - endTimeMillis, filter, opNamesFilter, attributionTagFilter, - flagsFilter); + getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis); } } } @@ -473,6 +525,10 @@ final class DiscreteRegistry { mPackageOps = new ArrayMap<>(); } + boolean isEmpty() { + return mPackageOps.isEmpty(); + } + void addDiscreteAccess(int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -480,6 +536,35 @@ final class DiscreteRegistry { accessDuration); } + void merge(DiscretePackageOps other) { + int nOps = other.mPackageOps.size(); + for (int i = 0; i < nOps; i++) { + int opId = other.mPackageOps.keyAt(i); + DiscreteOp op = other.mPackageOps.valueAt(i); + getOrCreateDiscreteOp(opId).merge(op); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) { + int nOps = mPackageOps.size(); + for (int i = nOps - 1; i >= 0; i--) { + int opId = mPackageOps.keyAt(i); + if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, + AppOpsManager.opToPublicName(opId))) { + mPackageOps.removeAt(i); + continue; + } + mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, + attributionTagFilter, flagsFilter); + if (mPackageOps.valueAt(i).isEmpty()) { + mPackageOps.removeAt(i); + } + } + } + private DiscreteOp getOrCreateDiscreteOp(int op) { DiscreteOp result = mPackageOps.get(op); if (result == null) { @@ -519,20 +604,12 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_OP.equals(parser.getName())) { int op = parser.getAttributeInt(null, ATTR_OP_ID); - if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, - AppOpsManager.opToPublicName(op))) { - continue; - } - getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis, - filter, attributionTagFilter, flagsFilter); + getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis); } } } @@ -545,31 +622,66 @@ final class DiscreteRegistry { mAttributedOps = new ArrayMap<>(); } + boolean isEmpty() { + return mAttributedOps.isEmpty(); + } + + void merge(DiscreteOp other) { + int nTags = other.mAttributedOps.size(); + for (int i = 0; i < nTags; i++) { + String tag = other.mAttributedOps.keyAt(i); + List otherEvents = other.mAttributedOps.valueAt(i); + List events = getOrCreateDiscreteOpEventsList(tag); + mAttributedOps.put(tag, stableListMerge(events, otherEvents)); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) { + ArrayMap> attributedOps = new ArrayMap<>(); + attributedOps.put(attributionTagFilter, + getOrCreateDiscreteOpEventsList(attributionTagFilter)); + mAttributedOps = attributedOps; + } + + int nTags = mAttributedOps.size(); + for (int i = nTags - 1; i >= 0; i--) { + String tag = mAttributedOps.keyAt(i); + List list = mAttributedOps.valueAt(i); + list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter); + mAttributedOps.put(tag, list); + if (list.size() == 0) { + mAttributedOps.removeAt(i); + } + } + } + void addDiscreteAccess(@Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { List attributedOps = getOrCreateDiscreteOpEventsList( attributionTag); - accessTime = Instant.ofEpochMilli(accessTime).truncatedTo( - ChronoUnit.MINUTES).toEpochMilli(); + accessTime = accessTime / TIMELINE_QUANTIZATION * TIMELINE_QUANTIZATION; int nAttributedOps = attributedOps.size(); - for (int i = nAttributedOps - 1; i >= 0; i--) { - DiscreteOpEvent previousOp = attributedOps.get(i); - if (i == nAttributedOps - 1 && previousOp.mNoteTime == accessTime - && accessDuration > -1) { - // existing event with updated duration - attributedOps.remove(i); - break; - } + int i = nAttributedOps; + for (; i > 0; i--) { + DiscreteOpEvent previousOp = attributedOps.get(i - 1); if (previousOp.mNoteTime < accessTime) { break; } if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { - return; + if (accessDuration != previousOp.mNoteDuration + && accessDuration > TIMELINE_QUANTIZATION) { + break; + } else { + return; + } } } - attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); } private List getOrCreateDiscreteOpEventsList(String attributionTag) { @@ -633,18 +745,11 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (TAG_TAG.equals(parser.getName())) { String attributionTag = parser.getAttributeValue(null, ATTR_TAG); - if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals( - attributionTagFilter)) { - continue; - } List events = getOrCreateDiscreteOpEventsList( attributionTag); int innerDepth = parser.getDepth(); @@ -655,11 +760,7 @@ final class DiscreteRegistry { -1); int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); - if ((flagsFilter & opFlags) == 0) { - continue; - } - if ((noteTime + noteDuration < beginTimeMillis - && noteTime > endTimeMillis)) { + if (noteTime + noteDuration < beginTimeMillis) { continue; } DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, @@ -715,5 +816,41 @@ final class DiscreteRegistry { out.attributeInt(null, ATTR_FLAGS, mOpFlag); } } + + private static List stableListMerge(List a, + List b) { + int nA = a.size(); + int nB = b.size(); + int i = 0; + int k = 0; + List result = new ArrayList<>(nA + nB); + while (i < nA || k < nB) { + if (i == nA) { + result.add(b.get(k++)); + } else if (k == nB) { + result.add(a.get(i++)); + } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) { + result.add(a.get(i++)); + } else { + result.add(b.get(k++)); + } + } + return result; + } + + private static List filterEventsList(List list, + long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter) { + int n = list.size(); + List result = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + DiscreteOpEvent event = list.get(i); + if ((event.mOpFlag & flagsFilter) != 0 + && event.mNoteTime + event.mNoteDuration > beginTimeMillis + && event.mNoteTime < endTimeMillis) { + result.add(event); + } + } + return result; + } } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index eeed4521f6c4b..4435c4795730c 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -532,7 +532,7 @@ final class HistoricalRegistry { System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, increment, eventStartTime); + flags, uidState, eventStartTime, increment); } } }