blob: a6ea1e37845cc9d7137be49aa06df4d9f92b5871 [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.
*/
#include <android-base/file.h>
#include <android-base/properties.h>
#include <gtest/gtest.h>
#include <log/log.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
TEST(drop_caches, set_perf_property) {
// Create a 32 MiB file.
size_t filesize = 33554432;
// Write 4 KiB sparsely.
size_t chunksize = 4096;
char buf[chunksize];
// Write every 256 KiB.
size_t blocksize = 262144;
// fault_around_bytes creates pre-allocated pages that are larger than
// a standard page. We write chunks of data sparsely across large blocks
// so that each chunk of data we read back is on a different page, even
// if they are the larger, pre-allocated ones.
ALOGI("Allocating %d byte file with %d chunks every %d bytes.",
static_cast<int>(filesize), static_cast<int>(chunksize),
static_cast<int>(blocksize));
android::base::unique_fd fd(
open("/data/local/tmp/garbage.data", O_CREAT | O_RDWR, 0666));
ASSERT_NE(-1, fd) << "Failed to allocate a file for the test.";
for (unsigned int chunk = 0; chunk < filesize / blocksize; chunk++) {
lseek(fd, chunk * blocksize, SEEK_SET);
for (unsigned int c = 0; c < chunksize; c++) {
buf[c] = (random() % 26) + 'A';
}
write(fd, buf, chunksize);
}
lseek(fd, 0, SEEK_SET);
ASSERT_NE(-1, fdatasync(fd.get()))
<< "Failed to sync file in memory with storage.";
// Read the chunks of data created earlier in the file 3 times. The first
// read promotes these pages to the inactive LRU cache. The second promotes
// them to the active LRU cache. The third is just for good measure. The
// next time these pages are read will now be a minor fault.
for (unsigned int times = 3; times > 0; times--) {
ssize_t n;
unsigned int counter = 0;
while ((n = read(fd.get(), buf, sizeof(buf))) > 0) {
counter++;
}
lseek(fd, 0, SEEK_SET);
}
// Read a few bytes from every block while all the data is cached. Every
// page accessed will cause a minor fault. We later compare this number
// to the number of major faults from the same operation when the data is
// not cached.
void* ptr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd.get(), 0);
ASSERT_NE(ptr, MAP_FAILED) << "Failed to mmap the data file.";
// This advice will prevent readaheads from the OS, which might cause the
// existing pages in the pagecache to get mapped, reducing the number of
// minor faults.
madvise(ptr, filesize, MADV_RANDOM);
struct rusage usage_before_minor, usage_after_minor;
getrusage(RUSAGE_SELF, &usage_before_minor);
for (unsigned int i = 0; i < filesize / blocksize; i++) {
volatile int tmp = *((char*)ptr + (i * blocksize));
(void)tmp; // Bypass the unused error.
}
getrusage(RUSAGE_SELF, &usage_after_minor);
ASSERT_NE(-1, munmap(ptr, filesize)) << "Failed to unmap the data file.";
android::base::SetProperty("perf.drop_caches", "3");
// This command can occasionally be delayed from running.
int attempts_left = 10;
while (android::base::GetProperty("perf.drop_caches", "-1") != "0") {
attempts_left--;
if (attempts_left == 0) {
FAIL() << "The perf.drop_caches property was never set back to 0. It's "
"currently equal to"
<< android::base::GetProperty("perf.drop_caches", "") << ".";
} else {
sleep(1);
}
}
// Read a few bytes from every block while all the data is not cached.
// Every page accessed will cause a major fault if the page cache has
// been dropped like we expect.
ptr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd.get(), 0);
ASSERT_NE(ptr, MAP_FAILED) << "Failed to mmap the data file.";
// This advice will prevent readaheads from the OS, which may turn major
// faults into minor faults and obscure whether data was previously cached.
madvise(ptr, filesize, MADV_RANDOM);
struct rusage usage_before_major, usage_after_major;
getrusage(RUSAGE_SELF, &usage_before_major);
for (unsigned int i = 0; i < filesize / blocksize; i++) {
volatile int tmp = *((char*)ptr + (i * blocksize));
(void)tmp; // Bypass the unused error.
}
getrusage(RUSAGE_SELF, &usage_after_major);
ASSERT_NE(-1, munmap(ptr, filesize)) << "Failed to unmap the data file.";
long with_cache_minor_faults =
usage_after_minor.ru_minflt - usage_before_minor.ru_minflt;
long without_cache_major_faults =
usage_after_major.ru_majflt - usage_before_major.ru_majflt;
long with_cache_major_faults =
usage_after_minor.ru_majflt - usage_before_minor.ru_majflt;
long without_cache_minor_faults =
usage_after_major.ru_minflt - usage_before_major.ru_minflt;
ASSERT_NEAR(with_cache_minor_faults, without_cache_major_faults, 2)
<< "with_cache_major_faults=" << with_cache_major_faults
<< " without_cache_minor_faults=" << without_cache_minor_faults;
// Try to clean up the garbage.data file from the device.
remove("/data/local/tmp/garbage.data");
}