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

Commit 85b49af0 authored by Chiachang Wang's avatar Chiachang Wang
Browse files

update structure of TcpInfo

The TCP infoamtion will be kept for evaluating data stall. The
memoery usage inside TST will increase based on the number of
TCP sockets exist in the device. For the evaluation, TST needs
only 4 fields from TcpInfo. The others are redundant currently.
Thus, keep only necessary field inside TcpInfo to reduce the
memory usgae.

Bug: 148115807
Test: atest NetworkStackTests NetworkStackNextTests
Test: manual test to check memory change
Change-Id: I35275f3d77bbf1e076f2fd327a961278fe038b63
parent d5b9b267
Loading
Loading
Loading
Loading
+43 −89
Original line number Diff line number Diff line
@@ -22,11 +22,9 @@ import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
@@ -91,27 +89,39 @@ public class TcpInfo {
    }

    private static final String TAG = "TcpInfo";
    private final Map<Field, Number> mFieldsValues;
    @VisibleForTesting
    static final int LOST_OFFSET = getFieldOffset(Field.LOST);
    @VisibleForTesting
    static final int RETRANSMITS_OFFSET = getFieldOffset(Field.RETRANSMITS);
    @VisibleForTesting
    static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN);
    @VisibleForTesting
    static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT);
    final int mSegsIn;
    final int mSegsOut;
    final int mLost;
    final int mRetransmits;

    private static int getFieldOffset(@NonNull final Field needle) {
        int offset = 0;
        for (final Field field : Field.values()) {
            if (field == needle) return offset;
            offset += field.size;
        }
        throw new IllegalArgumentException("Unknown field");
    }

    private TcpInfo(@NonNull ByteBuffer bytes, int infolen) {
        // SEGS_IN is the last required field in the buffer, so if the buffer is long enough for
        // SEGS_IN it's long enough for everything
        if (SEGS_IN_OFFSET + Field.SEGS_IN.size > infolen) {
            throw new IllegalArgumentException("Length " + infolen + " is less than required.");
        }
        final int start = bytes.position();
        final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>();
        for (final Field field : Field.values()) {
            switch (field.size) {
                case Byte.BYTES:
                    fields.put(field, getByte(bytes, start, infolen));
                    break;
                case Integer.BYTES:
                    fields.put(field, getInt(bytes, start, infolen));
                    break;
                case Long.BYTES:
                    fields.put(field, getLong(bytes, start, infolen));
                    break;
                default:
                    Log.e(TAG, "Unexpected size:" + field.size);
            }
        }
        mFieldsValues = Collections.unmodifiableMap(fields);
        mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET);
        mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET);
        mLost = bytes.getInt(start + LOST_OFFSET);
        mRetransmits = bytes.get(start + RETRANSMITS_OFFSET);
        // tcp_info structure grows over time as new fields are added. Jump to the end of the
        // structure, as unknown fields might remain at the end of the structure if the tcp_info
        // struct was expanded.
@@ -119,12 +129,11 @@ public class TcpInfo {
    }

    @VisibleForTesting
    TcpInfo(@NonNull Map<Field, Number> info) {
        final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>();
        for (final Field field : Field.values()) {
            fields.put(field, info.get(field));
        }
        mFieldsValues = Collections.unmodifiableMap(fields);
    TcpInfo(int retransmits, int lost, int segsOut, int segsIn) {
        mRetransmits = retransmits;
        mLost = lost;
        mSegsOut = segsOut;
        mSegsIn = segsIn;
    }

    /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */
@@ -132,53 +141,13 @@ public class TcpInfo {
    public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) {
        try {
            return new TcpInfo(bytes, infolen);
        } catch (BufferUnderflowException | IllegalArgumentException e) {
        } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException
                | IndexOutOfBoundsException e) {
            Log.e(TAG, "parsing error.", e);
            return null;
        }
    }

    /**
     * Helper function for handling different struct tcp_info versions in the kernel.
     */
    private static boolean isValidTargetPosition(int start, int len, int pos, int targetBytes)
            throws IllegalArgumentException {
        // Equivalent to new Range(start, start + len).contains(new Range(pos, pos + targetBytes))
        if (len < 0 || targetBytes < 0) throw new IllegalArgumentException();
        // Check that start < pos < start + len
        if (pos < start || pos > start + len) return false;
        // Pos is inside the range and targetBytes is positive. Offset is valid if end of 2nd range
        // is below end of 1st range.
        return pos + targetBytes <= start + len;
    }

    /** Get value for specific key. */
    @Nullable
    public Number getValue(@NonNull Field key) {
        return mFieldsValues.get(key);
    }

    @Nullable
    private static Byte getByte(@NonNull ByteBuffer buffer, int start, int len) {
        if (!isValidTargetPosition(start, len, buffer.position(), Byte.BYTES)) return null;

        return buffer.get();
    }

    @Nullable
    private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) {
        if (!isValidTargetPosition(start, len, buffer.position(), Integer.BYTES)) return null;

        return buffer.getInt();
    }

    @Nullable
    private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) {
        if (!isValidTargetPosition(start, len, buffer.position(), Long.BYTES)) return null;

        return buffer.getLong();
    }

    private static String decodeWscale(byte num) {
        return String.valueOf((num >> 4) & 0x0f)  + ":" + String.valueOf(num & 0x0f);
    }
