1 统计流程
//NetworkStats.java -> INetworkStatsSession.stub -> NetworkStatsCollection.java
startSummaryEnumeration() -> getSummaryForAllUid() -> getSummary()
startHistoryEnumeration() -> getHistoryIntervalForUid() -> getHistory()
startUserUidEnumeration() -> getHistoryIntervalForUid() -> getHistory()
NetworkStatsManager 中按照时间段统计网络流量的 API 中都是调用了 NetworkStats 中的以上三种函数。而这其中又调用了 INetworkStatsSession 中的以上两种函数。这两种函数分别又调用了 NetworkStatsCollection 中的函数。
大致可以看到,getSummary 和 getHistory 统计数据是根据 NetworkStatsCollection 中的 NetworkStatsHistory array 来累计的。
getSummary
public NetworkStats getSummary(NetworkTemplate template, long start, long end,
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
final long now = System.currentTimeMillis();
final NetworkStats stats = new NetworkStats(end - start, 24);
// shortcut when we know stats will be empty
if (start == end) return stats;
final NetworkStats.Entry entry = new NetworkStats.Entry();
NetworkStatsHistory.Entry historyEntry = null;
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
if (templateMatches(template, key.ident)
&& NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
&& key.set < NetworkStats.SET_DEBUG_START) {
final NetworkStatsHistory value = mStats.valueAt(i);
historyEntry = value.getValues(start, end, now, historyEntry);
entry.iface = IFACE_ALL;
entry.uid = key.uid;
entry.set = key.set;
entry.tag = key.tag;
entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ?
DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
entry.rxBytes = historyEntry.rxBytes;
entry.rxPackets = historyEntry.rxPackets;
entry.txBytes = historyEntry.txBytes;
entry.txPackets = historyEntry.txPackets;
entry.operations = historyEntry.operations;
if (!entry.isEmpty()) {
stats.combineValues(entry);
}
}
}
return stats;
}
getHistory(传入的 augmentPlan 是 null,所以我直接删掉对应的代码了)
public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
int uid, int set, int tag, int fields, long start, long end,
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
throw new SecurityException("Network stats history of uid " + uid
+ " is forbidden for caller " + callerUid);
}
// 180 days of history should be enough for anyone; if we end up needing
// more, we'll dynamically grow the history object.
final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0,
(180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration);
final NetworkStatsHistory combined = new NetworkStatsHistory(
mBucketDuration, bucketEstimate, fields);
// shortcut when we know stats will be empty
if (start == end) return combined;
long collectStart = start;
long collectEnd = end;
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
&& templateMatches(template, key.ident)) {
final NetworkStatsHistory value = mStats.valueAt(i);
combined.recordHistory(value, collectStart, collectEnd);
}
}
return combined;
}
2 统计数据来自何处
首先要看这个 Collection 内的数据从哪里获取。
//frameworks/base/services/core/java/com/android/server/net/NetworkStatsService.java
public void systemReady() {
synchronized (mStatsLock) {
mSystemReady = true;
// create data recorders along with historical rotators
mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
updatePersistThresholdsLocked();
// upgrade any legacy stats, migrating them to rotated files
maybeUpgradeLegacyStatsLocked();
// read historical network stats from disk, since policy service
// might need them right away.
mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked();
// bootstrap initial stats to prevent double-counting later
bootstrapStatsLocked();
}
//...
}
在 NetworkStatsService 中创建了四个 NetworkStatsRecorder,然后在 maybeUpgradeLegacyStatsLocked 导入了历史流量统计信息。
//frameworks/base/services/core/java/com/android/server/net/NetworkStatsService.java
private void maybeUpgradeLegacyStatsLocked() {
File file;
//...
file = new File(mSystemDir, "netstats_uid.bin");
if (file.exists()) {
mUidRecorder.importLegacyUidLocked(file);
mUidTagRecorder.importLegacyUidLocked(file);
file.delete();
}
}
//frameworks/base/services/core/java/com/android/server/net/NetworkStatsRecoder.java
public void importLegacyUidLocked(File file) throws IOException {
Objects.requireNonNull(mRotator, "missing FileRotator");
// legacy file still exists; start empty to avoid double importing
mRotator.deleteAll();
final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
collection.readLegacyUid(file, mOnlyTags);
final long startMillis = collection.getStartMillis();
final long endMillis = collection.getEndMillis();
if (!collection.isEmpty()) {
// process legacy data, creating active file at starting time, then
// using end time to possibly trigger rotation.
mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
mRotator.maybeRotate(endMillis);
}
}
根据 /data/system 目录下的 netstats_uid.bin 创建了一个临时 NetworkStatsCollection 来读取了历史网络统计信息,接着将 NetworkStatsCollection 作为 Rewriter 传递给 FileRotator,创建(更新)了 /data/system/netstats 目录下的统计文件,创建完 netstats_uid.bin 就会自动删除掉。
//frameworks/base/services/core/java/com/android/server/net/NetworkStatsService.java/INetworkStatsSession.stub
private NetworkStatsCollection getUidComplete() {
synchronized (mStatsLock) {
if (mUidComplete == null) {
mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
}
return mUidComplete;
}
}
//frameworks/base/services/core/java/com/android/server/net/NetworkStatsRecoder.java
// getOrLoadCompleteLocked -> loadLocked
private NetworkStatsCollection loadLocked(long start, long end) {
if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie);
final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
//...
mRotator.readMatching(res, start, end);
res.recordCollection(mPending);
return res;
}
在最开始提到的调用过程中,在 INetworkStatsSession 中首先要获取到特定的 NetworkStatsCollection,然后才能调用相应的 getHistory 或者 getSummary。
获取这个特定的 NetworkStatsCollection 的过程就是从上面 /data/system/netstats 目录下的统计文件中获取。(在 2.2以后 xt_qtaguid 都已经被废弃了,但
systemReady -> bootstrapStatsLocked -> recordSnapshotLocked -> mUidRecorder.recordSnapshotLocked
mUidRecorder.recordSnapshotLocked 用到参数 uidSnapshot。
recordSnapshotLocked -> getNetworkStatsUidDetail -> readNetworkStatsUidDetail。
这里创建的 uidSnapshot 还涉及到 tetherSnapshot,providerStats,providerStatas 可能有新的数据,来自OffloadController 和 BpfCoorDinator。)
知道了 SystemReady 时如何获取这个统计数据,接下来看如何在系统运行时实时更新这个数据。
frameworks/base/services/core/java/com/android/server/net/NetworkStatsService.java/systemReady.Method
// schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}.
final PendingIntent pollIntent =
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL),
PendingIntent.FLAG_IMMUTABLE);
final long currentRealtime = SystemClock.elapsedRealtime();
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
mSettings.getPollInterval(), pollIntent);
mContentResolver.registerContentObserver(Settings.Global
.getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED),
false /* notifyForDescendants */, mContentObserver);
注册了一个轮询定时器,定期执行performPoll -> performPollLocked ,代码如下
private void performPollLocked(int flags) {
if (!mSystemReady) return;
if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
performPollFromProvidersLocked();
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mClock.millis();
try {
recordSnapshotLocked(currentTime);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem reading network stats", e);
return;
} catch (RemoteException e) {
// ignored; service lives in system_server
return;
}
// persist any pending data depending on requested flags
Trace.traceBegin(TRACE_TAG_NETWORK, "[persisting]");
//...
if (persistNetwork) {
mDevRecorder.maybePersistLocked(currentTime);
mXtRecorder.maybePersistLocked(currentTime);
}
if (persistUid) {
mUidRecorder.maybePersistLocked(currentTime);
mUidTagRecorder.maybePersistLocked(currentTime);
}
Trace.traceEnd(TRACE_TAG_NETWORK);
if (mSettings.getSampleEnabled()) {
// sample stats after each full poll
performSampleLocked();
}
// finally, dispatch updated event to any listeners
mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
Trace.traceEnd(TRACE_TAG_NETWORK);
}
在 performPollFromProvidersLocked 中从 BpfCoordinator 以及 OffloaderController 中获取到最新的流量统计信息,然后通知到 NetworkStatsService 中进行更新,接着在 recordSnapshotLocked 获取到最新的流量统计信息的变化,然后添加到相应的 Recoder 中。最后在 Recorder 中将最新的流量统计信息以增量的方式叠加。