Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5325bb1 authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Gerrit Code Review
Browse files

Merge "Refine validation metrics"

parents 2b0d8046 7a21cda8
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -228,6 +228,15 @@ public class NetworkStackUtils {
    public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION =
            "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 {
        System.loadLibrary("networkstackutilsjni");
    }
@@ -350,6 +359,9 @@ public class NetworkStackUtils {
     * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
     * with current version of property. If this property version is valid, the corresponding
     * 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 namespace The namespace containing the property to look up.
     * @param name The name of the property to look up.
@@ -365,6 +377,9 @@ public class NetworkStackUtils {
     * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
     * with current version of property. If this property version is valid, the corresponding
     * 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 namespace The namespace containing the property to look up.
     * @param name The name of the property to look up.
+59 −25
Original line number Diff line number Diff line
@@ -60,9 +60,11 @@ public class NetworkValidationMetrics {
    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();
        mProbeEventsBuilder.clear();
        mCapportApiDataBuilder.clear();
@@ -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
     * core/proto/android/stats/connectivity/network_stack.proto
     */
    @VisibleForTesting
    public static TransportType getTransportTypeFromNC(
            @Nullable NetworkCapabilities nc) {
    public static TransportType getTransportTypeFromNC(@Nullable NetworkCapabilities nc) {
        if (nc == null) return TransportType.TT_UNKNOWN;

        final int trCount = nc.getTransportTypes().length;
        boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR);
        boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI);
        boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH);
@@ -90,13 +99,29 @@ public class NetworkValidationMetrics {
        boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE);
        boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN);

        if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN;
        if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI;
        if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR;
        if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH;
        if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET;
        // VPN networks are not subject to validation and should not see validation stats, but
        // metrics could be added to measure private DNS probes only.
        if (trCount == 3 && hasCellular && hasWifi && hasVpn) {
            return TransportType.TT_WIFI_CELLULAR_VPN;
        }

        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 (hasLopan) return TransportType.TT_LOWPAN;
            // TODO: consider having a TT_VPN for VPN-only transport
        }

        return TransportType.TT_UNKNOWN;
    }