@@ -210,33 +179,18 @@ public class TcpInfo {
        if (!(obj instanceof TcpInfo)) return false;
        TcpInfo other = (TcpInfo) obj;

        for (final Field key : mFieldsValues.keySet()) {
            if (!Objects.equals(mFieldsValues.get(key), other.mFieldsValues.get(key))) {
                return false;
            }
        }
        return true;
        return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut
            && mRetransmits == other.mRetransmits && mLost == other.mLost;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mFieldsValues.values().toArray());
        return Objects.hash(mLost, mRetransmits, mSegsIn, mSegsOut);
    }

    @Override
    public String toString() {
        String str = "TcpInfo{ ";
        for (final Field key : mFieldsValues.keySet()) {
            str += key.name().toLowerCase() + "=";
            if (key == Field.STATE) {
                str += getTcpStateName(mFieldsValues.get(key).intValue()) + " ";
            } else if (key == Field.WSCALE) {
                str += decodeWscale(mFieldsValues.get(key).byteValue()) + " ";
            } else {
                str += mFieldsValues.get(key) + " ";
            }
        }
        str += "}";
        return str;
        return "TcpInfo{lost=" + mLost + ", retransmit=" + mRetransmits + ", received=" + mSegsIn
                + ", sent=" + mSegsOut + "}";
    }
}
+8 −8
Original line number Diff line number Diff line
@@ -340,16 +340,16 @@ public class TcpSocketTracker {
            return null;
        }

        stat.sentCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue();
        stat.receivedCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue();
        stat.lostCount = current.tcpInfo.getValue(TcpInfo.Field.LOST).intValue();
        stat.retransmitCount = current.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue();
        stat.sentCount = current.tcpInfo.mSegsOut;
        stat.receivedCount = current.tcpInfo.mSegsIn;
        stat.lostCount = current.tcpInfo.mLost;
        stat.retransmitCount = current.tcpInfo.mRetransmits;

        if (previous != null && previous.tcpInfo != null) {
            stat.sentCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue();
            stat.receivedCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue();
            stat.lostCount -= previous.tcpInfo.getValue(TcpInfo.Field.LOST).intValue();
            stat.retransmitCount -= previous.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue();
            stat.sentCount -= previous.tcpInfo.mSegsOut;
            stat.receivedCount -= previous.tcpInfo.mSegsIn;
            stat.lostCount -= previous.tcpInfo.mLost;
            stat.retransmitCount -= previous.tcpInfo.mRetransmits;
        }

        return stat;
+26 −41
Original line number Diff line number Diff line
@@ -89,6 +89,8 @@ public class TcpInfoTest {
            "0000000000000000";   // sndBufLimited = 0
    private static final byte[] TCP_INFO_BYTES =
            HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false);
    private static final TcpInfo TEST_TCPINFO =
            new TcpInfo(0 /* retransmits */, 0 /* lost */, 2 /* segsOut */, 1 /* segsIn */);

    private static final String EXPANDED_TCP_INFO_HEX = TCP_INFO_HEX
            + "00000000"         // tcpi_delivered
