blob: c6a7105bec8f917e38e33d077afd59d7c3c76264 [file] [log] [blame]
/*
* Copyright (C) 2016 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;
import android.net.NetworkAgent;
import android.net.wifi.WifiInfo;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Locale;
/**
* Class used to calculate scores for connected wifi networks and report it to the associated
* network agent.
*/
public class WifiScoreReport {
private static final String TAG = "WifiScoreReport";
private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 3600; // 3 hours on 3 second poll
private boolean mVerboseLoggingEnabled = false;
private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016
// Cache of the last score
private int mScore = NetworkAgent.WIFI_BASE_SCORE;
private final ScoringParams mScoringParams;
private final Clock mClock;
private int mSessionNumber = 0;
ConnectedScore mAggressiveConnectedScore;
VelocityBasedConnectedScore mVelocityBasedConnectedScore;
WifiScoreReport(ScoringParams scoringParams, Clock clock) {
mScoringParams = scoringParams;
mClock = clock;
mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock);
}
/**
* Reset the last calculated score.
*/
public void reset() {
mSessionNumber++;
mScore = NetworkAgent.WIFI_BASE_SCORE;
mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
mAggressiveConnectedScore.reset();
mVelocityBasedConnectedScore.reset();
if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
}
/**
* Enable/Disable verbose logging in score report generation.
*/
public void enableVerboseLogging(boolean enable) {
mVerboseLoggingEnabled = enable;
}
/**
* Calculate wifi network score based on updated link layer stats and send the score to
* the provided network agent.
*
* If the score has changed from the previous value, update the WifiNetworkAgent.
*
* Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
*
* @param wifiInfo WifiInfo instance pointing to the currently connected network.
* @param networkAgent NetworkAgent to be notified of new score.
* @param wifiMetrics for reporting our scores.
*/
public void calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent,
WifiMetrics wifiMetrics) {
int score;
long millis = mClock.getWallClockMillis();
int netId = 0;
if (networkAgent != null) {
netId = networkAgent.netId;
}
mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
mVelocityBasedConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
int s1 = mAggressiveConnectedScore.generateScore();
int s2 = mVelocityBasedConnectedScore.generateScore();
score = s2;
if (wifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE
&& score <= ConnectedScore.WIFI_TRANSITION_SCORE
&& wifiInfo.txSuccessRate >= mScoringParams.getYippeeSkippyPacketsPerSecond()
&& wifiInfo.rxSuccessRate >= mScoringParams.getYippeeSkippyPacketsPerSecond()) {
score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
}
if (wifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE
&& score <= ConnectedScore.WIFI_TRANSITION_SCORE) {
// We don't want to trigger a downward breach unless the rssi is
// below the entry threshold. There is noise in the measured rssi, and
// the kalman-filtered rssi is affected by the trend, so check them both.
// TODO(b/74613347) skip this if there are other indications to support the low score
int entry = mScoringParams.getEntryRssi(wifiInfo.getFrequency());
if (mVelocityBasedConnectedScore.getFilteredRssi() >= entry
|| wifiInfo.getRssi() >= entry) {
// Stay a notch above the transition score to reduce ambiguity.
score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
}
}
//sanitize boundaries
if (score > NetworkAgent.WIFI_BASE_SCORE) {
score = NetworkAgent.WIFI_BASE_SCORE;
}
if (score < 0) {
score = 0;
}
logLinkMetrics(wifiInfo, millis, netId, s1, s2, score);
//report score
if (score != wifiInfo.score) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "report new wifi score " + score);
}
wifiInfo.score = score;
if (networkAgent != null) {
networkAgent.sendNetworkScore(score);
}
}
wifiMetrics.incrementWifiScoreCount(score);
mScore = score;
}
private static final double TIME_CONSTANT_MILLIS = 30.0e+3;
private static final long NUD_THROTTLE_MILLIS = 5000;
private long mLastKnownNudCheckTimeMillis = 0;
private int mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
private int mNudYes = 0; // Counts when we voted for a NUD
private int mNudCount = 0; // Counts when we were told a NUD was sent
/**
* Recommends that a layer 3 check be done
*
* The caller can use this to (help) decide that an IP reachability check
* is desirable. The check is not done here; that is the caller's responsibility.
*
* @return true to indicate that an IP reachability check is recommended
*/
public boolean shouldCheckIpLayer() {
int nud = mScoringParams.getNudKnob();
if (nud == 0) {
return false;
}
long millis = mClock.getWallClockMillis();
long deltaMillis = millis - mLastKnownNudCheckTimeMillis;
// Don't ever ask back-to-back - allow at least 5 seconds
// for the previous one to finish.
if (deltaMillis < NUD_THROTTLE_MILLIS) {
return false;
}
// nud is between 1 and 10 at this point
double deltaLevel = 11 - nud;
// nextNudBreach is the bar the score needs to cross before we ask for NUD
double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE;
// If we were below threshold the last time we checked, then compute a new bar
// that starts down from there and decays exponentially back up to the steady-state
// bar. If 5 time constants have passed, we are 99% of the way there, so skip the math.
if (mLastKnownNudCheckScore < ConnectedScore.WIFI_TRANSITION_SCORE
&& deltaMillis < 5.0 * TIME_CONSTANT_MILLIS) {
double a = Math.exp(-deltaMillis / TIME_CONSTANT_MILLIS);
nextNudBreach = a * (mLastKnownNudCheckScore - deltaLevel) + (1.0 - a) * nextNudBreach;
}
if (mScore >= nextNudBreach) {
return false;
}
mNudYes++;
return true;
}
/**
* Should be called when a reachability check has been issued
*
* When the caller has requested an IP reachability check, calling this will
* help to rate-limit requests via shouldCheckIpLayer()
*/
public void noteIpCheck() {
long millis = mClock.getWallClockMillis();
mLastKnownNudCheckTimeMillis = millis;
mLastKnownNudCheckScore = mScore;
mNudCount++;
}
/**
* Data for dumpsys
*
* These are stored as csv formatted lines
*/
private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>();
/**
* Data logging for dumpsys
*/
private void logLinkMetrics(WifiInfo wifiInfo, long now, int netId,
int s1, int s2, int score) {
if (now < FIRST_REASONABLE_WALL_CLOCK) return;
double rssi = wifiInfo.getRssi();
double filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi();
double rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold();
int freq = wifiInfo.getFrequency();
int linkSpeed = wifiInfo.getLinkSpeed();
double txSuccessRate = wifiInfo.txSuccessRate;
double txRetriesRate = wifiInfo.txRetriesRate;
double txBadRate = wifiInfo.txBadRate;
double rxSuccessRate = wifiInfo.rxSuccessRate;
String s;
try {
String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
"%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
timestamp, mSessionNumber, netId,
rssi, filteredRssi, rssiThreshold, freq, linkSpeed,
txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
mNudYes, mNudCount,
s1, s2, score);
} catch (Exception e) {
Log.e(TAG, "format problem", e);
return;
}
synchronized (mLinkMetricsHistory) {
mLinkMetricsHistory.add(s);
while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
mLinkMetricsHistory.removeFirst();
}
}
}
/**
* Tag to be used in dumpsys request
*/
public static final String DUMP_ARG = "WifiScoreReport";
/**
* Dump logged signal strength and traffic measurements.
* @param fd unused
* @param pw PrintWriter for writing dump to
* @param args unused
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
LinkedList<String> history;
synchronized (mLinkMetricsHistory) {
history = new LinkedList<>(mLinkMetricsHistory);
}
pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,"
+ "freq,linkspeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
for (String line : history) {
pw.println(line);
}
history.clear();
}
}