【Android】(八) 按时间段流量统计源码分析

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 中将最新的流量统计信息以增量的方式叠加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值