Merge changes I9b4e55c2,I429562a0 into nyc-mr1-dev

* changes:
  Add tests for MetricsLoggerService
  Tests for IpConnectivityMetricsLog
This commit is contained in:
Hugo Benichi
2016-06-29 00:15:48 +00:00
committed by Android (Google) Code Review
5 changed files with 381 additions and 17 deletions

View File

@@ -23,6 +23,8 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/** {@hide} */
@SystemApi
public class ConnectivityMetricsLogger {
@@ -49,8 +51,14 @@ public class ConnectivityMetricsLogger {
private int mNumSkippedEvents;
public ConnectivityMetricsLogger() {
mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService(
CONNECTIVITY_METRICS_LOGGER_SERVICE));
this(IConnectivityMetricsLogger.Stub.asInterface(
ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE)));
}
/** {@hide} */
@VisibleForTesting
public ConnectivityMetricsLogger(IConnectivityMetricsLogger service) {
mService = service;
}
/**
@@ -153,11 +161,10 @@ public class ConnectivityMetricsLogger {
public boolean unregister(PendingIntent newEventsIntent) {
try {
mService.unregister(newEventsIntent);
return true;
} catch (RemoteException e) {
Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
return false;
}
return true;
}
}

View File

@@ -18,18 +18,29 @@ package android.net.metrics;
import android.net.ConnectivityMetricsEvent;
import android.net.ConnectivityMetricsLogger;
import android.net.IConnectivityMetricsLogger;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/**
* Specialization of the ConnectivityMetricsLogger class for recording IP connectivity events.
* {@hide}
*/
class IpConnectivityLog extends ConnectivityMetricsLogger {
public class IpConnectivityLog extends ConnectivityMetricsLogger {
private static String TAG = "IpConnectivityMetricsLogger";
private static final boolean DBG = false;
public IpConnectivityLog() {
}
@VisibleForTesting
public IpConnectivityLog(IConnectivityMetricsLogger service) {
super(service);
}
/**
* Log an IpConnectivity event. Contrary to logEvent(), this method does not
* keep track of skipped events and is thread-safe for callers.

View File

@@ -16,6 +16,7 @@
package com.android.server.connectivity;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import android.app.PendingIntent;
@@ -60,17 +61,11 @@ public class MetricsLoggerService extends SystemService {
}
}
// TODO: read from system property
private final int MAX_NUMBER_OF_EVENTS = 1000;
// TODO: read from system property
private final int EVENTS_NOTIFICATION_THRESHOLD = 300;
// TODO: read from system property
private final int THROTTLING_TIME_INTERVAL_MILLIS = 60 * 60 * 1000; // 1 hour
// TODO: read from system property
// TODO: read these constants from system property
private final int EVENTS_NOTIFICATION_THRESHOLD = 300;
private final int MAX_NUMBER_OF_EVENTS = 1000;
private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000;
private final long THROTTLING_TIME_INTERVAL_MILLIS = DateUtils.HOUR_IN_MILLIS;
private int mEventCounter = 0;
@@ -127,10 +122,13 @@ public class MetricsLoggerService extends SystemService {
mEvents.addLast(e);
}
@VisibleForTesting
final MetricsLoggerImpl mBinder = new MetricsLoggerImpl();
/**
* Implementation of the IConnectivityMetricsLogger interface.
*/
private final IConnectivityMetricsLogger.Stub mBinder = new IConnectivityMetricsLogger.Stub() {
final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub {
private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();
@@ -223,7 +221,9 @@ public class MetricsLoggerService extends SystemService {
}
pw.println();
mDnsListener.dump(pw);
if (mDnsListener != null) {
mDnsListener.dump(pw);
}
}
public long logEvent(ConnectivityMetricsEvent event) {

View File

@@ -0,0 +1,165 @@
/*
* 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 android.net.metrics;
import android.os.Parcel;
import android.os.Parcelable;
import android.net.ConnectivityMetricsEvent;
import android.net.IConnectivityMetricsLogger;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
public class IpConnectivityLogTest extends TestCase {
static class FakeEvent extends IpConnectivityEvent implements Parcelable {
public int describeContents() { return 0; }
public void writeToParcel(Parcel p, int flag) { }
}
static final FakeEvent FAKE_EV = new FakeEvent();
@Mock IConnectivityMetricsLogger mService;
ArgumentCaptor<ConnectivityMetricsEvent> evCaptor;
IpConnectivityLog mLog;
public void setUp() {
MockitoAnnotations.initMocks(this);
evCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
mLog = new IpConnectivityLog(mService);
}
public void testLogEvents() throws Exception {
assertTrue(mLog.log(1, FAKE_EV));
assertTrue(mLog.log(2, FAKE_EV));
assertTrue(mLog.log(3, FAKE_EV));
List<ConnectivityMetricsEvent> gotEvents = verifyEvents(3);
assertEventsEqual(expectedEvent(1), gotEvents.get(0));
assertEventsEqual(expectedEvent(2), gotEvents.get(1));
assertEventsEqual(expectedEvent(3), gotEvents.get(2));
}
public void testLogEventTriggerThrottling() throws Exception {
when(mService.logEvent(any())).thenReturn(1234L);
assertFalse(mLog.log(1, FAKE_EV));
}
public void testLogEventFails() throws Exception {
when(mService.logEvent(any())).thenReturn(-1L); // Error.
assertFalse(mLog.log(1, FAKE_EV));
}
public void testLogEventWhenThrottling() throws Exception {
when(mService.logEvent(any())).thenReturn(Long.MAX_VALUE); // Throttled
// No events are logged. The service is only called once
// After that, throttling state is maintained locally.
assertFalse(mLog.log(1, FAKE_EV));
assertFalse(mLog.log(2, FAKE_EV));
List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
assertEventsEqual(expectedEvent(1), gotEvents.get(0));
}
public void testLogEventRecoverFromThrottling() throws Exception {
final long throttleTimeout = System.currentTimeMillis() + 50;
when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
assertFalse(mLog.log(1, FAKE_EV));
new Thread() {
public void run() {
busySpinLog(FAKE_EV);
}
}.start();
List<ConnectivityMetricsEvent> gotEvents = verifyEvents(2, 200);
assertEventsEqual(expectedEvent(1), gotEvents.get(0));
assertEventsEqual(expectedEvent(2), gotEvents.get(1));
}
public void testLogEventRecoverFromThrottlingWithMultipleCallers() throws Exception {
final long throttleTimeout = System.currentTimeMillis() + 50;
when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
assertFalse(mLog.log(1, FAKE_EV));
final int nCallers = 10;
for (int i = 0; i < nCallers; i++) {
new Thread() {
public void run() {
busySpinLog(FAKE_EV);
}
}.start();
}
List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1 + nCallers, 200);
assertEventsEqual(expectedEvent(1), gotEvents.get(0));
for (int i = 0; i < nCallers; i++) {
assertEventsEqual(expectedEvent(2), gotEvents.get(1 + i));
}
}
void busySpinLog(Parcelable ev) {
final long timeout = 200;
final long stop = System.currentTimeMillis() + timeout;
try {
while (System.currentTimeMillis() < stop) {
if (mLog.log(2, FAKE_EV)) {
return;
}
Thread.sleep(10);
}
} catch (InterruptedException e) { }
}
List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
verify(mService, times(n)).logEvent(evCaptor.capture());
return evCaptor.getAllValues();
}
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
verify(mService, timeout(timeoutMs).times(n)).logEvent(evCaptor.capture());
return evCaptor.getAllValues();
}
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);
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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.os.Bundle;
import android.os.RemoteException;
import static android.net.ConnectivityMetricsEvent.Reference;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/*
* TODO:
* - allow overriding MetricsLoggerService constants in tests.
* - test intents are correctly sent after the notification threshold.
* - test oldest events are correctly pushed out when internal deque is full.
* - test throttling triggers correctly.
*/
public class MetricsLoggerServiceTest extends TestCase {
static final int COMPONENT_TAG = 1;
static final long N_EVENTS = 10L;
static final ConnectivityMetricsEvent EVENTS[] = new ConnectivityMetricsEvent[(int)N_EVENTS];
static {
for (int i = 0; i < N_EVENTS; i++) {
EVENTS[i] = new ConnectivityMetricsEvent(i, COMPONENT_TAG, i, new Bundle());
}
}
static final ConnectivityMetricsEvent NO_EVENTS[] = new ConnectivityMetricsEvent[0];
@Mock Context mContext;
MetricsLoggerService mService;
public void setUp() {
MockitoAnnotations.initMocks(this);
mService = new MetricsLoggerService(mContext);
mService.onStart();
}
public void testGetNoEvents() throws Exception {
Reference r = new Reference(0);
assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
assertEquals(0, r.getValue());
}
public void testLogAndGetEvents() throws Exception {
mService.mBinder.logEvents(EVENTS);
Reference r = new Reference(0);
assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
}
public void testLogOneByOne() throws Exception {
for (ConnectivityMetricsEvent ev : EVENTS) {
mService.mBinder.logEvent(ev);
}
Reference r = new Reference(0);
assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
}
public void testInterleavedLogAndGet() throws Exception {
mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 0, 3));
Reference r = new Reference(0);
assertArrayEquals(Arrays.copyOfRange(EVENTS, 0, 3), mService.mBinder.getEvents(r));
assertEquals(3, r.getValue());
mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 8));
mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 8, 10));
assertArrayEquals(Arrays.copyOfRange(EVENTS, 3, 10), mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
assertEquals(N_EVENTS, r.getValue());
}
public void testMultipleGetAll() throws Exception {
mService.mBinder.logEvents(Arrays.copyOf(EVENTS, 3));
Reference r1 = new Reference(0);
assertArrayEquals(Arrays.copyOf(EVENTS, 3), mService.mBinder.getEvents(r1));
assertEquals(3, r1.getValue());
mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 10));
Reference r2 = new Reference(0);
assertArrayEquals(EVENTS, mService.mBinder.getEvents(r2));
assertEquals(N_EVENTS, r2.getValue());
}
public void testLogAndDumpConcurrently() throws Exception {
for (int i = 0; i < 50; i++) {
mContext = null;
mService = null;
setUp();
logAndDumpConcurrently();
}
}
public void logAndDumpConcurrently() throws Exception {
final CountDownLatch latch = new CountDownLatch((int)N_EVENTS);
final FileDescriptor fd = new FileOutputStream("/dev/null").getFD();
for (ConnectivityMetricsEvent ev : EVENTS) {
new Thread() {
public void run() {
mService.mBinder.logEvent(ev);
latch.countDown();
}
}.start();
}
new Thread() {
public void run() {
while (latch.getCount() > 0) {
mService.mBinder.dump(fd, new String[]{"--all"});
}
}
}.start();
latch.await(100, TimeUnit.MILLISECONDS);
Reference r = new Reference(0);
ConnectivityMetricsEvent[] got = mService.mBinder.getEvents(r);
Arrays.sort(got, new EventComparator());
assertArrayEquals(EVENTS, got);
assertEquals(N_EVENTS, r.getValue());
}
static class EventComparator implements Comparator<ConnectivityMetricsEvent> {
public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) {
return Long.compare(ev1.timestamp, ev2.timestamp);
}
public boolean equal(Object o) {
return o instanceof EventComparator;
}
};
}