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

Commit ab7cab4a authored by Hugo Benichi's avatar Hugo Benichi Committed by Android (Google) Code Review
Browse files

Merge "resolve merge conflicts of 9355bce0 to master"

parents 7d8875a1 01432b30
Loading
Loading
Loading
Loading
+3 −100
Original line number Diff line number Diff line
@@ -46,32 +46,7 @@ public class ConnectivityMetricsLogger {

    public static final String DATA_KEY_EVENTS_COUNT = "count";

    /** {@hide} */ protected IConnectivityMetricsLogger mService;
    /** {@hide} */ protected volatile long mServiceUnblockedTimestampMillis;
    private int mNumSkippedEvents;

    public ConnectivityMetricsLogger() {
        // TODO: consider not initializing mService in constructor
        this(IConnectivityMetricsLogger.Stub.asInterface(
                ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE)));
    }

    /** {@hide} */
    @VisibleForTesting
    public ConnectivityMetricsLogger(IConnectivityMetricsLogger service) {
        mService = service;
    }

    /** {@hide} */
    protected boolean checkLoggerService() {
        if (mService != null) {
            return true;
        }
        // Two threads racing here will write the same pointer because getService
        // is idempotent once MetricsLoggerService is initialized.
        mService = IConnectivityMetricsLogger.Stub.asInterface(
                ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE));
        return mService != null;
    }

    /**
@@ -88,62 +63,6 @@ public class ConnectivityMetricsLogger {
     * @param data is a Parcelable instance representing the event.
     */
    public void logEvent(long timestamp, int componentTag, int eventTag, Parcelable data) {
        if (mService == null) {
            if (DBG) {
                Log.d(TAG, "logEvent(" + componentTag + "," + eventTag + ") Service not ready");
            }
            return;
        }

        if (mServiceUnblockedTimestampMillis > 0) {
            if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) {
                // Service is throttling events.
                // Don't send new events because they will be dropped.
                mNumSkippedEvents++;
                return;
            }
        }

        ConnectivityMetricsEvent skippedEventsEvent = null;
        if (mNumSkippedEvents > 0) {
            // Log number of skipped events
            Bundle b = new Bundle();
            b.putInt(DATA_KEY_EVENTS_COUNT, mNumSkippedEvents);

            // Log the skipped event.
            // TODO: Note that some of the clients push all states events into the server,
            // If we lose some states logged here, we might mess up the statistics happened at the
            // backend. One of the options is to introduce a non-skippable flag for important events
            // that are logged.
            skippedEventsEvent = new ConnectivityMetricsEvent(mServiceUnblockedTimestampMillis,
                    componentTag, TAG_SKIPPED_EVENTS, b);

            mServiceUnblockedTimestampMillis = 0;
        }

        ConnectivityMetricsEvent event = new ConnectivityMetricsEvent(timestamp, componentTag,
                eventTag, data);

        try {
            long result;
            if (skippedEventsEvent == null) {
                result = mService.logEvent(event);
            } else {
                result = mService.logEvents(new ConnectivityMetricsEvent[]
                        {skippedEventsEvent, event});
            }

            if (result == 0) {
                mNumSkippedEvents = 0;
            } else {
                mNumSkippedEvents++;
                if (result > 0) { // events are throttled
                    mServiceUnblockedTimestampMillis = result;
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error logging event", e);
        }
    }

    /**
@@ -157,33 +76,17 @@ public class ConnectivityMetricsLogger {
     * @return events
     */
    public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
        try {
            return mService.getEvents(reference);
        } catch (RemoteException e) {
            Log.e(TAG, "IConnectivityMetricsLogger.getEvents", e);
            return null;
        }
        return new ConnectivityMetricsEvent[0];
    }

    /**
     * Register PendingIntent which will be sent when new events are ready to be retrieved.
     */
    public boolean register(PendingIntent newEventsIntent) {
        try {
            return mService.register(newEventsIntent);
        } catch (RemoteException e) {
            Log.e(TAG, "IConnectivityMetricsLogger.register", e);
        return false;
    }
    }

    public boolean unregister(PendingIntent newEventsIntent) {
        try {
            mService.unregister(newEventsIntent);
            return true;
        } catch (RemoteException e) {
            Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
        return false;
    }
}
}
+0 −375
Original line number Diff line number Diff line
/*
 * 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 com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;

import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityMetricsEvent;
import android.net.ConnectivityMetricsLogger;
import android.net.IConnectivityMetricsLogger;
import android.os.Binder;
import android.os.Parcel;
import android.text.format.DateUtils;
import android.util.Log;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;

/** {@hide} */
public class MetricsLoggerService extends SystemService {
    private static String TAG = "ConnectivityMetricsLoggerService";
    private static final boolean DBG = true;
    private static final boolean VDBG = false;

