/* * 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 long mTxByteCount; public long mRxByteCount; public long mTxPacketCount; public long mRxPacketCount; public long mTxPacketErrorCount; public long mRxPacketErrorCount; public long mTimestamp; } public static void getSamplingSnapshots(Map 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)) { try { SamplingSnapshot ss = new SamplingSnapshot(); ss.mTxByteCount = Long.parseLong(tokens[1]); ss.mTxPacketCount = Long.parseLong(tokens[2]); ss.mTxPacketErrorCount = Long.parseLong(tokens[3]); ss.mRxByteCount = Long.parseLong(tokens[9]); ss.mRxPacketCount = Long.parseLong(tokens[10]); ss.mRxPacketErrorCount = Long.parseLong(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); } catch (NumberFormatException e) { // just ignore this data point } } } 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 long getSampledTxByteCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampledTxPacketCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampledTxPacketErrorCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampledRxByteCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampledRxPacketCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampledPacketCount() { return getSampledPacketCount(mBeginningSample, mEndingSample); } public long getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) { if (begin != null && end != null) { long rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount; long txPacketCount = end.mTxPacketCount - begin.mTxPacketCount; return rxPacketCount + txPacketCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } public long getSampledPacketErrorCount() { if (mBeginningSample != null && mEndingSample != null) { long rxPacketErrorCount = getSampledRxPacketErrorCount(); long txPacketErrorCount = getSampledTxPacketErrorCount(); return rxPacketErrorCount + txPacketErrorCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } public long getSampledRxPacketErrorCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public long getSampleTimestamp() { synchronized(mSamplingDataLock) { if (mEndingSample != null) { return mEndingSample.mTimestamp; } else { return LinkQualityInfo.UNKNOWN_LONG; } } } public int getSampleDuration() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return (int) (mEndingSample.mTimestamp - mBeginningSample.mTimestamp); } else { return LinkQualityInfo.UNKNOWN_INT; } } } public void setCommonLinkQualityInfoFields(LinkQualityInfo li) { synchronized(mSamplingDataLock) { li.setLastDataSampleTime(getSampleTimestamp()); li.setDataSampleDuration(getSampleDuration()); li.setPacketCount(getSampledPacketCount()); li.setPacketErrorCount(getSampledPacketErrorCount()); } } }