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

Commit 7a068c2a authored by Ajay Panicker's avatar Ajay Panicker
Browse files

Move ScanStats to its own file

ScanStats was growing too large and had many components so
it was moved into its own file.

Bug: 27294154
Change-Id: Ic20c9e74d5d8b074f7e966625fb1bffab6c94f2d
parent f848f42a
Loading
Loading
Loading
Loading
+207 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2016 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.bluetooth.gatt;

import android.bluetooth.le.ScanSettings;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import com.android.bluetooth.btservice.BluetoothProto;
/**
 * ScanStats class helps keep track of information about scans
 * on a per application basis.
 * @hide
 */
/*package*/ class AppScanStats {
    static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    /* ContextMap here is needed to grab Apps and Connections */
    ContextMap contextMap;

    class LastScan {
        long duration;
        long timestamp;
        boolean opportunistic;
        boolean background;

        public LastScan(long timestamp, long duration,
                        boolean opportunistic, boolean background) {
            this.duration = duration;
            this.timestamp = timestamp;
            this.opportunistic = opportunistic;
            this.background = background;
        }
    }

    static final int NUM_SCAN_DURATIONS_KEPT = 5;

    String appName;
    int scansStarted = 0;
    int scansStopped = 0;
    boolean isScanning = false;
    boolean isRegistered = false;
    long minScanTime = Long.MAX_VALUE;
    long maxScanTime = 0;
    long totalScanTime = 0;
    List<LastScan> lastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT + 1);
    long startTime = 0;
    long stopTime = 0;

    public AppScanStats(String name, ContextMap map) {
        appName = name;
        contextMap = map;
    }

    synchronized void recordScanStart(ScanSettings settings) {
        if (isScanning)
            return;

        this.scansStarted++;
        isScanning = true;
        startTime = System.currentTimeMillis();

        LastScan scan = new LastScan(startTime, 0, false, false);
        if (settings != null) {
          scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
          scan.background = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
        }
        lastScans.add(scan);

        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START);
        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
        scanEvent.setInitiator(appName);
        scanEvent.setEventTimeMillis(System.currentTimeMillis());
        contextMap.addScanEvent(scanEvent);
    }

    synchronized void recordScanStop() {
        if (!isScanning)
          return;

        this.scansStopped++;
        isScanning = false;
        stopTime = System.currentTimeMillis();
        long scanDuration = stopTime - startTime;

        minScanTime = Math.min(scanDuration, minScanTime);
        maxScanTime = Math.max(scanDuration, maxScanTime);
        totalScanTime += scanDuration;

        LastScan curr = lastScans.get(lastScans.size() - 1);
        curr.duration = scanDuration;

        if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) {
            lastScans.remove(0);
        }

        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP);
        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
        scanEvent.setInitiator(appName);
        scanEvent.setEventTimeMillis(System.currentTimeMillis());
        contextMap.addScanEvent(scanEvent);
    }

    synchronized void dumpToString(StringBuilder sb) {
        long currTime = System.currentTimeMillis();
        long maxScan = maxScanTime;
        long minScan = minScanTime;
        long scanDuration = 0;

        if (lastScans.isEmpty())
            return;

        if (isScanning) {
            scanDuration = currTime - startTime;
            minScan = Math.min(scanDuration, minScan);
            maxScan = Math.max(scanDuration, maxScan);
        }

        if (minScan == Long.MAX_VALUE) {
            minScan = 0;
        }

        long avgScan = 0;
        if (scansStarted > 0) {
            avgScan = (totalScanTime + scanDuration) / scansStarted;
        }

        LastScan lastScan = lastScans.get(lastScans.size() - 1);
        sb.append("  " + appName);
        if (isRegistered) sb.append(" (Registered)");
        if (lastScan.opportunistic) sb.append(" (Opportunistic)");
        if (lastScan.background) sb.append(" (Background)");
        sb.append("\n");

        sb.append("  LE scans (started/stopped)         : " +
                  scansStarted + " / " +
                  scansStopped + "\n");
        sb.append("  Scan time in ms (min/max/avg/total): " +
                  minScan + " / " +
                  maxScan + " / " +
                  avgScan + " / " +
                  totalScanTime + "\n");

        if (lastScans.size() != 0) {
            int lastScansSize = scansStopped < NUM_SCAN_DURATIONS_KEPT ?
                                scansStopped : NUM_SCAN_DURATIONS_KEPT;
            sb.append("  Last " + lastScansSize +
                      " scans                       :\n");

            for (int i = 0; i < lastScansSize; i++) {
                LastScan scan = lastScans.get(i);
                Date timestamp = new Date(scan.timestamp);
                sb.append("    " + dateFormat.format(timestamp) + " - ");
                sb.append(scan.duration + "ms ");
                if (scan.opportunistic) sb.append("Opp ");
                if (scan.background) sb.append("Back");
                sb.append("\n");
            }
        }

        if (isRegistered) {
            ContextMap.App appEntry = contextMap.getByName(appName);
            sb.append("  Application ID                     : " +
                      appEntry.id + "\n");
            sb.append("  UUID                               : " +
                      appEntry.uuid + "\n");

            if (isScanning) {
                sb.append("  Current scan duration in ms        : " +
                          scanDuration + "\n");
            }

            List<ContextMap.Connection> connections =
              contextMap.getConnectionByApp(appEntry.id);

            sb.append("  Connections: " + connections.size() + "\n");

            Iterator<ContextMap.Connection> ii = connections.iterator();
            while(ii.hasNext()) {
                ContextMap.Connection connection = ii.next();
                long connectionTime = System.currentTimeMillis() - connection.startTime;
                sb.append("    " + connection.connId + ": " +
                          connection.address + " " + connectionTime + "ms\n");
            }
        }
        sb.append("\n");
    }
}
+29 −223
Original line number Original line Diff line number Diff line
@@ -15,7 +15,6 @@
 */
 */