    public MetricsLoggerService(Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        resetThrottlingCounters(System.currentTimeMillis());
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
            publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE,
                    mBinder);
        }
    }

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

    /**
     * Reference of the last event in the list of cached events.
     *
     * When client of this service retrieves events by calling getEvents, it is passing
     * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will
     * contain this reference. The client can save it and use next time it calls getEvents.
     * This way only new events will be returned.
     */
    private long mLastEventReference = 0;

    private final int mThrottlingCounters[] =
            new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS];

    private long mThrottlingIntervalBoundaryMillis;

    private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>();

    private void enforceConnectivityInternalPermission() {
        getContext().enforceCallingOrSelfPermission(
                android.Manifest.permission.CONNECTIVITY_INTERNAL,
                "MetricsLoggerService");
    }

    private void enforceDumpPermission() {
        getContext().enforceCallingOrSelfPermission(
                android.Manifest.permission.DUMP,
                "MetricsLoggerService");
    }

    private void resetThrottlingCounters(long currentTimeMillis) {
        synchronized (mThrottlingCounters) {
            for (int i = 0; i < mThrottlingCounters.length; i++) {
                mThrottlingCounters[i] = 0;
            }
            mThrottlingIntervalBoundaryMillis =
                    currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS;
        }
    }

    private void addEvent(ConnectivityMetricsEvent e) {
        if (VDBG) {
            Log.v(TAG, "writeEvent(" + e.toString() + ")");
        }

        while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
            mEvents.removeFirst();
        }

        mEvents.addLast(e);
    }

    @VisibleForTesting
    final MetricsLoggerImpl mBinder = new MetricsLoggerImpl();

    /**
     * Implementation of the IConnectivityMetricsLogger interface.
     */
    final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub {

        private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();

        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                    != PackageManager.PERMISSION_GRANTED) {
                pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " +
                        "from from pid=" + Binder.getCallingPid() + ", uid=" +
                        Binder.getCallingUid());
                return;
            }

            boolean dumpSerializedSize = false;
            boolean dumpEvents = false;
            boolean dumpDebugInfo = false;
            for (String arg : args) {
                switch (arg) {
                    case "--debug":
                        dumpDebugInfo = true;
                        break;

                    case "--events":
                        dumpEvents = true;
                        break;

                    case "--size":
                        dumpSerializedSize = true;
                        break;

                    case "--all":
                        dumpDebugInfo = true;
                        dumpEvents = true;
                        dumpSerializedSize = true;
                        break;
                }
            }

            synchronized (mEvents) {
                pw.println("Number of events: " + mEvents.size());
                pw.println("Counter: " + mEventCounter);
                if (mEvents.size() > 0) {
                    pw.println("Time span: " +
                            DateUtils.formatElapsedTime(
                                    (System.currentTimeMillis() - mEvents.peekFirst().timestamp)
                                            / 1000));
                }

                if (dumpSerializedSize) {
                    Parcel p = Parcel.obtain();
                    for (ConnectivityMetricsEvent e : mEvents) {
                        p.writeParcelable(e, 0);
                    }
                    pw.println("Serialized data size: " + p.dataSize());
                    p.recycle();
                }

                if (dumpEvents) {
                    pw.println();
                    pw.println("Events:");
                    for (ConnectivityMetricsEvent e : mEvents) {
                        pw.println(e.toString());
                    }
                }
            }

            if (dumpDebugInfo) {
                synchronized (mThrottlingCounters) {
                    pw.println();
                    for (int i = 0; i < ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS; i++) {
                        if (mThrottlingCounters[i] > 0) {
                            pw.println("Throttling Counter #" + i + ": " + mThrottlingCounters[i]);
                        }
                    }
                    pw.println("Throttling Time Remaining: " +
                            DateUtils.formatElapsedTime(
                                    (mThrottlingIntervalBoundaryMillis - System.currentTimeMillis())
                                            / 1000));
                }
            }

            synchronized (mPendingIntents) {
                if (!mPendingIntents.isEmpty()) {
                    pw.println();
                    pw.println("Pending intents:");
                    for (PendingIntent pi : mPendingIntents) {
                        pw.println(pi.toString());
                    }
                }
            }
        }

        public long logEvent(ConnectivityMetricsEvent event) {
            ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
            return logEvents(events);
        }

        /**
         * @param events
         *
         * Note: All events must belong to the same component.
         *
         * @return 0 on success
         *        <0 if error happened
         *        >0 timestamp after which new events will be accepted
         */
        public long logEvents(ConnectivityMetricsEvent[] events) {
            enforceConnectivityInternalPermission();

            if (events == null || events.length == 0) {
                Log.wtf(TAG, "No events passed to logEvents()");
                return -1;
            }

            int componentTag = events[0].componentTag;
            if (componentTag < 0 ||
                    componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) {
                Log.wtf(TAG, "Unexpected tag: " + componentTag);
                return -1;
            }

            synchronized (mThrottlingCounters) {
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) {
                    resetThrottlingCounters(currentTimeMillis);
                }

                mThrottlingCounters[componentTag] += events.length;

                if (mThrottlingCounters[componentTag] >
                        THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) {
                    Log.w(TAG, "Too many events from #" + componentTag +
                            ". Block until " + mThrottlingIntervalBoundaryMillis);

                    return mThrottlingIntervalBoundaryMillis;
                }
            }

            boolean sendPendingIntents = false;

            synchronized (mEvents) {
                for (ConnectivityMetricsEvent e : events) {
                    if (e.componentTag != componentTag) {
                        Log.wtf(TAG, "Unexpected tag: " + e.componentTag);
                        return -1;
                    }

                    addEvent(e);
                }

                mLastEventReference += events.length;

                mEventCounter += events.length;
                if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) {
                    mEventCounter = 0;
                    sendPendingIntents = true;
                }
            }

            if (sendPendingIntents) {
                synchronized (mPendingIntents) {
                    for (PendingIntent pi : mPendingIntents) {
                        if (VDBG) Log.v(TAG, "Send pending intent");
                        try {
                            pi.send(getContext(), 0, null, null, null);
                        } catch (PendingIntent.CanceledException e) {
                            Log.e(TAG, "Pending intent canceled: " + pi);
                            mPendingIntents.remove(pi);
                        }
                    }
                }
            }

            return 0;
        }

        /**
         * Retrieve events
         *
         * @param reference of the last event previously returned. The function will return
         *                  events following it.
         *                  If 0 then all events will be returned.
         *                  After the function call it will contain reference of the
         *                  last returned event.
         * @return events
         */
        public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
            enforceDumpPermission();
            long ref = reference.getValue();
            if (VDBG) Log.v(TAG, "getEvents(" + ref + ")");

            ConnectivityMetricsEvent[] result;
            synchronized (mEvents) {
                if (ref > mLastEventReference) {
                    Log.e(TAG, "Invalid reference");
                    reference.setValue(mLastEventReference);
                    return null;
                }
                if (ref < mLastEventReference - mEvents.size()) {
                    ref = mLastEventReference - mEvents.size();
                }

                int numEventsToSkip =
                        mEvents.size() // Total number of events
                        - (int)(mLastEventReference - ref); // Number of events to return

                result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip];
                int i = 0;
                for (ConnectivityMetricsEvent e : mEvents) {
                    if (numEventsToSkip > 0) {
                        numEventsToSkip--;
                    } else {
                        result[i++] = e;
                    }
                }

                reference.setValue(mLastEventReference);
            }

            return result;
        }

        public boolean register(PendingIntent newEventsIntent) {
            enforceDumpPermission();
            if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")");

            synchronized (mPendingIntents) {
                if (mPendingIntents.remove(newEventsIntent)) {
                    Log.w(TAG, "Replacing registered pending intent");
                }
                mPendingIntents.add(newEventsIntent);
            }

            return true;
        }

        public void unregister(PendingIntent newEventsIntent) {
            enforceDumpPermission();
            if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")");

            synchronized (mPendingIntents) {
                if (!mPendingIntents.remove(newEventsIntent)) {
                    Log.e(TAG, "Pending intent is not registered");
                }
            }
        }
    };
}
+0 −5
Original line number Diff line number Diff line
@@ -64,7 +64,6 @@ import com.android.server.audio.AudioService;
import com.android.server.camera.CameraService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MetricsLoggerService;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
@@ -829,10 +828,6 @@ public final class SystemServer {
                traceEnd();
            }

            traceBeginAndSlog("ConnectivityMetricsLoggerService");
            mSystemServiceManager.startService(MetricsLoggerService.class);
            traceEnd();

            traceBeginAndSlog("IpConnectivityMetrics");
            mSystemServiceManager.startService(IpConnectivityMetrics.class);
            traceEnd();
