summaryrefslogtreecommitdiff
path: root/core/java/android/net/SamplingDataTracker.java
diff options
context:
space:
mode:
authorVinit Deshapnde <vinitd@google.com>2013-08-21 13:09:01 -0700
committerVinit Deshapnde <vinitd@google.com>2013-08-21 13:09:01 -0700
commit1f12cb52a494a3eaefc62d03a8d2fdf47a5535e9 (patch)
treebcb2aaae049ed47fd49863289d582698ca87282f /core/java/android/net/SamplingDataTracker.java
parent2241d45c68739e5bdf187ba3325ee237ef143e21 (diff)
Introduce network link quality statistics
This change starts tracking traffic quality data for WiFi and mobile networks. The quality is tracked based on incidental traffic, and not on specific measurements. Theoretical bandwidths are hard-coded, as well as sampling interval; although sampling interval can be changed by setting a system policy. Bugs filed to remove shortcomings of this change - 10342372 Change LinkInfo name to something better 10342318 Move hardcoded values of MobileLinkInfo to resources so they can be updated without changing code Bug: 10006249 Change-Id: I83d8c7594da20fe53abbd5e1f909b1f606b035bb
Diffstat (limited to 'core/java/android/net/SamplingDataTracker.java')
-rw-r--r--core/java/android/net/SamplingDataTracker.java295
1 files changed, 295 insertions, 0 deletions
diff --git a/core/java/android/net/SamplingDataTracker.java b/core/java/android/net/SamplingDataTracker.java
new file mode 100644
index 000000000000..b5dc14081c85
--- /dev/null
+++ b/core/java/android/net/SamplingDataTracker.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class SamplingDataTracker
+{
+ private static final boolean DBG = false;
+ private static final String TAG = "SamplingDataTracker";
+
+ public static class SamplingSnapshot
+ {
+ public int mTxByteCount;
+ public int mRxByteCount;
+ public int mTxPacketCount;
+ public int mRxPacketCount;
+ public int mTxPacketErrorCount;
+ public int mRxPacketErrorCount;
+ public long mTimestamp;
+ }
+
+ public static void getSamplingSnapshots(Map<String, SamplingSnapshot> mapIfaceToSample) {
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader("/proc/net/dev"));
+
+ // Skip over the line bearing column titles (there are 2 lines)
+ String line;
+ reader.readLine();
+ reader.readLine();
+
+ while ((line = reader.readLine()) != null) {
+
+ // remove leading whitespace
+ line = line.trim();
+
+ String[] tokens = line.split("[ ]+");
+ if (tokens.length < 17) {
+ continue;
+ }
+
+ /* column format is
+ * Interface (Recv)bytes packets errs drop fifo frame compressed multicast \
+ * (Transmit)bytes packets errs drop fifo colls carrier compress
+ */
+
+ String currentIface = tokens[0].split(":")[0];
+ if (DBG) Slog.d(TAG, "Found data for interface " + currentIface);
+ if (mapIfaceToSample.containsKey(currentIface)) {
+
+ SamplingSnapshot ss = new SamplingSnapshot();
+
+ ss.mTxByteCount = Integer.parseInt(tokens[1]);
+ ss.mTxPacketCount = Integer.parseInt(tokens[2]);
+ ss.mTxPacketErrorCount = Integer.parseInt(tokens[3]);
+ ss.mRxByteCount = Integer.parseInt(tokens[9]);
+ ss.mRxPacketCount = Integer.parseInt(tokens[10]);
+ ss.mRxPacketErrorCount = Integer.parseInt(tokens[11]);
+
+ ss.mTimestamp = SystemClock.elapsedRealtime();
+
+ if (DBG) {
+ Slog.d(TAG, "Interface = " + currentIface);
+ Slog.d(TAG, "ByteCount = " + String.valueOf(ss.mTxByteCount));
+ Slog.d(TAG, "TxPacketCount = " + String.valueOf(ss.mTxPacketCount));
+ Slog.d(TAG, "TxPacketErrorCount = "
+ + String.valueOf(ss.mTxPacketErrorCount));
+ Slog.d(TAG, "RxByteCount = " + String.valueOf(ss.mRxByteCount));
+ Slog.d(TAG, "RxPacketCount = " + String.valueOf(ss.mRxPacketCount));
+ Slog.d(TAG, "RxPacketErrorCount = "
+ + String.valueOf(ss.mRxPacketErrorCount));
+ Slog.d(TAG, "Timestamp = " + String.valueOf(ss.mTimestamp));
+ Slog.d(TAG, "---------------------------");
+ }
+
+ mapIfaceToSample.put(currentIface, ss);
+ }
+ }
+
+ if (DBG) {
+ Iterator it = mapIfaceToSample.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry kvpair = (Map.Entry)it.next();
+ if (kvpair.getValue() == null) {
+ Slog.d(TAG, "could not find snapshot for interface " + kvpair.getKey());
+ }
+ }
+ }
+ } catch(FileNotFoundException e) {
+ Slog.e(TAG, "could not find /proc/net/dev");
+ } catch (IOException e) {
+ Slog.e(TAG, "could not read /proc/net/dev");
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "could not close /proc/net/dev");
+ }
+ }
+ }
+
+ // Snapshots from previous sampling interval
+ private SamplingSnapshot mBeginningSample;
+ private SamplingSnapshot mEndingSample;
+
+ // Starting snapshot of current interval
+ private SamplingSnapshot mLastSample;
+
+ // Protects sampling data from concurrent access
+ public final Object mSamplingDataLock = new Object();
+
+ // We need long enough time for a good sample
+ private final int MINIMUM_SAMPLING_INTERVAL = 15 * 1000;
+
+ // statistics is useless unless we have enough data
+ private final int MINIMUM_SAMPLED_PACKETS = 30;
+
+ public void startSampling(SamplingSnapshot s) {
+ synchronized(mSamplingDataLock) {
+ mLastSample = s;
+ }
+ }
+
+ public void stopSampling(SamplingSnapshot s) {
+ synchronized(mSamplingDataLock) {
+ if (mLastSample != null) {
+ if (s.mTimestamp - mLastSample.mTimestamp > MINIMUM_SAMPLING_INTERVAL
+ && getSampledPacketCount(mLastSample, s) > MINIMUM_SAMPLED_PACKETS) {
+ mBeginningSample = mLastSample;
+ mEndingSample = s;
+ mLastSample = null;
+ } else {
+ if (DBG) Slog.d(TAG, "Throwing current sample away because it is too small");
+ }
+ }
+ }
+ }
+
+ public void resetSamplingData() {
+ if (DBG) Slog.d(TAG, "Resetting sampled network data");
+ synchronized(mSamplingDataLock) {
+
+ // We could just take another sample here and treat it as an
+ // 'ending sample' effectively shortening sampling interval, but that
+ // requires extra work (specifically, reading the sample needs to be
+ // done asynchronously)
+
+ mLastSample = null;
+ }
+ }
+
+ public int getSampledTxByteCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampledTxPacketCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampledTxPacketErrorCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampledRxByteCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampledRxPacketCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampledPacketCount() {
+ return getSampledPacketCount(mBeginningSample, mEndingSample);
+ }
+
+ public int getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) {
+ if (begin != null && end != null) {
+ int rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount;
+ int txPacketCount = end.mTxPacketCount - begin.mTxPacketCount;
+ return rxPacketCount + txPacketCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+
+ public int getSampledPacketErrorCount() {
+ if (mBeginningSample != null && mEndingSample != null) {
+ int rxPacketErrorCount = getSampledRxPacketErrorCount();
+ int txPacketErrorCount = getSampledTxPacketErrorCount();
+ return rxPacketErrorCount + txPacketErrorCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+
+ public int getSampledRxPacketErrorCount() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public long getSampleTimestamp() {
+ synchronized(mSamplingDataLock) {
+ if (mEndingSample != null) {
+ return mEndingSample.mTimestamp;
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public int getSampleDuration() {
+ synchronized(mSamplingDataLock) {
+ if (mBeginningSample != null && mEndingSample != null) {
+ return (int) (mEndingSample.mTimestamp - mBeginningSample.mTimestamp);
+ } else {
+ return LinkInfo.UNKNOWN;
+ }
+ }
+ }
+
+ public void setCommonLinkInfoFields(LinkInfo li) {
+ synchronized(mSamplingDataLock) {
+ li.mLastDataSampleTime = getSampleTimestamp();
+ li.mDataSampleDuration = getSampleDuration();
+ li.mPacketCount = getSampledPacketCount();
+ li.mPacketErrorCount = getSampledPacketErrorCount();
+ }
+ }
+}
+