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