/* * Copyright (C) 2018 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.server.wifi.rtt; import static com.android.server.wifi.util.MetricsUtils.addValueToLinearHistogram; import static com.android.server.wifi.util.MetricsUtils.addValueToLogHistogram; import static com.android.server.wifi.util.MetricsUtils.linearHistogramToGenericBuckets; import static com.android.server.wifi.util.MetricsUtils.logHistogramToGenericBuckets; import android.hardware.wifi.V1_0.RttResult; import android.hardware.wifi.V1_0.RttStatus; import android.net.MacAddress; import android.net.wifi.rtt.RangingRequest; import android.net.wifi.rtt.ResponderConfig; import android.os.WorkSource; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.server.wifi.Clock; import com.android.server.wifi.nano.WifiMetricsProto; import com.android.server.wifi.util.MetricsUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Wi-Fi RTT metric container/processor. */ public class RttMetrics { private static final String TAG = "RttMetrics"; private static final boolean VDBG = false; /* package */ boolean mDbg = false; private final Object mLock = new Object(); private final Clock mClock; // accumulated metrics data // Histogram: 7 buckets (i=0, ..., 6) of 1 slots in range 10^i -> 10^(i+1) // Buckets: // 1 -> 10 // 10 -> 100 // 100 -> 1000 // 10^3 -> 10^4 // 10^4 -> 10^5 // 10^5 -> 10^6 // 10^5 -> 10^7 (10^7 ms = 160 minutes) private static final MetricsUtils.LogHistParms COUNT_LOG_HISTOGRAM = new MetricsUtils.LogHistParms(0, 1, 10, 1, 7); // Histogram for ranging limits in discovery. Indicates the following 7 buckets (in meters): // < 0 // [0, 5) // [5, 15) // [15, 30) // [30, 60) // [60, 100) // >= 100 private static final int[] DISTANCE_MM_HISTOGRAM = {0, 5 * 1000, 15 * 1000, 30 * 1000, 60 * 1000, 100 * 1000}; private static final int PEER_AP = 0; private static final int PEER_AWARE = 1; private int mNumStartRangingCalls = 0; private SparseIntArray mOverallStatusHistogram = new SparseIntArray(); private PerPeerTypeInfo[] mPerPeerTypeInfo; public RttMetrics(Clock clock) { mClock = clock; mPerPeerTypeInfo = new PerPeerTypeInfo[2]; mPerPeerTypeInfo[PEER_AP] = new PerPeerTypeInfo(); mPerPeerTypeInfo[PEER_AWARE] = new PerPeerTypeInfo(); } private class PerUidInfo { public int numRequests; public long lastRequestMs; @Override public String toString() { return "numRequests=" + numRequests + ", lastRequestMs=" + lastRequestMs; } } private class PerPeerTypeInfo { public int numCalls; public int numIndividualCalls; public SparseArray perUidInfo = new SparseArray<>(); public SparseIntArray numRequestsHistogram = new SparseIntArray(); public SparseIntArray requestGapHistogram = new SparseIntArray(); public SparseIntArray statusHistogram = new SparseIntArray(); public SparseIntArray measuredDistanceHistogram = new SparseIntArray(); @Override public String toString() { return "numCalls=" + numCalls + ", numIndividualCalls=" + numIndividualCalls + ", perUidInfo=" + perUidInfo + ", numRequestsHistogram=" + numRequestsHistogram + ", requestGapHistogram=" + requestGapHistogram + ", measuredDistanceHistogram=" + measuredDistanceHistogram; } } // metric recording API /** * Record metrics for the range request. */ public void recordRequest(WorkSource ws, RangingRequest requests) { mNumStartRangingCalls++; int numApRequests = 0; int numAwareRequests = 0; for (ResponderConfig request : requests.mRttPeers) { if (request == null) { continue; } if (request.responderType == ResponderConfig.RESPONDER_AWARE) { numAwareRequests++; } else if (request.responderType == ResponderConfig.RESPONDER_AP) { numApRequests++; } else { if (mDbg) Log.d(TAG, "Unexpected Responder type: " + request.responderType); } } updatePeerInfoWithRequestInfo(mPerPeerTypeInfo[PEER_AP], ws, numApRequests); updatePeerInfoWithRequestInfo(mPerPeerTypeInfo[PEER_AWARE], ws, numAwareRequests); } /** * Record metrics for the range results. */ public void recordResult(RangingRequest requests, List results) { Map requestEntries = new HashMap<>(); for (ResponderConfig responder : requests.mRttPeers) { requestEntries.put(responder.macAddress, responder); } if (results != null) { for (RttResult result : results) { if (result == null) { continue; } ResponderConfig responder = requestEntries.remove( MacAddress.fromBytes(result.addr)); if (responder == null) { Log.e(TAG, "recordResult: found a result which doesn't match any requests: " + result); continue; } if (responder.responderType == ResponderConfig.RESPONDER_AP) { updatePeerInfoWithResultInfo(mPerPeerTypeInfo[PEER_AP], result); } else if (responder.responderType == ResponderConfig.RESPONDER_AWARE) { updatePeerInfoWithResultInfo(mPerPeerTypeInfo[PEER_AWARE], result); } else { Log.e(TAG, "recordResult: unexpected peer type in responder: " + responder); } } } for (ResponderConfig responder : requestEntries.values()) { PerPeerTypeInfo peerInfo; if (responder.responderType == ResponderConfig.RESPONDER_AP) { peerInfo = mPerPeerTypeInfo[PEER_AP]; } else if (responder.responderType == ResponderConfig.RESPONDER_AWARE) { peerInfo = mPerPeerTypeInfo[PEER_AWARE]; } else { Log.e(TAG, "recordResult: unexpected peer type in responder: " + responder); continue; } peerInfo.statusHistogram.put(WifiMetricsProto.WifiRttLog.MISSING_RESULT, peerInfo.statusHistogram.get(WifiMetricsProto.WifiRttLog.MISSING_RESULT) + 1); } } /** * Record metrics for the overall ranging request status. */ public void recordOverallStatus(int status) { mOverallStatusHistogram.put(status, mOverallStatusHistogram.get(status) + 1); } private void updatePeerInfoWithRequestInfo(PerPeerTypeInfo peerInfo, WorkSource ws, int numIndividualCalls) { if (numIndividualCalls == 0) { return; } long nowMs = mClock.getElapsedSinceBootMillis(); peerInfo.numCalls++; peerInfo.numIndividualCalls += numIndividualCalls; peerInfo.numRequestsHistogram.put(numIndividualCalls, peerInfo.numRequestsHistogram.get(numIndividualCalls) + 1); boolean recordedIntervals = false; for (int i = 0; i < ws.size(); ++i) { int uid = ws.get(i); PerUidInfo perUidInfo = peerInfo.perUidInfo.get(uid); if (perUidInfo == null) { perUidInfo = new PerUidInfo(); } perUidInfo.numRequests++; if (!recordedIntervals && perUidInfo.lastRequestMs != 0) { recordedIntervals = true; // don't want to record twice addValueToLogHistogram(nowMs - perUidInfo.lastRequestMs, peerInfo.requestGapHistogram, COUNT_LOG_HISTOGRAM); } perUidInfo.lastRequestMs = nowMs; peerInfo.perUidInfo.put(uid, perUidInfo); } } private void updatePeerInfoWithResultInfo(PerPeerTypeInfo peerInfo, RttResult result) { int protoStatus = convertRttStatusTypeToProtoEnum(result.status); peerInfo.statusHistogram.put(protoStatus, peerInfo.statusHistogram.get(protoStatus) + 1); addValueToLinearHistogram(result.distanceInMm, peerInfo.measuredDistanceHistogram, DISTANCE_MM_HISTOGRAM); } /** * Consolidate all metrics into the proto. */ public WifiMetricsProto.WifiRttLog consolidateProto() { WifiMetricsProto.WifiRttLog log = new WifiMetricsProto.WifiRttLog(); log.rttToAp = new WifiMetricsProto.WifiRttLog.RttToPeerLog(); log.rttToAware = new WifiMetricsProto.WifiRttLog.RttToPeerLog(); synchronized (mLock) { log.numRequests = mNumStartRangingCalls; log.histogramOverallStatus = consolidateOverallStatus(mOverallStatusHistogram); consolidatePeerType(log.rttToAp, mPerPeerTypeInfo[PEER_AP]); consolidatePeerType(log.rttToAware, mPerPeerTypeInfo[PEER_AWARE]); } return log; } private WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[] consolidateOverallStatus( SparseIntArray histogram) { WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[] h = new WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket[histogram.size()]; for (int i = 0; i < histogram.size(); i++) { h[i] = new WifiMetricsProto.WifiRttLog.RttOverallStatusHistogramBucket(); h[i].statusType = histogram.keyAt(i); h[i].count = histogram.valueAt(i); } return h; } private void consolidatePeerType(WifiMetricsProto.WifiRttLog.RttToPeerLog peerLog, PerPeerTypeInfo peerInfo) { peerLog.numRequests = peerInfo.numCalls; peerLog.numIndividualRequests = peerInfo.numIndividualCalls; peerLog.numApps = peerInfo.perUidInfo.size(); peerLog.histogramNumPeersPerRequest = consolidateNumPeersPerRequest( peerInfo.numRequestsHistogram); peerLog.histogramNumRequestsPerApp = consolidateNumRequestsPerApp(peerInfo.perUidInfo); peerLog.histogramRequestIntervalMs = genericBucketsToRttBuckets( logHistogramToGenericBuckets(peerInfo.requestGapHistogram, COUNT_LOG_HISTOGRAM)); peerLog.histogramIndividualStatus = consolidateIndividualStatus(peerInfo.statusHistogram); peerLog.histogramDistance = genericBucketsToRttBuckets( linearHistogramToGenericBuckets(peerInfo.measuredDistanceHistogram, DISTANCE_MM_HISTOGRAM)); } private WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[] consolidateIndividualStatus(SparseIntArray histogram) { WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[] h = new WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket[histogram.size( )]; for (int i = 0; i < histogram.size(); i++) { h[i] = new WifiMetricsProto.WifiRttLog.RttIndividualStatusHistogramBucket(); h[i].statusType = histogram.keyAt(i); h[i].count = histogram.valueAt(i); } return h; } private WifiMetricsProto.WifiRttLog.HistogramBucket[] consolidateNumPeersPerRequest( SparseIntArray data) { WifiMetricsProto.WifiRttLog.HistogramBucket[] protoArray = new WifiMetricsProto.WifiRttLog.HistogramBucket[data.size()]; for (int i = 0; i < data.size(); i++) { protoArray[i] = new WifiMetricsProto.WifiRttLog.HistogramBucket(); protoArray[i].start = data.keyAt(i); protoArray[i].end = data.keyAt(i); protoArray[i].count = data.valueAt(i); } return protoArray; } private WifiMetricsProto.WifiRttLog.HistogramBucket[] consolidateNumRequestsPerApp( SparseArray perUidInfos) { SparseIntArray histogramNumRequestsPerUid = new SparseIntArray(); for (int i = 0; i < perUidInfos.size(); i++) { addValueToLogHistogram(perUidInfos.valueAt(i).numRequests, histogramNumRequestsPerUid, COUNT_LOG_HISTOGRAM); } return genericBucketsToRttBuckets(logHistogramToGenericBuckets( histogramNumRequestsPerUid, COUNT_LOG_HISTOGRAM)); } private WifiMetricsProto.WifiRttLog.HistogramBucket[] genericBucketsToRttBuckets( MetricsUtils.GenericBucket[] genericHistogram) { WifiMetricsProto.WifiRttLog.HistogramBucket[] histogram = new WifiMetricsProto.WifiRttLog.HistogramBucket[genericHistogram.length]; for (int i = 0; i < genericHistogram.length; i++) { histogram[i] = new WifiMetricsProto.WifiRttLog.HistogramBucket(); histogram[i].start = genericHistogram[i].start; histogram[i].end = genericHistogram[i].end; histogram[i].count = genericHistogram[i].count; } return histogram; } /** * Dump all RttMetrics to console (pw) - this method is never called to dump the serialized * metrics (handled by parent WifiMetrics). * * @param fd unused * @param pw PrintWriter for writing dump to * @param args unused */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mLock) { pw.println("RTT Metrics:"); pw.println("mNumStartRangingCalls:" + mNumStartRangingCalls); pw.println("mOverallStatusHistogram:" + mOverallStatusHistogram); pw.println("AP:" + mPerPeerTypeInfo[PEER_AP]); pw.println("AWARE:" + mPerPeerTypeInfo[PEER_AWARE]); } } /** * clear Wi-Fi RTT metrics */ public void clear() { synchronized (mLock) { mNumStartRangingCalls = 0; mOverallStatusHistogram.clear(); mPerPeerTypeInfo[PEER_AP] = new PerPeerTypeInfo(); mPerPeerTypeInfo[PEER_AWARE] = new PerPeerTypeInfo(); } } /** * Convert a HAL RttStatus enum to a Metrics proto enum RttIndividualStatusTypeEnum. */ public static int convertRttStatusTypeToProtoEnum(int rttStatusType) { switch (rttStatusType) { case RttStatus.SUCCESS: return WifiMetricsProto.WifiRttLog.SUCCESS; case RttStatus.FAILURE: return WifiMetricsProto.WifiRttLog.FAILURE; case RttStatus.FAIL_NO_RSP: return WifiMetricsProto.WifiRttLog.FAIL_NO_RSP; case RttStatus.FAIL_REJECTED: return WifiMetricsProto.WifiRttLog.FAIL_REJECTED; case RttStatus.FAIL_NOT_SCHEDULED_YET: return WifiMetricsProto.WifiRttLog.FAIL_NOT_SCHEDULED_YET; case RttStatus.FAIL_TM_TIMEOUT: return WifiMetricsProto.WifiRttLog.FAIL_TM_TIMEOUT; case RttStatus.FAIL_AP_ON_DIFF_CHANNEL: return WifiMetricsProto.WifiRttLog.FAIL_AP_ON_DIFF_CHANNEL; case RttStatus.FAIL_NO_CAPABILITY: return WifiMetricsProto.WifiRttLog.FAIL_NO_CAPABILITY; case RttStatus.ABORTED: return WifiMetricsProto.WifiRttLog.ABORTED; case RttStatus.FAIL_INVALID_TS: return WifiMetricsProto.WifiRttLog.FAIL_INVALID_TS; case RttStatus.FAIL_PROTOCOL: return WifiMetricsProto.WifiRttLog.FAIL_PROTOCOL; case RttStatus.FAIL_SCHEDULE: return WifiMetricsProto.WifiRttLog.FAIL_SCHEDULE; case RttStatus.FAIL_BUSY_TRY_LATER: return WifiMetricsProto.WifiRttLog.FAIL_BUSY_TRY_LATER; case RttStatus.INVALID_REQ: return WifiMetricsProto.WifiRttLog.INVALID_REQ; case RttStatus.NO_WIFI: return WifiMetricsProto.WifiRttLog.NO_WIFI; case RttStatus.FAIL_FTM_PARAM_OVERRIDE: return WifiMetricsProto.WifiRttLog.FAIL_FTM_PARAM_OVERRIDE; default: Log.e(TAG, "Unrecognized RttStatus: " + rttStatusType); return WifiMetricsProto.WifiRttLog.UNKNOWN; } } }