@@ -146,6 +171,8 @@ public class NetworkValidationMetrics {
     */
    @VisibleForTesting
    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) {
            return ValidationResult.VR_SUCCESS;
        } else if (redirectUrl != null) {
@@ -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) {
        // When the number of ProbeEvents of mProbeEventsBuilder exceeds
        // 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;

        int latencyUs = NetworkStackUtils.saturatedCast(durationUs);
@@ -178,7 +207,9 @@ public class NetworkValidationMetrics {
                    (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000;
            mCapportApiDataBuilder
                .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))
                .setHasVenueInfo((capportData.getVenueInfoUrl() != null));
            probeEventBuilder.setCapportApiData(mCapportApiDataBuilder);
@@ -196,25 +227,28 @@ public class NetworkValidationMetrics {

    /**
     * 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;
        mStatsBuilder.setProbeEvents(mProbeEventsBuilder);
        mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop()));
        mStatsBuilder.setValidationIndex(mValidationIndex);
        // write a random value(0 ~ 999) for sampling.
        mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
        final NetworkValidationReported mStats = mStatsBuilder.build();
        final byte[] probeEvents = mStats.getProbeEvents().toByteArray();
        final NetworkValidationReported stats = mStatsBuilder.build();
        final byte[] probeEvents = stats.getProbeEvents().toByteArray();

        NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED,
                mStats.getTransportType().getNumber(),
                stats.getTransportType().getNumber(),
                probeEvents,
                mStats.getValidationResult().getNumber(),
                mStats.getLatencyMicros(),
                mStats.getValidationIndex(),
                mStats.getRandomNumber());
                stats.getValidationResult().getNumber(),
                stats.getLatencyMicros(),
                stats.getValidationIndex(),
                stats.getRandomNumber());
        mWatch.reset();
        return mStats;
        return stats;
    }
}
+75 −26
Original line number Diff line number Diff line
@@ -451,7 +451,14 @@ public class NetworkMonitor extends StateMachine {
    protected boolean mIsCaptivePortalCheckEnabled;

    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;

    // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
@@ -506,6 +513,14 @@ public class NetworkMonitor extends StateMachine {

    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")
    private final NetworkValidationMetrics mNetworkValidationMetrics =
            new NetworkValidationMetrics();
@@ -574,6 +589,8 @@ public class NetworkMonitor extends StateMachine {

        mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
        mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
        mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
                NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
        mUseHttps = getUseHttpsValidation();
        mCaptivePortalUserAgent = getCaptivePortalUserAgent();
        mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
@@ -786,28 +803,48 @@ public class NetworkMonitor extends StateMachine {
        }
    }

    private void recordMetricsReset(@Nullable NetworkCapabilities nc) {
    private void startMetricsCollection() {
        if (!mMetricsEnabled) return;
        try {
            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) {
        if (!mMetricsEnabled) return;
        try {
            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) {
                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) {
            mNetworkValidationMetrics.sendValidationStats();
                mNetworkValidationMetrics.maybeStopCollectionAndSend();
            }
        } catch (Exception e) {
            Log.wtf(TAG, "Error sending validation stats", e);
        }
    }

@@ -823,7 +860,7 @@ public class NetworkMonitor extends StateMachine {
                    transitionTo(mEvaluatingState);
                    return HANDLED;
                case CMD_NETWORK_DISCONNECTED:
                    recordMetricsValidationStats();
                    maybeStopCollectionAndSendMetrics();
                    logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
                    quit();
                    return HANDLED;
@@ -975,7 +1012,7 @@ public class NetworkMonitor extends StateMachine {
            initSocketTrackingIfRequired();
            // start periodical polling.
            sendTcpPollingEvent();
            recordMetricsValidationStats();
            maybeStopCollectionAndSendMetrics();
        }

        private void initSocketTrackingIfRequired() {
@@ -1326,7 +1363,7 @@ public class NetworkMonitor extends StateMachine {
            sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                    CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
            mValidations++;
            recordMetricsValidationStats();
            maybeStopCollectionAndSendMetrics();
        }

        @Override
@@ -1360,7 +1397,9 @@ public class NetworkMonitor extends StateMachine {
                                handlePrivateDnsEvaluationFailure();
                                // The private DNS probe fails-fast if the server hostname cannot
                                // 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 */);
                                break;
                            }
@@ -1471,7 +1510,7 @@ public class NetworkMonitor extends StateMachine {
                validationLog(PROBE_PRIVDNS, host,
                        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 */);
            logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
            return success;
@@ -1483,8 +1522,14 @@ public class NetworkMonitor extends StateMachine {

        @Override
        public void enter() {
            recordMetricsValidationStats();
            recordMetricsReset(mNetworkCapabilities);
            // When starting a full probe cycle here, record any pending stats (for example if
            // 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) {
                //Don't continue to blame UID forever.
                TrafficStats.clearThreadStatsUid();
@@ -1566,7 +1611,9 @@ public class NetworkMonitor extends StateMachine {
    private class WaitingForNextProbeState extends State {
        @Override
        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();
        }

@@ -2312,7 +2359,7 @@ public class NetworkMonitor extends StateMachine {
        // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe.
        if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP
                && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) {
            recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType),
            recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
                    0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */);
            return CaptivePortalProbeResult.PRIVATE_IP;
        }
@@ -2345,7 +2392,7 @@ public class NetworkMonitor extends StateMachine {
            result = ValidationProbeEvent.DNS_FAILURE;
        }
        final long latency = watch.stop();
        recordMetricsProbeEvent(ProbeType.PT_DNS, latency,
        recordProbeEventMetrics(ProbeType.PT_DNS, latency,
                (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS :
                ProbeResult.PR_FAILURE, null /* capportData */);
        logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
@@ -2472,7 +2519,7 @@ public class NetworkMonitor extends StateMachine {
        } else {
            probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
        }
        recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType),
        recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
                probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult),
                null /* capportData */);
        return probeResult;
@@ -2630,6 +2677,8 @@ public class NetworkMonitor extends StateMachine {
        }

        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);

            final String apiContent;
