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

Commit 3ac08d27 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add tracing log, variance in periodic job runs" into main

parents 08f5b2b7 1bdf5c9a
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Trace;
import android.util.IntArray;
import android.util.Slog;

@@ -81,7 +82,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper {
        if (appOpEvents.isEmpty()) {
            return;
        }

        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
                "AppOpHistoryDbHelper_" + mAggregationTimeWindow + "_Write");
        try {
            SQLiteDatabase db = getWritableDatabase();
            db.beginTransaction();
@@ -118,7 +120,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper {
                                event.totalRejectCount());
                        statement.step();
                    } catch (Exception exception) {
                        Slog.e(LOG_TAG, "Couldn't insert app op event: " + event, exception);
                        Slog.e(LOG_TAG, "Couldn't insert app op event: " + event + ", database "
                                + mDatabaseFile.getName(), exception);
                    } finally {
                        statement.reset();
                    }
@@ -128,12 +131,15 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper {
                try {
                    db.endTransaction();
                } catch (SQLiteException exception) {
                    Slog.e(LOG_TAG, "Couldn't commit transaction when inserting app ops, database"
                            + " file size (bytes) : " + mDatabaseFile.length(), exception);
                    Slog.e(LOG_TAG, "Couldn't commit transaction inserting app ops, database"
                            + mDatabaseFile.getName() + ", file size (bytes) : "
                            + mDatabaseFile.length(), exception);
                }
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "Couldn't insert app op records in " + mDatabaseFile.getName(), ex);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

+49 −39
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;

/**
@@ -65,19 +66,21 @@ import java.util.Set;
 */
public class AppOpHistoryHelper {
    private static final String TAG = "AppOpHistoryHelper";
    private static final long PERIODIC_JOB_MAX_VARIATION_MILLIS = Duration.ofMinutes(1).toMillis();
    private static final long DB_WRITE_INTERVAL_PERIODIC_MILLIS =
            Duration.ofMinutes(10).toMillis();
    private static final long EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS =
            Duration.ofHours(6).toMillis();
    // Event type handled by SqliteWriteHandler
    private static final int WRITE_DATABASE_PERIODIC = 1;
    private static final int DELETE_EXPIRED_ENTRIES = 2;
    private static final int DELETE_EXPIRED_ENTRIES_PERIODIC = 2;
    private static final int WRITE_DATABASE_CACHE_FULL = 3;

    // Used in adding variation to periodic job interval
    private final Random mRandom = new Random();
    // time window interval for aggregation
    private long mQuantizationMillis;
    private long mHistoryRetentionMillis;
    private final File mDatabaseFileName;
    private final File mDatabaseFile;
    private final Context mContext;
    private final AppOpHistoryDbHelper mDbHelper;
    private final SqliteWriteHandler mSqliteWriteHandler;
@@ -86,7 +89,7 @@ public class AppOpHistoryHelper {
    AppOpHistoryHelper(@NonNull Context context, File databaseFile,
            AggregationTimeWindow aggregationTimeWindow, int databaseVersion) {
        mContext = context;
        mDatabaseFileName = databaseFile;
        mDatabaseFile = databaseFile;
        mDbHelper = new AppOpHistoryDbHelper(
                context, databaseFile, aggregationTimeWindow, databaseVersion);
        ServiceThread thread =
@@ -99,10 +102,8 @@ public class AppOpHistoryHelper {
    void systemReady(long quantizationMillis, long historyRetentionMillis) {
        mQuantizationMillis = quantizationMillis;
        mHistoryRetentionMillis = historyRetentionMillis;
        mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC,
                DB_WRITE_INTERVAL_PERIODIC_MILLIS);
        mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
                EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS);

        ensurePeriodicJobsAreScheduled();
    }

    void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
@@ -225,9 +226,9 @@ public class AppOpHistoryHelper {
                attributionTagFilter, opCodes, opFlagsFilter, -1, null, false);
    }

