blob: ecc1c4812ed3eefd174dfdbac84d0d091d344c7b [file] [log] [blame]
/*
* 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> 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
+ ", statusHistogram=" + statusHistogram
+ ", 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<RttResult> results) {
Map<MacAddress, ResponderConfig> 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<PerUidInfo> 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;
}
}
}