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