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

Commit bf45240a authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Android (Google) Code Review
Browse files

Merge "Refine validation metrics" into rvc-dev

parents e1b31f10 7b6435fd
Loading
Loading
Loading
Loading
+15 −0
Original line number Original line Diff line number Diff line
@@ -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");
    }
    }
@@ -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.
@@ -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.
+59 −25
Original line number Original line Diff line number Diff line
@@ -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();
@@ -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);
@@ -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;
    }
    }


@@ -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) {
@@ -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);
@@ -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);
@@ -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;
    }
    }
}
}
+75 −26
Original line number Original line Diff line number Diff line
@@ -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.
@@ -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();
@@ -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();
@@ -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);
        }
        }
    }
    }


@@ -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;
@@ -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() {
@@ -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
@@ -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;
                            }
                            }
@@ -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;
@@ -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();
@@ -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();
        }
        }


@@ -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;
        }
        }
@@ -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);
@@ -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;
@@ -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;
@@ -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;
@@ -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
+20 −22
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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);


@@ -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();
@@ -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());
@@ -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());
        }
        }