1 /* 2 * Copyright (C) 2016 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; 18 19 import android.net.Network; 20 import android.net.NetworkAgent; 21 import android.net.wifi.WifiInfo; 22 import android.util.Log; 23 24 import java.io.FileDescriptor; 25 import java.io.PrintWriter; 26 import java.text.SimpleDateFormat; 27 import java.util.Date; 28 import java.util.LinkedList; 29 import java.util.Locale; 30 31 /** 32 * Class used to calculate scores for connected wifi networks and report it to the associated 33 * network agent. 34 */ 35 public class WifiScoreReport { 36 private static final String TAG = "WifiScoreReport"; 37 38 private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 3600; // 3 hours on 3 second poll 39 40 private boolean mVerboseLoggingEnabled = false; 41 private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016 42 43 private static final long MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS = 9000; 44 private long mLastDownwardBreachTimeMillis = 0; 45 46 // Cache of the last score 47 private int mScore = ConnectedScore.WIFI_MAX_SCORE; 48 49 private final ScoringParams mScoringParams; 50 private final Clock mClock; 51 private int mSessionNumber = 0; 52 53 ConnectedScore mAggressiveConnectedScore; 54 VelocityBasedConnectedScore mVelocityBasedConnectedScore; 55 WifiScoreReport(ScoringParams scoringParams, Clock clock)56 WifiScoreReport(ScoringParams scoringParams, Clock clock) { 57 mScoringParams = scoringParams; 58 mClock = clock; 59 mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock); 60 mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock); 61 } 62 63 /** 64 * Reset the last calculated score. 65 */ reset()66 public void reset() { 67 mSessionNumber++; 68 mScore = ConnectedScore.WIFI_MAX_SCORE; 69 mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE; 70 mAggressiveConnectedScore.reset(); 71 mVelocityBasedConnectedScore.reset(); 72 mLastDownwardBreachTimeMillis = 0; 73 if (mVerboseLoggingEnabled) Log.d(TAG, "reset"); 74 } 75 76 /** 77 * Enable/Disable verbose logging in score report generation. 78 */ enableVerboseLogging(boolean enable)79 public void enableVerboseLogging(boolean enable) { 80 mVerboseLoggingEnabled = enable; 81 } 82 83 /** 84 * Calculate wifi network score based on updated link layer stats and send the score to 85 * the provided network agent. 86 * 87 * If the score has changed from the previous value, update the WifiNetworkAgent. 88 * 89 * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds. 90 * 91 * @param wifiInfo WifiInfo instance pointing to the currently connected network. 92 * @param networkAgent NetworkAgent to be notified of new score. 93 * @param wifiMetrics for reporting our scores. 94 */ calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent, WifiMetrics wifiMetrics)95 public void calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent, 96 WifiMetrics wifiMetrics) { 97 if (wifiInfo.getRssi() == WifiInfo.INVALID_RSSI) { 98 Log.d(TAG, "Not reporting score because RSSI is invalid"); 99 return; 100 } 101 int score; 102 103 long millis = mClock.getWallClockMillis(); 104 int netId = 0; 105 106 if (networkAgent != null) { 107 final Network network = networkAgent.getNetwork(); 108 if (network != null) { 109 netId = network.netId; 110 } 111 } 112 113 mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis); 114 mVelocityBasedConnectedScore.updateUsingWifiInfo(wifiInfo, millis); 115 116 int s1 = mAggressiveConnectedScore.generateScore(); 117 int s2 = mVelocityBasedConnectedScore.generateScore(); 118 119 score = s2; 120 121 if (wifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE 122 && score <= ConnectedScore.WIFI_TRANSITION_SCORE 123 && wifiInfo.txSuccessRate >= mScoringParams.getYippeeSkippyPacketsPerSecond() 124 && wifiInfo.rxSuccessRate >= mScoringParams.getYippeeSkippyPacketsPerSecond()) { 125 score = ConnectedScore.WIFI_TRANSITION_SCORE + 1; 126 } 127 128 if (wifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE 129 && score <= ConnectedScore.WIFI_TRANSITION_SCORE) { 130 // We don't want to trigger a downward breach unless the rssi is 131 // below the entry threshold. There is noise in the measured rssi, and 132 // the kalman-filtered rssi is affected by the trend, so check them both. 133 // TODO(b/74613347) skip this if there are other indications to support the low score 134 int entry = mScoringParams.getEntryRssi(wifiInfo.getFrequency()); 135 if (mVelocityBasedConnectedScore.getFilteredRssi() >= entry 136 || wifiInfo.getRssi() >= entry) { 137 // Stay a notch above the transition score to reduce ambiguity. 138 score = ConnectedScore.WIFI_TRANSITION_SCORE + 1; 139 } 140 } 141 142 if (wifiInfo.score >= ConnectedScore.WIFI_TRANSITION_SCORE 143 && score < ConnectedScore.WIFI_TRANSITION_SCORE) { 144 mLastDownwardBreachTimeMillis = millis; 145 } else if (wifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE 146 && score >= ConnectedScore.WIFI_TRANSITION_SCORE) { 147 // Staying at below transition score for a certain period of time 148 // to prevent going back to wifi network again in a short time. 149 long elapsedMillis = millis - mLastDownwardBreachTimeMillis; 150 if (elapsedMillis < MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS) { 151 score = wifiInfo.score; 152 } 153 } 154 155 //sanitize boundaries 156 if (score > ConnectedScore.WIFI_MAX_SCORE) { 157 score = ConnectedScore.WIFI_MAX_SCORE; 158 } 159 if (score < 0) { 160 score = 0; 161 } 162 163 logLinkMetrics(wifiInfo, millis, netId, s1, s2, score); 164 165 //report score 166 if (score != wifiInfo.score) { 167 if (mVerboseLoggingEnabled) { 168 Log.d(TAG, "report new wifi score " + score); 169 } 170 wifiInfo.score = score; 171 if (networkAgent != null) { 172 networkAgent.sendNetworkScore(score); 173 } 174 } 175 176 wifiMetrics.incrementWifiScoreCount(score); 177 mScore = score; 178 } 179 180 private static final double TIME_CONSTANT_MILLIS = 30.0e+3; 181 private static final long NUD_THROTTLE_MILLIS = 5000; 182 private long mLastKnownNudCheckTimeMillis = 0; 183 private int mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE; 184 private int mNudYes = 0; // Counts when we voted for a NUD 185 private int mNudCount = 0; // Counts when we were told a NUD was sent 186 187 /** 188 * Recommends that a layer 3 check be done 189 * 190 * The caller can use this to (help) decide that an IP reachability check 191 * is desirable. The check is not done here; that is the caller's responsibility. 192 * 193 * @return true to indicate that an IP reachability check is recommended 194 */ shouldCheckIpLayer()195 public boolean shouldCheckIpLayer() { 196 int nud = mScoringParams.getNudKnob(); 197 if (nud == 0) { 198 return false; 199 } 200 long millis = mClock.getWallClockMillis(); 201 long deltaMillis = millis - mLastKnownNudCheckTimeMillis; 202 // Don't ever ask back-to-back - allow at least 5 seconds 203 // for the previous one to finish. 204 if (deltaMillis < NUD_THROTTLE_MILLIS) { 205 return false; 206 } 207 // nud is between 1 and 10 at this point 208 double deltaLevel = 11 - nud; 209 // nextNudBreach is the bar the score needs to cross before we ask for NUD 210 double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE; 211 // If we were below threshold the last time we checked, then compute a new bar 212 // that starts down from there and decays exponentially back up to the steady-state 213 // bar. If 5 time constants have passed, we are 99% of the way there, so skip the math. 214 if (mLastKnownNudCheckScore < ConnectedScore.WIFI_TRANSITION_SCORE 215 && deltaMillis < 5.0 * TIME_CONSTANT_MILLIS) { 216 double a = Math.exp(-deltaMillis / TIME_CONSTANT_MILLIS); 217 nextNudBreach = a * (mLastKnownNudCheckScore - deltaLevel) + (1.0 - a) * nextNudBreach; 218 } 219 if (mScore >= nextNudBreach) { 220 return false; 221 } 222 mNudYes++; 223 return true; 224 } 225 226 /** 227 * Should be called when a reachability check has been issued 228 * 229 * When the caller has requested an IP reachability check, calling this will 230 * help to rate-limit requests via shouldCheckIpLayer() 231 */ noteIpCheck()232 public void noteIpCheck() { 233 long millis = mClock.getWallClockMillis(); 234 mLastKnownNudCheckTimeMillis = millis; 235 mLastKnownNudCheckScore = mScore; 236 mNudCount++; 237 } 238 239 /** 240 * Data for dumpsys 241 * 242 * These are stored as csv formatted lines 243 */ 244 private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>(); 245 246 /** 247 * Data logging for dumpsys 248 */ logLinkMetrics(WifiInfo wifiInfo, long now, int netId, int s1, int s2, int score)249 private void logLinkMetrics(WifiInfo wifiInfo, long now, int netId, 250 int s1, int s2, int score) { 251 if (now < FIRST_REASONABLE_WALL_CLOCK) return; 252 double rssi = wifiInfo.getRssi(); 253 double filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi(); 254 double rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold(); 255 int freq = wifiInfo.getFrequency(); 256 int txLinkSpeed = wifiInfo.getLinkSpeed(); 257 int rxLinkSpeed = wifiInfo.getRxLinkSpeedMbps(); 258 double txSuccessRate = wifiInfo.txSuccessRate; 259 double txRetriesRate = wifiInfo.txRetriesRate; 260 double txBadRate = wifiInfo.txBadRate; 261 double rxSuccessRate = wifiInfo.rxSuccessRate; 262 String s; 263 try { 264 String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now)); 265 s = String.format(Locale.US, // Use US to avoid comma/decimal confusion 266 "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d", 267 timestamp, mSessionNumber, netId, 268 rssi, filteredRssi, rssiThreshold, freq, txLinkSpeed, rxLinkSpeed, 269 txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate, 270 mNudYes, mNudCount, 271 s1, s2, score); 272 } catch (Exception e) { 273 Log.e(TAG, "format problem", e); 274 return; 275 } 276 synchronized (mLinkMetricsHistory) { 277 mLinkMetricsHistory.add(s); 278 while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) { 279 mLinkMetricsHistory.removeFirst(); 280 } 281 } 282 } 283 284 /** 285 * Tag to be used in dumpsys request 286 */ 287 public static final String DUMP_ARG = "WifiScoreReport"; 288 289 /** 290 * Dump logged signal strength and traffic measurements. 291 * @param fd unused 292 * @param pw PrintWriter for writing dump to 293 * @param args unused 294 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)295 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 296 LinkedList<String> history; 297 synchronized (mLinkMetricsHistory) { 298 history = new LinkedList<>(mLinkMetricsHistory); 299 } 300 pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold, freq,txLinkSpeed," 301 + "rxLinkSpeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score"); 302 for (String line : history) { 303 pw.println(line); 304 } 305 history.clear(); 306 } 307 } 308