Clamp non-monotonic stats instead of dropping.
When encountering non-monotonic stats rows, recover remaining data by
clamping to 0. In particular, this avoids edge-case where persisting
threshold checks would never trigger. Also recover when tethering
snapshots are missing.
Bug: 5600785, 5433871, 5600678
Change-Id: I1871954ce3955cc4ac8846f9841bae0066176ffe
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 3605652..f6e627c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -464,6 +464,19 @@
* time, and that none of them have disappeared.
*/
public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
+ return subtract(value, false);
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ *
+ * @param clampNonMonotonic When non-monotonic stats are found, just clamp
+ * to 0 instead of throwing {@link NonMonotonicException}.
+ */
+ public NetworkStats subtract(NetworkStats value, boolean clampNonMonotonic)
+ throws NonMonotonicException {
final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
if (deltaRealtime < 0) {
throw new NonMonotonicException(this, value);
@@ -497,7 +510,15 @@
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0 || entry.operations < 0) {
- throw new NonMonotonicException(this, i, value, j);
+ if (clampNonMonotonic) {
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
+ } else {
+ throw new NonMonotonicException(this, i, value, j);
+ }
}
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 77b0d96..6365525 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -806,9 +806,7 @@
final NetworkStats networkDevSnapshot;
try {
// collect any tethering stats
- final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs();
- final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
- tetheredIfacePairs);
+ final NetworkStats tetherSnapshot = getNetworkStatsTethering();
// record uid stats, folding in tethering stats
uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
@@ -1505,7 +1503,7 @@
NetworkStats before, NetworkStats current, boolean collectStale, String type) {
if (before != null) {
try {
- return current.subtract(before);
+ return current.subtract(before, false);
} catch (NonMonotonicException e) {
Log.w(TAG, "found non-monotonic values; saving to dropbox");
@@ -1517,8 +1515,13 @@
builder.append("right=").append(e.right).append('\n');
mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
- // return empty delta to avoid recording broken stats
- return new NetworkStats(0L, 10);
+ try {
+ // return clamped delta to help recover
+ return current.subtract(before, true);
+ } catch (NonMonotonicException e1) {
+ Log.wtf(TAG, "found non-monotonic values; returning empty delta", e1);
+ return new NetworkStats(0L, 10);
+ }
}
} else if (collectStale) {
// caller is okay collecting stale stats for first call.
@@ -1530,6 +1533,20 @@
}
}
+ /**
+ * Return snapshot of current tethering statistics. Will return empty
+ * {@link NetworkStats} if any problems are encountered.
+ */
+ private NetworkStats getNetworkStatsTethering() throws RemoteException {
+ try {
+ final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs();
+ return mNetworkManager.getNetworkStatsTethering(tetheredIfacePairs);
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem reading network stats", e);
+ return new NetworkStats(0L, 10);
+ }
+ }
+
private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) {
return uidSnapshot.groupedByIface();
}