package com.android.bluetooth.gatt;
package com.android.bluetooth.gatt;


import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Context;
import android.os.Binder;
import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder;
@@ -23,13 +22,9 @@ import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.IInterface;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.Log;
import android.util.Log;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Set;
@@ -38,7 +33,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Map;


import com.android.bluetooth.btservice.BluetoothProto;
import com.android.bluetooth.btservice.BluetoothProto;

/**
/**
 * Helper class that keeps track of registered GATT applications.
 * Helper class that keeps track of registered GATT applications.
 * This class manages application callbacks and keeps track of GATT connections.
 * This class manages application callbacks and keeps track of GATT connections.
@@ -46,207 +40,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
 */
 */
/*package*/ class ContextMap<T> {
/*package*/ class ContextMap<T> {
    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";

    static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    static final int NUM_SCAN_EVENTS_KEPT = 20;
    static final int NUM_SCAN_EVENTS_KEPT = 20;
    ArrayList<BluetoothProto.ScanEvent> mScanEvents =
      new ArrayList<BluetoothProto.ScanEvent>(NUM_SCAN_EVENTS_KEPT);

    /**
     * ScanStats class helps keep track of information about scans
     * on a per application basis.
     */
    class ScanStats {

        class LastScan {
            long durration;
            long timestamp;
            boolean opportunistic;
            boolean background;

            public LastScan(long timestamp, long durration,
                            boolean opportunistic, boolean background) {
                this.durration = durration;
                this.timestamp = timestamp;
                this.opportunistic = opportunistic;
                this.background = background;
            }
        }

        static final int NUM_SCAN_DURATIONS_KEPT = 5;

        String appName;
        int scansStarted = 0;
        int scansStopped = 0;
        boolean isScanning = false;
        boolean isRegistered = false;
        boolean isOpportunisticScan = false;
        boolean isBackgroundScan = false;
        long minScanTime = Long.MAX_VALUE;
        long maxScanTime = 0;
        long totalScanTime = 0;
        List<LastScan> lastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT + 1);
        long startTime = 0;
        long stopTime = 0;

        public ScanStats(String name) {
            appName = name;
        }

        synchronized void recordScanStart(ScanSettings settings) {
            if (isScanning)
                return;

            this.scansStarted++;
            isScanning = true;
            startTime = System.currentTimeMillis();

            if (settings != null) {
                isOpportunisticScan = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
                isBackgroundScan = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
            }

            BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
            scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START);
            scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
            scanEvent.setInitiator(appName);
            scanEvent.setEventTimeMillis(System.currentTimeMillis());

            lastScans.add(new LastScan(startTime, 0, false, false));

            if (settings != null) {
              isOpportunisticScan = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
              isBackgroundScan = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
            }

            synchronized(mScanEvents) {
                if(mScanEvents.size() == NUM_SCAN_EVENTS_KEPT)
                    mScanEvents.remove(0);
                mScanEvents.add(scanEvent);
            }
        }

        synchronized void recordScanStop() {
            if (!isScanning)
              return;

            this.scansStopped++;
            isScanning = false;
            stopTime = System.currentTimeMillis();
            long currTime = stopTime - startTime;

            minScanTime = Math.min(currTime, minScanTime);
            maxScanTime = Math.max(currTime, maxScanTime);
            totalScanTime += currTime;

            LastScan curr = lastScans.get(lastScans.size() - 1);
            curr.durration = currTime;
            curr.opportunistic = isOpportunisticScan;
            curr.background = isBackgroundScan;

            isOpportunisticScan = false;
            isBackgroundScan = false;

            if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) {
                lastScans.remove(0);
            }

            BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
            scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP);
            scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
            scanEvent.setInitiator(appName);
            scanEvent.setEventTimeMillis(System.currentTimeMillis());
            synchronized(mScanEvents) {
                if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT)
                    mScanEvents.remove(0);
                mScanEvents.add(scanEvent);
            }
        }

        synchronized void dumpToString(StringBuilder sb) {
            long currTime = System.currentTimeMillis();
            long maxScan = maxScanTime;
            long minScan = minScanTime;
            long currScan = 0;

            if (isScanning) {
                currScan = currTime - startTime;
                minScan = Math.min(currScan, minScan);
                maxScan = Math.max(currScan, maxScan);
            }

            if (minScan == Long.MAX_VALUE) {
                minScan = 0;
            }

            long lastScan = 0;
            if (stopTime != 0) {
                lastScan = currTime - stopTime;
            }

            long avgScan = 0;
            if (scansStarted > 0) {
                avgScan = (totalScanTime + currScan) / scansStarted;
            }

            sb.append("  " + appName);
            if (isRegistered) sb.append(" (Registered)");
            if (isOpportunisticScan) sb.append(" (Opportunistic)");
            if (isBackgroundScan) sb.append(" (Background)");
            sb.append("\n");

            sb.append("  LE scans (started/stopped)         : " +
                      scansStarted + " / " +
                      scansStopped + "\n");
            sb.append("  Scan time in ms (min/max/avg/total): " +
                      minScan + " / " +
                      maxScan + " / " +
                      avgScan + " / " +
                      totalScanTime + "\n");

            if (lastScans.size() != 0) {
                int lastScansSize = scansStopped < NUM_SCAN_DURATIONS_KEPT ?
                                    scansStopped : NUM_SCAN_DURATIONS_KEPT;
                sb.append("  Last " + lastScansSize +
                          " scans                       :\n");

                for (int i = 0; i < lastScansSize; i++) {
                    LastScan scan = lastScans.get(i);
                    Date timestamp = new Date(scan.timestamp);
                    sb.append("    " + dateFormat.format(timestamp) + " - ");
                    sb.append(scan.durration + "ms ");
                    if (scan.opportunistic) sb.append("Opp ");
                    if (scan.background) sb.append("Back");
                    sb.append("\n");
                }
            }

            if (isRegistered) {
                App appEntry = getByName(appName);
                sb.append("  Application ID                     : " +
                          appEntry.id + "\n");
                sb.append("  UUID                               : " +
                          appEntry.uuid + "\n");

                if (isScanning) {
                    sb.append("  Current scan duration in ms        : " +
                              currScan + "\n");
                }

                List<Connection> connections = getConnectionByApp(appEntry.id);
                sb.append("  Connections: " + connections.size() + "\n");

                Iterator<Connection> ii = connections.iterator();
                while(ii.hasNext()) {
                    Connection connection = ii.next();
                    long connectionTime = System.currentTimeMillis() - connection.startTime;
                    sb.append("    " + connection.connId + ": " +
                              connection.address + " " + connectionTime + "ms\n");
                }
            }
            sb.append("\n");
        }
    }


    /**
    /**
     * Connection class helps map connection IDs to device addresses.
     * Connection class helps map connection IDs to device addresses.
@@ -340,7 +134,11 @@ import com.android.bluetooth.btservice.BluetoothProto;
    List<App> mApps = new ArrayList<App>();
    List<App> mApps = new ArrayList<App>();


    /** Internal map to keep track of logging information by app name */
    /** Internal map to keep track of logging information by app name */
    HashMap<String, ScanStats> mScanStats = new HashMap<String, ScanStats>();
    HashMap<String, AppScanStats> mAppScanStats = new HashMap<String, AppScanStats>();

    /** Internal list of scan events to use with the proto */
    ArrayList<BluetoothProto.ScanEvent> mScanEvents =
        new ArrayList<BluetoothProto.ScanEvent>(NUM_SCAN_EVENTS_KEPT);


    /** Internal list of connected devices **/
    /** Internal list of connected devices **/
    Set<Connection> mConnections = new HashSet<Connection>();
    Set<Connection> mConnections = new HashSet<Connection>();
