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

Commit 67c5e03b authored by Hugo Benichi's avatar Hugo Benichi
Browse files

Extract RingBuffer class from NetdEventListenerService

This patch takes out the ring buffer array added for NFLOG wakeup packet
events logging and extract it into its own class for reuse. This new
RingBuffer class has the two minimal useful functions append() and
toArray().

Bug: 65164242
Bug: 65700460
Test: runtest frameworks-net, with new unit test
Change-Id: Ib94d79a93f4e99661b7d0fac67117b91d57af980
parent b0510407
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.internal.util;

import static com.android.internal.util.Preconditions.checkArgumentPositive;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * A simple ring buffer structure with bounded capacity backed by an array.
 * Events can always be added at the logical end of the buffer. If the buffer is
 * full, oldest events are dropped when new events are added.
 * {@hide}
 */
public class RingBuffer<T> {

    // Array for storing events.
    private final T[] mBuffer;
    // Cursor keeping track of the logical end of the array. This cursor never
    // wraps and instead keeps track of the total number of append() operations.
    private long mCursor = 0;

    public RingBuffer(Class<T> c, int capacity) {
        checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
        // Java cannot create generic arrays without a runtime hint.
        mBuffer = (T[]) Array.newInstance(c, capacity);
    }

    public int size() {
        return (int) Math.min(mBuffer.length, (long) mCursor);
    }

    public void append(T t) {
        mBuffer[indexOf(mCursor++)] = t;
    }

    public T[] toArray() {
        // Only generic way to create a T[] from another T[]
        T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
        // Reverse iteration from youngest event to oldest event.
        long inCursor = mCursor - 1;
        int outIdx = out.length - 1;
        while (outIdx >= 0) {
            out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
        }
        return out;
    }

    private int indexOf(long cursor) {
        return (int) Math.abs(cursor % mBuffer.length);
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -44,7 +44,11 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.ToIntFunction;

/** {@hide} */
/**
 * Event buffering service for core networking and connectivity metrics.
 *
 * {@hide}
 */
final public class IpConnectivityMetrics extends SystemService {
    private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
    private static final boolean DBG = false;
+5 −24
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
@@ -82,9 +83,8 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
    // Ring buffer array for storing packet wake up events sent by Netd.
    @GuardedBy("this")
    private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
    @GuardedBy("this")
    private long mWakeupEventCursor = 0;
    private final RingBuffer<WakeupEvent> mWakeupEvents =
            new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);

    private final ConnectivityManager mCm;

@@ -175,13 +175,11 @@ public class NetdEventListenerService extends INetdEventListener.Stub {

    @GuardedBy("this")
    private void addWakeupEvent(String iface, long timestampMs, int uid) {
        int index = wakeupEventIndex(mWakeupEventCursor);
        mWakeupEventCursor++;
        WakeupEvent event = new WakeupEvent();
        event.iface = iface;
        event.timestampMs = timestampMs;
        event.uid = uid;
        mWakeupEvents[index] = event;
        mWakeupEvents.append(event);
        WakeupStats stats = mWakeupStats.get(iface);
        if (stats == null) {
            stats = new WakeupStats(iface);
@@ -190,23 +188,6 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
        stats.countEvent(event);
    }

    @GuardedBy("this")
    private WakeupEvent[] getWakeupEvents() {
        int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
        WakeupEvent[] out = new WakeupEvent[length];
        // Reverse iteration from youngest event to oldest event.
        long inCursor = mWakeupEventCursor - 1;
        int outIdx = out.length - 1;
        while (outIdx >= 0) {
            out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
        }
        return out;
    }

    private static int wakeupEventIndex(long cursor) {
        return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
    }

    public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
        flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
        flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
@@ -230,7 +211,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
        for (int i = 0; i < mWakeupStats.size(); i++) {
            pw.println(mWakeupStats.valueAt(i));
        }
        for (WakeupEvent wakeup : getWakeupEvents()) {
        for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
            pw.println(wakeup);
        }
    }
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.internal.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Objects;


@SmallTest
@RunWith(AndroidJUnit4.class)
public class RingBufferTest {

    @Test
    public void testEmptyRingBuffer() {
        RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);

        assertArraysEqual(new String[0], buffer.toArray());
    }

    @Test
    public void testIncorrectConstructorArguments() {
        try {
            RingBuffer<String> buffer = new RingBuffer<>(String.class, -10);
            fail("Should not be able to create a negative capacity RingBuffer");
        } catch (IllegalArgumentException expected) {
        }

        try {
            RingBuffer<String> buffer = new RingBuffer<>(String.class, 0);
            fail("Should not be able to create a 0 capacity RingBuffer");
        } catch (IllegalArgumentException expected) {
        }
    }

    @Test
    public void testRingBufferWithNoWrapping() {
        RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);

        buffer.append("a");
        buffer.append("b");
        buffer.append("c");
        buffer.append("d");
        buffer.append("e");

        String[] expected = {"a", "b", "c", "d", "e"};
        assertArraysEqual(expected, buffer.toArray());
    }

    @Test
    public void testRingBufferWithCapacity1() {
        RingBuffer<String> buffer = new RingBuffer<>(String.class, 1);

        buffer.append("a");
        assertArraysEqual(new String[]{"a"}, buffer.toArray());

        buffer.append("b");
        assertArraysEqual(new String[]{"b"}, buffer.toArray());

        buffer.append("c");
        assertArraysEqual(new String[]{"c"}, buffer.toArray());

        buffer.append("d");
        assertArraysEqual(new String[]{"d"}, buffer.toArray());

        buffer.append("e");
        assertArraysEqual(new String[]{"e"}, buffer.toArray());
    }

    @Test
    public void testRingBufferWithWrapping() {
        int capacity = 100;
        RingBuffer<String> buffer = new RingBuffer<>(String.class, capacity);

        buffer.append("a");
        buffer.append("b");
        buffer.append("c");
        buffer.append("d");
        buffer.append("e");

        String[] expected1 = {"a", "b", "c", "d", "e"};
        assertArraysEqual(expected1, buffer.toArray());

        String[] expected2 = new String[capacity];
        int firstIndex = 0;
        int lastIndex = capacity - 1;

        expected2[firstIndex] = "e";
        for (int i = 1; i < capacity; i++) {
            buffer.append("x");
            expected2[i] = "x";
        }
        assertArraysEqual(expected2, buffer.toArray());

        buffer.append("x");
        expected2[firstIndex] = "x";
        assertArraysEqual(expected2, buffer.toArray());

        for (int i = 0; i < 10; i++) {
            for (String s : expected2) {
                buffer.append(s);
            }
        }
        assertArraysEqual(expected2, buffer.toArray());

        buffer.append("a");
        expected2[lastIndex] = "a";
        assertArraysEqual(expected2, buffer.toArray());
    }

    static <T> void assertArraysEqual(T[] expected, T[] got) {
        if (expected.length != got.length) {
            fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
                    + " did not have the same length");
        }

        for (int i = 0; i < expected.length; i++) {
            if (!Objects.equals(expected[i], got[i])) {
                fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
                        + " were not equal");
            }
        }
    }
}