AppScanStats.java revision b7af521b6c2d1ccaaea687207dfbcd0c34489a3c
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.bluetooth.gatt;
17
18import android.bluetooth.le.ScanSettings;
19import java.text.DateFormat;
20import java.text.SimpleDateFormat;
21import java.util.ArrayList;
22import java.util.Date;
23import java.util.Iterator;
24import java.util.List;
25
26import com.android.bluetooth.btservice.BluetoothProto;
27/**
28 * ScanStats class helps keep track of information about scans
29 * on a per application basis.
30 * @hide
31 */
32/*package*/ class AppScanStats {
33    static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
34
35    /* ContextMap here is needed to grab Apps and Connections */
36    ContextMap contextMap;
37
38    class LastScan {
39        long duration;
40        long timestamp;
41        boolean opportunistic;
42        boolean background;
43
44        public LastScan(long timestamp, long duration,
45                        boolean opportunistic, boolean background) {
46            this.duration = duration;
47            this.timestamp = timestamp;
48            this.opportunistic = opportunistic;
49            this.background = background;
50        }
51    }
52
53    static final int NUM_SCAN_DURATIONS_KEPT = 5;
54
55    String appName;
56    int scansStarted = 0;
57    int scansStopped = 0;
58    boolean isScanning = false;
59    boolean isRegistered = false;
60    long minScanTime = Long.MAX_VALUE;
61    long maxScanTime = 0;
62    long totalScanTime = 0;
63    List<LastScan> lastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT + 1);
64    long startTime = 0;
65    long stopTime = 0;
66
67    public AppScanStats(String name, ContextMap map) {
68        appName = name;
69        contextMap = map;
70    }
71
72    synchronized void recordScanStart(ScanSettings settings) {
73        if (isScanning)
74            return;
75
76        this.scansStarted++;
77        isScanning = true;
78        startTime = System.currentTimeMillis();
79
80        LastScan scan = new LastScan(startTime, 0, false, false);
81        if (settings != null) {
82          scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
83          scan.background = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
84        }
85        lastScans.add(scan);
86
87        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
88        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START);
89        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
90        scanEvent.setInitiator(appName);
91        scanEvent.setEventTimeMillis(System.currentTimeMillis());
92        contextMap.addScanEvent(scanEvent);
93    }
94
95    synchronized void recordScanStop() {
96        if (!isScanning)
97          return;
98
99        this.scansStopped++;
100        isScanning = false;
101        stopTime = System.currentTimeMillis();
102        long scanDuration = stopTime - startTime;
103
104        minScanTime = Math.min(scanDuration, minScanTime);
105        maxScanTime = Math.max(scanDuration, maxScanTime);
106        totalScanTime += scanDuration;
107
108        LastScan curr = lastScans.get(lastScans.size() - 1);
109        curr.duration = scanDuration;
110
111        if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) {
112            lastScans.remove(0);
113        }
114
115        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
116        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP);
117        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
118        scanEvent.setInitiator(appName);
119        scanEvent.setEventTimeMillis(System.currentTimeMillis());
120        contextMap.addScanEvent(scanEvent);
121    }
122
123    synchronized void dumpToString(StringBuilder sb) {
124        long currTime = System.currentTimeMillis();
125        long maxScan = maxScanTime;
126        long minScan = minScanTime;
127        long scanDuration = 0;
128
129        if (lastScans.isEmpty())
130            return;
131
132        if (isScanning) {
133            scanDuration = currTime - startTime;
134            minScan = Math.min(scanDuration, minScan);
135            maxScan = Math.max(scanDuration, maxScan);
136        }
137
138        if (minScan == Long.MAX_VALUE) {
139            minScan = 0;
140        }
141
142        long avgScan = 0;
143        if (scansStarted > 0) {
144            avgScan = (totalScanTime + scanDuration) / scansStarted;
145        }
146
147        LastScan lastScan = lastScans.get(lastScans.size() - 1);
148        sb.append("  " + appName);
149        if (isRegistered) sb.append(" (Registered)");
150        if (lastScan.opportunistic) sb.append(" (Opportunistic)");
151        if (lastScan.background) sb.append(" (Background)");
152        sb.append("\n");
153
154        sb.append("  LE scans (started/stopped)         : " +
155                  scansStarted + " / " +
156                  scansStopped + "\n");
157        sb.append("  Scan time in ms (min/max/avg/total): " +
158                  minScan + " / " +
159                  maxScan + " / " +
160                  avgScan + " / " +
161                  totalScanTime + "\n");
162
163        if (lastScans.size() != 0) {
164            int lastScansSize = scansStopped < NUM_SCAN_DURATIONS_KEPT ?
165                                scansStopped : NUM_SCAN_DURATIONS_KEPT;
166            sb.append("  Last " + lastScansSize +
167                      " scans                       :\n");
168
169            for (int i = 0; i < lastScansSize; i++) {
170                LastScan scan = lastScans.get(i);
171                Date timestamp = new Date(scan.timestamp);
172                sb.append("    " + dateFormat.format(timestamp) + " - ");
173                sb.append(scan.duration + "ms ");
174                if (scan.opportunistic) sb.append("Opp ");
175                if (scan.background) sb.append("Back");
176                sb.append("\n");
177            }
178        }
179
180        if (isRegistered) {
181            ContextMap.App appEntry = contextMap.getByName(appName);
182            sb.append("  Application ID                     : " +
183                      appEntry.id + "\n");
184            sb.append("  UUID                               : " +
185                      appEntry.uuid + "\n");
186
187            if (isScanning) {
188                sb.append("  Current scan duration in ms        : " +
189                          scanDuration + "\n");
190            }
191
192            List<ContextMap.Connection> connections =
193              contextMap.getConnectionByApp(appEntry.id);
194
195            sb.append("  Connections: " + connections.size() + "\n");
196
197            Iterator<ContextMap.Connection> ii = connections.iterator();
198            while(ii.hasNext()) {
199                ContextMap.Connection connection = ii.next();
200                long connectionTime = System.currentTimeMillis() - connection.startTime;
201                sb.append("    " + connection.connId + ": " +
202                          connection.address + " " + connectionTime + "ms\n");
203            }
204        }
205        sb.append("\n");
206    }
207}
208