@@ -357,12 +155,12 @@ import com.android.bluetooth.btservice.BluetoothProto;
        }
        }
        synchronized (mApps) {
        synchronized (mApps) {
            mApps.add(new App(uuid, callback, appName));
            mApps.add(new App(uuid, callback, appName));
            ScanStats scanStats = mScanStats.get(appName);
            AppScanStats appScanStats = mAppScanStats.get(appName);
            if (scanStats == null) {
            if (appScanStats == null) {
                scanStats = new ScanStats(appName);
                appScanStats = new AppScanStats(appName, this);
                mScanStats.put(appName, scanStats);
                mAppScanStats.put(appName, appScanStats);
            }
            }
            scanStats.isRegistered = true;
            appScanStats.isRegistered = true;
        }
        }
    }
    }


@@ -376,7 +174,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
                App entry = i.next();
                App entry = i.next();
                if (entry.uuid.equals(uuid)) {
                if (entry.uuid.equals(uuid)) {
                    entry.unlinkToDeath();
                    entry.unlinkToDeath();
                    mScanStats.get(entry.name).isRegistered = false;
                    mAppScanStats.get(entry.name).isRegistered = false;
                    i.remove();
                    i.remove();
                    break;
                    break;
                }
                }
@@ -394,7 +192,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
                App entry = i.next();
                App entry = i.next();
                if (entry.id == id) {
                if (entry.id == id) {
                    entry.unlinkToDeath();
                    entry.unlinkToDeath();
                    mScanStats.get(entry.name).isRegistered = false;
                    mAppScanStats.get(entry.name).isRegistered = false;
                    i.remove();
                    i.remove();
                    break;
                    break;
                }
                }
