diff --git a/service-t/src/com/android/metrics/NetworkStatsMetricsLogger.java b/service-t/src/com/android/metrics/NetworkStatsMetricsLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..2cf49ba5b0b9e20ecc2b2fc17446fd6f0a641214 --- /dev/null +++ b/service-t/src/com/android/metrics/NetworkStatsMetricsLogger.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 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.metrics; + +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT; + +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__FAST_DATA_INPUT_STATE__FDIS_DISABLED; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__OPERATION_TYPE__ROT_READ; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UID; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UIDTAG; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UNKNOWN; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_XT; + +import android.annotation.NonNull; +import android.net.NetworkStatsCollection; +import android.net.NetworkStatsHistory; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ConnectivityStatsLog; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Helper class to log NetworkStats related metrics. + * + * This class does not provide thread-safe. + */ +public class NetworkStatsMetricsLogger { + final Dependencies mDeps; + int mReadIndex = 1; + + /** Dependency class */ + @VisibleForTesting + public static class Dependencies { + /** + * Writes a NETWORK_STATS_RECORDER_FILE_OPERATION_REPORTED event to ConnectivityStatsLog. + */ + public void writeRecorderFileReadingStats(int recorderType, int readIndex, + int readLatencyMillis, + int fileCount, int totalFileSize, + int keys, int uids, int totalHistorySize) { + ConnectivityStatsLog.write(NETWORK_STATS_RECORDER_FILE_OPERATED, + NETWORK_STATS_RECORDER_FILE_OPERATED__OPERATION_TYPE__ROT_READ, + recorderType, + readIndex, + readLatencyMillis, + fileCount, + totalFileSize, + keys, + uids, + totalHistorySize, + NETWORK_STATS_RECORDER_FILE_OPERATED__FAST_DATA_INPUT_STATE__FDIS_DISABLED); + } + } + + public NetworkStatsMetricsLogger() { + mDeps = new Dependencies(); + } + + @VisibleForTesting + public NetworkStatsMetricsLogger(Dependencies deps) { + mDeps = deps; + } + + private static int prefixToRecorderType(@NonNull String prefix) { + switch (prefix) { + case PREFIX_XT: + return NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_XT; + case PREFIX_UID: + return NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UID; + case PREFIX_UID_TAG: + return NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UIDTAG; + default: + return NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UNKNOWN; + } + } + + /** + * Log statistics from the NetworkStatsRecorder file reading process into statsd. + */ + public void logRecorderFileReading(@NonNull String prefix, int readLatencyMillis, + @NonNull NetworkStatsCollection collection) { + final Set<Integer> uids = new HashSet<>(); + final Map<NetworkStatsCollection.Key, NetworkStatsHistory> entries = + collection.getEntries(); + + for (final NetworkStatsCollection.Key key : entries.keySet()) { + uids.add(key.uid); + } + + int totalHistorySize = 0; + for (final NetworkStatsHistory history : entries.values()) { + totalHistorySize += history.size(); + } + + // TODO: Fill file characteristics. + mDeps.writeRecorderFileReadingStats(prefixToRecorderType(prefix), + mReadIndex++, + readLatencyMillis, + 0 /* fileCount */, + 0 /* totalFileSize */, + entries.size(), + uids.size(), + totalHistorySize); + } +} diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java index 5f9b65c65139c739ded9a2b4fba36659b11f0ccb..456a212cdbade5028c3c6a00943c8c0d083e1ed8 100644 --- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java +++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java @@ -32,12 +32,14 @@ import android.net.NetworkTemplate; import android.net.TrafficStats; import android.os.Binder; import android.os.DropBoxManager; +import android.os.SystemClock; import android.service.NetworkStatsRecorderProto; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.util.FileRotator; +import com.android.metrics.NetworkStatsMetricsLogger; import com.android.net.module.util.NetworkStatsUtils; import libcore.io.IoUtils; @@ -90,6 +92,7 @@ public class NetworkStatsRecorder { private final CombiningRewriter mPendingRewriter; private WeakReference<NetworkStatsCollection> mComplete; + private final NetworkStatsMetricsLogger mMetricsLogger = new NetworkStatsMetricsLogger(); /** * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}. @@ -182,8 +185,15 @@ public class NetworkStatsRecorder { Objects.requireNonNull(mRotator, "missing FileRotator"); NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; if (res == null) { + final long readStart = SystemClock.elapsedRealtime(); res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); mComplete = new WeakReference<NetworkStatsCollection>(res); + final long readEnd = SystemClock.elapsedRealtime(); + // For legacy recorders which are used for data integrity check, which + // have wipeOnError flag unset, skip reporting metrics. + if (mWipeOnError) { + mMetricsLogger.logRecorderFileReading(mCookie, (int) (readEnd - readStart), res); + } } return res; } diff --git a/tests/unit/java/android/net/NetworkStatsRecorderTest.java b/tests/unit/java/android/net/NetworkStatsRecorderTest.java index e8f853e37a4e914d6d37506fd37d1d3918a930db..9ec9a7919ef20af5c43e6ea22faa6a62bf225b8f 100644 --- a/tests/unit/java/android/net/NetworkStatsRecorderTest.java +++ b/tests/unit/java/android/net/NetworkStatsRecorderTest.java @@ -16,8 +16,15 @@ package com.android.server.net; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT; import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UID; +import static com.android.server.ConnectivityStatsLog.NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_XT; import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static org.mockito.Mockito.any; @@ -29,12 +36,16 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.net.NetworkIdentity; +import android.net.NetworkIdentitySet; import android.net.NetworkStats; +import android.net.NetworkStatsCollection; import android.os.DropBoxManager; import androidx.test.filters.SmallTest; import com.android.internal.util.FileRotator; +import com.android.metrics.NetworkStatsMetricsLogger; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; @@ -53,6 +64,8 @@ public final class NetworkStatsRecorderTest { private static final String TAG = NetworkStatsRecorderTest.class.getSimpleName(); private static final String TEST_PREFIX = "test"; + private static final int TEST_UID1 = 1234; + private static final int TEST_UID2 = 1235; @Mock private DropBoxManager mDropBox; @Mock private NetworkStats.NonMonotonicObserver mObserver; @@ -86,4 +99,65 @@ public final class NetworkStatsRecorderTest { // Verify that the rotator won't delete files. verify(rotator, never()).deleteAll(); } + + @Test + public void testFileReadingMetrics_empty() { + final NetworkStatsCollection collection = new NetworkStatsCollection(30); + final NetworkStatsMetricsLogger.Dependencies deps = + mock(NetworkStatsMetricsLogger.Dependencies.class); + final NetworkStatsMetricsLogger logger = new NetworkStatsMetricsLogger(deps); + logger.logRecorderFileReading(PREFIX_XT, 888, collection); + verify(deps).writeRecorderFileReadingStats( + NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_XT, + 1 /* readIndex */, + 888 /* readLatencyMillis */, + 0 /* fileCount */, + 0 /* totalFileSize */, + 0 /* keys */, + 0 /* uids */, + 0 /* totalHistorySize */ + ); + + // Write second time, verify the index increases. + logger.logRecorderFileReading(PREFIX_XT, 567, collection); + verify(deps).writeRecorderFileReadingStats( + NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_XT, + 2 /* readIndex */, + 567 /* readLatencyMillis */, + 0 /* fileCount */, + 0 /* totalFileSize */, + 0 /* keys */, + 0 /* uids */, + 0 /* totalHistorySize */ + ); + } + + @Test + public void testFileReadingMetrics() { + final NetworkStatsCollection collection = new NetworkStatsCollection(30); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + final NetworkIdentitySet identSet = new NetworkIdentitySet(); + identSet.add(new NetworkIdentity.Builder().build()); + // Empty entries will be skipped, put some ints to make sure they can be recorded. + entry.rxBytes = 1; + + collection.recordData(identSet, TEST_UID1, SET_DEFAULT, TAG_NONE, 0, 60, entry); + collection.recordData(identSet, TEST_UID2, SET_DEFAULT, TAG_NONE, 0, 60, entry); + collection.recordData(identSet, TEST_UID2, SET_FOREGROUND, TAG_NONE, 30, 60, entry); + + final NetworkStatsMetricsLogger.Dependencies deps = + mock(NetworkStatsMetricsLogger.Dependencies.class); + final NetworkStatsMetricsLogger logger = new NetworkStatsMetricsLogger(deps); + logger.logRecorderFileReading(PREFIX_UID, 123, collection); + verify(deps).writeRecorderFileReadingStats( + NETWORK_STATS_RECORDER_FILE_OPERATED__RECORDER_PREFIX__PREFIX_UID, + 1 /* readIndex */, + 123 /* readLatencyMillis */, + 0 /* fileCount */, + 0 /* totalFileSize */, + 3 /* keys */, + 2 /* uids */, + 5 /* totalHistorySize */ + ); + } }