    void deleteDatabase() {
    boolean deleteDatabase() {
        mDbHelper.close();
        mContext.deleteDatabase(mDatabaseFileName.getAbsolutePath());
        return mContext.deleteDatabase(mDatabaseFile.getAbsolutePath());
    }

    long getLargestAttributionChainId() {
@@ -356,25 +357,33 @@ public class AppOpHistoryHelper {
        pw.println();
    }

    private void ensurePeriodicJobsAreScheduled() {
        if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) {
            mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC,
                    DB_WRITE_INTERVAL_PERIODIC_MILLIS + mRandom.nextLong(0,
                            PERIODIC_JOB_MAX_VARIATION_MILLIS));
        }
        if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES_PERIODIC)) {
            mSqliteWriteHandler.sendEmptyMessageDelayed(
                    DELETE_EXPIRED_ENTRIES_PERIODIC,
                    EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS + mRandom.nextLong(0,
                            PERIODIC_JOB_MAX_VARIATION_MILLIS));
        }
    }

    private class SqliteWriteHandler extends Handler {
        SqliteWriteHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@androidx.annotation.NonNull Message msg) {
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case WRITE_DATABASE_PERIODIC -> {
                    try {
                        mDbHelper.insertAppOpHistory(mCache.evict());
                    } finally {
                        mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC,
                                DB_WRITE_INTERVAL_PERIODIC_MILLIS);
                        // Schedule a cleanup to truncate older (before cutoff time) entries.
                        if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) {
                            mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
                                    EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS);
                        }
                        ensurePeriodicJobsAreScheduled();
                    }
                }
                case WRITE_DATABASE_CACHE_FULL -> {
@@ -390,37 +399,38 @@ public class AppOpHistoryHelper {
                        }
                        mDbHelper.insertAppOpHistory(evictedEvents);
                    } finally {
                        // Just in case initial message is not scheduled.
                        if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) {
                            mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC,
                                    DB_WRITE_INTERVAL_PERIODIC_MILLIS);
                        }
                        ensurePeriodicJobsAreScheduled();
                    }
                }
                case DELETE_EXPIRED_ENTRIES -> {
                case DELETE_EXPIRED_ENTRIES_PERIODIC -> {
                    try {
                        long cutOffTimeStamp = System.currentTimeMillis() - mHistoryRetentionMillis;
                        mDbHelper.execSQL(
                            AppOpHistoryTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
                            new Object[]{cutOffTimeStamp});
                    } finally {
                        ensurePeriodicJobsAreScheduled();
                    }
                }
            }
        }

        void removeAllPendingMessages() {
            removeMessages(WRITE_DATABASE_PERIODIC);
            removeMessages(DELETE_EXPIRED_ENTRIES);
            removeMessages(DELETE_EXPIRED_ENTRIES_PERIODIC);
            removeMessages(WRITE_DATABASE_CACHE_FULL);
        }
    }

    /**
     * A cache for aggregating app op access counts for a time window. Individual app op events
     * aren't stored on the disk, instead an aggregated event is persisted on the disk.
     * A cache for aggregating app op accesses in a time window. Individual app op events
     * aren't stored on the disk, instead an aggregated events are persisted on the disk.
     *
     * <p>
     * These events are persisted into sqlite database
     * 1) Periodic interval.
     * 2) When the cache become full.
     * 3) During read call, flush the whole cache to disk.
     * 3) During read call, flush the cache to disk to make read simpler.
     * 4) During shutdown.
     */
    class AppOpHistoryCache {
@@ -441,7 +451,7 @@ public class AppOpHistoryHelper {
         * @param rejectCount Reject counts to be aggregated for an event.
         * @param duration    Access duration to be aggregated for an event.
         */
        public void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount,
        private void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount,
                int rejectCount, long duration) {
            synchronized (this) {
                AggregatedAppOpValues appOpAccessValue = mCache.get(accessKey);
@@ -500,7 +510,7 @@ public class AppOpHistoryHelper {
        /**
         * Evict specified app ops from cache, and return the list of evicted ops.
         */
        public List<AggregatedAppOpAccessEvent> evict(IntArray ops) {
        private List<AggregatedAppOpAccessEvent> evict(IntArray ops) {
            synchronized (this) {
                List<AggregatedAppOpAccessEvent> cachedOps = new ArrayList<>();
                List<AppOpAccessEvent> keysToBeRemoved = new ArrayList<>();
@@ -528,7 +538,7 @@ public class AppOpHistoryHelper {
         *
         * @return return all removed entries.
         */
        public List<AggregatedAppOpAccessEvent> evictAll() {
        private List<AggregatedAppOpAccessEvent> evictAll() {
            synchronized (this) {
                List<AggregatedAppOpAccessEvent> cachedOps = snapshot();
                mCache.clear();
@@ -539,13 +549,13 @@ public class AppOpHistoryHelper {
        /**
         * Remove all entries from the cache.
         */
        public void clear() {
        private void clear() {
            synchronized (this) {
                mCache.clear();
            }
        }

        public List<AggregatedAppOpAccessEvent> snapshot() {
        private List<AggregatedAppOpAccessEvent> snapshot() {
            List<AggregatedAppOpAccessEvent> events = new ArrayList<>();
            synchronized (this) {
                for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event :
@@ -557,7 +567,7 @@ public class AppOpHistoryHelper {
        }

        /** Remove cached events for given UID and package. */
        public void clear(int uid, String packageName) {
        private void clear(int uid, String packageName) {
            synchronized (this) {
                List<AppOpAccessEvent> keysToBeDeleted = new ArrayList<>();
                for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event :
+3 −2
Original line number Diff line number Diff line
@@ -18,8 +18,9 @@ package com.android.server.appop;

/**
 * SQLite table for storing aggregated app op access events.
 * Each row summarizes the count of accessed and rejected events that occurred within a specific
 * time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently storing the app op
 * Each row summarizes the count of accessed, rejected events, and duration sum that occurred
 * within a specific time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently
 * storing the app op
 * access events.
 *
 * <p>The following columns form a composite key to uniquely identify an aggregated record
+0 −1
Original line number Diff line number Diff line
@@ -6360,7 +6360,6 @@ public class AppOpsService extends IAppOpsService.Stub {
                        pw.println("Wrong parameter: " + args[i]);
                        return;
                    }
                    includeDiscreteOps = true;
                } else if ("--history".equals(arg)) {
                    dumpHistory = true;
                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+92 −53
Original line number Diff line number Diff line
@@ -16,66 +16,83 @@

package com.android.server.appop;

import android.annotation.NonNull;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;

/**
 * Helper class for migrating discrete ops from xml to sqlite
 * Helper class for migrating discrete ops from xml to sqlite or vice versa.
 */
public class DiscreteOpsMigrationHelper {
    private static final String LOG_TAG = "DiscreteOpsMigration";

    /**
     * migrate discrete ops from xml to sqlite.
     */
    static void migrateFromXmlToSqlite(DiscreteOpsXmlRegistry sourceRegistry,
            DiscreteOpsSqlRegistry targetRegistry) {
        try {
            DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps();
        List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps);
            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(
                    xmlOps);
            targetRegistry.migrateDiscreteAppOpHistory(discreteOps, xmlOps.mChainIdOffset);
            if (!sourceRegistry.deleteDiscreteOpsDir()) {
                Slog.w(LOG_TAG, "Couldn't delete appops xml directories.");
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "migrateFromXmlToSqlite failed.", ex);
            sourceRegistry.deleteDiscreteOpsDir();
        }
    }

    /**
     * migrate discrete ops from xml to unified sqlite schema.
     */
    static void migrateFromXmlToUnifiedSchemaSqlite(DiscreteOpsXmlRegistry sourceRegistry,
            AppOpHistoryHelper targetRegistry) {
        try {
            DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps();
        List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps);
        List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>();
        for (DiscreteOpsSqlRegistry.DiscreteOp event: discreteOps) {
            convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(),
                    event.getOpCode(), event.getDeviceId(), event.getAttributionTag(),
                    event.getOpFlags(), event.getUidState(),
                    event.getAttributionFlags(), event.getChainId(), event.getAccessTime(),
                    event.getDuration(), 0, 1, 0));
        }
            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(
                    xmlOps);
            List<AggregatedAppOpAccessEvent> convertedOps =
                    getAggregatedAppOpAccessEvents(discreteOps);
            targetRegistry.migrateDiscreteAppOpHistory(convertedOps);
            if (!sourceRegistry.deleteDiscreteOpsDir()) {
                Slog.w(LOG_TAG, "Couldn't delete appops xml directories.");
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "migrateFromXmlToUnifiedSchemaSqlite failed.", ex);
            sourceRegistry.deleteDiscreteOpsDir();
        }
    }

    /**
     * migrate discrete ops from sqlite to unified-schema sqlite.
     */
    static void migrateFromSqliteToUnifiedSchemaSqlite(DiscreteOpsSqlRegistry sourceRegistry,
            AppOpHistoryHelper targetRegistry) {
        try {
            List<DiscreteOpsSqlRegistry.DiscreteOp> sourceOps = sourceRegistry.getAllDiscreteOps();
        List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>();
        for (DiscreteOpsSqlRegistry.DiscreteOp event: sourceOps) {
            convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(),
                    event.getOpCode(), event.getDeviceId(), event.getAttributionTag(),
                    event.getOpFlags(), event.getUidState(),
                    event.getAttributionFlags(), event.getChainId(), event.getAccessTime(),
                    event.getDuration(), 0, 1, 0));
        }
            List<AggregatedAppOpAccessEvent> convertedOps =
                    getAggregatedAppOpAccessEvents(sourceOps);
            targetRegistry.migrateDiscreteAppOpHistory(convertedOps);
            if (!sourceRegistry.deleteDatabase()) {
                Slog.w(LOG_TAG, "Couldn't delete appops sql database.");
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "migrateFromSqliteToUnifiedSchemaSqlite failed.", ex);
            sourceRegistry.deleteDatabase();
        }
    }

    /**
     * rollback discrete ops from unified schema sqlite to sqlite schema.
     */
    static void rollbackFromUnifiedSchemaSqliteToSqlite(AppOpHistoryHelper sourceRegistry,
            DiscreteOpsSqlRegistry targetRegistry) {
        try {
            List<AggregatedAppOpAccessEvent> unifiedSchemaSqliteOps =
                    sourceRegistry.getAppOpHistory();
            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = new ArrayList<>();
@@ -91,8 +108,13 @@ public class DiscreteOpsMigrationHelper {
                largestChainId = Math.max(largestChainId, event.attributionChainId());
            }
            targetRegistry.migrateDiscreteAppOpHistory(discreteOps, largestChainId);
            if (!sourceRegistry.deleteDatabase()) {
                Slog.w(LOG_TAG, "Couldn't delete appops unified sql database.");
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "rollbackFromUnifiedSchemaSqliteToSqlite failed.", ex);
            sourceRegistry.deleteDatabase();
        sourceRegistry.shutdown();
        }
    }

    /**
@@ -100,24 +122,23 @@ public class DiscreteOpsMigrationHelper {
     */
    static void rollbackFromSqliteToXml(DiscreteOpsSqlRegistry sourceRegistry,
            DiscreteOpsXmlRegistry targetRegistry) {
        try {
            List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sourceRegistry.getAllDiscreteOps();

        // Only migrate configured discrete ops. Sqlite may contain all runtime ops, and more.
        List<DiscreteOpsSqlRegistry.DiscreteOp> filteredList = new ArrayList<>();
        for (DiscreteOpsSqlRegistry.DiscreteOp opEvent: sqlOps) {
            if (DiscreteOpsRegistry.isDiscreteOp(opEvent.getOpCode(), opEvent.getOpFlags())) {
                filteredList.add(opEvent);
            }
        }

        DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(filteredList);
            DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps);
            targetRegistry.migrateDiscreteAppOpHistory(xmlOps);
            if (!sourceRegistry.deleteDatabase()) {
                Slog.w(LOG_TAG, "Couldn't delete appops sql database.");
            }
        } catch (Exception ex) {
            Slog.e(LOG_TAG, "rollbackFromSqliteToXml failed.", ex);
            sourceRegistry.deleteDatabase();
        }
    }

    /**
     * Convert sqlite flat rows to hierarchical data.
     */
    @NonNull
    private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps(
            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) {
        DiscreteOpsXmlRegistry.DiscreteOps xmlOps =
@@ -140,6 +161,7 @@ public class DiscreteOpsMigrationHelper {
    /**
     * Convert xml (hierarchical) data to flat row based data.
     */
    @NonNull
    private static List<DiscreteOpsSqlRegistry.DiscreteOp> convertXmlToSqlDiscreteOps(
            DiscreteOpsXmlRegistry.DiscreteOps discreteOps) {
        List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>();
@@ -173,4 +195,21 @@ public class DiscreteOpsMigrationHelper {

        return opEvents;
    }

    @NonNull
    private static List<AggregatedAppOpAccessEvent> getAggregatedAppOpAccessEvents(
            List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) {
        List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>();
        for (DiscreteOpsSqlRegistry.DiscreteOp event : discreteOps) {
            convertedOps.add(
                    new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(),
                            event.getOpCode(), event.getDeviceId(), event.getAttributionTag(),
                            event.getOpFlags(), event.getUidState(),
                            event.getAttributionFlags(), event.getChainId(),
                            event.getAccessTime(),
                            event.getDuration(), 0, 1, 0));
        }
        return convertedOps;
    }

}
Loading