@@ -472,10 +270,10 @@ import com.android.bluetooth.btservice.BluetoothProto;
    /**
    /**
     * Get Logging info by ID
     * Get Logging info by ID
     */
     */
    ScanStats getScanStatsById(int id) {
    AppScanStats getAppScanStatsById(int id) {
        App temp = getById(id);
        App temp = getById(id);
        if (temp != null) {
        if (temp != null) {
            return mScanStats.get(temp.name);
            return mAppScanStats.get(temp.name);
        }
        }
        return null;
        return null;
    }
    }
@@ -483,8 +281,8 @@ import com.android.bluetooth.btservice.BluetoothProto;
    /**
    /**
     * Get Logging info by application name
     * Get Logging info by application name
     */
     */
    ScanStats getScanStatsByName(String name) {
    AppScanStats getAppScanStatsByName(String name) {
        return mScanStats.get(name);
        return mAppScanStats.get(name);
    }
    }


    /**
    /**
@@ -582,19 +380,27 @@ import com.android.bluetooth.btservice.BluetoothProto;
        return connectedmap;
        return connectedmap;
    }
    }


    void addScanEvent(BluetoothProto.ScanEvent event) {
        synchronized(mScanEvents) {
            if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT)
                mScanEvents.remove(0);
            mScanEvents.add(event);
        }
    }

    /**
    /**
     * Logs debug information.
     * Logs debug information.
     */
     */
    void dump(StringBuilder sb) {
    void dump(StringBuilder sb) {
        sb.append("  Entries: " + mScanStats.size() + "\n\n");
        sb.append("  Entries: " + mAppScanStats.size() + "\n\n");


        Iterator<Map.Entry<String,ScanStats>> it = mScanStats.entrySet().iterator();
        Iterator<Map.Entry<String, AppScanStats>> it = mAppScanStats.entrySet().iterator();
        while (it.hasNext()) {
        while (it.hasNext()) {
            Map.Entry<String, ScanStats> entry = it.next();
            Map.Entry<String, AppScanStats> entry = it.next();


            String name = entry.getKey();
            String name = entry.getKey();
            ScanStats scanStats = entry.getValue();
            AppScanStats appScanStats = entry.getValue();
            scanStats.dumpToString(sb);
            appScanStats.dumpToString(sb);
        }
        }
    }
    }


+2 −2
Original line number Original line Diff line number Diff line
@@ -1399,7 +1399,7 @@ public class GattService extends ProfileService {
        scanClient.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(
        scanClient.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(
                this);
                this);
        scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
        scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
        mClientMap.getScanStatsById(appIf).recordScanStart(settings);
        mClientMap.getAppScanStatsById(appIf).recordScanStart(settings);
        mScanManager.startScan(scanClient);
        mScanManager.startScan(scanClient);
    }
    }


@@ -1414,7 +1414,7 @@ public class GattService extends ProfileService {
        int scanQueueSize = mScanManager.getBatchScanQueue().size() +
        int scanQueueSize = mScanManager.getBatchScanQueue().size() +
                mScanManager.getRegularScanQueue().size();
                mScanManager.getRegularScanQueue().size();
        if (DBG) Log.d(TAG, "stopScan() - queue size =" + scanQueueSize);
        if (DBG) Log.d(TAG, "stopScan() - queue size =" + scanQueueSize);
        mClientMap.getScanStatsById(client.clientIf).recordScanStop();
        mClientMap.getAppScanStatsById(client.clientIf).recordScanStop();
        mScanManager.stopScan(client);
        mScanManager.stopScan(client);
    }
    }