Add wipeOnError flag to NetworkStatsRecorder

If reading data happens exception while doing data migration,
the file will be deleted by legacy recorders. This would cause
legacy persistent data being lost and cannot be retrieved by
any method. To avoid the files being deleted, add a wipeOnError
flag to recorder which indicates this recorder will wipe on
error or not . If the flag is set to true then deletes all files
when it throws, otherwise keeps all files.

(cherry-picked from ag/18910973)
Bug: 233828210
Test: FrameworksNetTests:NetworkStatsRecorderTest

Change-Id: Id7a3d8bebf8a00d814f9e84bf4c10d927e6ff749
Merged-In: Id7a3d8bebf8a00d814f9e84bf4c10d927e6ff749
This commit is contained in:
Aaron Huang
2022-06-16 23:56:35 +08:00
parent ce11dc9a2d
commit 80767e64ef
4 changed files with 120 additions and 17 deletions

View File

@@ -78,6 +78,7 @@ public class NetworkStatsRecorder {
private final long mBucketDuration;
private final boolean mOnlyTags;
private final boolean mWipeOnError;
private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
private NetworkStats mLastSnapshot;
@@ -102,6 +103,7 @@ public class NetworkStatsRecorder {
// slack to avoid overflow
mBucketDuration = YEAR_IN_MILLIS;
mOnlyTags = false;
mWipeOnError = true;
mPending = null;
mSinceBoot = new NetworkStatsCollection(mBucketDuration);
@@ -113,7 +115,8 @@ public class NetworkStatsRecorder {
* Persisted recorder.
*/
public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags,
boolean wipeOnError) {
mRotator = Objects.requireNonNull(rotator, "missing FileRotator");
mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver");
mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager");
@@ -121,6 +124,7 @@ public class NetworkStatsRecorder {
mBucketDuration = bucketDuration;
mOnlyTags = onlyTags;
mWipeOnError = wipeOnError;
mPending = new NetworkStatsCollection(bucketDuration);
mSinceBoot = new NetworkStatsCollection(bucketDuration);
@@ -552,7 +556,9 @@ public class NetworkStatsRecorder {
}
mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
}
mRotator.deleteAll();
// Delete all files if this recorder is set wipe on error.
if (mWipeOnError) {
mRotator.deleteAll();
}
}
}

View File

@@ -800,11 +800,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mSystemReady = true;
// create data recorders along with historical rotators
mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir);
mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir);
mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir);
mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir,
true /* wipeOnError */);
mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir,
true /* wipeOnError */);
mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir,
true /* wipeOnError */);
mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true,
mStatsDir);
mStatsDir, true /* wipeOnError */);
updatePersistThresholdsLocked();
@@ -869,12 +872,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private NetworkStatsRecorder buildRecorder(
String prefix, NetworkStatsSettings.Config config, boolean includeTags,
File baseDir) {
File baseDir, boolean wipeOnError) {
final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
Context.DROPBOX_SERVICE);
return new NetworkStatsRecorder(new FileRotator(
baseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags,
wipeOnError);
}
@GuardedBy("mStatsLock")
@@ -971,12 +975,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
final NetworkStatsRecorder[] legacyRecorders;
if (runComparison) {
final File legacyBaseDir = mDeps.getLegacyStatsDir();
// Set wipeOnError flag false so the recorder won't damage persistent data if reads
// failed and calling deleteAll.
legacyRecorders = new NetworkStatsRecorder[]{
buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
};
buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir,
false /* wipeOnError */),
buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir,
false /* wipeOnError */),
buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir,
false /* wipeOnError */),
buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir,
false /* wipeOnError */)};
} else {
legacyRecorders = null;
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*i
* 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.net;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.NetworkStats;
import android.os.DropBoxManager;
import androidx.test.filters.SmallTest;
import com.android.internal.util.FileRotator;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public final class NetworkStatsRecorderTest {
private static final String TAG = NetworkStatsRecorderTest.class.getSimpleName();
private static final String TEST_PREFIX = "test";
@Mock private DropBoxManager mDropBox;
@Mock private NetworkStats.NonMonotonicObserver mObserver;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
private NetworkStatsRecorder buildRecorder(FileRotator rotator, boolean wipeOnError) {
return new NetworkStatsRecorder(rotator, mObserver, mDropBox, TEST_PREFIX,
HOUR_IN_MILLIS, false /* includeTags */, wipeOnError);
}
@Test
public void testWipeOnError() throws Exception {
final FileRotator rotator = mock(FileRotator.class);
final NetworkStatsRecorder wipeOnErrorRecorder = buildRecorder(rotator, true);
// Assuming that the rotator gets an exception happened when read data.
doThrow(new IOException()).when(rotator).readMatching(any(), anyLong(), anyLong());
wipeOnErrorRecorder.getOrLoadPartialLocked(Long.MIN_VALUE, Long.MAX_VALUE);
// Verify that the files will be deleted.
verify(rotator, times(1)).deleteAll();
reset(rotator);
final NetworkStatsRecorder noWipeOnErrorRecorder = buildRecorder(rotator, false);
doThrow(new IOException()).when(rotator).readMatching(any(), anyLong(), anyLong());
noWipeOnErrorRecorder.getOrLoadPartialLocked(Long.MIN_VALUE, Long.MAX_VALUE);
// Verify that the rotator won't delete files.
verify(rotator, never()).deleteAll();
}
}

View File

@@ -2025,18 +2025,18 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
}
private NetworkStatsRecorder makeTestRecorder(File directory, String prefix, Config config,
boolean includeTags) {
boolean includeTags, boolean wipeOnError) {
final NetworkStats.NonMonotonicObserver observer =
mock(NetworkStats.NonMonotonicObserver.class);
final DropBoxManager dropBox = mock(DropBoxManager.class);
return new NetworkStatsRecorder(new FileRotator(
directory, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
observer, dropBox, prefix, config.bucketDuration, includeTags);
observer, dropBox, prefix, config.bucketDuration, includeTags, wipeOnError);
}
private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, prefix,
mSettings.getDevConfig(), includeTags);
mSettings.getDevConfig(), includeTags, false);
return recorder.getOrLoadCompleteLocked();
}