@@ -100,39 +102,47 @@ public class TcpInfoTest {
    @Test
    public void testParseTcpInfo() {
        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
        // Length is less than required
        final TcpInfo nullInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
        assertEquals(nullInfo, null);

        final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfo, TEST_TCPINFO);

        // Make a data that TcpInfo is not started from the begining of the buffer.
        final ByteBuffer bufferWithHeader =
                ByteBuffer.allocate(EXPANDED_TCP_INFO_BYTES.length + TCP_INFO_BYTES.length);
        bufferWithHeader.put(EXPANDED_TCP_INFO_BYTES);
        bufferWithHeader.put(TCP_INFO_BYTES);
        final TcpInfo infoWithHeader = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        bufferWithHeader.position(EXPANDED_TCP_INFO_BYTES.length);
        assertEquals(parsedInfo, TEST_TCPINFO);
    }

        assertEquals(parsedInfo, new TcpInfo(expected));
    @Test
    public void testFieldOffset() {
        assertEquals(TcpInfo.RETRANSMITS_OFFSET, 2);
        assertEquals(TcpInfo.LOST_OFFSET, 32);
        assertEquals(TcpInfo.SEGS_OUT_OFFSET, 136);
        assertEquals(TcpInfo.SEGS_IN_OFFSET, 140);
    }

    @Test
    public void testParseTcpInfoExpanded() {
        final ByteBuffer buffer = ByteBuffer.wrap(EXPANDED_TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
        final TcpInfo parsedInfo =
                TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1 + EXPANDED_TCP_INFO_LENGTH);

        assertEquals(parsedInfo, new TcpInfo(expected));
        assertEquals(parsedInfo, TEST_TCPINFO);
        assertEquals(buffer.limit(), buffer.position());

        // reset the index.
        buffer.position(0);
        final TcpInfo parsedInfoShorterLen = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfoShorterLen, new TcpInfo(expected));
        assertEquals(parsedInfoShorterLen, TEST_TCPINFO);
        assertEquals(TCP_INFO_LENGTH_V1, buffer.position());
    }

    @Test
    public void testValidOffset() {
        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);

        final Map<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();
        final TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);

        assertEquals(parsedInfo, new TcpInfo(expected));
    }

    @Test
    public void testTcpStateName() {
        assertEquals(TcpInfo.getTcpStateName(4), TCP_FIN_WAIT1);
@@ -156,39 +166,14 @@ public class TcpInfoTest {
    @Test
    public void testMalformedTcpInfo() {
        final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();

        TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
        assertEquals(parsedInfo, new TcpInfo(expected));
        assertEquals(parsedInfo, null);

        parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfo, null);
    }

    @Test
    public void testGetValue() {
        ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);

        final Map<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();
        expected.put(TcpInfo.Field.MAX_PACING_RATE, 10_000L);
        expected.put(TcpInfo.Field.FACKETS, 10);

        final TcpInfo expectedInfo = new TcpInfo(expected);
        assertEquals((byte) 0x01, expectedInfo.getValue(TcpInfo.Field.STATE));
        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.CASTATE));
        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.RETRANSMITS));
        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.PROBES));
        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.BACKOFF));
        assertEquals((byte) 0x07, expectedInfo.getValue(TcpInfo.Field.OPTIONS));
        assertEquals((byte) 0x88, expectedInfo.getValue(TcpInfo.Field.WSCALE));
        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED));

        assertEquals(10_000L, expectedInfo.getValue(TcpInfo.Field.MAX_PACING_RATE));
        assertEquals(10, expectedInfo.getValue(TcpInfo.Field.FACKETS));
        assertEquals(null, expectedInfo.getValue(TcpInfo.Field.RTT));

    }

    // Make a TcpInfo contains only first 8 bytes.
    private Map<TcpInfo.Field, Number> makeShortTestTcpInfoHash() {
        final Map<TcpInfo.Field, Number> info = new LinkedHashMap<>();
+4 −47
Original line number Diff line number Diff line
@@ -61,7 +61,6 @@ import org.mockito.quality.Strictness;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;

// TODO: Add more tests for missing coverage.
@RunWith(AndroidJUnit4.class)
@@ -174,6 +173,8 @@ public class TcpSocketTrackerTest {
            "0000000000000000"; // deliverRate = 0
    private static final byte[] SOCK_DIAG_TCP_INET_BYTES =
            HexEncoding.decode(SOCK_DIAG_TCP_INET_HEX.toCharArray(), false);
    private static final TcpInfo TEST_TCPINFO =
            new TcpInfo(5 /* retransmits */, 0 /* lost */, 10 /* segsOut */, 0 /* segsIn */);

    private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_INET_HEX
            // struct nlmsghdr
@@ -253,52 +254,8 @@ public class TcpSocketTrackerTest {
        buffer.position(SOCKDIAG_MSG_HEADER_SIZE);
        final TcpSocketTracker.SocketInfo parsed =
                tst.parseSockInfo(buffer, AF_INET, 276, 100L);
        final HashMap<TcpInfo.Field, Number> expected = new HashMap<>();
        expected.put(TcpInfo.Field.STATE, (byte) 0x01);
        expected.put(TcpInfo.Field.CASTATE, (byte) 0x00);
        expected.put(TcpInfo.Field.RETRANSMITS, (byte) 0x05);
        expected.put(TcpInfo.Field.PROBES, (byte) 0x00);
        expected.put(TcpInfo.Field.BACKOFF, (byte) 0x00);
        expected.put(TcpInfo.Field.OPTIONS, (byte) 0x07);
        expected.put(TcpInfo.Field.WSCALE, (byte) 0x88);
        expected.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00);
        expected.put(TcpInfo.Field.RTO, 1806666);
        expected.put(TcpInfo.Field.ATO, 0);
        expected.put(TcpInfo.Field.SND_MSS, 1326);
        expected.put(TcpInfo.Field.RCV_MSS, 536);
        expected.put(TcpInfo.Field.UNACKED, 0);
        expected.put(TcpInfo.Field.SACKED, 0);
        expected.put(TcpInfo.Field.LOST, 0);
        expected.put(TcpInfo.Field.RETRANS, 0);
        expected.put(TcpInfo.Field.FACKETS, 0);
        expected.put(TcpInfo.Field.LAST_DATA_SENT, 187);
        expected.put(TcpInfo.Field.LAST_ACK_SENT, 0);
        expected.put(TcpInfo.Field.LAST_DATA_RECV, 187);
        expected.put(TcpInfo.Field.LAST_ACK_RECV, 187);
        expected.put(TcpInfo.Field.PMTU, 1500);
        expected.put(TcpInfo.Field.RCV_SSTHRESH, 87600);
        expected.put(TcpInfo.Field.RTT, 601150);
        expected.put(TcpInfo.Field.RTTVAR, 300575);
        expected.put(TcpInfo.Field.SND_SSTHRESH, 1400);
        expected.put(TcpInfo.Field.SND_CWND, 10);
        expected.put(TcpInfo.Field.ADVMSS, 1448);
        expected.put(TcpInfo.Field.REORDERING, 3);
        expected.put(TcpInfo.Field.RCV_RTT, 0);
        expected.put(TcpInfo.Field.RCV_SPACE, 87600);
        expected.put(TcpInfo.Field.TOTAL_RETRANS, 0);
        expected.put(TcpInfo.Field.PACING_RATE, 44115L);
        expected.put(TcpInfo.Field.MAX_PACING_RATE, -1L);
        expected.put(TcpInfo.Field.BYTES_ACKED, 1L);
        expected.put(TcpInfo.Field.BYTES_RECEIVED, 0L);
        expected.put(TcpInfo.Field.SEGS_OUT, 10);
        expected.put(TcpInfo.Field.SEGS_IN, 0);
        expected.put(TcpInfo.Field.NOTSENT_BYTES, 0);
        expected.put(TcpInfo.Field.MIN_RTT, 601150);
        expected.put(TcpInfo.Field.DATA_SEGS_IN, 0);
        expected.put(TcpInfo.Field.DATA_SEGS_OUT, 0);
        expected.put(TcpInfo.Field.DELIVERY_RATE, 0L);

        assertEquals(parsed.tcpInfo, new TcpInfo(expected));

        assertEquals(parsed.tcpInfo, TEST_TCPINFO);
        assertEquals(parsed.fwmark, 789125);
        assertEquals(parsed.updateTime, 100);
        assertEquals(parsed.ipFamily, AF_INET);