summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRemi NGUYEN VAN <reminv@google.com>2020-07-06 03:11:34 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-07-06 03:11:34 +0000
commita0428bfc7c2b1eb1b2a9f9252378e5e93ff64ba1 (patch)
tree7b435896c128df15885d91598c357e646f504e04 /src
parent4412473e2679937dc5b0632da60827fe5a5111b0 (diff)
parente1b31f105824614c6a506a99faed954d0d6214d3 (diff)
Merge "Injecting network validation stats into statsd" into rvc-dev am: e1b31f1058
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/11952741 Change-Id: I3f2d48f967d60236dd86e4c025bf017b5d066472
Diffstat (limited to 'src')
-rw-r--r--src/com/android/networkstack/metrics/NetworkValidationMetrics.java220
-rwxr-xr-xsrc/com/android/server/connectivity/NetworkMonitor.java94
2 files changed, 302 insertions, 12 deletions
diff --git a/src/com/android/networkstack/metrics/NetworkValidationMetrics.java b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
new file mode 100644
index 0000000..3789c5c
--- /dev/null
+++ b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+
+import static java.lang.System.currentTimeMillis;
+
+import android.net.INetworkMonitor;
+import android.net.NetworkCapabilities;
+import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.metrics.ValidationProbeEvent;
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
+import android.stats.connectivity.TransportType;
+import android.stats.connectivity.ValidationResult;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Class to record the network validation into statsd.
+ * 1. Fill in NetworkValidationReported proto.
+ * 2. Write the NetworkValidationReported proto into statsd.
+ * @hide
+ */
+
+public class NetworkValidationMetrics {
+ private final NetworkValidationReported.Builder mStatsBuilder =
+ NetworkValidationReported.newBuilder();
+ private final ProbeEvents.Builder mProbeEventsBuilder = ProbeEvents.newBuilder();
+ private final CapportApiData.Builder mCapportApiDataBuilder = CapportApiData.newBuilder();
+ private final Stopwatch mWatch = new Stopwatch();
+ private int mValidationIndex = 0;
+ // Define a maximum size that can store events.
+ public static final int MAX_PROBE_EVENTS_COUNT = 20;
+
+ /**
+ * Reset this NetworkValidationMetrics.
+ */
+ public void reset(@Nullable NetworkCapabilities nc) {
+ mStatsBuilder.clear();
+ mProbeEventsBuilder.clear();
+ mCapportApiDataBuilder.clear();
+ mWatch.restart();
+ mStatsBuilder.setTransportType(getTransportTypeFromNC(nc));
+ mValidationIndex++;
+ }
+
+ /**
+ * Returns the enum TransportType
+ *
+ * @param NetworkCapabilities
+ * @return the TransportType which is defined in
+ * core/proto/android/stats/connectivity/network_stack.proto
+ */
+ @VisibleForTesting
+ public static TransportType getTransportTypeFromNC(
+ @Nullable NetworkCapabilities nc) {
+ if (nc == null) return TransportType.TT_UNKNOWN;
+ boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR);
+ boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI);
+ boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH);
+ boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET);
+ boolean hasVpn = nc.hasTransport(TRANSPORT_VPN);
+ 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;
+ if (hasWifiAware) return TransportType.TT_WIFI_AWARE;
+ if (hasLopan) return TransportType.TT_LOWPAN;
+ return TransportType.TT_UNKNOWN;
+ }
+
+ /**
+ * Map {@link ValidationProbeEvent} to {@link ProbeType}.
+ */
+ public static ProbeType probeTypeToEnum(final int probeType) {
+ switch(probeType) {
+ case ValidationProbeEvent.PROBE_DNS:
+ return ProbeType.PT_DNS;
+ case ValidationProbeEvent.PROBE_HTTP:
+ return ProbeType.PT_HTTP;
+ case ValidationProbeEvent.PROBE_HTTPS:
+ return ProbeType.PT_HTTPS;
+ case ValidationProbeEvent.PROBE_PAC:
+ return ProbeType.PT_PAC;
+ case ValidationProbeEvent.PROBE_FALLBACK:
+ return ProbeType.PT_FALLBACK;
+ case ValidationProbeEvent.PROBE_PRIVDNS:
+ return ProbeType.PT_PRIVDNS;
+ default:
+ return ProbeType.PT_UNKNOWN;
+ }
+ }
+
+ /**
+ * Map {@link CaptivePortalProbeResult} to {@link ProbeResult}.
+ */
+ public static ProbeResult httpProbeResultToEnum(final CaptivePortalProbeResult result) {
+ if (result == null) return ProbeResult.PR_UNKNOWN;
+
+ if (result.isSuccessful()) {
+ return ProbeResult.PR_SUCCESS;
+ } else if (result.isDnsPrivateIpResponse()) {
+ return ProbeResult.PR_PRIVATE_IP_DNS;
+ } else if (result.isFailed()) {
+ return ProbeResult.PR_FAILURE;
+ } else if (result.isPortal()) {
+ return ProbeResult.PR_PORTAL;
+ } else {
+ return ProbeResult.PR_UNKNOWN;
+ }
+ }
+
+ /**
+ * Map validation result (as per INetworkMonitor) to {@link ValidationResult}.
+ */
+ @VisibleForTesting
+ public static ValidationResult validationResultToEnum(int result, String redirectUrl) {
+ if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) {
+ return ValidationResult.VR_SUCCESS;
+ } else if (redirectUrl != null) {
+ return ValidationResult.VR_PORTAL;
+ } else if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL) != 0) {
+ return ValidationResult.VR_PARTIAL;
+ } else {
+ return ValidationResult.VR_FAILURE;
+ }
+ }
+
+ /**
+ * Write each network probe event to mProbeEventsBuilder.
+ */
+ public void setProbeEvent(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.
+ if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return;
+
+ int latencyUs = NetworkStackUtils.saturatedCast(durationUs);
+
+ final ProbeEvent.Builder probeEventBuilder = ProbeEvent.newBuilder()
+ .setLatencyMicros(latencyUs)
+ .setProbeType(type)
+ .setProbeResult(result);
+
+ if (capportData != null) {
+ final long secondsRemaining =
+ (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000;
+ mCapportApiDataBuilder
+ .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining))
+ .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit()))
+ .setHasPortalUrl((capportData.getUserPortalUrl() != null))
+ .setHasVenueInfo((capportData.getVenueInfoUrl() != null));
+ probeEventBuilder.setCapportApiData(mCapportApiDataBuilder);
+ }
+
+ mProbeEventsBuilder.addProbeEvent(probeEventBuilder);
+ }
+
+ /**
+ * Write the network validation info to mStatsBuilder.
+ */
+ public void setValidationResult(int result, String redirectUrl) {
+ mStatsBuilder.setValidationResult(validationResultToEnum(result, redirectUrl));
+ }
+
+ /**
+ * Write the NetworkValidationReported proto to statsd.
+ */
+ public NetworkValidationReported sendValidationStats() {
+ 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();
+
+ NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED,
+ mStats.getTransportType().getNumber(),
+ probeEvents,
+ mStats.getValidationResult().getNumber(),
+ mStats.getLatencyMicros(),
+ mStats.getValidationIndex(),
+ mStats.getRandomNumber());
+ mWatch.reset();
+ return mStats;
+ }
+}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index b6b1fff..47c910a 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -129,6 +129,8 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
import android.telephony.AccessNetworkConstants;
import android.telephony.CellIdentityNr;
import android.telephony.CellInfo;
@@ -155,6 +157,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -168,6 +171,7 @@ import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.metrics.DataStallDetectionStats;
import com.android.networkstack.metrics.DataStallStatsUtils;
+import com.android.networkstack.metrics.NetworkValidationMetrics;
import com.android.networkstack.netlink.TcpSocketTracker;
import com.android.networkstack.util.DnsUtils;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -500,6 +504,10 @@ public class NetworkMonitor extends StateMachine {
private final boolean mPrivateIpNoInternetEnabled;
+ @GuardedBy("mNetworkValidationMetrics")
+ private final NetworkValidationMetrics mNetworkValidationMetrics =
+ new NetworkValidationMetrics();
+
private int getCallbackVersion(INetworkMonitorCallbacks cb) {
int version;
try {
@@ -775,6 +783,31 @@ public class NetworkMonitor extends StateMachine {
}
}
+ private void recordMetricsReset(@Nullable NetworkCapabilities nc) {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.reset(nc);
+ }
+ }
+
+ private void recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result,
+ CaptivePortalDataShim capportData) {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, capportData);
+ }
+ }
+
+ private void recordMetricsValidationResult(int result, String redirectUrl) {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.setValidationResult(result, redirectUrl);
+ }
+ }
+
+ private void recordMetricsValidationStats() {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.sendValidationStats();
+ }
+ }
+
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@@ -787,6 +820,7 @@ public class NetworkMonitor extends StateMachine {
transitionTo(mEvaluatingState);
return HANDLED;
case CMD_NETWORK_DISCONNECTED:
+ recordMetricsValidationStats();
logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
quit();
return HANDLED;
@@ -938,6 +972,7 @@ public class NetworkMonitor extends StateMachine {
initSocketTrackingIfRequired();
// start periodical polling.
sendTcpPollingEvent();
+ recordMetricsValidationStats();
}
private void initSocketTrackingIfRequired() {
@@ -957,6 +992,9 @@ public class NetworkMonitor extends StateMachine {
transitionTo(mValidatedState);
break;
case CMD_EVALUATE_PRIVATE_DNS:
+ // TODO: this causes reevaluation of a single probe that is not counted in
+ // metrics. Add support for such reevaluation probes in metrics, and log them
+ // separately.
transitionTo(mEvaluatingPrivateDnsState);
break;
case EVENT_DNS_NOTIFICATION:
@@ -1285,6 +1323,7 @@ public class NetworkMonitor extends StateMachine {
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
mValidations++;
+ recordMetricsValidationStats();
}
@Override
@@ -1316,6 +1355,10 @@ public class NetworkMonitor extends StateMachine {
notifyPrivateDnsConfigResolved();
} else {
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 */,
+ ProbeResult.PR_FAILURE, null /* capportData */);
break;
}
}
@@ -1425,6 +1468,8 @@ 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 :
+ ProbeResult.PR_FAILURE, null /* capportData */);
logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
return success;
}
@@ -1435,6 +1480,8 @@ public class NetworkMonitor extends StateMachine {
@Override
public void enter() {
+ recordMetricsValidationStats();
+ recordMetricsReset(mNetworkCapabilities);
if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
//Don't continue to blame UID forever.
TrafficStats.clearThreadStatsUid();
@@ -1516,6 +1563,7 @@ public class NetworkMonitor extends StateMachine {
private class WaitingForNextProbeState extends State {
@Override
public void enter() {
+ recordMetricsValidationStats();
scheduleNextProbe();
}
@@ -2265,6 +2313,8 @@ 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),
+ 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */);
return CaptivePortalProbeResult.PRIVATE_IP;
}
return sendHttpProbe(url, probeType, null);
@@ -2296,6 +2346,9 @@ public class NetworkMonitor extends StateMachine {
result = ValidationProbeEvent.DNS_FAILURE;
}
final long latency = watch.stop();
+ recordMetricsProbeEvent(ProbeType.PT_DNS, latency,
+ (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS :
+ ProbeResult.PR_FAILURE, null /* capportData */);
logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
return addresses;
}
@@ -2413,12 +2466,17 @@ public class NetworkMonitor extends StateMachine {
}
logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
+ final CaptivePortalProbeResult probeResult;
if (probeSpec == null) {
- return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(),
- 1 << probeType);
+ probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl,
+ url.toString(), 1 << probeType);
} else {
- return probeSpec.getResult(httpResponseCode, redirectUrl);
+ probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
}
+ recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType),
+ probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult),
+ null /* capportData */);
+ return probeResult;
}
@VisibleForTesting
@@ -2570,8 +2628,7 @@ public class NetworkMonitor extends StateMachine {
super(deps, proxy, url, captivePortalApiUrl);
}
- private CaptivePortalDataShim tryCapportApiProbe() {
- if (mCaptivePortalApiUrl == null) return null;
+ private CaptivePortalDataShim sendCapportApiProbe() {
validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl);
final String apiContent;
@@ -2610,26 +2667,38 @@ public class NetworkMonitor extends StateMachine {
try {
final JSONObject info = new JSONObject(apiContent);
- return CaptivePortalDataShimImpl.fromJson(info);
+ final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info);
+ if (capportData != null && capportData.isCaptive()
+ && capportData.getUserPortalUrl() == null) {
+ validationLog("Missing user-portal-url from capport response");
+ return null;
+ }
+ return capportData;
} catch (JSONException e) {
validationLog("Could not parse capport API JSON: " + e.getMessage());
return null;
} catch (UnsupportedApiLevelException e) {
+ // This should never happen because LinkProperties would not have a capport URL
+ // before R.
validationLog("Platform API too low to support capport API");
return null;
}
}
+ private CaptivePortalDataShim tryCapportApiProbe() {
+ if (mCaptivePortalApiUrl == null) return null;
+ final Stopwatch capportApiWatch = new Stopwatch().start();
+ final CaptivePortalDataShim capportData = sendCapportApiProbe();
+ recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(),
+ capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS,
+ capportData);
+ return capportData;
+ }
+
@Override
protected CaptivePortalProbeResult sendProbe() {
final CaptivePortalDataShim capportData = tryCapportApiProbe();
if (capportData != null && capportData.isCaptive()) {
- if (capportData.getUserPortalUrl() == null) {
- validationLog("Missing user-portal-url from capport response");
- return new CapportApiProbeResult(
- sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP),
- null /* capportData */);
- }
final String loginUrlString = capportData.getUserPortalUrl().toString();
// Starting from R (where CaptivePortalData was introduced), the captive portal app
// delegates to NetworkMonitor for verifying when the network validates instead of
@@ -3343,6 +3412,7 @@ public class NetworkMonitor extends StateMachine {
p.redirectUrl = redirectUrl;
p.timestampMillis = SystemClock.elapsedRealtime();
notifyNetworkTested(p);
+ recordMetricsValidationResult(result, redirectUrl);
}
@VisibleForTesting