summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChiachang Wang <chiachangwang@google.com>2020-02-07 14:43:05 +0800
committerChiachang Wang <chiachangwang@google.com>2020-02-12 10:44:38 +0800
commit85b49af029494009da6574cfa275758585da5301 (patch)
tree3063414761ecebb1539752cd5c2517fe3c2bf725
parentd5b9b267f6d0cfd2bbf10fcb4bc5ef57de7e3101 (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
-rw-r--r--src/com/android/networkstack/netlink/TcpInfo.java132
-rw-r--r--src/com/android/networkstack/netlink/TcpSocketTracker.java16
-rw-r--r--tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java67
-rw-r--r--tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java51
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);