IpConnectivityLog uses new metrics service
This patch connects existing IpConnectivityLog to the new
IpConnectivityMetrics service:
- IpConnectivityLog is now an independent class that pushes events
directly to the new IpConnectivityMetrics service.
- DnsEventListenerService is moved from MetricsLoggerService to
IpConnectivityMetrics.
- this patch also features end to end tests from IpConnectivityLog to
IpConnectivityMetrics dumpsys output.
Bug: 31254800
Change-Id: I4fe4a209eedde2814d5f13c574a1a0d854bd05c9
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 11e2775..173e5fd 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -17,65 +17,65 @@
package android.net.metrics;
import android.net.ConnectivityMetricsEvent;
-import android.net.ConnectivityMetricsLogger;
-import android.net.IConnectivityMetricsLogger;
+import android.net.IIpConnectivityMetrics;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
-
import com.android.internal.annotations.VisibleForTesting;
/**
- * Specialization of the ConnectivityMetricsLogger class for recording IP connectivity events.
+ * Class for logging IpConnectvity events with IpConnectivityMetrics
* {@hide}
*/
-public class IpConnectivityLog extends ConnectivityMetricsLogger {
- private static String TAG = "IpConnectivityMetricsLogger";
- private static final boolean DBG = true;
+public class IpConnectivityLog {
+ private static final String TAG = IpConnectivityLog.class.getSimpleName();
+ private static final boolean DBG = false;
public static final String SERVICE_NAME = "connmetrics";
+ private IIpConnectivityMetrics mService;
+
public IpConnectivityLog() {
- // mService initialized in super constructor.
}
@VisibleForTesting
- public IpConnectivityLog(IConnectivityMetricsLogger service) {
- super(service);
+ public IpConnectivityLog(IIpConnectivityMetrics service) {
+ mService = service;
+ }
+
+ private boolean checkLoggerService() {
+ if (mService != null) {
+ return true;
+ }
+ final IIpConnectivityMetrics service =
+ IIpConnectivityMetrics.Stub.asInterface(ServiceManager.getService(SERVICE_NAME));
+ if (service == null) {
+ return false;
+ }
+ // Two threads racing here will write the same pointer because getService
+ // is idempotent once MetricsLoggerService is initialized.
+ mService = service;
+ return true;
}
/**
- * Log an IpConnectivity event. Contrary to logEvent(), this method does not
- * keep track of skipped events and is thread-safe for callers.
- *
+ * Log an IpConnectivity event.
* @param timestamp is the epoch timestamp of the event in ms.
* @param data is a Parcelable instance representing the event.
- *
* @return true if the event was successfully logged.
*/
public boolean log(long timestamp, Parcelable data) {
if (!checkLoggerService()) {
if (DBG) {
- Log.d(TAG, CONNECTIVITY_METRICS_LOGGER_SERVICE + " service was not ready");
- }
- return false;
- }
-
- if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) {
- if (DBG) {
- Log.d(TAG, "skipping logging due to throttling for IpConnectivity component");
+ Log.d(TAG, SERVICE_NAME + " service was not ready");
}
return false;
}
try {
- final ConnectivityMetricsEvent event =
- new ConnectivityMetricsEvent(timestamp, COMPONENT_TAG_CONNECTIVITY, 0, data);
- final long result = mService.logEvent(event);
- if (result >= 0) {
- mServiceUnblockedTimestampMillis = result;
- }
- return (result == 0);
+ int left = mService.logEvent(new ConnectivityMetricsEvent(timestamp, 0, 0, data));
+ return left >= 0;
} catch (RemoteException e) {
Log.e(TAG, "Error logging event", e);
return false;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 8f419d8..bcbcf54 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -54,6 +54,8 @@
@VisibleForTesting
public final Impl impl = new Impl();
+ private DnsEventListenerService mDnsListener;
+
@GuardedBy("mLock")
private ArrayList<ConnectivityMetricsEvent> mBuffer;
@GuardedBy("mLock")
@@ -75,8 +77,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
if (DBG) Log.d(TAG, "onBootPhase");
+ mDnsListener = new DnsEventListenerService(getContext());
publishBinderService(SERVICE_NAME, impl);
+ publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
}
}
@@ -165,6 +169,9 @@
pw.println("Buffer capacity: " + mCapacity);
pw.println("Dropped events: " + mDropped);
}
+ if (mDnsListener != null) {
+ mDnsListener.dump(pw);
+ }
}
private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
index 05f1a6e..1c9feb2 100644
--- a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
+++ b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
@@ -56,8 +56,6 @@
if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE,
mBinder);
- mDnsListener = new DnsEventListenerService(getContext());
- publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
}
}
@@ -86,8 +84,6 @@
private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>();
- private DnsEventListenerService mDnsListener;
-
private void enforceConnectivityInternalPermission() {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
@@ -219,11 +215,6 @@
}
}
}
-
- pw.println();
- if (mDnsListener != null) {
- mDnsListener.dump(pw);
- }
}
public long logEvent(ConnectivityMetricsEvent event) {
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
new file mode 100644
index 0000000..3fc89b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016, 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.server.connectivity;
+
+import android.content.Context;
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.metrics.ApfStats;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.metrics.IpReachabilityEvent;
+import android.net.metrics.RaEvent;
+import android.net.metrics.ValidationProbeEvent;
+import android.os.Parcelable;
+import android.util.Base64;
+import com.android.server.connectivity.metrics.IpConnectivityLogClass;
+import com.google.protobuf.nano.MessageNano;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import junit.framework.TestCase;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class IpConnectivityMetricsTest extends TestCase {
+ static final IpReachabilityEvent FAKE_EV =
+ new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
+
+ @Mock Context mCtx;
+ @Mock IIpConnectivityMetrics mMockService;
+
+ IpConnectivityMetrics mService;
+
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mService = new IpConnectivityMetrics(mCtx);
+ }
+
+ public void testLoggingEvents() throws Exception {
+ IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+ assertTrue(logger.log(1, FAKE_EV));
+ assertTrue(logger.log(2, FAKE_EV));
+ assertTrue(logger.log(3, FAKE_EV));
+
+ List<ConnectivityMetricsEvent> got = verifyEvents(3);
+ assertEventsEqual(expectedEvent(1), got.get(0));
+ assertEventsEqual(expectedEvent(2), got.get(1));
+ assertEventsEqual(expectedEvent(3), got.get(2));
+ }
+
+ public void testLoggingEventsWithMultipleCallers() throws Exception {
+ IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+ final int nCallers = 10;
+ final int nEvents = 10;
+ for (int n = 0; n < nCallers; n++) {
+ final int i = n;
+ new Thread() {
+ public void run() {
+ for (int j = 0; j < nEvents; j++) {
+ assertTrue(logger.log(i * 100 + j, FAKE_EV));
+ }
+ }
+ }.start();
+ }
+
+ List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 100);
+ Collections.sort(got, EVENT_COMPARATOR);
+ Iterator<ConnectivityMetricsEvent> iter = got.iterator();
+ for (int i = 0; i < nCallers; i++) {
+ for (int j = 0; j < nEvents; j++) {
+ int expectedTimestamp = i * 100 + j;
+ assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
+ }
+ }
+ }
+
+ public void testBufferFlushing() {
+ String output1 = getdump("flush");
+ assertEquals("", output1);
+
+ new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
+ String output2 = getdump("flush");
+ assertFalse("".equals(output2));
+
+ String output3 = getdump("flush");
+ assertEquals("", output3);
+ }
+
+ public void testEndToEndLogging() {
+ IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
+
+ Parcelable[] events = {
+ new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
+ new DhcpClientEvent("wlan0", "SomeState", 192),
+ new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
+ new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
+ new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
+ new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048),
+ new RaEvent(2000, 400, 300, -1, 1000, -1)
+ };
+
+ for (int i = 0; i < events.length; i++) {
+ logger.log(100 * (i + 1), events[i]);
+ }
+
+ String want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"wlan0\"",
+ " >",
+ " time_ms: 100",
+ ">",
+ "events <",
+ " dhcp_event <",
+ " duration_ms: 192",
+ " error_code: 0",
+ " if_name: \"wlan0\"",
+ " state_transition: \"SomeState\"",
+ " >",
+ " time_ms: 200",
+ ">",
+ "events <",
+ " default_network_event <",
+ " network_id <",
+ " network_id: 102",
+ " >",
+ " previous_network_id <",
+ " network_id: 101",
+ " >",
+ " previous_network_ip_support: 1",
+ " transport_types: 1",
+ " transport_types: 2",
+ " transport_types: 3",
+ " >",
+ " time_ms: 300",
+ ">",
+ "events <",
+ " ip_provisioning_event <",
+ " event_type: 1",
+ " if_name: \"wlan0\"",
+ " latency_ms: 5678",
+ " >",
+ " time_ms: 400",
+ ">",
+ "events <",
+ " time_ms: 500",
+ " validation_probe_event <",
+ " latency_ms: 40730",
+ " network_id <",
+ " network_id: 120",
+ " >",
+ " probe_result: 204",
+ " probe_type: 1",
+ " >",
+ ">",
+ "events <",
+ " apf_statistics <",
+ " dropped_ras: 2",
+ " duration_ms: 45000",
+ " matching_ras: 2",
+ " max_program_size: 2048",
+ " parse_errors: 2",
+ " program_updates: 4",
+ " received_ras: 10",
+ " zero_lifetime_ras: 1",
+ " >",
+ " time_ms: 600",
+ ">",
+ "events <",
+ " ra_event <",
+ " dnssl_lifetime: -1",
+ " prefix_preferred_lifetime: 300",
+ " prefix_valid_lifetime: 400",
+ " rdnss_lifetime: 1000",
+ " route_info_lifetime: -1",
+ " router_lifetime: 2000",
+ " >",
+ " time_ms: 700",
+ ">");
+
+ verifySerialization(want, getdump("flush"));
+ }
+
+ String getdump(String ... command) {
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ mService.impl.dump(null, writer, command);
+ return buffer.toString();
+ }
+
+ List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+ ArgumentCaptor<ConnectivityMetricsEvent> captor =
+ ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+ verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+ return captor.getAllValues();
+ }
+
+ List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+ return verifyEvents(n, 10);
+ }
+
+ static void verifySerialization(String want, String output) {
+ try {
+ byte[] got = Base64.decode(output, Base64.DEFAULT);
+ IpConnectivityLogClass.IpConnectivityLog log =
+ new IpConnectivityLogClass.IpConnectivityLog();
+ MessageNano.mergeFrom(log, got);
+ assertEquals(want, log.toString());
+ } catch (Exception e) {
+ fail(e.toString());
+ }
+ }
+
+ static String joinLines(String ... elems) {
+ StringBuilder b = new StringBuilder();
+ for (String s : elems) {
+ b.append(s).append("\n");
+ }
+ return b.toString();
+ }
+
+ static ConnectivityMetricsEvent expectedEvent(int timestamp) {
+ return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV);
+ }
+
+ /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+ static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
+ assertEquals(expected.timestamp, got.timestamp);
+ assertEquals(expected.componentTag, got.componentTag);
+ assertEquals(expected.eventTag, got.eventTag);
+ assertEquals(expected.data, got.data);
+ }
+
+ static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
+ new Comparator<ConnectivityMetricsEvent>() {
+ @Override
+ public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) {
+ return (int) (ev1.timestamp - ev2.timestamp);
+ }
+ };
+}