Loading src/android/net/util/NetworkStackUtils.java +15 −0 Original line number Original line Diff line number Diff line Loading @@ -226,6 +226,15 @@ public class NetworkStackUtils { public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION = public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION = "dns_probe_private_ip_no_internet"; "dns_probe_private_ip_no_internet"; /** * Experiment flag to enable validation metrics sent by NetworkMonitor. * * Metrics are sent by default. They can be disabled by setting the flag to a number greater * than the APK version (for example 999999999). * @see #isFeatureEnabled(Context, String, String, boolean) */ public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version"; static { static { System.loadLibrary("networkstackutilsjni"); System.loadLibrary("networkstackutilsjni"); } } Loading Loading @@ -348,6 +357,9 @@ public class NetworkStackUtils { * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * with current version of property. If this property version is valid, the corresponding * with current version of property. If this property version is valid, the corresponding * experimental feature would be enabled, otherwise disabled. * experimental feature would be enabled, otherwise disabled. * * This is useful to ensure that if a module install is rolled back, flags are not left fully * rolled out on a version where they have not been well tested. * @param context The global context information about an app environment. * @param context The global context information about an app environment. * @param namespace The namespace containing the property to look up. * @param namespace The namespace containing the property to look up. * @param name The name of the property to look up. * @param name The name of the property to look up. Loading @@ -363,6 +375,9 @@ public class NetworkStackUtils { * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * with current version of property. If this property version is valid, the corresponding * with current version of property. If this property version is valid, the corresponding * experimental feature would be enabled, otherwise disabled. * experimental feature would be enabled, otherwise disabled. * * This is useful to ensure that if a module install is rolled back, flags are not left fully * rolled out on a version where they have not been well tested. * @param context The global context information about an app environment. * @param context The global context information about an app environment. * @param namespace The namespace containing the property to look up. * @param namespace The namespace containing the property to look up. * @param name The name of the property to look up. * @param name The name of the property to look up. Loading src/com/android/networkstack/metrics/NetworkValidationMetrics.java +59 −25 Original line number Original line Diff line number Diff line Loading @@ -60,9 +60,11 @@ public class NetworkValidationMetrics { public static final int MAX_PROBE_EVENTS_COUNT = 20; public static final int MAX_PROBE_EVENTS_COUNT = 20; /** /** * Reset this NetworkValidationMetrics. * Reset this NetworkValidationMetrics and start collecting timing and metrics. * * <p>This must be called when validation starts. */ */ public void reset(@Nullable NetworkCapabilities nc) { public void startCollection(@Nullable NetworkCapabilities nc) { mStatsBuilder.clear(); mStatsBuilder.clear(); mProbeEventsBuilder.clear(); mProbeEventsBuilder.clear(); mCapportApiDataBuilder.clear(); mCapportApiDataBuilder.clear(); Loading @@ -72,16 +74,23 @@ public class NetworkValidationMetrics { } } /** /** * Returns the enum TransportType * Returns the enum TransportType. * * <p>This method only supports a limited set of common transport type combinations that can be * measured through metrics, and will return {@link TransportType#TT_UNKNOWN} for others. This * ensures that, for example, metrics for a TRANSPORT_NEW_UNKNOWN | TRANSPORT_ETHERNET network * cannot get aggregated with / compared with a "normal" TRANSPORT_ETHERNET network without * noticing. * * * @param NetworkCapabilities * @param nc Capabilities to extract transport type from. * @return the TransportType which is defined in * @return the TransportType which is defined in * core/proto/android/stats/connectivity/network_stack.proto * core/proto/android/stats/connectivity/network_stack.proto */ */ @VisibleForTesting @VisibleForTesting public static TransportType getTransportTypeFromNC( public static TransportType getTransportTypeFromNC(@Nullable NetworkCapabilities nc) { @Nullable NetworkCapabilities nc) { if (nc == null) return TransportType.TT_UNKNOWN; if (nc == null) return TransportType.TT_UNKNOWN; final int trCount = nc.getTransportTypes().length; boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); Loading @@ -90,13 +99,29 @@ public class NetworkValidationMetrics { boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN; // VPN networks are not subject to validation and should not see validation stats, but if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI; // metrics could be added to measure private DNS probes only. if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR; if (trCount == 3 && hasCellular && hasWifi && hasVpn) { if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH; return TransportType.TT_WIFI_CELLULAR_VPN; if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET; } if (trCount == 2 && hasVpn) { if (hasWifi) return TransportType.TT_WIFI_VPN; if (hasCellular) return TransportType.TT_CELLULAR_VPN; if (hasBT) return TransportType.TT_BLUETOOTH_VPN; if (hasEthernet) return TransportType.TT_ETHERNET_VPN; } if (trCount == 1) { if (hasWifi) return TransportType.TT_WIFI; if (hasCellular) return TransportType.TT_CELLULAR; if (hasBT) return TransportType.TT_BLUETOOTH; if (hasEthernet) return TransportType.TT_ETHERNET; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasLopan) return TransportType.TT_LOWPAN; if (hasLopan) return TransportType.TT_LOWPAN; // TODO: consider having a TT_VPN for VPN-only transport } return TransportType.TT_UNKNOWN; return TransportType.TT_UNKNOWN; } } Loading Loading @@ -146,6 +171,8 @@ public class NetworkValidationMetrics { */ */ @VisibleForTesting @VisibleForTesting public static ValidationResult validationResultToEnum(int result, String redirectUrl) { public static ValidationResult validationResultToEnum(int result, String redirectUrl) { // TODO: consider adding a VR_PARTIAL_SUCCESS field to track cases where users accepted // partial connectivity if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { return ValidationResult.VR_SUCCESS; return ValidationResult.VR_SUCCESS; } else if (redirectUrl != null) { } else if (redirectUrl != null) { Loading @@ -158,12 +185,14 @@ public class NetworkValidationMetrics { } } /** /** * Write each network probe event to mProbeEventsBuilder. * Add a network probe event to the metrics builder. */ */ public void setProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, public void addProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, @Nullable final CaptivePortalDataShim capportData) { @Nullable final CaptivePortalDataShim capportData) { // When the number of ProbeEvents of mProbeEventsBuilder exceeds // When the number of ProbeEvents of mProbeEventsBuilder exceeds // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. // TODO: consider recording the total number of probes in a separate field to know how // many probes are skipped. if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; int latencyUs = NetworkStackUtils.saturatedCast(durationUs); int latencyUs = NetworkStackUtils.saturatedCast(durationUs); Loading @@ -178,7 +207,9 @@ public class NetworkValidationMetrics { (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; mCapportApiDataBuilder mCapportApiDataBuilder .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit())) // TODO: rename this field to setRemainingKBytes, or use a long .setRemainingBytes( NetworkStackUtils.saturatedCast(capportData.getByteLimit() / 1000)) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); Loading @@ -196,25 +227,28 @@ public class NetworkValidationMetrics { /** /** * Write the NetworkValidationReported proto to statsd. * Write the NetworkValidationReported proto to statsd. * * <p>This is a no-op if {@link #startCollection(NetworkCapabilities)} was not called since the * last call to this method. */ */ public NetworkValidationReported sendValidationStats() { public NetworkValidationReported maybeStopCollectionAndSend() { if (!mWatch.isStarted()) return null; if (!mWatch.isStarted()) return null; mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setValidationIndex(mValidationIndex); mStatsBuilder.setValidationIndex(mValidationIndex); // write a random value(0 ~ 999) for sampling. // write a random value(0 ~ 999) for sampling. mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); final NetworkValidationReported mStats = mStatsBuilder.build(); final NetworkValidationReported stats = mStatsBuilder.build(); final byte[] probeEvents = mStats.getProbeEvents().toByteArray(); final byte[] probeEvents = stats.getProbeEvents().toByteArray(); NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, mStats.getTransportType().getNumber(), stats.getTransportType().getNumber(), probeEvents, probeEvents, mStats.getValidationResult().getNumber(), stats.getValidationResult().getNumber(), mStats.getLatencyMicros(), stats.getLatencyMicros(), mStats.getValidationIndex(), stats.getValidationIndex(), mStats.getRandomNumber()); stats.getRandomNumber()); mWatch.reset(); mWatch.reset(); return mStats; return stats; } } } } src/com/android/server/connectivity/NetworkMonitor.java +75 −26 Original line number Original line Diff line number Diff line Loading @@ -450,7 +450,14 @@ public class NetworkMonitor extends StateMachine { protected boolean mIsCaptivePortalCheckEnabled; protected boolean mIsCaptivePortalCheckEnabled; private boolean mUseHttps; private boolean mUseHttps; // The total number of captive portal detection attempts for this NetworkMonitor instance. /** * The total number of completed validation attempts (network validated or a captive portal was * detected) for this NetworkMonitor instance. * This does not include attempts that were interrupted, retried or finished with a result that * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics} * for a count of all attempts. * TODO: remove when removing legacy metrics. */ private int mValidations = 0; private int mValidations = 0; // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. Loading Loading @@ -504,6 +511,14 @@ public class NetworkMonitor extends StateMachine { private final boolean mPrivateIpNoInternetEnabled; private final boolean mPrivateIpNoInternetEnabled; private final boolean mMetricsEnabled; // The validation metrics are accessed by individual probe threads, and by the StateMachine // thread. All accesses must be synchronized to make sure the StateMachine thread can see // reports from all probes. // TODO: as that most usage is in the StateMachine thread and probes only add their probe // events, consider having probes return their stats to the StateMachine, and only access this // member on the StateMachine thread without synchronization. @GuardedBy("mNetworkValidationMetrics") @GuardedBy("mNetworkValidationMetrics") private final NetworkValidationMetrics mNetworkValidationMetrics = private final NetworkValidationMetrics mNetworkValidationMetrics = new NetworkValidationMetrics(); new NetworkValidationMetrics(); Loading Loading @@ -571,6 +586,8 @@ public class NetworkMonitor extends StateMachine { mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */); mUseHttps = getUseHttpsValidation(); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls(); mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls(); Loading Loading @@ -783,28 +800,48 @@ public class NetworkMonitor extends StateMachine { } } } } private void recordMetricsReset(@Nullable NetworkCapabilities nc) { private void startMetricsCollection() { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.reset(nc); mNetworkValidationMetrics.startCollection(mNetworkCapabilities); } } catch (Exception e) { Log.wtf(TAG, "Error resetting validation metrics", e); } } } } private void recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result, private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result, CaptivePortalDataShim capportData) { CaptivePortalDataShim capportData) { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, capportData); mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData); } } catch (Exception e) { Log.wtf(TAG, "Error recording probe event", e); } } } } private void recordMetricsValidationResult(int result, String redirectUrl) { private void recordValidationResult(int result, String redirectUrl) { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setValidationResult(result, redirectUrl); mNetworkValidationMetrics.setValidationResult(result, redirectUrl); } } } catch (Exception e) { Log.wtf(TAG, "Error recording validation result", e); } } } private void recordMetricsValidationStats() { private void maybeStopCollectionAndSendMetrics() { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.sendValidationStats(); mNetworkValidationMetrics.maybeStopCollectionAndSend(); } } catch (Exception e) { Log.wtf(TAG, "Error sending validation stats", e); } } } } Loading @@ -820,7 +857,7 @@ public class NetworkMonitor extends StateMachine { transitionTo(mEvaluatingState); transitionTo(mEvaluatingState); return HANDLED; return HANDLED; case CMD_NETWORK_DISCONNECTED: case CMD_NETWORK_DISCONNECTED: recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); quit(); quit(); return HANDLED; return HANDLED; Loading Loading @@ -972,7 +1009,7 @@ public class NetworkMonitor extends StateMachine { initSocketTrackingIfRequired(); initSocketTrackingIfRequired(); // start periodical polling. // start periodical polling. sendTcpPollingEvent(); sendTcpPollingEvent(); recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); } } private void initSocketTrackingIfRequired() { private void initSocketTrackingIfRequired() { Loading Loading @@ -1323,7 +1360,7 @@ public class NetworkMonitor extends StateMachine { sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); mValidations++; mValidations++; recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); } } @Override @Override Loading Loading @@ -1357,7 +1394,9 @@ public class NetworkMonitor extends StateMachine { handlePrivateDnsEvaluationFailure(); handlePrivateDnsEvaluationFailure(); // The private DNS probe fails-fast if the server hostname cannot // The private DNS probe fails-fast if the server hostname cannot // be resolved. Record it as a failure with zero latency. // be resolved. Record it as a failure with zero latency. recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, 0 /* latency */, // TODO: refactor this together with the probe recorded in // sendPrivateDnsProbe, so logging is symmetric / easier to follow. recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); break; break; } } Loading Loading @@ -1468,7 +1507,7 @@ public class NetworkMonitor extends StateMachine { validationLog(PROBE_PRIVDNS, host, validationLog(PROBE_PRIVDNS, host, String.format("%dus - Error: %s", time, uhe.getMessage())); String.format("%dus - Error: %s", time, uhe.getMessage())); } } recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); return success; return success; Loading @@ -1480,8 +1519,14 @@ public class NetworkMonitor extends StateMachine { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); // When starting a full probe cycle here, record any pending stats (for example if recordMetricsReset(mNetworkCapabilities); // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in // EvaluatingPrivateDnsState). maybeStopCollectionAndSendMetrics(); // Restart the metrics collection timers. Metrics will be stopped and sent when the // validation attempt finishes (as success, failure or portal), or if it is interrupted // (by being restarted or if NetworkMonitor stops). startMetricsCollection(); if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { //Don't continue to blame UID forever. //Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); TrafficStats.clearThreadStatsUid(); Loading Loading @@ -1563,7 +1608,9 @@ public class NetworkMonitor extends StateMachine { private class WaitingForNextProbeState extends State { private class WaitingForNextProbeState extends State { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be // restarted when the next probe starts. maybeStopCollectionAndSendMetrics(); scheduleNextProbe(); scheduleNextProbe(); } } Loading Loading @@ -2313,7 +2360,7 @@ public class NetworkMonitor extends StateMachine { // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); return CaptivePortalProbeResult.PRIVATE_IP; return CaptivePortalProbeResult.PRIVATE_IP; } } Loading Loading @@ -2346,7 +2393,7 @@ public class NetworkMonitor extends StateMachine { result = ValidationProbeEvent.DNS_FAILURE; result = ValidationProbeEvent.DNS_FAILURE; } } final long latency = watch.stop(); final long latency = watch.stop(); recordMetricsProbeEvent(ProbeType.PT_DNS, latency, recordProbeEventMetrics(ProbeType.PT_DNS, latency, (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); Loading Loading @@ -2473,7 +2520,7 @@ public class NetworkMonitor extends StateMachine { } else { } else { probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); } } recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), null /* capportData */); null /* capportData */); return probeResult; return probeResult; Loading Loading @@ -2629,6 +2676,8 @@ public class NetworkMonitor extends StateMachine { } } private CaptivePortalDataShim sendCapportApiProbe() { private CaptivePortalDataShim sendCapportApiProbe() { // TODO: consider adding metrics counters for each case returning null in this method // (cases where the API is not implemented properly). validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); final String apiContent; final String apiContent; Loading Loading @@ -2689,7 +2738,7 @@ public class NetworkMonitor extends StateMachine { if (mCaptivePortalApiUrl == null) return null; if (mCaptivePortalApiUrl == null) return null; final Stopwatch capportApiWatch = new Stopwatch().start(); final Stopwatch capportApiWatch = new Stopwatch().start(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData); capportData); return capportData; return capportData; Loading Loading @@ -3412,7 +3461,7 @@ public class NetworkMonitor extends StateMachine { p.redirectUrl = redirectUrl; p.redirectUrl = redirectUrl; p.timestampMillis = SystemClock.elapsedRealtime(); p.timestampMillis = SystemClock.elapsedRealtime(); notifyNetworkTested(p); notifyNetworkTested(p); recordMetricsValidationResult(result, redirectUrl); recordValidationResult(result, redirectUrl); } } @VisibleForTesting @VisibleForTesting Loading tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java +20 −22 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import android.net.INetworkMonitor; import android.net.INetworkMonitor; import android.net.NetworkCapabilities; import android.net.NetworkCapabilities; Loading Loading @@ -54,7 +53,7 @@ public class NetworkValidationMetricsTest { private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info"; private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info"; private static final int TTL_TOLERANCE_SECS = 10; private static final int TTL_TOLERANCE_SECS = 10; private static final NetworkCapabilities WIFI_NOT_METERED_CAPABILITIES = private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities() new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); Loading Loading @@ -136,17 +135,17 @@ public class NetworkValidationMetricsTest { @Test @Test public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception { public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception { final NetworkValidationMetrics Metrics = new NetworkValidationMetrics(); final NetworkValidationMetrics metrics = new NetworkValidationMetrics(); Metrics.reset(WIFI_NOT_METERED_CAPABILITIES); metrics.startCollection(WIFI_CAPABILITIES); // 1. PT_DNS probe // 1. PT_DNS probe Metrics.setProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null); metrics.addProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null); // 2. Consecutive PT_HTTP probe failure // 2. Consecutive PT_HTTP probe failure for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) { Metrics.setProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null); } } // Write metric into statsd // Write metric into statsd final NetworkValidationReported stats = Metrics.sendValidationStats(); final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend(); // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT final ProbeEvents probeEvents = stats.getProbeEvents(); final ProbeEvents probeEvents = stats.getProbeEvents(); Loading @@ -156,40 +155,39 @@ public class NetworkValidationMetricsTest { @Test @Test public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception { public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); final long bytesRemaining = 12_345L; final long bytesRemaining = 10L; final long secondsRemaining = 3000L; final long secondsRemaining = 3000L; String apiContent = "{'captive': true," String apiContent = "{'captive': true," + "'user-portal-url': '" + TEST_LOGIN_URL + "'," + "'user-portal-url': '" + TEST_LOGIN_URL + "'," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'bytes-remaining': " + bytesRemaining + "," + "'bytes-remaining': " + bytesRemaining + "," + "'seconds-remaining': " + secondsRemaining + "}"; + "'seconds-remaining': " + secondsRemaining + "}"; final NetworkValidationMetrics Metrics = new NetworkValidationMetrics(); final NetworkValidationMetrics metrics = new NetworkValidationMetrics(); final int validationIndex = 1; final int validationIndex = 1; final long longlatency = 2147483649L; final long longlatency = Integer.MAX_VALUE + 12344567L; Metrics.reset(WIFI_NOT_METERED_CAPABILITIES); metrics.startCollection(WIFI_CAPABILITIES); final JSONObject info = new JSONObject(apiContent); final JSONObject info = new JSONObject(apiContent); final CaptivePortalDataShim captivePortalData = final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported() CaptivePortalDataShimImpl.fromJson(info); ? CaptivePortalDataShimImpl.fromJson(info) : null; // 1. PT_CAPPORT_API probe w CapportApiData info // 1. PT_CAPPORT_API probe w CapportApiData info Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS, metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS, captivePortalData); captivePortalData); // 2. PT_CAPPORT_API probe w/o CapportApiData info // 2. PT_CAPPORT_API probe w/o CapportApiData info Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null); // 3. PT_DNS probe // 3. PT_DNS probe Metrics.setProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null); // 4. PT_HTTP probe // 4. PT_HTTP probe Metrics.setProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null); metrics.addProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null); // add Validation result // add Validation result Metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null); metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null); // Write metric into statsd // Write metric into statsd final NetworkValidationReported stats = Metrics.sendValidationStats(); final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend(); // Verify: TransportType: WIFI // Verify: TransportType: WIFI assertEquals(TransportType.TT_WIFI, stats.getTransportType()); assertEquals(TransportType.TT_WIFI, stats.getTransportType()); Loading @@ -207,13 +205,13 @@ public class NetworkValidationMetricsTest { assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType()); assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType()); assertEquals(1234, probeEvent.getLatencyMicros()); assertEquals(1234, probeEvent.getLatencyMicros()); assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult()); assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult()); assertEquals(true, probeEvent.hasCapportApiData()); if (CaptivePortalDataShimImpl.isSupported()) { if (CaptivePortalDataShimImpl.isSupported()) { assertTrue(probeEvent.hasCapportApiData()); // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds final CapportApiData capportData = probeEvent.getCapportApiData(); final CapportApiData capportData = probeEvent.getCapportApiData(); assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining); assertEquals(captivePortalData.getByteLimit(), capportData.getRemainingBytes()); assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes()); } else { } else { assertFalse(probeEvent.hasCapportApiData()); assertFalse(probeEvent.hasCapportApiData()); } } Loading Loading
src/android/net/util/NetworkStackUtils.java +15 −0 Original line number Original line Diff line number Diff line Loading @@ -226,6 +226,15 @@ public class NetworkStackUtils { public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION = public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION = "dns_probe_private_ip_no_internet"; "dns_probe_private_ip_no_internet"; /** * Experiment flag to enable validation metrics sent by NetworkMonitor. * * Metrics are sent by default. They can be disabled by setting the flag to a number greater * than the APK version (for example 999999999). * @see #isFeatureEnabled(Context, String, String, boolean) */ public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version"; static { static { System.loadLibrary("networkstackutilsjni"); System.loadLibrary("networkstackutilsjni"); } } Loading Loading @@ -348,6 +357,9 @@ public class NetworkStackUtils { * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * with current version of property. If this property version is valid, the corresponding * with current version of property. If this property version is valid, the corresponding * experimental feature would be enabled, otherwise disabled. * experimental feature would be enabled, otherwise disabled. * * This is useful to ensure that if a module install is rolled back, flags are not left fully * rolled out on a version where they have not been well tested. * @param context The global context information about an app environment. * @param context The global context information about an app environment. * @param namespace The namespace containing the property to look up. * @param namespace The namespace containing the property to look up. * @param name The name of the property to look up. * @param name The name of the property to look up. Loading @@ -363,6 +375,9 @@ public class NetworkStackUtils { * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} * with current version of property. If this property version is valid, the corresponding * with current version of property. If this property version is valid, the corresponding * experimental feature would be enabled, otherwise disabled. * experimental feature would be enabled, otherwise disabled. * * This is useful to ensure that if a module install is rolled back, flags are not left fully * rolled out on a version where they have not been well tested. * @param context The global context information about an app environment. * @param context The global context information about an app environment. * @param namespace The namespace containing the property to look up. * @param namespace The namespace containing the property to look up. * @param name The name of the property to look up. * @param name The name of the property to look up. Loading
src/com/android/networkstack/metrics/NetworkValidationMetrics.java +59 −25 Original line number Original line Diff line number Diff line Loading @@ -60,9 +60,11 @@ public class NetworkValidationMetrics { public static final int MAX_PROBE_EVENTS_COUNT = 20; public static final int MAX_PROBE_EVENTS_COUNT = 20; /** /** * Reset this NetworkValidationMetrics. * Reset this NetworkValidationMetrics and start collecting timing and metrics. * * <p>This must be called when validation starts. */ */ public void reset(@Nullable NetworkCapabilities nc) { public void startCollection(@Nullable NetworkCapabilities nc) { mStatsBuilder.clear(); mStatsBuilder.clear(); mProbeEventsBuilder.clear(); mProbeEventsBuilder.clear(); mCapportApiDataBuilder.clear(); mCapportApiDataBuilder.clear(); Loading @@ -72,16 +74,23 @@ public class NetworkValidationMetrics { } } /** /** * Returns the enum TransportType * Returns the enum TransportType. * * <p>This method only supports a limited set of common transport type combinations that can be * measured through metrics, and will return {@link TransportType#TT_UNKNOWN} for others. This * ensures that, for example, metrics for a TRANSPORT_NEW_UNKNOWN | TRANSPORT_ETHERNET network * cannot get aggregated with / compared with a "normal" TRANSPORT_ETHERNET network without * noticing. * * * @param NetworkCapabilities * @param nc Capabilities to extract transport type from. * @return the TransportType which is defined in * @return the TransportType which is defined in * core/proto/android/stats/connectivity/network_stack.proto * core/proto/android/stats/connectivity/network_stack.proto */ */ @VisibleForTesting @VisibleForTesting public static TransportType getTransportTypeFromNC( public static TransportType getTransportTypeFromNC(@Nullable NetworkCapabilities nc) { @Nullable NetworkCapabilities nc) { if (nc == null) return TransportType.TT_UNKNOWN; if (nc == null) return TransportType.TT_UNKNOWN; final int trCount = nc.getTransportTypes().length; boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); Loading @@ -90,13 +99,29 @@ public class NetworkValidationMetrics { boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN; // VPN networks are not subject to validation and should not see validation stats, but if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI; // metrics could be added to measure private DNS probes only. if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR; if (trCount == 3 && hasCellular && hasWifi && hasVpn) { if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH; return TransportType.TT_WIFI_CELLULAR_VPN; if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET; } if (trCount == 2 && hasVpn) { if (hasWifi) return TransportType.TT_WIFI_VPN; if (hasCellular) return TransportType.TT_CELLULAR_VPN; if (hasBT) return TransportType.TT_BLUETOOTH_VPN; if (hasEthernet) return TransportType.TT_ETHERNET_VPN; } if (trCount == 1) { if (hasWifi) return TransportType.TT_WIFI; if (hasCellular) return TransportType.TT_CELLULAR; if (hasBT) return TransportType.TT_BLUETOOTH; if (hasEthernet) return TransportType.TT_ETHERNET; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasLopan) return TransportType.TT_LOWPAN; if (hasLopan) return TransportType.TT_LOWPAN; // TODO: consider having a TT_VPN for VPN-only transport } return TransportType.TT_UNKNOWN; return TransportType.TT_UNKNOWN; } } Loading Loading @@ -146,6 +171,8 @@ public class NetworkValidationMetrics { */ */ @VisibleForTesting @VisibleForTesting public static ValidationResult validationResultToEnum(int result, String redirectUrl) { public static ValidationResult validationResultToEnum(int result, String redirectUrl) { // TODO: consider adding a VR_PARTIAL_SUCCESS field to track cases where users accepted // partial connectivity if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { return ValidationResult.VR_SUCCESS; return ValidationResult.VR_SUCCESS; } else if (redirectUrl != null) { } else if (redirectUrl != null) { Loading @@ -158,12 +185,14 @@ public class NetworkValidationMetrics { } } /** /** * Write each network probe event to mProbeEventsBuilder. * Add a network probe event to the metrics builder. */ */ public void setProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, public void addProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, @Nullable final CaptivePortalDataShim capportData) { @Nullable final CaptivePortalDataShim capportData) { // When the number of ProbeEvents of mProbeEventsBuilder exceeds // When the number of ProbeEvents of mProbeEventsBuilder exceeds // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. // TODO: consider recording the total number of probes in a separate field to know how // many probes are skipped. if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; int latencyUs = NetworkStackUtils.saturatedCast(durationUs); int latencyUs = NetworkStackUtils.saturatedCast(durationUs); Loading @@ -178,7 +207,9 @@ public class NetworkValidationMetrics { (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; mCapportApiDataBuilder mCapportApiDataBuilder .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit())) // TODO: rename this field to setRemainingKBytes, or use a long .setRemainingBytes( NetworkStackUtils.saturatedCast(capportData.getByteLimit() / 1000)) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); Loading @@ -196,25 +227,28 @@ public class NetworkValidationMetrics { /** /** * Write the NetworkValidationReported proto to statsd. * Write the NetworkValidationReported proto to statsd. * * <p>This is a no-op if {@link #startCollection(NetworkCapabilities)} was not called since the * last call to this method. */ */ public NetworkValidationReported sendValidationStats() { public NetworkValidationReported maybeStopCollectionAndSend() { if (!mWatch.isStarted()) return null; if (!mWatch.isStarted()) return null; mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setValidationIndex(mValidationIndex); mStatsBuilder.setValidationIndex(mValidationIndex); // write a random value(0 ~ 999) for sampling. // write a random value(0 ~ 999) for sampling. mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); final NetworkValidationReported mStats = mStatsBuilder.build(); final NetworkValidationReported stats = mStatsBuilder.build(); final byte[] probeEvents = mStats.getProbeEvents().toByteArray(); final byte[] probeEvents = stats.getProbeEvents().toByteArray(); NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, mStats.getTransportType().getNumber(), stats.getTransportType().getNumber(), probeEvents, probeEvents, mStats.getValidationResult().getNumber(), stats.getValidationResult().getNumber(), mStats.getLatencyMicros(), stats.getLatencyMicros(), mStats.getValidationIndex(), stats.getValidationIndex(), mStats.getRandomNumber()); stats.getRandomNumber()); mWatch.reset(); mWatch.reset(); return mStats; return stats; } } } }
src/com/android/server/connectivity/NetworkMonitor.java +75 −26 Original line number Original line Diff line number Diff line Loading @@ -450,7 +450,14 @@ public class NetworkMonitor extends StateMachine { protected boolean mIsCaptivePortalCheckEnabled; protected boolean mIsCaptivePortalCheckEnabled; private boolean mUseHttps; private boolean mUseHttps; // The total number of captive portal detection attempts for this NetworkMonitor instance. /** * The total number of completed validation attempts (network validated or a captive portal was * detected) for this NetworkMonitor instance. * This does not include attempts that were interrupted, retried or finished with a result that * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics} * for a count of all attempts. * TODO: remove when removing legacy metrics. */ private int mValidations = 0; private int mValidations = 0; // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. Loading Loading @@ -504,6 +511,14 @@ public class NetworkMonitor extends StateMachine { private final boolean mPrivateIpNoInternetEnabled; private final boolean mPrivateIpNoInternetEnabled; private final boolean mMetricsEnabled; // The validation metrics are accessed by individual probe threads, and by the StateMachine // thread. All accesses must be synchronized to make sure the StateMachine thread can see // reports from all probes. // TODO: as that most usage is in the StateMachine thread and probes only add their probe // events, consider having probes return their stats to the StateMachine, and only access this // member on the StateMachine thread without synchronization. @GuardedBy("mNetworkValidationMetrics") @GuardedBy("mNetworkValidationMetrics") private final NetworkValidationMetrics mNetworkValidationMetrics = private final NetworkValidationMetrics mNetworkValidationMetrics = new NetworkValidationMetrics(); new NetworkValidationMetrics(); Loading Loading @@ -571,6 +586,8 @@ public class NetworkMonitor extends StateMachine { mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */); mUseHttps = getUseHttpsValidation(); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls(); mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls(); Loading Loading @@ -783,28 +800,48 @@ public class NetworkMonitor extends StateMachine { } } } } private void recordMetricsReset(@Nullable NetworkCapabilities nc) { private void startMetricsCollection() { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.reset(nc); mNetworkValidationMetrics.startCollection(mNetworkCapabilities); } } catch (Exception e) { Log.wtf(TAG, "Error resetting validation metrics", e); } } } } private void recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result, private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result, CaptivePortalDataShim capportData) { CaptivePortalDataShim capportData) { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, capportData); mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData); } } catch (Exception e) { Log.wtf(TAG, "Error recording probe event", e); } } } } private void recordMetricsValidationResult(int result, String redirectUrl) { private void recordValidationResult(int result, String redirectUrl) { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setValidationResult(result, redirectUrl); mNetworkValidationMetrics.setValidationResult(result, redirectUrl); } } } catch (Exception e) { Log.wtf(TAG, "Error recording validation result", e); } } } private void recordMetricsValidationStats() { private void maybeStopCollectionAndSendMetrics() { if (!mMetricsEnabled) return; try { synchronized (mNetworkValidationMetrics) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.sendValidationStats(); mNetworkValidationMetrics.maybeStopCollectionAndSend(); } } catch (Exception e) { Log.wtf(TAG, "Error sending validation stats", e); } } } } Loading @@ -820,7 +857,7 @@ public class NetworkMonitor extends StateMachine { transitionTo(mEvaluatingState); transitionTo(mEvaluatingState); return HANDLED; return HANDLED; case CMD_NETWORK_DISCONNECTED: case CMD_NETWORK_DISCONNECTED: recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); quit(); quit(); return HANDLED; return HANDLED; Loading Loading @@ -972,7 +1009,7 @@ public class NetworkMonitor extends StateMachine { initSocketTrackingIfRequired(); initSocketTrackingIfRequired(); // start periodical polling. // start periodical polling. sendTcpPollingEvent(); sendTcpPollingEvent(); recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); } } private void initSocketTrackingIfRequired() { private void initSocketTrackingIfRequired() { Loading Loading @@ -1323,7 +1360,7 @@ public class NetworkMonitor extends StateMachine { sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); mValidations++; mValidations++; recordMetricsValidationStats(); maybeStopCollectionAndSendMetrics(); } } @Override @Override Loading Loading @@ -1357,7 +1394,9 @@ public class NetworkMonitor extends StateMachine { handlePrivateDnsEvaluationFailure(); handlePrivateDnsEvaluationFailure(); // The private DNS probe fails-fast if the server hostname cannot // The private DNS probe fails-fast if the server hostname cannot // be resolved. Record it as a failure with zero latency. // be resolved. Record it as a failure with zero latency. recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, 0 /* latency */, // TODO: refactor this together with the probe recorded in // sendPrivateDnsProbe, so logging is symmetric / easier to follow. recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); break; break; } } Loading Loading @@ -1468,7 +1507,7 @@ public class NetworkMonitor extends StateMachine { validationLog(PROBE_PRIVDNS, host, validationLog(PROBE_PRIVDNS, host, String.format("%dus - Error: %s", time, uhe.getMessage())); String.format("%dus - Error: %s", time, uhe.getMessage())); } } recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); return success; return success; Loading @@ -1480,8 +1519,14 @@ public class NetworkMonitor extends StateMachine { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); // When starting a full probe cycle here, record any pending stats (for example if recordMetricsReset(mNetworkCapabilities); // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in // EvaluatingPrivateDnsState). maybeStopCollectionAndSendMetrics(); // Restart the metrics collection timers. Metrics will be stopped and sent when the // validation attempt finishes (as success, failure or portal), or if it is interrupted // (by being restarted or if NetworkMonitor stops). startMetricsCollection(); if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { //Don't continue to blame UID forever. //Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); TrafficStats.clearThreadStatsUid(); Loading Loading @@ -1563,7 +1608,9 @@ public class NetworkMonitor extends StateMachine { private class WaitingForNextProbeState extends State { private class WaitingForNextProbeState extends State { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be // restarted when the next probe starts. maybeStopCollectionAndSendMetrics(); scheduleNextProbe(); scheduleNextProbe(); } } Loading Loading @@ -2313,7 +2360,7 @@ public class NetworkMonitor extends StateMachine { // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); return CaptivePortalProbeResult.PRIVATE_IP; return CaptivePortalProbeResult.PRIVATE_IP; } } Loading Loading @@ -2346,7 +2393,7 @@ public class NetworkMonitor extends StateMachine { result = ValidationProbeEvent.DNS_FAILURE; result = ValidationProbeEvent.DNS_FAILURE; } } final long latency = watch.stop(); final long latency = watch.stop(); recordMetricsProbeEvent(ProbeType.PT_DNS, latency, recordProbeEventMetrics(ProbeType.PT_DNS, latency, (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); Loading Loading @@ -2473,7 +2520,7 @@ public class NetworkMonitor extends StateMachine { } else { } else { probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); } } recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), null /* capportData */); null /* capportData */); return probeResult; return probeResult; Loading Loading @@ -2629,6 +2676,8 @@ public class NetworkMonitor extends StateMachine { } } private CaptivePortalDataShim sendCapportApiProbe() { private CaptivePortalDataShim sendCapportApiProbe() { // TODO: consider adding metrics counters for each case returning null in this method // (cases where the API is not implemented properly). validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); final String apiContent; final String apiContent; Loading Loading @@ -2689,7 +2738,7 @@ public class NetworkMonitor extends StateMachine { if (mCaptivePortalApiUrl == null) return null; if (mCaptivePortalApiUrl == null) return null; final Stopwatch capportApiWatch = new Stopwatch().start(); final Stopwatch capportApiWatch = new Stopwatch().start(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData); capportData); return capportData; return capportData; Loading Loading @@ -3412,7 +3461,7 @@ public class NetworkMonitor extends StateMachine { p.redirectUrl = redirectUrl; p.redirectUrl = redirectUrl; p.timestampMillis = SystemClock.elapsedRealtime(); p.timestampMillis = SystemClock.elapsedRealtime(); notifyNetworkTested(p); notifyNetworkTested(p); recordMetricsValidationResult(result, redirectUrl); recordValidationResult(result, redirectUrl); } } @VisibleForTesting @VisibleForTesting Loading
tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java +20 −22 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import android.net.INetworkMonitor; import android.net.INetworkMonitor; import android.net.NetworkCapabilities; import android.net.NetworkCapabilities; Loading Loading @@ -54,7 +53,7 @@ public class NetworkValidationMetricsTest { private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info"; private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info"; private static final int TTL_TOLERANCE_SECS = 10; private static final int TTL_TOLERANCE_SECS = 10; private static final NetworkCapabilities WIFI_NOT_METERED_CAPABILITIES = private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities() new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); Loading Loading @@ -136,17 +135,17 @@ public class NetworkValidationMetricsTest { @Test @Test public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception { public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception { final NetworkValidationMetrics Metrics = new NetworkValidationMetrics(); final NetworkValidationMetrics metrics = new NetworkValidationMetrics(); Metrics.reset(WIFI_NOT_METERED_CAPABILITIES); metrics.startCollection(WIFI_CAPABILITIES); // 1. PT_DNS probe // 1. PT_DNS probe Metrics.setProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null); metrics.addProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null); // 2. Consecutive PT_HTTP probe failure // 2. Consecutive PT_HTTP probe failure for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) { Metrics.setProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null); } } // Write metric into statsd // Write metric into statsd final NetworkValidationReported stats = Metrics.sendValidationStats(); final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend(); // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT final ProbeEvents probeEvents = stats.getProbeEvents(); final ProbeEvents probeEvents = stats.getProbeEvents(); Loading @@ -156,40 +155,39 @@ public class NetworkValidationMetricsTest { @Test @Test public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception { public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); final long bytesRemaining = 12_345L; final long bytesRemaining = 10L; final long secondsRemaining = 3000L; final long secondsRemaining = 3000L; String apiContent = "{'captive': true," String apiContent = "{'captive': true," + "'user-portal-url': '" + TEST_LOGIN_URL + "'," + "'user-portal-url': '" + TEST_LOGIN_URL + "'," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'bytes-remaining': " + bytesRemaining + "," + "'bytes-remaining': " + bytesRemaining + "," + "'seconds-remaining': " + secondsRemaining + "}"; + "'seconds-remaining': " + secondsRemaining + "}"; final NetworkValidationMetrics Metrics = new NetworkValidationMetrics(); final NetworkValidationMetrics metrics = new NetworkValidationMetrics(); final int validationIndex = 1; final int validationIndex = 1; final long longlatency = 2147483649L; final long longlatency = Integer.MAX_VALUE + 12344567L; Metrics.reset(WIFI_NOT_METERED_CAPABILITIES); metrics.startCollection(WIFI_CAPABILITIES); final JSONObject info = new JSONObject(apiContent); final JSONObject info = new JSONObject(apiContent); final CaptivePortalDataShim captivePortalData = final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported() CaptivePortalDataShimImpl.fromJson(info); ? CaptivePortalDataShimImpl.fromJson(info) : null; // 1. PT_CAPPORT_API probe w CapportApiData info // 1. PT_CAPPORT_API probe w CapportApiData info Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS, metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS, captivePortalData); captivePortalData); // 2. PT_CAPPORT_API probe w/o CapportApiData info // 2. PT_CAPPORT_API probe w/o CapportApiData info Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null); // 3. PT_DNS probe // 3. PT_DNS probe Metrics.setProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null); metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null); // 4. PT_HTTP probe // 4. PT_HTTP probe Metrics.setProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null); metrics.addProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null); // add Validation result // add Validation result Metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null); metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null); // Write metric into statsd // Write metric into statsd final NetworkValidationReported stats = Metrics.sendValidationStats(); final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend(); // Verify: TransportType: WIFI // Verify: TransportType: WIFI assertEquals(TransportType.TT_WIFI, stats.getTransportType()); assertEquals(TransportType.TT_WIFI, stats.getTransportType()); Loading @@ -207,13 +205,13 @@ public class NetworkValidationMetricsTest { assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType()); assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType()); assertEquals(1234, probeEvent.getLatencyMicros()); assertEquals(1234, probeEvent.getLatencyMicros()); assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult()); assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult()); assertEquals(true, probeEvent.hasCapportApiData()); if (CaptivePortalDataShimImpl.isSupported()) { if (CaptivePortalDataShimImpl.isSupported()) { assertTrue(probeEvent.hasCapportApiData()); // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds final CapportApiData capportData = probeEvent.getCapportApiData(); final CapportApiData capportData = probeEvent.getCapportApiData(); assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining); assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining); assertEquals(captivePortalData.getByteLimit(), capportData.getRemainingBytes()); assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes()); } else { } else { assertFalse(probeEvent.hasCapportApiData()); assertFalse(probeEvent.hasCapportApiData()); } } Loading