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

Commit 03db8e4b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I1a544a8d,Ie78d3fb0

* changes:
  IpConnectivityMetrics: rate limit ApfProgramEvents
  IpConnectivityMetrics reads buffer size in settings
parents 4e527816 e1c173d2
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -23,7 +23,8 @@ import android.net.ConnectivityMetricsEvent;
interface IIpConnectivityMetrics {

    /**
     * @return number of remaining available slots in buffer.
     * @return the number of remaining available slots in buffer,
     * or -1 if the event was dropped due to rate limiting.
     */
    int logEvent(in ConnectivityMetricsEvent event);
}
+7 −0
Original line number Diff line number Diff line
@@ -7232,6 +7232,13 @@ public final class Settings {
        */
       public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";

        /**
         * Size of the event buffer for IP connectivity metrics.
         * @hide
         */
        public static final String CONNECTIVITY_METRICS_BUFFER_SIZE =
              "connectivity_metrics_buffer_size";

       /** {@hide} */
       public static final String NETSTATS_ENABLED = "netstats_enabled";
       /** {@hide} */
+0 −1
Original line number Diff line number Diff line
@@ -177,4 +177,3 @@ public class TokenBucketTest extends TestCase {

    interface Fn { void call(); }
}
+46 −2
Original line number Diff line number Diff line
@@ -19,19 +19,25 @@ package com.android.server.connectivity;
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.IBinder;
import android.os.Parcelable;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.TokenBucket;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.function.ToIntFunction;

import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;

@@ -51,6 +57,10 @@ final public class IpConnectivityMetrics extends SystemService {

    // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
    private static final int DEFAULT_BUFFER_SIZE = 2000;
    // Maximum size of the event buffer.
    private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;

    private static final int ERROR_RATE_LIMITED = -1;

    // Lock ensuring that concurrent manipulations of the event buffer are correct.
    // There are three concurrent operations to synchronize:
@@ -69,12 +79,21 @@ final public class IpConnectivityMetrics extends SystemService {
    private int mDropped;
    @GuardedBy("mLock")
    private int mCapacity;
    @GuardedBy("mLock")
    private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();

    public IpConnectivityMetrics(Context ctx) {
    private final ToIntFunction<Context> mCapacityGetter;

    public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
        super(ctx);
        mCapacityGetter = capacityGetter;
        initBuffer();
    }

    public IpConnectivityMetrics(Context ctx) {
        this(ctx, READ_BUFFER_SIZE);
    }

    @Override
    public void onStart() {
        if (DBG) Log.d(TAG, "onStart");
@@ -93,7 +112,7 @@ final public class IpConnectivityMetrics extends SystemService {

    @VisibleForTesting
    public int bufferCapacity() {
        return DEFAULT_BUFFER_SIZE; // TODO: read from config
        return mCapacityGetter.applyAsInt(getContext());
    }

    private void initBuffer() {
@@ -111,6 +130,10 @@ final public class IpConnectivityMetrics extends SystemService {
            if (event == null) {
                return left;
            }
            if (isRateLimited(event)) {
                // Do not count as a dropped event. TODO: consider adding separate counter
                return ERROR_RATE_LIMITED;
            }
            if (left == 0) {
                mDropped++;
                return 0;
@@ -120,6 +143,11 @@ final public class IpConnectivityMetrics extends SystemService {
        }
    }

    private boolean isRateLimited(ConnectivityMetricsEvent event) {
        TokenBucket tb = mBuckets.get(event.data.getClass());
        return (tb != null) && !tb.get();
    }

    private String flushEncodedOutput() {
        final ArrayList<ConnectivityMetricsEvent> events;
        final int dropped;
@@ -236,4 +264,20 @@ final public class IpConnectivityMetrics extends SystemService {
            getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
        }
    };

    private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
        int size = Settings.Global.getInt(ctx.getContentResolver(),
                Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
        if (size <= 0) {
            return DEFAULT_BUFFER_SIZE;
        }
        return Math.min(size, MAXIMUM_BUFFER_SIZE);
    };

    private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
        ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
        // one token every minute, 50 tokens max: burst of ~50 events every hour.
        map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
        return map;
    }
}
+23 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.connectivity;
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
@@ -57,7 +58,7 @@ public class IpConnectivityMetricsTest extends TestCase {

    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mService = new IpConnectivityMetrics(mCtx);
        mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
    }

    public void testLoggingEvents() throws Exception {
@@ -112,6 +113,27 @@ public class IpConnectivityMetricsTest extends TestCase {
        assertEquals("", output3);
    }

    public void testRateLimiting() {
        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
        final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
        final long fakeTimestamp = 1;

        int attempt = 100; // More than burst quota, but less than buffer size.
        for (int i = 0; i < attempt; i++) {
            logger.log(ev);
        }

        String output1 = getdump("flush");
        assertFalse("".equals(output1));

        for (int i = 0; i < attempt; i++) {
            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
        }

        String output2 = getdump("flush");
        assertEquals("", output2);
    }

    public void testEndToEndLogging() {
        IpConnectivityLog logger = new IpConnectivityLog(mService.impl);