Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44ad17fa authored by Ivo Kay's avatar Ivo Kay Committed by Android (Google) Code Review
Browse files

Merge changes from topic "networkstats-since-boot" into main

* changes:
  Accumulate NetworkStats since boot in StatsPullAtomService
  Declare flag accumulate_network_stats_since_boot
parents 9a60b712 c299b2aa
Loading
Loading
Loading
Loading
+46 −7
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.accumulateNetworkStatsSinceBoot;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
@@ -229,6 +230,7 @@ import com.android.server.power.stats.KernelWakelockStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
import com.android.server.stats.pull.netstats.NetworkStatsExt;
import com.android.server.stats.pull.netstats.SubInfo;
import com.android.server.storage.DiskStatsFileLogger;
@@ -424,6 +426,14 @@ public class StatsPullAtomService extends SystemService {
    @GuardedBy("mDataBytesTransferLock")
    private final ArrayList<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();

    // Accumulates NetworkStats from initialization till the present moment.
    // It is necessary to accumulate stats internally, because NetworkStats persists data for a
    // limited amount of time, after which diff becomes incorrect without accumulation.
    @NonNull
    @GuardedBy("mDataBytesTransferLock")
    private final ArrayList<NetworkStatsAccumulator> mNetworkStatsAccumulators =
            new ArrayList<>();

    @GuardedBy("mDataBytesTransferLock")
    private long mLastNetworkStatsPollTime = -NETSTATS_POLL_RATE_LIMIT_MS;

@@ -1568,16 +1578,45 @@ public class StatsPullAtomService extends SystemService {
    private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
            @NonNull NetworkTemplate template, boolean includeTags) {
        final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
        final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
        final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
        final long currentTimeMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
        final long bootTimeMillis = currentTimeMillis - elapsedMillisSinceBoot;
        final long bucketDurationMillis = Settings.Global.getLong(mContext.getContentResolver(),
                NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);

        // Set startTime before boot so that NetworkStats includes at least one full bucket.
        // Set endTime in the future so that NetworkStats includes everything in the active bucket.
        final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
        final long endTime = currentTimeInMillis + bucketDuration;
        if (accumulateNetworkStatsSinceBoot()) {
            NetworkStatsAccumulator accumulator = CollectionUtils.find(
                    mNetworkStatsAccumulators, it -> it.hasEqualParameters(template, includeTags));
            if (accumulator == null) {
                accumulator = new NetworkStatsAccumulator(
                        template,
                        includeTags,
                        bucketDurationMillis,
                        bootTimeMillis - bucketDurationMillis);
                mNetworkStatsAccumulators.add(accumulator);
            }

            return accumulator.queryStats(currentTimeMillis,
                    (aTemplate, aIncludeTags, aStartTime, aEndTime) -> {
                        synchronized (mDataBytesTransferLock) {
                            return getUidNetworkStatsSnapshotForTemplateLocked(aTemplate,
                                    aIncludeTags, aStartTime, aEndTime);
                        }
                    });

        } else {
            // Set end time in the future to include all stats in the active bucket.
            return getUidNetworkStatsSnapshotForTemplateLocked(
                    template, includeTags,
                    bootTimeMillis - bucketDurationMillis,
                    currentTimeMillis + bucketDurationMillis);
        }
    }

        // NetworkStatsManager#forceUpdate updates stats for all networks
    @GuardedBy("mDataBytesTransferLock")
    @Nullable
    private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
            @NonNull NetworkTemplate template, boolean includeTags, long startTime, long endTime) {
        final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
        if (applyNetworkStatsPollRateLimit()) {
            // The new way: rate-limit force-polling for all NetworkStats queries
            if (elapsedMillisSinceBoot - mLastNetworkStatsPollTime >= NETSTATS_POLL_RATE_LIMIT_MS) {
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.server.stats.pull.netstats;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkStats;
import android.net.NetworkTemplate;

import java.util.Objects;

/**
 * A class that queries NetworkStats, accumulates query results, and exposes cumulative stats across
 * the time range covered by the queries. This class is not thread-safe.
 * <p>
 * This is class should be used when querying NetworkStats since boot, as NetworkStats persists data
 * only for a limited period of time.
 *
 * @hide
 */
public class NetworkStatsAccumulator {

    private final NetworkTemplate mTemplate;
    private final boolean mWithTags;
    private final long mBucketDurationMillis;
    private NetworkStats mSnapshot;
    private long mSnapshotEndTimeMillis;

    public NetworkStatsAccumulator(@NonNull NetworkTemplate template, boolean withTags,
            long bucketDurationMillis, long snapshotEndTimeMillis) {
        mTemplate = template;
        mWithTags = withTags;
        mBucketDurationMillis = bucketDurationMillis;
        mSnapshot = new NetworkStats(0, 1);
        mSnapshotEndTimeMillis = snapshotEndTimeMillis;
    }

    /**
     * Provides cumulative NetworkStats until given timestamp.
     * <p>
     * This method method may call {@code queryFunction} more than once, which includes maintaining
     * an internal cumulative stats snapshot and querying stats after the snapshot.
     */
    @Nullable
    public NetworkStats queryStats(long currentTimeMillis,
            @NonNull StatsQueryFunction queryFunction) {
        maybeExpandSnapshot(currentTimeMillis, queryFunction);
        return snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
    }

    /**
     * Returns true if the accumulator is using given query parameters.
     */
    public boolean hasEqualParameters(@NonNull NetworkTemplate template, boolean withTags) {
        return Objects.equals(mTemplate, template) && mWithTags == withTags;
    }

    /**
     * Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
     */
    private void maybeExpandSnapshot(long currentTimeMillis,
            @NonNull StatsQueryFunction queryFunction) {
        // Update snapshot only if it is possible to expand it by at least one full bucket, and only
        // if the new snapshot's end is not in the active bucket.
        long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
        if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
            NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
                    mSnapshotEndTimeMillis, newEndTimeMillis);
            if (extraStats != null) {
                mSnapshot = mSnapshot.add(extraStats);
                mSnapshotEndTimeMillis = newEndTimeMillis;
            }
        }
    }

    /**
     * Adds up stats in the internal cumulative snapshot and the stats that follow after it.
     */
    @Nullable
    private NetworkStats snapshotPlusFollowingStats(long currentTimeMillis,
            @NonNull StatsQueryFunction queryFunction) {
        // Set end time in the future to include all stats in the active bucket.
        NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
                mSnapshotEndTimeMillis, currentTimeMillis + mBucketDurationMillis);
        return extraStats != null ? mSnapshot.add(extraStats) : null;
    }

    @FunctionalInterface
    public interface StatsQueryFunction {
        /**
         * Returns network stats during the given time period.
         */
        @Nullable
        NetworkStats queryNetworkStats(@NonNull NetworkTemplate template, boolean includeTags,
                long startTime, long endTime);
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -30,3 +30,11 @@ flag {
    bug: "352495181"
    is_fixed_read_only: true
}

flag {
    name: "accumulate_network_stats_since_boot"
    namespace: "statsd"
    description: "Accumulate results of NetworkStats queries to avoid hitting NetworkStats persistence limit"
    bug: "352537247"
    is_fixed_read_only: true
}
+189 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.server.stats.pull.netstats

import android.net.NetworkStats
import android.net.NetworkStats.DEFAULT_NETWORK_YES
import android.net.NetworkStats.METERED_NO
import android.net.NetworkStats.ROAMING_NO
import android.net.NetworkStats.SET_DEFAULT
import android.net.NetworkStats.TAG_NONE
import android.net.NetworkTemplate
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.testutils.assertNetworkStatsEquals
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class NetworkStatsAccumulatorTest {

    @Test
    fun hasEqualParameters_differentParameters_returnsFalse() {
        val wifiTemplate = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
        val mobileTemplate = NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).build()

        val snapshot = NetworkStatsAccumulator(wifiTemplate, false, 0, 0)

        assertThat(snapshot.hasEqualParameters(mobileTemplate, false)).isFalse()
        assertThat(snapshot.hasEqualParameters(wifiTemplate, true)).isFalse()
    }

    @Test
    fun hasSameParameters_equalParameters_returnsTrue() {
        val wifiTemplate1 = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
        val wifiTemplate2 = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()

        val snapshot = NetworkStatsAccumulator(wifiTemplate1, false, 0, 0)

        assertThat(snapshot.hasEqualParameters(wifiTemplate1, false)).isTrue()
        assertThat(snapshot.hasEqualParameters(wifiTemplate2, false)).isTrue()
    }

    @Test
    fun queryStats_lessThanOneBucketFromSnapshotEndTime_returnsAllStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
        // Current time is less than one bucket away from snapshot end-point: 1050 - 1000 < 200
        val stats = snapshot.queryStats(1050, FakeStats(500, 1050, 1))!!

        // After the query at 1050, accumulator should have 1 * (1050 - 1000) = 50 bytes.
        assertNetworkStatsEquals(stats, networkStatsWithBytes(50))
    }

    @Test
    fun queryStats_oneBucketFromSnapshotEndTime_returnsCumulativeStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
        // Current time is one bucket away from snapshot end-point: 1250 - 1000 > 200
        val stats = snapshot.queryStats(1250, FakeStats(550, 1250, 2))!!

        // After the query at 1250, accumulator should have 2 * (1250 - 1000) = 500 bytes.
        assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
    }

    @Test
    fun queryStats_twoBucketsFromSnapshotEndTime_returnsCumulativeStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator has data until 1000 (= 0), and its end-point is in the history period.
        // Current time is two buckets away from snapshot end-point: 1450 - 1000 > 2*200
        val stats = snapshot.queryStats(1450, FakeStats(600, 1450, 3))!!

        // After the query at 1450, accumulator should have 3 * (1450 - 1000) = 1350 bytes.
        assertNetworkStatsEquals(stats, networkStatsWithBytes(1350))
    }

    @Test
    fun queryStats_manyBucketsFromSnapshotEndTime_returnsCumulativeStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
        // Current time is many buckets away from snapshot end-point
        val stats = snapshot.queryStats(6100, FakeStats(900, 6100, 1))!!

        // After the query at 6100, accumulator should have 1 * (6100 - 1000) = 5100 bytes.
        assertNetworkStatsEquals(stats, networkStatsWithBytes(5100))
    }

    @Test
    fun queryStats_multipleQueriesAndSameHistoryWindow_returnsCumulativeStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator is queried within the history period, whose starting point stays the same.
        // After each query, accumulator should contain bytes from the initial end-point until now.
        val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))!!
        val stats2 = snapshot.queryStats(10100, FakeStats(900, 10100, 1))!!
        val stats3 = snapshot.queryStats(15100, FakeStats(900, 15100, 1))!!

        assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
        assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
        assertNetworkStatsEquals(stats3, networkStatsWithBytes(14100))
    }

    @Test
    fun queryStats_multipleQueriesAndSlidingHistoryWindow_returnsCumulativeStats() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)

        // Accumulator is queried within the history period, whose starting point is moving.
        // After each query, accumulator should contain bytes from the initial end-point until now.
        val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))!!
        val stats2 = snapshot.queryStats(10100, FakeStats(4000, 10100, 1))!!
        val stats3 = snapshot.queryStats(15100, FakeStats(7000, 15100, 1))!!

        assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
        assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
        assertNetworkStatsEquals(stats3, networkStatsWithBytes(14100))
    }

    @Test
    fun queryStats_withSnapshotEndTimeBeforeHistoryStart_addsOnlyStatsWithinHistory() {
        val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1900)

        // Accumulator has data until 1000 (= 0), but its end-point is not in the history period.
        // After the query, accumulator should add only those bytes that are covered by the history.
        val stats = snapshot.queryStats(2700, FakeStats(2200, 2700, 1))!!

        assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
    }

    /**
     * Simulates equally distributed traffic stats persisted over a set period of time.
     */
    private class FakeStats(
        val historyStartMillis: Long, val currentTimeMillis: Long, val bytesPerMilli: Long
    ) : NetworkStatsAccumulator.StatsQueryFunction {

        override fun queryNetworkStats(
            template: NetworkTemplate, includeTags: Boolean, startTime: Long, endTime: Long
        ): NetworkStats {
            val overlap = overlap(startTime, endTime, historyStartMillis, currentTimeMillis)
            return networkStatsWithBytes(overlap * bytesPerMilli)
        }
    }

    companion object {

        private val TEMPLATE = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()

        fun networkStatsWithBytes(bytes: Long): NetworkStats {
            val stats = NetworkStats(0, 1).addEntry(
                NetworkStats.Entry(
                    null,
                    0,
                    SET_DEFAULT,
                    TAG_NONE,
                    METERED_NO,
                    ROAMING_NO,
                    DEFAULT_NETWORK_YES,
                    bytes,
                    bytes / 100,
                    bytes,
                    bytes / 100,
                    0
                )
            )
            return stats
        }

        fun overlap(aStart: Long, aEnd: Long, bStart: Long, bEnd: Long): Long {
            return maxOf(0L, minOf(aEnd, bEnd) - maxOf(aStart, bStart))
        }
    }
}
 No newline at end of file