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

Commit d63b4314 authored by David Christie's avatar David Christie Committed by Android (Google) Code Review
Browse files

Merge "Log history of location requests in LocationManager. -Assists with...

Merge "Log history of location requests in LocationManager. -Assists with debugging power issues. Bug: 12824233"
parents 6b1a93fc 2ff96af2
Loading
Loading
Loading
Loading
+26 −8
Original line number Diff line number Diff line
@@ -73,6 +73,9 @@ import com.android.server.location.LocationBlacklist;
import com.android.server.location.LocationFudger;
import com.android.server.location.LocationProviderInterface;
import com.android.server.location.LocationProviderProxy;
import com.android.server.location.LocationRequestStatistics;
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.MockProvider;
import com.android.server.location.PassiveProvider;

@@ -178,6 +181,8 @@ public class LocationManagerService extends ILocationManager.Stub {
    private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
            new HashMap<String, ArrayList<UpdateRecord>>();

    private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();

    // mapping from provider name to last known location
    private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();

@@ -1288,13 +1293,18 @@ public class LocationManagerService extends ILocationManager.Stub {
            if (!records.contains(this)) {
                records.add(this);
            }

            // Update statistics for historical location requests by package/provider
            mRequestStatistics.startRequesting(
                    mReceiver.mPackageName, provider, request.getInterval());
        }

        /**
         * Method to be called when a record will no longer be used.  Calling this multiple times
         * must have the same effect as calling it once.
         * Method to be called when a record will no longer be used.
         */
        void disposeLocked(boolean removeReceiver) {
            mRequestStatistics.stopRequesting(mReceiver.mPackageName, mProvider);

            // remove from mRecordsByProvider
            ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
            if (globalRecords != null) {
@@ -1541,6 +1551,7 @@ public class LocationManagerService extends ILocationManager.Stub {
        if (oldRecords != null) {
            // Call dispose() on the obsolete update records.
            for (UpdateRecord record : oldRecords.values()) {
                // Update statistics for historical location requests by package/provider
                record.disposeLocked(false);
            }
            // Accumulate providers
@@ -2351,13 +2362,20 @@ public class LocationManagerService extends ILocationManager.Stub {
            for (Receiver receiver : mReceivers.values()) {
                pw.println("    " + receiver);
            }
            pw.println("  Records by Provider:");
            pw.println("  Active Records by Provider:");
            for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
                pw.println("    " + entry.getKey() + ":");
                for (UpdateRecord record : entry.getValue()) {
                    pw.println("      " + record);
                }
            }
            pw.println("  Historical Records by Provider:");
            for (Map.Entry<PackageProviderKey, PackageStatistics> entry
                    : mRequestStatistics.statistics.entrySet()) {
                PackageProviderKey key = entry.getKey();
                PackageStatistics stats = entry.getValue();
                pw.println("    " + key.packageName + ": " + key.providerName + ": " + stats);
            }
            pw.println("  Last Known Locations:");
            for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
                String provider = entry.getKey();
+205 −0
Original line number Diff line number Diff line
package com.android.server.location;

import android.os.SystemClock;
import android.util.Log;

import java.util.HashMap;

/**
 * Holds statistics for location requests (active requests by provider).
 *
 * <p>Must be externally synchronized.
 */
public class LocationRequestStatistics {
    private static final String TAG = "LocationStats";

    // Maps package name nad provider to location request statistics.
    public final HashMap<PackageProviderKey, PackageStatistics> statistics
            = new HashMap<PackageProviderKey, PackageStatistics>();

    /**
     * Signals that a package has started requesting locations.
     *
     * @param packageName Name of package that has requested locations.
     * @param providerName Name of provider that is requested (e.g. "gps").
     * @param intervalMs The interval that is requested in ms.
     */
    public void startRequesting(String packageName, String providerName, long intervalMs) {
        PackageProviderKey key = new PackageProviderKey(packageName, providerName);
        PackageStatistics stats = statistics.get(key);
        if (stats == null) {
            stats = new PackageStatistics();
            statistics.put(key, stats);
        }
        stats.startRequesting(intervalMs);
    }

    /**
     * Signals that a package has stopped requesting locations.
     *
     * @param packageName Name of package that has stopped requesting locations.
     * @param providerName Provider that is no longer being requested.
     */
    public void stopRequesting(String packageName, String providerName) {
        PackageProviderKey key = new PackageProviderKey(packageName, providerName);
        PackageStatistics stats = statistics.get(key);
        if (stats != null) {
            stats.stopRequesting();
        } else {
            // This shouldn't be a possible code path.
            Log.e(TAG, "Couldn't find package statistics when removing location request.");
        }
    }

    /**
     * A key that holds both package and provider names.
     */
    public static class PackageProviderKey {
        /**
         * Name of package requesting location.
         */
        public final String packageName;
        /**
         * Name of provider being requested (e.g. "gps").
         */
        public final String providerName;

        public PackageProviderKey(String packageName, String providerName) {
            this.packageName = packageName;
            this.providerName = providerName;
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof PackageProviderKey)) {
                return false;
            }

            PackageProviderKey otherKey = (PackageProviderKey) other;
            return packageName.equals(otherKey.packageName)
                    && providerName.equals(otherKey.providerName);
        }

        @Override
        public int hashCode() {
            return packageName.hashCode() + 31 * providerName.hashCode();
        }
    }

    /**
     * Usage statistics for a package/provider pair.
     */
    public static class PackageStatistics {
        // Time when this package first requested location.
        private final long mInitialElapsedTimeMs;
        // Number of active location requests this package currently has.
        private int mNumActiveRequests;
        // Time when this package most recently went from not requesting location to requesting.
        private long mLastActivitationElapsedTimeMs;
        // The fastest interval this package has ever requested.
        private long mFastestIntervalMs;
        // The slowest interval this package has ever requested.
        private long mSlowestIntervalMs;
        // The total time this app has requested location (not including currently running requests).
        private long mTotalDurationMs;

        private PackageStatistics() {
            mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
            mNumActiveRequests = 0;
            mTotalDurationMs = 0;
            mFastestIntervalMs = Long.MAX_VALUE;
            mSlowestIntervalMs = 0;
        }

        private void startRequesting(long intervalMs) {
            if (mNumActiveRequests == 0) {
                mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
            }

            if (intervalMs < mFastestIntervalMs) {
                mFastestIntervalMs = intervalMs;
            }

            if (intervalMs > mSlowestIntervalMs) {
                mSlowestIntervalMs = intervalMs;
            }

            mNumActiveRequests++;
        }

        private void stopRequesting() {
            if (mNumActiveRequests <= 0) {
                // Shouldn't be a possible code path
                Log.e(TAG, "Reference counting corrupted in usage statistics.");
                return;
            }

            mNumActiveRequests--;
            if (mNumActiveRequests == 0) {
                long lastDurationMs
                        = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
                mTotalDurationMs += lastDurationMs;
            }
        }

        /**
         * Returns the duration that this request has been active.
         */
        public long getDurationMs() {
            long currentDurationMs = mTotalDurationMs;
            if (mNumActiveRequests > 0) {
                currentDurationMs
                        += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
            }
            return currentDurationMs;
        }

        /**
         * Returns the time since the initial request in ms.
         */
        public long getTimeSinceFirstRequestMs() {
            return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
        }

        /**
         * Returns the fastest interval that has been tracked.
         */
        public long getFastestIntervalMs() {
            return mFastestIntervalMs;
        }

        /**
         * Returns the slowest interval that has been tracked.
         */
        public long getSlowestIntervalMs() {
            return mSlowestIntervalMs;
        }

        /**
         * Returns true if a request is active for these tracked statistics.
         */
        public boolean isActive() {
            return mNumActiveRequests > 0;
        }

        @Override
        public String toString() {
            StringBuilder s = new StringBuilder();
            if (mFastestIntervalMs == mSlowestIntervalMs) {
                s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
            } else {
                s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
                s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
            }
            s.append(": Duration requested ")
                    .append((getDurationMs() / 1000) / 60)
                    .append(" out of the last ")
                    .append((getTimeSinceFirstRequestMs() / 1000) / 60)
                    .append(" minutes");
            if (isActive()) {
                s.append(": Currently active");
            }
            return s.toString();
        }
    }
}
+175 −0
Original line number Diff line number Diff line
package com.android.server.location;

import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;

import android.os.SystemClock;
import android.test.AndroidTestCase;

/**
 * Unit tests for {@link LocationRequestStatistics}.
 */
public class LocationRequestStatisticsTest extends AndroidTestCase {
    private static final String PACKAGE1 = "package1";
    private static final String PACKAGE2 = "package2";
    private static final String PROVIDER1 = "provider1";
    private static final String PROVIDER2 = "provider2";
    private static final long INTERVAL1 = 5000;
    private static final long INTERVAL2 = 100000;

    private LocationRequestStatistics mStatistics;
    private long mStartElapsedRealtimeMs;

    @Override
    public void setUp() {
        mStatistics = new LocationRequestStatistics();
        mStartElapsedRealtimeMs = SystemClock.elapsedRealtime();
    }

    /**
     * Tests that adding a single package works correctly.
     */
    public void testSinglePackage() {
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);

        assertEquals(1, mStatistics.statistics.size());
        PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
        assertEquals(PACKAGE1, key.packageName);
        assertEquals(PROVIDER1, key.providerName);
        PackageStatistics stats = mStatistics.statistics.values().iterator().next();
        verifyStatisticsTimes(stats);
        assertEquals(INTERVAL1, stats.getFastestIntervalMs());
        assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
        assertTrue(stats.isActive());
    }

    /**
     * Tests that adding a single package works correctly when it is stopped and restarted.
     */
    public void testSinglePackage_stopAndRestart() {
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);

        assertEquals(1, mStatistics.statistics.size());
        PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
        assertEquals(PACKAGE1, key.packageName);
        assertEquals(PROVIDER1, key.providerName);
        PackageStatistics stats = mStatistics.statistics.values().iterator().next();
        verifyStatisticsTimes(stats);
        assertEquals(INTERVAL1, stats.getFastestIntervalMs());
        assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
        assertTrue(stats.isActive());

        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        assertFalse(stats.isActive());
    }

    /**
     * Tests that adding a single package works correctly when multiple intervals are used.
     */
    public void testSinglePackage_multipleIntervals() {
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL2);

        assertEquals(1, mStatistics.statistics.size());
        PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
        assertEquals(PACKAGE1, key.packageName);
        assertEquals(PROVIDER1, key.providerName);
        PackageStatistics stats = mStatistics.statistics.values().iterator().next();
        verifyStatisticsTimes(stats);
        assertEquals(INTERVAL1, stats.getFastestIntervalMs());
        assertTrue(stats.isActive());

        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        assertTrue(stats.isActive());
        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        assertFalse(stats.isActive());
    }

    /**
     * Tests that adding a single package works correctly when multiple providers are used.
     */
    public void testSinglePackage_multipleProviders() {
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
        mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL2);

        assertEquals(2, mStatistics.statistics.size());
        PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, PROVIDER1);
        PackageStatistics stats1 = mStatistics.statistics.get(key1);
        verifyStatisticsTimes(stats1);
        assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
        assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
        assertTrue(stats1.isActive());
        PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, PROVIDER2);
        PackageStatistics stats2 = mStatistics.statistics.get(key2);
        verifyStatisticsTimes(stats2);
        assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
        assertEquals(INTERVAL2, stats2.getFastestIntervalMs());
        assertTrue(stats2.isActive());

        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        assertFalse(stats1.isActive());
        assertTrue(stats2.isActive());
        mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
        assertFalse(stats1.isActive());
        assertFalse(stats2.isActive());
    }

    /**
     * Tests that adding multiple packages works correctly.
     */
    public void testMultiplePackages() {
        mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
        mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL1);
        mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL2);
        mStatistics.startRequesting(PACKAGE2, PROVIDER1, INTERVAL1);

        assertEquals(3, mStatistics.statistics.size());
        PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, PROVIDER1);
        PackageStatistics stats1 = mStatistics.statistics.get(key1);
        verifyStatisticsTimes(stats1);
        assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
        assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
        assertTrue(stats1.isActive());

        PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, PROVIDER2);
        PackageStatistics stats2 = mStatistics.statistics.get(key2);
        verifyStatisticsTimes(stats2);
        assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
        assertEquals(INTERVAL1, stats2.getFastestIntervalMs());
        assertTrue(stats2.isActive());

        PackageProviderKey key3 = new PackageProviderKey(PACKAGE2, PROVIDER1);
        PackageStatistics stats3 = mStatistics.statistics.get(key3);
        verifyStatisticsTimes(stats3);
        assertEquals(INTERVAL1, stats3.getSlowestIntervalMs());
        assertEquals(INTERVAL1, stats3.getFastestIntervalMs());
        assertTrue(stats3.isActive());

        mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
        assertFalse(stats1.isActive());
        assertTrue(stats2.isActive());
        assertTrue(stats3.isActive());

        mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
        assertFalse(stats1.isActive());
        assertTrue(stats2.isActive());
        assertTrue(stats3.isActive());
        mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
        assertFalse(stats2.isActive());

        mStatistics.stopRequesting(PACKAGE2, PROVIDER1);
        assertFalse(stats1.isActive());
        assertFalse(stats2.isActive());
        assertFalse(stats3.isActive());
    }

    private void verifyStatisticsTimes(PackageStatistics stats) {
        long durationMs = stats.getDurationMs();
        long timeSinceFirstRequestMs = stats.getTimeSinceFirstRequestMs();
        long maxDeltaMs = SystemClock.elapsedRealtime() - mStartElapsedRealtimeMs;
        assertTrue("Duration is too large", durationMs <= maxDeltaMs);
        assertTrue("Time since first request is too large", timeSinceFirstRequestMs <= maxDeltaMs);
    }
}