1 /*
2  * Copyright (C) 2021 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 #include <android-base/file.h>
18 #include <android-base/properties.h>
19 #include <gtest/gtest.h>
20 #include <log/log.h>
21 #include <sys/mman.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string>
27 
TEST(drop_caches,set_perf_property)28 TEST(drop_caches, set_perf_property) {
29   // Create a 32 MiB file.
30   size_t filesize = 33554432;
31   // Write 4 KiB sparsely.
32   size_t chunksize = 4096;
33   char buf[chunksize];
34   // Write every 256 KiB.
35   size_t blocksize = 262144;
36 
37   // fault_around_bytes creates pre-allocated pages that are larger than
38   // a standard page. We write chunks of data sparsely across large blocks
39   // so that each chunk of data we read back is on a different page, even
40   // if they are the larger, pre-allocated ones.
41   ALOGI("Allocating %d byte file with %d chunks every %d bytes.",
42         static_cast<int>(filesize), static_cast<int>(chunksize),
43         static_cast<int>(blocksize));
44 
45   android::base::unique_fd fd(
46       open("/data/local/tmp/garbage.data", O_CREAT | O_RDWR, 0666));
47   ASSERT_NE(-1, fd) << "Failed to allocate a file for the test.";
48 
49   for (unsigned int chunk = 0; chunk < filesize / blocksize; chunk++) {
50     lseek(fd, chunk * blocksize, SEEK_SET);
51     for (unsigned int c = 0; c < chunksize; c++) {
52       buf[c] = (random() % 26) + 'A';
53     }
54     write(fd, buf, chunksize);
55   }
56   lseek(fd, 0, SEEK_SET);
57   ASSERT_NE(-1, fdatasync(fd.get()))
58       << "Failed to sync file in memory with storage.";
59 
60   // Read the chunks of data created earlier in the file 3 times. The first
61   // read promotes these pages to the inactive LRU cache. The second promotes
62   // them to the active LRU cache. The third is just for good measure. The
63   // next time these pages are read will now be a minor fault.
64   for (unsigned int times = 3; times > 0; times--) {
65     ssize_t n;
66     unsigned int counter = 0;
67     while ((n = read(fd.get(), buf, sizeof(buf))) > 0) {
68       counter++;
69     }
70     lseek(fd, 0, SEEK_SET);
71   }
72 
73   // Read a few bytes from every block while all the data is cached. Every
74   // page accessed will cause a minor fault. We later compare this number
75   // to the number of major faults from the same operation when the data is
76   // not cached.
77 
78   void* ptr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd.get(), 0);
79   ASSERT_NE(ptr, MAP_FAILED) << "Failed to mmap the data file.";
80   // This advice will prevent readaheads from the OS, which might cause the
81   // existing pages in the pagecache to get mapped, reducing the number of
82   // minor faults.
83   madvise(ptr, filesize, MADV_RANDOM);
84 
85   struct rusage usage_before_minor, usage_after_minor;
86   getrusage(RUSAGE_SELF, &usage_before_minor);
87   for (unsigned int i = 0; i < filesize / blocksize; i++) {
88     volatile int tmp = *((char*)ptr + (i * blocksize));
89     (void)tmp;  // Bypass the unused error.
90   }
91   getrusage(RUSAGE_SELF, &usage_after_minor);
92 
93   ASSERT_NE(-1, munmap(ptr, filesize)) << "Failed to unmap the data file.";
94 
95   android::base::SetProperty("perf.drop_caches", "3");
96   // This command can occasionally be delayed from running.
97   int attempts_left = 10;
98   while (android::base::GetProperty("perf.drop_caches", "-1") != "0") {
99     attempts_left--;
100     if (attempts_left == 0) {
101       FAIL() << "The perf.drop_caches property was never set back to 0. It's "
102                 "currently equal to"
103              << android::base::GetProperty("perf.drop_caches", "") << ".";
104     } else {
105       sleep(1);
106     }
107   }
108 
109   // Read a few bytes from every block while all the data is not cached.
110   // Every page accessed will cause a major fault if the page cache has
111   // been dropped like we expect.
112 
113   ptr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd.get(), 0);
114   ASSERT_NE(ptr, MAP_FAILED) << "Failed to mmap the data file.";
115   // This advice will prevent readaheads from the OS, which may turn major
116   // faults into minor faults and obscure whether data was previously cached.
117   madvise(ptr, filesize, MADV_RANDOM);
118 
119   struct rusage usage_before_major, usage_after_major;
120   getrusage(RUSAGE_SELF, &usage_before_major);
121   for (unsigned int i = 0; i < filesize / blocksize; i++) {
122     volatile int tmp = *((char*)ptr + (i * blocksize));
123     (void)tmp;  // Bypass the unused error.
124   }
125   getrusage(RUSAGE_SELF, &usage_after_major);
126 
127   ASSERT_NE(-1, munmap(ptr, filesize)) << "Failed to unmap the data file.";
128 
129   long with_cache_minor_faults =
130       usage_after_minor.ru_minflt - usage_before_minor.ru_minflt;
131   long without_cache_major_faults =
132       usage_after_major.ru_majflt - usage_before_major.ru_majflt;
133 
134   long with_cache_major_faults =
135       usage_after_minor.ru_majflt - usage_before_minor.ru_majflt;
136   long without_cache_minor_faults =
137       usage_after_major.ru_minflt - usage_before_major.ru_minflt;
138 
139   ASSERT_NEAR(with_cache_minor_faults, without_cache_major_faults, 2)
140     << "with_cache_major_faults=" << with_cache_major_faults
141     << " without_cache_minor_faults=" << without_cache_minor_faults;
142 
143   // Try to clean up the garbage.data file from the device.
144   remove("/data/local/tmp/garbage.data");
145 }
146