+0 −136
Original line number Diff line number Diff line
/*
 * 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;

import android.os.Bundle;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
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.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ConnectivityMetricsLoggerTest extends TestCase {

    // use same Parcel object everywhere for pointer equality
    static final Bundle FAKE_EV = new Bundle();
    static final int FAKE_COMPONENT = 1;
    static final int FAKE_EVENT = 2;

    @Mock IConnectivityMetricsLogger mService;
    ArgumentCaptor<ConnectivityMetricsEvent> evCaptor;
    ArgumentCaptor<ConnectivityMetricsEvent[]> evArrayCaptor;

    ConnectivityMetricsLogger mLog;

    public void setUp() {
        MockitoAnnotations.initMocks(this);
        evCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
        evArrayCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent[].class);
        mLog = new ConnectivityMetricsLogger(mService);
    }

    @SmallTest
    public void testLogEvents() throws Exception {
        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(3, FAKE_COMPONENT, FAKE_EVENT, 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));
    }

    @SmallTest
    public void testLogEventTriggerThrottling() throws Exception {
        when(mService.logEvent(any())).thenReturn(1234L);

        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);

        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
    }

    @SmallTest
    public void testLogEventFails() throws Exception {
        when(mService.logEvent(any())).thenReturn(-1L); // Error.

        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);

        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
    }

    @SmallTest
    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.
        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);

        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
    }

    @SmallTest
    public void testLogEventRecoverFromThrottling() throws Exception {
        final long throttleTimeout = System.currentTimeMillis() + 10;
        when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);

        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        mLog.logEvent(3, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
        Thread.sleep(100);
        mLog.logEvent(53, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);

        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
        assertEventsEqual(expectedEvent(1), gotEvents.get(0));

        verify(mService, times(1)).logEvents(evArrayCaptor.capture());
        ConnectivityMetricsEvent[] gotOtherEvents = evArrayCaptor.getAllValues().get(0);
        assertEquals(ConnectivityMetricsLogger.TAG_SKIPPED_EVENTS, gotOtherEvents[0].eventTag);
        assertEventsEqual(expectedEvent(53), gotOtherEvents[1]);
    }

    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
        verify(mService, times(n)).logEvent(evCaptor.capture());
        return evCaptor.getAllValues();
    }

    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
        return new ConnectivityMetricsEvent((long)timestamp, FAKE_COMPONENT, FAKE_EVENT, 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);
    }
}
+0 −188

File deleted.

Preview size limit exceeded, changes collapsed.