1 /*
2  * Copyright (C) 2018 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  */
16 
17 package com.android.server.wifi.rtt;
18 
19 import static com.android.server.wifi.util.MetricsUtils.addValueToLinearHistogram;
20 import static com.android.server.wifi.util.MetricsUtils.addValueToLogHistogram;
21 import static com.android.server.wifi.util.MetricsUtils.linearHistogramToGenericBuckets;
22 import static com.android.server.wifi.util.MetricsUtils.logHistogramToGenericBuckets;
23 
24 import android.net.MacAddress;
25 import android.net.wifi.rtt.RangingRequest;
26 import android.net.wifi.rtt.RangingResult;
27 import android.net.wifi.rtt.ResponderConfig;
28 import android.os.WorkSource;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.util.SparseIntArray;
32 
33 import com.android.server.wifi.Clock;
34 import com.android.server.wifi.hal.WifiRttController;
35 import com.android.server.wifi.proto.nano.WifiMetricsProto;
36 import com.android.server.wifi.util.MetricsUtils;
37 
38 import java.io.FileDescriptor;
39 import java.io.PrintWriter;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * Wi-Fi RTT metric container/processor.
46  */
47 public class RttMetrics {
48     private static final String TAG = "RttMetrics";
49     private static final boolean VDBG = false;
50     private boolean mVerboseLoggingEnabled = false;
51 
52     private final Object mLock = new Object();
53     private final Clock mClock;
54 
55     // accumulated metrics data
56 
57     // Histogram: 7 buckets (i=0, ..., 6) of 1 slots in range 10^i -> 10^(i+1)
58     // Buckets:
59     //    1 -> 10
60     //    10 -> 100
61     //    100 -> 1000
62     //    10^3 -> 10^4
63     //    10^4 -> 10^5
64     //    10^5 -> 10^6
65     //    10^5 -> 10^7 (10^7 ms = 160 minutes)
66     private static final MetricsUtils.LogHistParms COUNT_LOG_HISTOGRAM =
67             new MetricsUtils.LogHistParms(0, 1, 10, 1, 7);
68 
69     // Histogram for ranging limits in discovery. Indicates the following 7 buckets (in meters):
70     //   < 0
71     //   [0, 5)
72     //   [5, 15)
73     //   [15, 30)
74     //   [30, 60)
75     //   [60, 100)
76     //   >= 100
77     private static final int[] DISTANCE_MM_HISTOGRAM =
78             {0, 5 * 1000, 15 * 1000, 30 * 1000, 60 * 1000, 100 * 1000};
79     // Histogram for duration for ap only measurement. Indicates 5 buckets with 1000 ms interval.
80     private static final int[] MEASUREMENT_DURATION_HISTOGRAM_AP =
81             {1 * 1000, 2 * 1000, 3 * 1000, 4 * 1000};
82 
83     // Histogram for duration for measurement with aware. Indicates 5 buckets with 2000 ms interval.
84     private static final int[] MEASUREMENT_DURATION_HISTOGRAM_AWARE =
85             {2 * 1000, 4 * 1000, 6 * 1000, 8 * 1000};
86 
87     private static final int PEER_AP = 0;
88     private static final int PEER_AWARE = 1;
89 
90     private int mNumStartRangingCalls = 0;
91     private SparseIntArray mOverallStatusHistogram = new SparseIntArray();
92     private SparseIntArray mMeasurementDurationApOnlyHistogram = new SparseIntArray();
93     private SparseIntArray mMeasurementDurationWithAwareHistogram = new SparseIntArray();
94     private PerPeerTypeInfo[] mPerPeerTypeInfo;
95 
RttMetrics(Clock clock)96     public RttMetrics(Clock clock) {
97         mClock = clock;
98 
99         mPerPeerTypeInfo = new PerPeerTypeInfo[2];
100         mPerPeerTypeInfo[PEER_AP] = new PerPeerTypeInfo();
101         mPerPeerTypeInfo[PEER_AWARE] = new PerPeerTypeInfo();
102     }
103 
104     private class PerUidInfo {
105         public int numRequests;
106         public long lastRequestMs;
107 
108         @Override
toString()109         public String toString() {
110             return "numRequests=" + numRequests + ", lastRequestMs=" + lastRequestMs;
111         }
112     }
113 
114     private class PerPeerTypeInfo {
115         public int numCalls;
116         public int numIndividualCalls;
117         public SparseArray<PerUidInfo> perUidInfo = new SparseArray<>();
118         public SparseIntArray numRequestsHistogram = new SparseIntArray();
119         public SparseIntArray requestGapHistogram = new SparseIntArray();
120         public SparseIntArray statusHistogram = new SparseIntArray();
121         public SparseIntArray measuredDistanceHistogram = new SparseIntArray();
122 
123         @Override
toString()124         public String toString() {
125             return "numCalls=" + numCalls + ", numIndividualCalls=" + numIndividualCalls
126                     + ", perUidInfo=" + perUidInfo + ", numRequestsHistogram="
127                     + numRequestsHistogram + ", requestGapHistogram=" + requestGapHistogram
128                     + ", statusHistogram=" + statusHistogram
129                     + ", measuredDistanceHistogram=" + measuredDistanceHistogram;
130         }
131     }
132 
133     /**
134      * Enable/Disable verbose logging.
135      */
enableVerboseLogging(boolean verboseEnabled)136     public void enableVerboseLogging(boolean verboseEnabled) {
137         mVerboseLoggingEnabled = verboseEnabled;
138     }
139 
140     // metric recording API
141 
142     /**
143      * Record metrics for the range request.
144      */
recordRequest(WorkSource ws, RangingRequest requests)145     public void recordRequest(WorkSource ws, RangingRequest requests) {
146         mNumStartRangingCalls++;
147 
148         int numApRequests = 0;
149         int numAwareRequests = 0;
150         for (ResponderConfig request : requests.mRttPeers) {
151             if (request == null) {
152                 continue;
153             }
154             if (request.responderType == ResponderConfig.RESPONDER_AWARE) {
155                 numAwareRequests++;
156             } else if (request.responderType == ResponderConfig.RESPONDER_AP) {
157                 numApRequests++;
158             } else {
159                 if (mVerboseLoggingEnabled) {
160                     Log.d(TAG,
161                             "Unexpected Responder type: " + request.responderType);
162                 }
163             }
164         }
165 
166         updatePeerInfoWithRequestInfo(mPerPeerTypeInfo[PEER_AP], ws, numApRequests);
167         updatePeerInfoWithRequestInfo(mPerPeerTypeInfo[PEER_AWARE], ws, numAwareRequests);
168     }
169 
170     /**
171      * Record metrics for the range results.
172      */
recordResult(RangingRequest requests, List<RangingResult> results, int measurementDuration)173     public void recordResult(RangingRequest requests, List<RangingResult> results,
174             int measurementDuration) {
175         Map<MacAddress, ResponderConfig> requestEntries = new HashMap<>();
176         for (ResponderConfig responder : requests.mRttPeers) {
177             requestEntries.put(responder.macAddress, responder);
178         }
179         if (results != null) {
180             boolean containsAwarePeer = false;
181             for (RangingResult result : results) {
182                 if (result == null) {
183                     continue;
184                 }
185                 ResponderConfig responder = requestEntries.remove(result.getMacAddress());
186                 if (responder == null) {
187                     Log.e(TAG,
188                             "recordResult: found a result which doesn't match any requests: "
189                                     + result);
190                     continue;
191                 }
192 
193                 if (responder.responderType == ResponderConfig.RESPONDER_AP) {
194                     updatePeerInfoWithResultInfo(mPerPeerTypeInfo[PEER_AP], result);
195                 } else if (responder.responderType == ResponderConfig.RESPONDER_AWARE) {
196                     containsAwarePeer = true;
197                     updatePeerInfoWithResultInfo(mPerPeerTypeInfo[PEER_AWARE], result);
198                 } else {
199                     Log.e(TAG, "recordResult: unexpected peer type in responder: " + responder);
200                 }
201             }
202             if (containsAwarePeer) {
203                 addValueToLinearHistogram(measurementDuration,
204                         mMeasurementDurationWithAwareHistogram,
205                         MEASUREMENT_DURATION_HISTOGRAM_AWARE);
206             } else {
207                 addValueToLinearHistogram(measurementDuration,
208                         mMeasurementDurationApOnlyHistogram,
209                         MEASUREMENT_DURATION_HISTOGRAM_AP);
210             }
211         }
212 
213         for (ResponderConfig responder : requestEntries.values()) {
214             PerPeerTypeInfo peerInfo;
215             if (responder.responderType == ResponderConfig.RESPONDER_AP) {
216                 peerInfo = mPerPeerTypeInfo[PEER_AP];
217             } else if (responder.responderType == ResponderConfig.RESPONDER_AWARE) {
218                 peerInfo = mPerPeerTypeInfo[PEER_AWARE];
219             } else {
220                 Log.e(TAG, "recordResult: unexpected peer type in responder: " + responder);
221                 continue;
222             }
223             peerInfo.statusHistogram.put(WifiMetricsProto.WifiRttLog.MISSING_RESULT,
224                     peerInfo.statusHistogram.get(WifiMetricsProto.WifiRttLog.MISSING_RESULT) + 1);
225         }
226     }
227 
228     /**
229      * Record metrics for the overall ranging request status.
230      */
recordOverallStatus(int status)231     public void recordOverallStatus(int status) {
232         mOverallStatusHistogram.put(status, mOverallStatusHistogram.get(status) + 1);
233     }
234 
updatePeerInfoWithRequestInfo(PerPeerTypeInfo peerInfo, WorkSource ws, int numIndividualCalls)235     private void updatePeerInfoWithRequestInfo(PerPeerTypeInfo peerInfo, WorkSource ws,
236             int numIndividualCalls) {
237         if (numIndividualCalls == 0) {
238             return;
239         }
240 
241         long nowMs = mClock.getElapsedSinceBootMillis();
242         peerInfo.numCalls++;
243         peerInfo.numIndividualCalls += numIndividualCalls;
244         peerInfo.numRequestsHistogram.put(numIndividualCalls,
245                 peerInfo.numRequestsHistogram.get(numIndividualCalls) + 1);
246         boolean recordedIntervals = false;
247 
248         for (int i = 0; i < ws.size(); ++i) {
249             int uid = ws.getUid(i);
250 
251             PerUidInfo perUidInfo = peerInfo.perUidInfo.get(uid);
252             if (perUidInfo == null) {
253                 perUidInfo = new PerUidInfo();
254             }
255 
256             perUidInfo.numRequests++;
257 
258             if (!recordedIntervals && perUidInfo.lastRequestMs != 0) {
259                 recordedIntervals = true; // don't want to record twice
260                 addValueToLogHistogram(nowMs - perUidInfo.lastRequestMs,
261                         peerInfo.requestGapHistogram, COUNT_LOG_HISTOGRAM);
262             }
263             perUidInfo.lastRequestMs = nowMs;
264 
265             peerInfo.perUidInfo.put(uid, perUidInfo);
266         }
267     }
268 
updatePeerInfoWithResultInfo(PerPeerTypeInfo peerInfo, RangingResult result)269     private void updatePeerInfoWithResultInfo(PerPeerTypeInfo peerInfo, RangingResult result) {
270         int protoStatus = convertRttStatusTypeToProtoEnum(result.getStatus());
271         peerInfo.statusHistogram.put(protoStatus, peerInfo.statusHistogram.get(protoStatus) + 1);
272         if (result.getStatus() != RangingResult.STATUS_SUCCESS) {
273             return;
274         }
275         addValueToLinearHistogram(result.getDistanceMm(), peerInfo.measuredDistanceHistogram,
276                 DISTANCE_MM_HISTOGRAM);
277     }
278 
279     /**
280      * Consolidate all metrics into the proto.
281      */
consolidateProto()282     public WifiMetricsProto.WifiRttLog consolidateProto() {
283         WifiMetricsProto.WifiRttLog log = new WifiMetricsProto.WifiRttLog();
284         log.rttToAp = new WifiMetricsProto.WifiRttLog.RttToPeerLog();
285         log.rttToAware = new WifiMetricsProto.WifiRttLog.RttToPeerLog();
286         synchronized (mLock) {
287             log.numRequests = mNumStartRangingCalls;
288             log.histogramOverallStatus = consolidateOverallStatus(mOverallStatusHistogram);
289             log.histogramMeasurementDurationApOnly = genericBucketsToRttBuckets(
290                     linearHistogramToGenericBuckets(mMeasurementDurationApOnlyHistogram,
291                             MEASUREMENT_DURATION_HISTOGRAM_AP));
292             log.histogramMeasurementDurationWithAware = genericBucketsToRttBuckets(
293                     linearHistogramToGenericBuckets(mMeasurementDurationWithAwareHistogram,
294                             MEASUREMENT_DURATION_HISTOGRAM_AWARE));
295 
296             consolidatePeerType(log.rttToAp, mPerPeerTypeInfo[PEER_AP]);
297             consolidatePeerType(log.rttToAware, mPerPeerTypeInfo[PEER_AWARE]);
298         }
299         return log;
300     }
301 
consolidateOverallStatus( SparseIntArray histogram)302     private WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[] consolidateOverallStatus(
303             SparseIntArray histogram) {
304         WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[] h =
305                 new WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[histogram.size()];
306         for (int i = 0; i < histogram.size(); i++) {
307             h[i] = new WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket();
308             h[i].statusType = histogram.keyAt(i);
309             h[i].count = histogram.valueAt(i);
310         }
311         return h;
312     }
313 
consolidatePeerType(WifiMetricsProto.WifiRttLog.RttToPeerLog peerLog, PerPeerTypeInfo peerInfo)314     private void consolidatePeerType(WifiMetricsProto.WifiRttLog.RttToPeerLog peerLog,
315             PerPeerTypeInfo peerInfo) {
316         peerLog.numRequests = peerInfo.numCalls;
317         peerLog.numIndividualRequests = peerInfo.numIndividualCalls;
318         peerLog.numApps = peerInfo.perUidInfo.size();
319         peerLog.histogramNumPeersPerRequest = consolidateNumPeersPerRequest(
320                 peerInfo.numRequestsHistogram);
321         peerLog.histogramNumRequestsPerApp = consolidateNumRequestsPerApp(peerInfo.perUidInfo);
322         peerLog.histogramRequestIntervalMs = genericBucketsToRttBuckets(
323                 logHistogramToGenericBuckets(peerInfo.requestGapHistogram, COUNT_LOG_HISTOGRAM));
324         peerLog.histogramIndividualStatus = consolidateIndividualStatus(peerInfo.statusHistogram);
325         peerLog.histogramDistance = genericBucketsToRttBuckets(
326                 linearHistogramToGenericBuckets(peerInfo.measuredDistanceHistogram,
327                         DISTANCE_MM_HISTOGRAM));
328     }
329 
330     private WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[]
consolidateIndividualStatus(SparseIntArray histogram)331             consolidateIndividualStatus(SparseIntArray histogram) {
332         WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[] h =
333                 new WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[histogram.size(
334                 )];
335         for (int i = 0; i < histogram.size(); i++) {
336             h[i] = new WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket();
337             h[i].statusType = histogram.keyAt(i);
338             h[i].count = histogram.valueAt(i);
339         }
340         return h;
341     }
342 
consolidateNumPeersPerRequest( SparseIntArray data)343     private WifiMetricsProto.WifiRttLog.HistogramBucket[] consolidateNumPeersPerRequest(
344             SparseIntArray data) {
345         WifiMetricsProto.WifiRttLog.HistogramBucket[] protoArray =
346                 new WifiMetricsProto.WifiRttLog.HistogramBucket[data.size()];
347 
348         for (int i = 0; i < data.size(); i++) {
349             protoArray[i] = new WifiMetricsProto.WifiRttLog.HistogramBucket();
350             protoArray[i].start = data.keyAt(i);
351             protoArray[i].end = data.keyAt(i);
352             protoArray[i].count = data.valueAt(i);
353         }
354 
355         return protoArray;
356     }
357 
consolidateNumRequestsPerApp( SparseArray<PerUidInfo> perUidInfos)358     private WifiMetricsProto.WifiRttLog.HistogramBucket[] consolidateNumRequestsPerApp(
359             SparseArray<PerUidInfo> perUidInfos) {
360         SparseIntArray histogramNumRequestsPerUid = new SparseIntArray();
361         for (int i = 0; i < perUidInfos.size(); i++) {
362             addValueToLogHistogram(perUidInfos.valueAt(i).numRequests, histogramNumRequestsPerUid,
363                     COUNT_LOG_HISTOGRAM);
364         }
365 
366         return genericBucketsToRttBuckets(logHistogramToGenericBuckets(
367                 histogramNumRequestsPerUid, COUNT_LOG_HISTOGRAM));
368     }
369 
genericBucketsToRttBuckets( MetricsUtils.GenericBucket[] genericHistogram)370     private WifiMetricsProto.WifiRttLog.HistogramBucket[] genericBucketsToRttBuckets(
371             MetricsUtils.GenericBucket[] genericHistogram) {
372         WifiMetricsProto.WifiRttLog.HistogramBucket[] histogram =
373                 new WifiMetricsProto.WifiRttLog.HistogramBucket[genericHistogram.length];
374         for (int i = 0; i < genericHistogram.length; i++) {
375             histogram[i] = new WifiMetricsProto.WifiRttLog.HistogramBucket();
376             histogram[i].start = genericHistogram[i].start;
377             histogram[i].end = genericHistogram[i].end;
378             histogram[i].count = genericHistogram[i].count;
379         }
380         return histogram;
381     }
382 
383     /**
384      * Dump all RttMetrics to console (pw) - this method is never called to dump the serialized
385      * metrics (handled by parent WifiMetrics).
386      *
387      * @param fd   unused
388      * @param pw   PrintWriter for writing dump to
389      * @param args unused
390      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)391     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
392         synchronized (mLock) {
393             pw.println("RTT Metrics:");
394             pw.println("mNumStartRangingCalls:" + mNumStartRangingCalls);
395             pw.println("mOverallStatusHistogram:" + mOverallStatusHistogram);
396             pw.println("mMeasurementDurationApOnlyHistogram" + mMeasurementDurationApOnlyHistogram);
397             pw.println("mMeasurementDurationWithAwareHistogram"
398                     + mMeasurementDurationWithAwareHistogram);
399             pw.println("AP:" + mPerPeerTypeInfo[PEER_AP]);
400             pw.println("AWARE:" + mPerPeerTypeInfo[PEER_AWARE]);
401         }
402     }
403 
404     /**
405      * clear Wi-Fi RTT metrics
406      */
clear()407     public void clear() {
408         synchronized (mLock) {
409             mNumStartRangingCalls = 0;
410             mOverallStatusHistogram.clear();
411             mPerPeerTypeInfo[PEER_AP] = new PerPeerTypeInfo();
412             mPerPeerTypeInfo[PEER_AWARE] = new PerPeerTypeInfo();
413             mMeasurementDurationApOnlyHistogram.clear();
414             mMeasurementDurationWithAwareHistogram.clear();
415         }
416     }
417 
418     /**
419      * Convert a HAL RttStatus enum to a Metrics proto enum RttIndividualStatusTypeEnum.
420      */
convertRttStatusTypeToProtoEnum(int rttStatusType)421     public static int convertRttStatusTypeToProtoEnum(int rttStatusType) {
422         switch (rttStatusType) {
423             case WifiRttController.FRAMEWORK_RTT_STATUS_SUCCESS:
424                 return WifiMetricsProto.WifiRttLog.SUCCESS;
425             case WifiRttController.FRAMEWORK_RTT_STATUS_FAILURE:
426                 return WifiMetricsProto.WifiRttLog.FAILURE;
427             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_NO_RSP:
428                 return WifiMetricsProto.WifiRttLog.FAIL_NO_RSP;
429             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_REJECTED:
430                 return WifiMetricsProto.WifiRttLog.FAIL_REJECTED;
431             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_NOT_SCHEDULED_YET:
432                 return WifiMetricsProto.WifiRttLog.FAIL_NOT_SCHEDULED_YET;
433             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_TM_TIMEOUT:
434                 return WifiMetricsProto.WifiRttLog.FAIL_TM_TIMEOUT;
435             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL:
436                 return WifiMetricsProto.WifiRttLog.FAIL_AP_ON_DIFF_CHANNEL;
437             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_NO_CAPABILITY:
438                 return WifiMetricsProto.WifiRttLog.FAIL_NO_CAPABILITY;
439             case WifiRttController.FRAMEWORK_RTT_STATUS_ABORTED:
440                 return WifiMetricsProto.WifiRttLog.ABORTED;
441             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_INVALID_TS:
442                 return WifiMetricsProto.WifiRttLog.FAIL_INVALID_TS;
443             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_PROTOCOL:
444                 return WifiMetricsProto.WifiRttLog.FAIL_PROTOCOL;
445             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_SCHEDULE:
446                 return WifiMetricsProto.WifiRttLog.FAIL_SCHEDULE;
447             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_BUSY_TRY_LATER:
448                 return WifiMetricsProto.WifiRttLog.FAIL_BUSY_TRY_LATER;
449             case WifiRttController.FRAMEWORK_RTT_STATUS_INVALID_REQ:
450                 return WifiMetricsProto.WifiRttLog.INVALID_REQ;
451             case WifiRttController.FRAMEWORK_RTT_STATUS_NO_WIFI:
452                 return WifiMetricsProto.WifiRttLog.NO_WIFI;
453             case WifiRttController.FRAMEWORK_RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE:
454                 return WifiMetricsProto.WifiRttLog.FAIL_FTM_PARAM_OVERRIDE;
455             default:
456                 Log.e(TAG, "Unrecognized RttStatus: " + rttStatusType);
457                 return WifiMetricsProto.WifiRttLog.UNKNOWN;
458         }
459     }
460 }
461