blob: 8a69680f2b28582d619a5054f39a23fab0ce5c7c [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.car.cts;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import android.car.Car;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.IoOveruseStats;
import android.car.watchdog.PerStateBytes;
import android.car.watchdog.ResourceOveruseStats;
import android.content.Context;
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.atomic.AtomicReference;
@AppModeFull(reason = "Instant Apps cannot get car related permissions")
public class CarWatchdogManagerTest extends CarApiTestBase {
private static final String TAG = CarWatchdogManagerTest.class.getSimpleName();
private static final String CAR_WATCHDOG_SERVICE_NAME
= "android.automotive.watchdog.ICarWatchdog/default";
private static final long FIVE_HUNDRED_KILOBYTES = 1024 * 500;
// Wait time to sync I/O stats from proc fs -> watchdog daemon -> CarService.
private static final int STATS_SYNC_WAIT_MS = 3000;
private Context mContext;
private CarWatchdogManager mCarWatchdogManager;
private File mFile;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mFile = mContext.getFilesDir();
mCarWatchdogManager = (CarWatchdogManager) getCar().getCarManager(Car.CAR_WATCHDOG_SERVICE);
}
@Test
public void testGetResourceOveruseStats() throws Exception {
String packageName = mContext.getPackageName();
/*
* Start a custom performance collection with a 1 second interval. This enables watchdog
* daemon to read proc stats more frequently and reduces the test wait time.
*/
runShellCommand(
"dumpsys %s --start_perf "
+ "--max_duration 600 --interval 1", CAR_WATCHDOG_SERVICE_NAME);
/*
* Reset the resource overuse stats before running the test. Clears any previous stats
* saved by watchdog.
*/
runShellCommand(
"dumpsys %s --reset_resource_overuse_stats %s", CAR_WATCHDOG_SERVICE_NAME,
packageName);
long writtenBytes = writeToDisk(mFile, FIVE_HUNDRED_KILOBYTES);
assertWithMessage("Failed to write data to dir '" + mFile.getAbsolutePath() + "'").that(
writtenBytes).isGreaterThan(0L);
AtomicReference<ResourceOveruseStats> stats = new AtomicReference<>();
PollingCheck.waitFor(STATS_SYNC_WAIT_MS, () -> {
stats.set(mCarWatchdogManager.getResourceOveruseStats(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
return stats.get().getIoOveruseStats() != null;
});
/*
* Stop the custom performance collection. Resets watchdog's I/O stat collection to default
* interval.
*/
runShellCommand("dumpsys %s --stop_perf", CAR_WATCHDOG_SERVICE_NAME);
IoOveruseStats ioOveruseStats = stats.get().getIoOveruseStats();
PerStateBytes remainingWriteBytes = ioOveruseStats.getRemainingWriteBytes();
assertWithMessage("Package name").that(stats.get().getPackageName()).isEqualTo(packageName);
assertWithMessage("Total bytes written to disk").that(
ioOveruseStats.getTotalBytesWritten()).isAtLeast(FIVE_HUNDRED_KILOBYTES);
assertWithMessage("Remaining write bytes").that(remainingWriteBytes).isNotNull();
assertWithMessage("Remaining foreground write bytes").that(
remainingWriteBytes.getForegroundModeBytes()).isGreaterThan(0);
assertWithMessage("Remaining background write bytes").that(
remainingWriteBytes.getBackgroundModeBytes()).isGreaterThan(0);
assertWithMessage("Remaining garage mode write bytes").that(
remainingWriteBytes.getGarageModeBytes()).isGreaterThan(0);
assertWithMessage("Duration in seconds").that(
ioOveruseStats.getDurationInSeconds()).isGreaterThan(0);
assertWithMessage("Start time").that(ioOveruseStats.getStartTime()).isGreaterThan(0);
assertWithMessage("Total overuse").that(ioOveruseStats.getTotalOveruses()).isEqualTo(0);
assertWithMessage("Total times killed").that(
ioOveruseStats.getTotalTimesKilled()).isEqualTo(0);
assertWithMessage("Killable on overuse").that(
ioOveruseStats.isKillableOnOveruse()).isTrue();
assertWithMessage("User handle").that(stats.get().getUserHandle()).isEqualTo(
UserHandle.getUserHandleForUid(Process.myUid()));
}
/**
* Test that no exception is thrown when calling the addResourceOveruseListener and
* removeResourceOveruseListener client APIs.
*
* <p>The actual notification handling and killing will
* be tested with host side tests.
*/
@Test
public void testListenIoOveruse() {
CarWatchdogManager.ResourceOveruseListener listener = resourceOveruseStats -> {
// Do nothing
};
mCarWatchdogManager.addResourceOveruseListener(
mContext.getMainExecutor(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, listener);
mCarWatchdogManager.removeResourceOveruseListener(listener);
}
@Test
public void testThrowsExceptionOnNullResourceOveruseListener() {
assertThrows(NullPointerException.class,
() -> mCarWatchdogManager.addResourceOveruseListener(
mContext.getMainExecutor(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
null));
}
private static long writeToDisk(File dir, long size) throws Exception {
if (!dir.exists()) {
throw new FileNotFoundException(
"directory '" + dir.getAbsolutePath() + "' doesn't exist");
}
File uniqueFile = new File(dir, Long.toString(System.nanoTime()));
try (FileOutputStream fos = new FileOutputStream(uniqueFile)) {
Log.d(TAG, "Attempting to write " + size + " bytes");
writeToFos(fos, size);
fos.getFD().sync();
}
return size;
}
private static void writeToFos(FileOutputStream fos, long maxSize) throws IOException {
while (maxSize != 0) {
int writeSize = (int) Math.min(Integer.MAX_VALUE,
Math.min(Runtime.getRuntime().freeMemory(), maxSize));
Log.i(TAG, "writeSize:" + writeSize);
try {
fos.write(new byte[writeSize]);
} catch (InterruptedIOException e) {
Thread.currentThread().interrupt();
continue;
}
maxSize -= writeSize;
}
}
}