diff options
author | Chiachang Wang <chiachangwang@google.com> | 2020-02-07 14:43:05 +0800 |
---|---|---|
committer | Chiachang Wang <chiachangwang@google.com> | 2020-02-12 10:44:38 +0800 |
commit | 85b49af029494009da6574cfa275758585da5301 (patch) | |
tree | 3063414761ecebb1539752cd5c2517fe3c2bf725 | |
parent | d5b9b267f6d0cfd2bbf10fcb4bc5ef57de7e3101 (diff) |
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
4 files changed, 81 insertions, 185 deletions
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java index e6036b5..31a408f 100644 --- a/src/com/android/networkstack/netlink/TcpInfo.java +++ b/src/com/android/networkstack/netlink/TcpInfo.java @@ -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) { - 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); - } + // 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."); } - mFieldsValues = Collections.unmodifiableMap(fields); + final int start = bytes.position(); + 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 + "}"; } } diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java index 78813bd..f660f81 100644 --- a/src/com/android/networkstack/netlink/TcpSocketTracker.java +++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java @@ -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; diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java index f65de9c..ddab8c7 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java @@ -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,40 +102,48 @@ 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); assertEquals(TcpInfo.getTcpStateName(1), TCP_ESTABLISHED); @@ -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<>(); diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java index a21e7cf..6a09f12 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java @@ -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); |