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