@@ -2690,7 +2739,7 @@ public class NetworkMonitor extends StateMachine {
            if (mCaptivePortalApiUrl == null) return null;
            final Stopwatch capportApiWatch = new Stopwatch().start();
            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);
            return capportData;
@@ -3421,7 +3470,7 @@ public class NetworkMonitor extends StateMachine {
            p.redirectUrl = redirectUrl;
            p.timestampMillis = SystemClock.elapsedRealtime();
            notifyNetworkTested(p);
            recordMetricsValidationResult(result, redirectUrl);
            recordValidationResult(result, redirectUrl);
        }

        @VisibleForTesting
+20 −22
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;

import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import android.net.INetworkMonitor;
import android.net.NetworkCapabilities;
@@ -54,7 +53,7 @@ public class NetworkValidationMetricsTest {
    private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
    private static final int TTL_TOLERANCE_SECS = 10;

    private static final NetworkCapabilities WIFI_NOT_METERED_CAPABILITIES =
    private static final NetworkCapabilities WIFI_CAPABILITIES =
            new NetworkCapabilities()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);

@@ -136,17 +135,17 @@ public class NetworkValidationMetricsTest {

    @Test
    public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception {
        final NetworkValidationMetrics Metrics = new NetworkValidationMetrics();
        Metrics.reset(WIFI_NOT_METERED_CAPABILITIES);
        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
        metrics.startCollection(WIFI_CAPABILITIES);
        // 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
        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
        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
        final ProbeEvents probeEvents = stats.getProbeEvents();
@@ -156,40 +155,39 @@ public class NetworkValidationMetricsTest {

    @Test
    public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception {
        assumeTrue(CaptivePortalDataShimImpl.isSupported());
        final long bytesRemaining = 10L;
        final long bytesRemaining = 12_345L;
        final long secondsRemaining = 3000L;
        String apiContent = "{'captive': true,"
                + "'user-portal-url': '" + TEST_LOGIN_URL + "',"
                + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
                + "'bytes-remaining': " + bytesRemaining + ","
                + "'seconds-remaining': " + secondsRemaining + "}";
        final NetworkValidationMetrics Metrics = new NetworkValidationMetrics();
        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
        final int validationIndex = 1;
        final long longlatency = 2147483649L;
        Metrics.reset(WIFI_NOT_METERED_CAPABILITIES);
        final long longlatency = Integer.MAX_VALUE + 12344567L;
        metrics.startCollection(WIFI_CAPABILITIES);

        final JSONObject info = new JSONObject(apiContent);
        final CaptivePortalDataShim captivePortalData =
                CaptivePortalDataShimImpl.fromJson(info);
        final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported()
                ? CaptivePortalDataShimImpl.fromJson(info) : null;

        // 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);
        // 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
        Metrics.setProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);
        metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);

        // 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
        Metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);
        metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);

        // Write metric into statsd
        final NetworkValidationReported stats = Metrics.sendValidationStats();
        final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();

        // Verify: TransportType: WIFI
        assertEquals(TransportType.TT_WIFI, stats.getTransportType());
@@ -207,13 +205,13 @@ public class NetworkValidationMetricsTest {
        assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
        assertEquals(1234, probeEvent.getLatencyMicros());
        assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult());
        assertEquals(true, probeEvent.hasCapportApiData());
        if (CaptivePortalDataShimImpl.isSupported()) {
            assertTrue(probeEvent.hasCapportApiData());
            // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds
            final CapportApiData capportData = probeEvent.getCapportApiData();
            assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining);
            assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining);
            assertEquals(captivePortalData.getByteLimit(), capportData.getRemainingBytes());
            assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes());
        } else {
            assertFalse(probeEvent.hasCapportApiData());
        }