Home | History | Annotate | Download | only in car
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.car;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertNotNull;
     22 import static org.junit.Assert.assertNull;
     23 import static org.junit.Assert.assertTrue;
     24 import static org.junit.Assert.fail;
     25 
     26 import android.annotation.NonNull;
     27 import android.annotation.Nullable;
     28 import android.car.Car;
     29 import android.car.storagemonitoring.CarStorageMonitoringManager;
     30 import android.car.storagemonitoring.IoStatsEntry;
     31 import android.car.storagemonitoring.IoStats;
     32 import android.car.storagemonitoring.LifetimeWriteInfo;
     33 import android.car.storagemonitoring.UidIoRecord;
     34 import android.car.storagemonitoring.WearEstimate;
     35 import android.car.storagemonitoring.WearEstimateChange;
     36 import android.content.Intent;
     37 import android.content.pm.PackageManager.NameNotFoundException;
     38 import android.content.pm.ResolveInfo;
     39 import android.os.SystemClock;
     40 import android.support.test.filters.MediumTest;
     41 import android.support.test.runner.AndroidJUnit4;
     42 import android.util.JsonWriter;
     43 import android.util.Log;
     44 import android.util.Pair;
     45 import android.util.SparseArray;
     46 
     47 import com.android.car.storagemonitoring.LifetimeWriteInfoProvider;
     48 import com.android.car.storagemonitoring.UidIoStatsProvider;
     49 import com.android.car.storagemonitoring.WearEstimateRecord;
     50 import com.android.car.storagemonitoring.WearHistory;
     51 import com.android.car.storagemonitoring.WearInformation;
     52 import com.android.car.storagemonitoring.WearInformationProvider;
     53 import com.android.car.systeminterface.StorageMonitoringInterface;
     54 import com.android.car.systeminterface.SystemInterface;
     55 import com.android.car.systeminterface.SystemStateInterface;
     56 import com.android.car.systeminterface.TimeInterface;
     57 
     58 import com.android.car.test.utils.TemporaryFile;
     59 import java.util.ArrayDeque;
     60 import java.util.Collection;
     61 import java.util.Objects;
     62 import java.util.Queue;
     63 import org.json.JSONArray;
     64 import org.json.JSONObject;
     65 import org.junit.Rule;
     66 import org.junit.Test;
     67 import org.junit.rules.TestName;
     68 import org.junit.runner.RunWith;
     69 
     70 import java.io.File;
     71 import java.io.FileWriter;
     72 import java.io.IOException;
     73 import java.time.Duration;
     74 import java.time.Instant;
     75 import java.util.ArrayList;
     76 import java.util.Arrays;
     77 import java.util.Comparator;
     78 import java.util.HashMap;
     79 import java.util.List;
     80 import java.util.Map;
     81 
     82 /** Test the public entry points for the CarStorageMonitoringManager */
     83 @RunWith(AndroidJUnit4.class)
     84 @MediumTest
     85 public class CarStorageMonitoringTest extends MockedCarTestBase {
     86     private static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
     87 
     88     @Rule public TestName mTestName = new TestName();
     89 
     90     private static final WearInformation DEFAULT_WEAR_INFORMATION =
     91         new WearInformation(30, 0, WearInformation.PRE_EOL_INFO_NORMAL);
     92 
     93     final class TestContext extends MockContext {
     94         TestContext(MockContext context) {
     95             super(context.getBaseContext());
     96         }
     97 
     98         @Override
     99         public void sendBroadcast(Intent intent, String receiverPermission) {
    100             Log.d(TAG, "test context broadcasting " + intent);
    101             if (CarStorageMonitoringManager.INTENT_EXCESSIVE_IO.equals(intent.getAction())) {
    102                 assertEquals(Car.PERMISSION_STORAGE_MONITORING, receiverPermission);
    103 
    104                 List<ResolveInfo> resolveInfoList = mContext.getPackageManager().
    105                         queryBroadcastReceivers(intent, 0);
    106 
    107                 assertEquals(1,
    108                         resolveInfoList.stream().map(ri -> ri.activityInfo)
    109                             .filter(Objects::nonNull)
    110                             .map(ai -> ai.name)
    111                             .filter(CarStorageMonitoringBroadcastReceiver.class
    112                                     .getCanonicalName()::equals)
    113                             .count());
    114 
    115                 CarStorageMonitoringBroadcastReceiver.deliver(intent);
    116             } else {
    117                 super.sendBroadcast(intent, receiverPermission);
    118             }
    119         }
    120     }
    121 
    122     static class ResourceOverrides {
    123         private final HashMap<Integer, Integer> mIntegerOverrides = new HashMap<>();
    124         private final HashMap<Integer, String> mStringOverrides = new HashMap<>();
    125 
    126         void override(int id, int value) {
    127             mIntegerOverrides.put(id, value);
    128         }
    129         void override(int id, String value) {
    130             mStringOverrides.put(id, value);
    131         }
    132 
    133         void overrideResources(MockResources resources) {
    134             mIntegerOverrides.forEach(resources::overrideResource);
    135             mStringOverrides.forEach(resources::overrideResource);
    136         }
    137     }
    138 
    139     private final Map<String, ResourceOverrides> PER_TEST_RESOURCES =
    140             new HashMap<String, ResourceOverrides>() {
    141                 {
    142                     put("testAggregateIoStats",
    143                             new ResourceOverrides() {{
    144                                 override(R.integer.ioStatsNumSamplesToStore, 5);
    145                             }});
    146                     put("testIoStatsDeltas",
    147                             new ResourceOverrides() {{
    148                                 override(R.integer.ioStatsNumSamplesToStore, 5);
    149                             }});
    150                     put("testEventDelivery",
    151                         new ResourceOverrides() {{
    152                             override(R.integer.ioStatsNumSamplesToStore, 5);
    153                         }});
    154                     put("testIntentOnExcessiveWrite",
    155                             new ResourceOverrides() {{
    156                                 override(R.integer.ioStatsNumSamplesToStore, 5);
    157                                 override(R.integer.maxExcessiveIoSamplesInWindow, 0);
    158                                 override(R.integer.acceptableWrittenKBytesPerSample, 10);
    159                                 override(R.integer.acceptableFsyncCallsPerSample, 1000);
    160                                 override(R.string.intentReceiverForUnacceptableIoMetrics,
    161                                         getFlattenComponent(
    162                                                 CarStorageMonitoringBroadcastReceiver.class));
    163                             }});
    164 
    165                     put("testIntentOnExcessiveFsync",
    166                             new ResourceOverrides() {{
    167                                 override(R.integer.ioStatsNumSamplesToStore, 5);
    168                                 override(R.integer.maxExcessiveIoSamplesInWindow, 0);
    169                                 override(R.integer.acceptableWrittenKBytesPerSample, 1000);
    170                                 override(R.integer.acceptableFsyncCallsPerSample, 2);
    171                                 override(R.string.intentReceiverForUnacceptableIoMetrics,
    172                                         getFlattenComponent(
    173                                                 CarStorageMonitoringBroadcastReceiver.class));
    174                             }});
    175 
    176                     put("testZeroWindowDisablesCollection",
    177                             new ResourceOverrides() {{
    178                                 override(R.integer.ioStatsNumSamplesToStore, 0);
    179                             }});
    180 
    181                 }
    182             };
    183 
    184     private static final class TestData {
    185         static final TestData DEFAULT = new TestData(0, DEFAULT_WEAR_INFORMATION, null, null);
    186 
    187         final long uptime;
    188         @NonNull
    189         final WearInformation wearInformation;
    190         @Nullable
    191         final WearHistory wearHistory;
    192         @NonNull
    193         final UidIoRecord[] ioStats;
    194         @NonNull
    195         final LifetimeWriteInfo[] previousLifetimeWriteInfo;
    196         @NonNull
    197         final LifetimeWriteInfo[] currentLifetimeWriteInfo;
    198 
    199         TestData(long uptime,
    200             @Nullable WearInformation wearInformation,
    201             @Nullable WearHistory wearHistory,
    202             @Nullable UidIoRecord[] ioStats) {
    203             this(uptime, wearInformation, wearHistory, ioStats, null, null);
    204         }
    205 
    206         TestData(long uptime,
    207                 @Nullable WearInformation wearInformation,
    208                 @Nullable WearHistory wearHistory,
    209                 @Nullable UidIoRecord[] ioStats,
    210                 @Nullable LifetimeWriteInfo[] previousLifetimeWriteInfo,
    211                 @Nullable LifetimeWriteInfo[] currentLifetimeWriteInfo) {
    212             if (wearInformation == null) wearInformation = DEFAULT_WEAR_INFORMATION;
    213             if (ioStats == null) ioStats = new UidIoRecord[0];
    214             if (previousLifetimeWriteInfo == null) {
    215                 previousLifetimeWriteInfo = new LifetimeWriteInfo[0];
    216             }
    217             if (currentLifetimeWriteInfo == null) {
    218                 currentLifetimeWriteInfo = new LifetimeWriteInfo[0];
    219             }
    220             this.uptime = uptime;
    221             this.wearInformation = wearInformation;
    222             this.wearHistory = wearHistory;
    223             this.ioStats = ioStats;
    224             this.previousLifetimeWriteInfo = previousLifetimeWriteInfo;
    225             this.currentLifetimeWriteInfo = currentLifetimeWriteInfo;
    226         }
    227     }
    228 
    229     private static final Map<String, TestData> PER_TEST_DATA =
    230             new HashMap<String, TestData>() {
    231                 {
    232                     put("testReadWearHistory",
    233                         new TestData(6500, DEFAULT_WEAR_INFORMATION,
    234                             WearHistory.fromRecords(
    235                                 WearEstimateRecord.Builder.newBuilder()
    236                                     .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
    237                                     .toWearEstimate(new WearEstimate(10, 0))
    238                                     .atUptime(1000)
    239                                     .atTimestamp(Instant.ofEpochMilli(5000)).build(),
    240                                 WearEstimateRecord.Builder.newBuilder()
    241                                     .fromWearEstimate(new WearEstimate(10, 0))
    242                                     .toWearEstimate(new WearEstimate(20, 0))
    243                                     .atUptime(4000)
    244                                     .atTimestamp(Instant.ofEpochMilli(12000)).build(),
    245                                 WearEstimateRecord.Builder.newBuilder()
    246                                     .fromWearEstimate(new WearEstimate(20, 0))
    247                                     .toWearEstimate(new WearEstimate(30, 0))
    248                                     .atUptime(6500)
    249                                     .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
    250 
    251                     put("testNotAcceptableWearEvent",
    252                         new TestData(2520006499L,
    253                             new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    254                             WearHistory.fromRecords(
    255                                 WearEstimateRecord.Builder.newBuilder()
    256                                     .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
    257                                     .toWearEstimate(new WearEstimate(10, 0))
    258                                     .atUptime(1000)
    259                                     .atTimestamp(Instant.ofEpochMilli(5000)).build(),
    260                                 WearEstimateRecord.Builder.newBuilder()
    261                                     .fromWearEstimate(new WearEstimate(10, 0))
    262                                     .toWearEstimate(new WearEstimate(20, 0))
    263                                     .atUptime(4000)
    264                                     .atTimestamp(Instant.ofEpochMilli(12000)).build(),
    265                                 WearEstimateRecord.Builder.newBuilder()
    266                                     .fromWearEstimate(new WearEstimate(20, 0))
    267                                     .toWearEstimate(new WearEstimate(30, 0))
    268                                     .atUptime(6500)
    269                                     .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
    270 
    271                     put("testAcceptableWearEvent",
    272                         new TestData(2520006501L,
    273                             new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    274                             WearHistory.fromRecords(
    275                                 WearEstimateRecord.Builder.newBuilder()
    276                                     .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
    277                                     .toWearEstimate(new WearEstimate(10, 0))
    278                                     .atUptime(1000)
    279                                     .atTimestamp(Instant.ofEpochMilli(5000)).build(),
    280                                 WearEstimateRecord.Builder.newBuilder()
    281                                     .fromWearEstimate(new WearEstimate(10, 0))
    282                                     .toWearEstimate(new WearEstimate(20, 0))
    283                                     .atUptime(4000)
    284                                     .atTimestamp(Instant.ofEpochMilli(12000)).build(),
    285                                 WearEstimateRecord.Builder.newBuilder()
    286                                     .fromWearEstimate(new WearEstimate(20, 0))
    287                                     .toWearEstimate(new WearEstimate(30, 0))
    288                                     .atUptime(6500)
    289                                     .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
    290 
    291                     put("testBootIoStats",
    292                         new TestData(1000L,
    293                             new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    294                             null,
    295                             new UidIoRecord[]{
    296                                 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
    297                                     0, 0, 0, 0, 0),
    298                                 new UidIoRecord(1000, 200, 5000, 0, 4000, 0,
    299                                     1000, 0, 500, 0, 0)}));
    300 
    301                     put("testAggregateIoStats",
    302                         new TestData(1000L,
    303                             new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    304                             null,
    305                             new UidIoRecord[]{
    306                                 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
    307                                     0, 0, 0, 0, 0),
    308                                 new UidIoRecord(1000, 200, 5000, 0, 4000, 0,
    309                                     1000, 0, 500, 0, 0)}));
    310 
    311                     put("testIoStatsDeltas",
    312                         new TestData(1000L,
    313                             new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    314                             null,
    315                             new UidIoRecord[]{
    316                                 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
    317                                     0, 0, 0, 0, 0)}));
    318 
    319                     put("testComputeShutdownCost",
    320                         new TestData(1000L,
    321                             new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    322                             null,
    323                             null,
    324                             new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 120),
    325                                                       new LifetimeWriteInfo("p2", "ext4", 100),
    326                                                       new LifetimeWriteInfo("p3", "f2fs", 100)},
    327                             new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 200),
    328                                                       new LifetimeWriteInfo("p2", "ext4", 300),
    329                                                       new LifetimeWriteInfo("p3", "f2fs", 100)}));
    330 
    331                     put("testNegativeShutdownCost",
    332                         new TestData(1000L,
    333                             new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
    334                             null,
    335                             null,
    336                             new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 120),
    337                                 new LifetimeWriteInfo("p2", "ext4", 100),
    338                                 new LifetimeWriteInfo("p3", "f2fs", 200)},
    339                             new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 200),
    340                                 new LifetimeWriteInfo("p2", "ext4", 300),
    341                                 new LifetimeWriteInfo("p3", "f2fs", 100)}));
    342                 }};
    343 
    344     private final MockSystemStateInterface mMockSystemStateInterface =
    345             new MockSystemStateInterface();
    346     private final MockStorageMonitoringInterface mMockStorageMonitoringInterface =
    347             new MockStorageMonitoringInterface();
    348     private final MockTimeInterface mMockTimeInterface =
    349             new MockTimeInterface();
    350     private TestContext mContext;
    351 
    352     private CarStorageMonitoringManager mCarStorageMonitoringManager;
    353 
    354     @Override
    355     protected MockContext getCarServiceContext() throws NameNotFoundException {
    356         if (mContext == null) {
    357             mContext = new TestContext(super.getCarServiceContext());
    358         }
    359         return mContext;
    360     }
    361 
    362     @Override
    363     protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
    364         SystemInterface.Builder builder = super.getSystemInterfaceBuilder();
    365         return builder.withSystemStateInterface(mMockSystemStateInterface)
    366             .withStorageMonitoringInterface(mMockStorageMonitoringInterface)
    367             .withTimeInterface(mMockTimeInterface);
    368     }
    369 
    370     @Override
    371     protected synchronized void configureFakeSystemInterface() {
    372         try {
    373             final TestData wearData = PER_TEST_DATA.getOrDefault(getName(), TestData.DEFAULT);
    374             final WearHistory wearHistory = wearData.wearHistory;
    375 
    376             mMockStorageMonitoringInterface.setWearInformation(wearData.wearInformation);
    377             mMockStorageMonitoringInterface.setWriteInfo(wearData.currentLifetimeWriteInfo);
    378 
    379             if (wearHistory != null) {
    380                 File wearHistoryFile = new File(getFakeSystemInterface().getFilesDir(),
    381                     CarStorageMonitoringService.WEAR_INFO_FILENAME);
    382                 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(wearHistoryFile))) {
    383                     wearHistory.writeToJson(jsonWriter);
    384                 }
    385             }
    386 
    387             if (wearData.uptime > 0) {
    388                 File uptimeFile = new File(getFakeSystemInterface().getFilesDir(),
    389                     CarStorageMonitoringService.UPTIME_TRACKER_FILENAME);
    390                 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(uptimeFile))) {
    391                     jsonWriter.beginObject();
    392                     jsonWriter.name("uptime").value(wearData.uptime);
    393                     jsonWriter.endObject();
    394                 }
    395             }
    396 
    397             if (wearData.previousLifetimeWriteInfo.length > 0) {
    398                 File previousLifetimeFile = new File(getFakeSystemInterface().getFilesDir(),
    399                     CarStorageMonitoringService.LIFETIME_WRITES_FILENAME);
    400                 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(previousLifetimeFile))) {
    401                     jsonWriter.beginObject();
    402                     jsonWriter.name("lifetimeWriteInfo").beginArray();
    403                     for (LifetimeWriteInfo writeInfo : wearData.previousLifetimeWriteInfo) {
    404                         writeInfo.writeToJson(jsonWriter);
    405                     }
    406                     jsonWriter.endArray().endObject();
    407                 }
    408             }
    409 
    410             Arrays.stream(wearData.ioStats).forEach(
    411                     mMockStorageMonitoringInterface::addIoStatsRecord);
    412 
    413         } catch (IOException e) {
    414             Log.e(TAG, "failed to configure fake system interface", e);
    415             fail("failed to configure fake system interface instance");
    416         }
    417     }
    418 
    419     @Override
    420     protected synchronized void configureResourceOverrides(MockResources resources) {
    421         final ResourceOverrides overrides = PER_TEST_RESOURCES.getOrDefault(getName(), null);
    422         if (overrides != null) {
    423             overrides.overrideResources(resources);
    424         }
    425     }
    426 
    427     @Override
    428     public void setUp() throws Exception {
    429         super.setUp();
    430         mMockSystemStateInterface.executeBootCompletedActions();
    431 
    432         mCarStorageMonitoringManager =
    433             (CarStorageMonitoringManager) getCar().getCarManager(Car.STORAGE_MONITORING_SERVICE);
    434     }
    435 
    436     @Test
    437     public void testReadPreEolInformation() throws Exception {
    438         assertEquals(DEFAULT_WEAR_INFORMATION.preEolInfo,
    439                 mCarStorageMonitoringManager.getPreEolIndicatorStatus());
    440     }
    441 
    442     @Test
    443     public void testReadWearEstimate() throws Exception {
    444         final WearEstimate wearEstimate = mCarStorageMonitoringManager.getWearEstimate();
    445 
    446         assertNotNull(wearEstimate);
    447         assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateA, wearEstimate.typeA);
    448         assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateB, wearEstimate.typeB);
    449     }
    450 
    451     @Test
    452     public void testReadWearHistory() throws Exception {
    453         final List<WearEstimateChange> wearEstimateChanges =
    454                 mCarStorageMonitoringManager.getWearEstimateHistory();
    455 
    456         assertNotNull(wearEstimateChanges);
    457         assertFalse(wearEstimateChanges.isEmpty());
    458 
    459         final WearHistory expectedWearHistory = PER_TEST_DATA.get(getName()).wearHistory;
    460 
    461         assertEquals(expectedWearHistory.size(), wearEstimateChanges.size());
    462         for (int i = 0; i < wearEstimateChanges.size(); ++i) {
    463             final WearEstimateRecord expected = expectedWearHistory.get(i);
    464             final WearEstimateChange actual = wearEstimateChanges.get(i);
    465 
    466             assertTrue(expected.isSameAs(actual));
    467         }
    468     }
    469 
    470     private void checkLastWearEvent(boolean isAcceptable) throws Exception {
    471         final List<WearEstimateChange> wearEstimateChanges =
    472             mCarStorageMonitoringManager.getWearEstimateHistory();
    473 
    474         assertNotNull(wearEstimateChanges);
    475         assertFalse(wearEstimateChanges.isEmpty());
    476 
    477         final TestData wearData = PER_TEST_DATA.get(getName());
    478 
    479         final WearInformation expectedCurrentWear = wearData.wearInformation;
    480         final WearEstimate expectedPreviousWear = wearData.wearHistory.getLast().getNewWearEstimate();
    481 
    482         final WearEstimateChange actualCurrentWear =
    483                 wearEstimateChanges.get(wearEstimateChanges.size() - 1);
    484 
    485         assertEquals(isAcceptable, actualCurrentWear.isAcceptableDegradation);
    486         assertEquals(expectedCurrentWear.toWearEstimate(), actualCurrentWear.newEstimate);
    487         assertEquals(expectedPreviousWear, actualCurrentWear.oldEstimate);
    488     }
    489 
    490     @Test
    491     public void testNotAcceptableWearEvent() throws Exception {
    492         checkLastWearEvent(false);
    493     }
    494 
    495     @Test
    496     public void testAcceptableWearEvent() throws Exception {
    497         checkLastWearEvent(true);
    498     }
    499 
    500     @Test
    501     public void testBootIoStats() throws Exception {
    502         final List<IoStatsEntry> bootIoStats =
    503             mCarStorageMonitoringManager.getBootIoStats();
    504 
    505         assertNotNull(bootIoStats);
    506         assertFalse(bootIoStats.isEmpty());
    507 
    508         final UidIoRecord[] bootIoRecords = PER_TEST_DATA.get(getName()).ioStats;
    509 
    510         bootIoStats.forEach(uidIoStats -> assertTrue(Arrays.stream(bootIoRecords).anyMatch(
    511                 ioRecord -> uidIoStats.representsSameMetrics(ioRecord))));
    512     }
    513 
    514     @Test
    515     public void testAggregateIoStats() throws Exception {
    516         UidIoRecord oldRecord1000 = mMockStorageMonitoringInterface.getIoStatsRecord(1000);
    517 
    518         UidIoRecord newRecord1000 = new UidIoRecord(1000,
    519             oldRecord1000.foreground_rchar,
    520             oldRecord1000.foreground_wchar + 50,
    521             oldRecord1000.foreground_read_bytes,
    522             oldRecord1000.foreground_write_bytes + 100,
    523             oldRecord1000.foreground_fsync + 1,
    524             oldRecord1000.background_rchar,
    525             oldRecord1000.background_wchar,
    526             oldRecord1000.background_read_bytes,
    527             oldRecord1000.background_write_bytes,
    528             oldRecord1000.background_fsync);
    529 
    530         mMockStorageMonitoringInterface.addIoStatsRecord(newRecord1000);
    531 
    532         UidIoRecord record2000 = new UidIoRecord(2000,
    533             1024,
    534             2048,
    535             0,
    536             1024,
    537             1,
    538             0,
    539             0,
    540             0,
    541             0,
    542             0);
    543 
    544         mMockStorageMonitoringInterface.addIoStatsRecord(record2000);
    545 
    546         mMockTimeInterface.tick();
    547 
    548         List<IoStatsEntry> aggregateIoStats = mCarStorageMonitoringManager.getAggregateIoStats();
    549 
    550         assertNotNull(aggregateIoStats);
    551         assertFalse(aggregateIoStats.isEmpty());
    552 
    553         aggregateIoStats.forEach(serviceIoStat -> {
    554             UidIoRecord mockIoStat = mMockStorageMonitoringInterface.getIoStatsRecord(
    555                     serviceIoStat.uid);
    556 
    557             assertNotNull(mockIoStat);
    558 
    559             assertTrue(serviceIoStat.representsSameMetrics(mockIoStat));
    560         });
    561     }
    562 
    563     @Test
    564     public void testIoStatsDeltas() throws Exception {
    565         UidIoRecord oldRecord0 = mMockStorageMonitoringInterface.getIoStatsRecord(0);
    566 
    567         UidIoRecord newRecord0 = new UidIoRecord(0,
    568             oldRecord0.foreground_rchar,
    569             oldRecord0.foreground_wchar + 100,
    570             oldRecord0.foreground_read_bytes,
    571             oldRecord0.foreground_write_bytes + 50,
    572             oldRecord0.foreground_fsync,
    573             oldRecord0.background_rchar,
    574             oldRecord0.background_wchar,
    575             oldRecord0.background_read_bytes + 100,
    576             oldRecord0.background_write_bytes,
    577             oldRecord0.background_fsync);
    578 
    579         mMockStorageMonitoringInterface.addIoStatsRecord(newRecord0);
    580         mMockTimeInterface.setUptime(500).tick();
    581 
    582         List<IoStats> deltas = mCarStorageMonitoringManager.getIoStatsDeltas();
    583         assertNotNull(deltas);
    584         assertEquals(1, deltas.size());
    585 
    586         IoStats delta0 = deltas.get(0);
    587         assertNotNull(delta0);
    588         assertEquals(500, delta0.getTimestamp());
    589 
    590         List<IoStatsEntry> delta0Stats = delta0.getStats();
    591         assertNotNull(delta0Stats);
    592         assertEquals(1, delta0Stats.size());
    593 
    594         IoStatsEntry deltaRecord0 = delta0Stats.get(0);
    595 
    596         assertTrue(deltaRecord0.representsSameMetrics(newRecord0.delta(oldRecord0)));
    597 
    598         UidIoRecord newerRecord0 = new UidIoRecord(0,
    599             newRecord0.foreground_rchar + 200,
    600             newRecord0.foreground_wchar + 10,
    601             newRecord0.foreground_read_bytes,
    602             newRecord0.foreground_write_bytes,
    603             newRecord0.foreground_fsync,
    604             newRecord0.background_rchar,
    605             newRecord0.background_wchar + 100,
    606             newRecord0.background_read_bytes,
    607             newRecord0.background_write_bytes + 30,
    608             newRecord0.background_fsync + 2);
    609 
    610         mMockStorageMonitoringInterface.addIoStatsRecord(newerRecord0);
    611         mMockTimeInterface.setUptime(1000).tick();
    612 
    613         deltas = mCarStorageMonitoringManager.getIoStatsDeltas();
    614         assertNotNull(deltas);
    615         assertEquals(2, deltas.size());
    616 
    617         delta0 = deltas.get(0);
    618         assertNotNull(delta0);
    619         assertEquals(500, delta0.getTimestamp());
    620 
    621         delta0Stats = delta0.getStats();
    622         assertNotNull(delta0Stats);
    623         assertEquals(1, delta0Stats.size());
    624 
    625         deltaRecord0 = delta0Stats.get(0);
    626 
    627         assertTrue(deltaRecord0.representsSameMetrics(newRecord0.delta(oldRecord0)));
    628 
    629         IoStats delta1 = deltas.get(1);
    630         assertNotNull(delta1);
    631         assertEquals(1000, delta1.getTimestamp());
    632 
    633         List<IoStatsEntry> delta1Stats = delta1.getStats();
    634         assertNotNull(delta1Stats);
    635         assertEquals(1, delta1Stats.size());
    636 
    637         deltaRecord0 = delta1Stats.get(0);
    638 
    639         assertTrue(deltaRecord0.representsSameMetrics(newerRecord0.delta(newRecord0)));
    640     }
    641 
    642     @Test
    643     public void testEventDelivery() throws Exception {
    644         final Duration eventDeliveryDeadline = Duration.ofSeconds(5);
    645 
    646         UidIoRecord record = new UidIoRecord(0,
    647             0,
    648             100,
    649             0,
    650             75,
    651             1,
    652             0,
    653             0,
    654             0,
    655             0,
    656             0);
    657 
    658         Listener listener1 = new Listener("listener1");
    659         Listener listener2 = new Listener("listener2");
    660 
    661         mCarStorageMonitoringManager.registerListener(listener1);
    662         mCarStorageMonitoringManager.registerListener(listener2);
    663 
    664         mMockStorageMonitoringInterface.addIoStatsRecord(record);
    665         mMockTimeInterface.setUptime(500).tick();
    666 
    667         assertTrue(listener1.waitForEvent(eventDeliveryDeadline));
    668         assertTrue(listener2.waitForEvent(eventDeliveryDeadline));
    669 
    670         IoStats event1 = listener1.reset();
    671         IoStats event2 = listener2.reset();
    672 
    673         assertEquals(event1, event2);
    674         event1.getStats().forEach(stats -> assertTrue(stats.representsSameMetrics(record)));
    675 
    676         mCarStorageMonitoringManager.unregisterListener(listener1);
    677 
    678         mMockTimeInterface.setUptime(600).tick();
    679         assertFalse(listener1.waitForEvent(eventDeliveryDeadline));
    680         assertTrue(listener2.waitForEvent(eventDeliveryDeadline));
    681     }
    682 
    683     @Test
    684     public void testIntentOnExcessiveWrite() throws Exception {
    685         assertNull(CarStorageMonitoringBroadcastReceiver.reset());
    686 
    687         final Duration intentDeliveryDeadline = Duration.ofSeconds(5);
    688 
    689         UidIoRecord record = new UidIoRecord(0,
    690             0,
    691             5120,
    692             0,
    693             5000,
    694             1,
    695             0,
    696             7168,
    697             0,
    698             7000,
    699             0);
    700 
    701         mMockStorageMonitoringInterface.addIoStatsRecord(record);
    702         mMockTimeInterface.setUptime(500).tick();
    703 
    704         assertTrue(CarStorageMonitoringBroadcastReceiver.waitForIntent(intentDeliveryDeadline));
    705         Intent deliveredIntent = CarStorageMonitoringBroadcastReceiver.reset();
    706         assertNotNull(deliveredIntent);
    707         assertEquals(CarStorageMonitoringManager.INTENT_EXCESSIVE_IO, deliveredIntent.getAction());
    708     }
    709 
    710     @Test
    711     public void testIntentOnExcessiveFsync() throws Exception {
    712         assertNull(CarStorageMonitoringBroadcastReceiver.reset());
    713 
    714         final Duration intentDeliveryDeadline = Duration.ofSeconds(5);
    715 
    716         UidIoRecord record = new UidIoRecord(0,
    717             0,
    718             0,
    719             0,
    720             0,
    721             2,
    722             0,
    723             0,
    724             0,
    725             0,
    726             3);
    727 
    728         mMockStorageMonitoringInterface.addIoStatsRecord(record);
    729         mMockTimeInterface.setUptime(500).tick();
    730 
    731         assertTrue(CarStorageMonitoringBroadcastReceiver.waitForIntent(intentDeliveryDeadline));
    732         Intent deliveredIntent = CarStorageMonitoringBroadcastReceiver.reset();
    733         assertNotNull(deliveredIntent);
    734         assertEquals(CarStorageMonitoringManager.INTENT_EXCESSIVE_IO, deliveredIntent.getAction());
    735     }
    736 
    737     @Test
    738     public void testZeroWindowDisablesCollection() throws Exception {
    739         final Duration eventDeliveryDeadline = Duration.ofSeconds(5);
    740 
    741         UidIoRecord record = new UidIoRecord(0,
    742             0,
    743             100,
    744             0,
    745             75,
    746             1,
    747             0,
    748             0,
    749             0,
    750             0,
    751             0);
    752 
    753         Listener listener = new Listener("listener");
    754 
    755         mMockStorageMonitoringInterface.addIoStatsRecord(record);
    756         mMockTimeInterface.setUptime(500).tick();
    757 
    758         assertFalse(listener.waitForEvent(eventDeliveryDeadline));
    759 
    760         assertEquals(0, mCarStorageMonitoringManager.getIoStatsDeltas().size());
    761     }
    762 
    763     @Test
    764     public void testComputeShutdownCost() throws Exception {
    765         assertEquals(280, mCarStorageMonitoringManager.getShutdownDiskWriteAmount());
    766     }
    767 
    768     @Test
    769     public void testNegativeShutdownCost() throws Exception {
    770         assertEquals(CarStorageMonitoringManager.SHUTDOWN_COST_INFO_MISSING,
    771                 mCarStorageMonitoringManager.getShutdownDiskWriteAmount());
    772     }
    773 
    774     private String getName() {
    775         return mTestName.getMethodName();
    776     }
    777 
    778     static final class Listener implements CarStorageMonitoringManager.IoStatsListener {
    779         private final String mName;
    780         private final Object mSync = new Object();
    781 
    782         private IoStats mLastEvent = null;
    783 
    784         Listener(String name) {
    785             mName = name;
    786         }
    787 
    788         IoStats reset() {
    789             synchronized (mSync) {
    790                 IoStats lastEvent = mLastEvent;
    791                 mLastEvent = null;
    792                 return lastEvent;
    793             }
    794         }
    795 
    796         boolean waitForEvent(Duration duration) {
    797             long start = SystemClock.elapsedRealtime();
    798             long end = start + duration.toMillis();
    799             synchronized (mSync) {
    800                 while (mLastEvent == null && SystemClock.elapsedRealtime() < end) {
    801                     try {
    802                         mSync.wait(10L);
    803                     } catch (InterruptedException e) {
    804                         // ignore
    805                     }
    806                 }
    807             }
    808 
    809             return (mLastEvent != null);
    810         }
    811 
    812         @Override
    813         public void onSnapshot(IoStats event) {
    814             synchronized (mSync) {
    815                 Log.d(TAG, "listener " + mName + " received event " + event);
    816                 // We're going to hold a reference to this object
    817                 mLastEvent = event;
    818                 mSync.notify();
    819             }
    820         }
    821 
    822     }
    823 
    824     static final class MockStorageMonitoringInterface implements StorageMonitoringInterface,
    825         WearInformationProvider {
    826         private WearInformation mWearInformation = null;
    827         private SparseArray<UidIoRecord> mIoStats = new SparseArray<>();
    828         private UidIoStatsProvider mIoStatsProvider = () -> mIoStats;
    829         private LifetimeWriteInfo[] mWriteInfo = new LifetimeWriteInfo[0];
    830 
    831         void setWearInformation(WearInformation wearInformation) {
    832             mWearInformation = wearInformation;
    833         }
    834 
    835         void setWriteInfo(LifetimeWriteInfo[] writeInfo) {
    836             mWriteInfo = writeInfo;
    837         }
    838 
    839         void addIoStatsRecord(UidIoRecord record) {
    840             mIoStats.append(record.uid, record);
    841         }
    842 
    843         UidIoRecord getIoStatsRecord(int uid) {
    844             return mIoStats.get(uid);
    845         }
    846 
    847         void deleteIoStatsRecord(int uid) {
    848             mIoStats.delete(uid);
    849         }
    850 
    851         @Override
    852         public WearInformation load() {
    853             return mWearInformation;
    854         }
    855 
    856         @Override
    857         public LifetimeWriteInfoProvider getLifetimeWriteInfoProvider() {
    858             // cannot make this directly implement because Java does not allow
    859             // overloading based on return type and there already is a method named
    860             // load() in WearInformationProvider
    861             return new LifetimeWriteInfoProvider() {
    862                 @Override
    863                 public LifetimeWriteInfo[] load() {
    864                     return mWriteInfo;
    865                 }
    866             };
    867         }
    868 
    869         @Override
    870         public WearInformationProvider[] getFlashWearInformationProviders() {
    871             return new WearInformationProvider[] {this};
    872         }
    873 
    874         @Override
    875         public UidIoStatsProvider getUidIoStatsProvider() {
    876             return mIoStatsProvider;
    877         }
    878     }
    879 
    880     static final class MockTimeInterface implements TimeInterface {
    881         private final List<Pair<Runnable, Long>> mActionsList = new ArrayList<>();
    882         private long mUptime = 0;
    883 
    884         @Override
    885         public long getUptime(boolean includeDeepSleepTime) {
    886             return mUptime;
    887         }
    888 
    889         @Override
    890         public void scheduleAction(Runnable r, long delayMs) {
    891             mActionsList.add(Pair.create(r, delayMs));
    892             mActionsList.sort(Comparator.comparing(d -> d.second));
    893         }
    894 
    895         @Override
    896         public void cancelAllActions() {
    897             mActionsList.clear();
    898         }
    899 
    900         void tick() {
    901             mActionsList.forEach(pair -> pair.first.run());
    902         }
    903 
    904         MockTimeInterface setUptime(long time) {
    905             mUptime = time;
    906             return this;
    907         }
    908     }
    909 
    910     static final class MockSystemStateInterface implements SystemStateInterface {
    911         private final List<Pair<Runnable, Duration>> mActionsList = new ArrayList<>();
    912 
    913         @Override
    914         public void shutdown() {}
    915 
    916         @Override
    917         public boolean enterDeepSleep(int wakeupTimeSec) {
    918             return true;
    919         }
    920 
    921         @Override
    922         public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
    923             mActionsList.add(Pair.create(action, delay));
    924             mActionsList.sort(Comparator.comparing(d -> d.second));
    925         }
    926 
    927         void executeBootCompletedActions() {
    928             for (Pair<Runnable, Duration> action : mActionsList) {
    929                 action.first.run();
    930             }
    931         }
    932     }
    933 }
    934