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

Commit c54962a4 authored by Hugo Benichi's avatar Hugo Benichi Committed by android-build-merger
Browse files

Merge "IP connectivity metrics: delete obsolete logger service"

am: 37e3f22c

Change-Id: Ib335bc5e19c2c703d23267bf2835fc89fcf5a957
parents db0803b4 37e3f22c
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
@@ -61,7 +61,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.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
import com.android.server.display.NightDisplayService;
@@ -662,10 +661,6 @@ public final class SystemServer {
                mSystemServiceManager.startService(BluetoothService.class);
            }

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

            traceBeginAndSlog("IpConnectivityMetrics");
            mSystemServiceManager.startService(IpConnectivityMetrics.class);
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+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.