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