1 /* 2 * Copyright (C) 2018 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 <sstream> 18 #include <stdio.h> 19 #include <string.h> 20 #include <string> 21 #include <sys/mman.h> 22 #include <sys/types.h> 23 #include <unistd.h> 24 25 #include <android-base/file.h> 26 #include <android-base/logging.h> 27 #include <android-base/properties.h> 28 #include <android-base/stringprintf.h> 29 #include <android-base/strings.h> 30 #include <gtest/gtest.h> 31 #include <lmkd.h> 32 #include <liblmkd_utils.h> 33 #include <log/log_properties.h> 34 #include <private/android_filesystem_config.h> 35 36 using namespace android::base; 37 38 #define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" 39 #define LMKDTEST_RESPAWN_FLAG "LMKDTEST_RESPAWN" 40 41 #define LMKD_LOGCAT_MARKER "lowmemorykiller" 42 #define LMKD_KILL_MARKER_TEMPLATE LMKD_LOGCAT_MARKER ": Killing '%s'" 43 #define OOM_MARKER "Out of memory" 44 #define OOM_KILL_MARKER "Killed process" 45 #define MIN_LOG_SIZE 100 46 47 #define ONE_MB (1 << 20) 48 49 /* Test constant parameters */ 50 #define OOM_ADJ_MAX 1000 51 #define OOM_ADJ_MIN 0 52 #define OOM_ADJ_STEP 100 53 #define STEP_COUNT ((OOM_ADJ_MAX - OOM_ADJ_MIN) / OOM_ADJ_STEP + 1) 54 55 #define ALLOC_STEP (ONE_MB) 56 #define ALLOC_DELAY 1000 57 58 /* Utility functions */ 59 std::string readCommand(const std::string& command) { 60 FILE* fp = popen(command.c_str(), "r"); 61 std::string content; 62 ReadFdToString(fileno(fp), &content); 63 pclose(fp); 64 return content; 65 } 66 67 std::string readLogcat(const std::string& marker) { 68 std::string content = readCommand("logcat -d -b all"); 69 size_t pos = content.find(marker); 70 if (pos == std::string::npos) return ""; 71 content.erase(0, pos); 72 return content; 73 } 74 75 bool writeFile(const std::string& file, const std::string& string) { 76 if (getuid() == static_cast<unsigned>(AID_ROOT)) { 77 return WriteStringToFile(string, file); 78 } 79 return string == readCommand( 80 "echo -n '" + string + "' | su root tee " + file + " 2>&1"); 81 } 82 83 bool writeKmsg(const std::string& marker) { 84 return writeFile("/dev/kmsg", marker); 85 } 86 87 std::string getTextAround(const std::string& text, size_t pos, 88 size_t lines_before, size_t lines_after) { 89 size_t start_pos = pos; 90 91 // find start position 92 // move up lines_before number of lines 93 while (lines_before > 0 && 94 (start_pos = text.rfind('\n', start_pos)) != std::string::npos) { 95 lines_before--; 96 } 97 // move to the beginning of the line 98 start_pos = text.rfind('\n', start_pos); 99 start_pos = (start_pos == std::string::npos) ? 0 : start_pos + 1; 100 101 // find end position 102 // move down lines_after number of lines 103 while (lines_after > 0 && 104 (pos = text.find('\n', pos)) != std::string::npos) { 105 pos++; 106 lines_after--; 107 } 108 return text.substr(start_pos, (pos == std::string::npos) ? 109 std::string::npos : pos - start_pos); 110 } 111 112 bool getExecPath(std::string &path) { 113 char buf[PATH_MAX + 1]; 114 int ret = readlink("/proc/self/exe", buf, sizeof(buf) - 1); 115 if (ret < 0) { 116 return false; 117 } 118 buf[ret] = '\0'; 119 path = buf; 120 return true; 121 } 122 123 /* Child synchronization primitives */ 124 #define STATE_INIT 0 125 #define STATE_CHILD_READY 1 126 #define STATE_PARENT_READY 2 127 128 struct state_sync { 129 pthread_mutex_t mutex; 130 pthread_cond_t condition; 131 int state; 132 }; 133 134 struct state_sync * init_state_sync_obj() { 135 struct state_sync *ssync; 136 137 ssync = (struct state_sync*)mmap(NULL, sizeof(struct state_sync), 138 PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); 139 if (ssync == MAP_FAILED) { 140 return NULL; 141 } 142 143 pthread_mutexattr_t mattr; 144 pthread_mutexattr_init(&mattr); 145 pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); 146 pthread_mutex_init(&ssync->mutex, &mattr); 147 148 pthread_condattr_t cattr; 149 pthread_condattr_init(&cattr); 150 pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); 151 pthread_cond_init(&ssync->condition, &cattr); 152 153 ssync->state = STATE_INIT; 154 return ssync; 155 } 156 157 void destroy_state_sync_obj(struct state_sync *ssync) { 158 pthread_cond_destroy(&ssync->condition); 159 pthread_mutex_destroy(&ssync->mutex); 160 munmap(ssync, sizeof(struct state_sync)); 161 } 162 163 void signal_state(struct state_sync *ssync, int state) { 164 pthread_mutex_lock(&ssync->mutex); 165 ssync->state = state; 166 pthread_cond_signal(&ssync->condition); 167 pthread_mutex_unlock(&ssync->mutex); 168 } 169 170 void wait_for_state(struct state_sync *ssync, int state) { 171 pthread_mutex_lock(&ssync->mutex); 172 while (ssync->state != state) { 173 pthread_cond_wait(&ssync->condition, &ssync->mutex); 174 } 175 pthread_mutex_unlock(&ssync->mutex); 176 } 177 178 /* Memory allocation and data sharing */ 179 struct shared_data { 180 size_t allocated; 181 bool finished; 182 size_t total_size; 183 size_t step_size; 184 size_t step_delay; 185 int oomadj; 186 }; 187 188 volatile void *gptr; 189 void add_pressure(struct shared_data *data) { 190 volatile void *ptr; 191 size_t allocated_size = 0; 192 193 data->finished = false; 194 while (allocated_size < data->total_size) { 195 ptr = mmap(NULL, data->step_size, PROT_READ | PROT_WRITE, 196 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); 197 if (ptr != MAP_FAILED) { 198 /* create ptr aliasing to prevent compiler optimizing the access */ 199 gptr = ptr; 200 /* make data non-zero */ 201 memset((void*)ptr, (int)(allocated_size + 1), data->step_size); 202 allocated_size += data->step_size; 203 data->allocated = allocated_size; 204 } 205 usleep(data->step_delay); 206 } 207 data->finished = (allocated_size >= data->total_size); 208 } 209 210 /* Memory stress test main body */ 211 void runMemStressTest() { 212 struct shared_data *data; 213 struct state_sync *ssync; 214 int sock; 215 pid_t pid; 216 uid_t uid = getuid(); 217 218 ASSERT_FALSE((sock = lmkd_connect()) < 0) 219 << "Failed to connect to lmkd process, err=" << strerror(errno); 220 221 /* allocate shared memory to communicate params with a child */ 222 data = (struct shared_data*)mmap(NULL, sizeof(struct shared_data), 223 PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); 224 ASSERT_FALSE(data == MAP_FAILED) << "Memory allocation failure"; 225 data->total_size = (size_t)-1; /* allocate until killed */ 226 data->step_size = ALLOC_STEP; 227 data->step_delay = ALLOC_DELAY; 228 229 /* allocate state sync object */ 230 ASSERT_FALSE((ssync = init_state_sync_obj()) == NULL) 231 << "Memory allocation failure"; 232 233 /* run the test gradually decreasing oomadj */ 234 data->oomadj = OOM_ADJ_MAX; 235 while (data->oomadj >= OOM_ADJ_MIN) { 236 ASSERT_FALSE((pid = fork()) < 0) 237 << "Failed to spawn a child process, err=" << strerror(errno); 238 if (pid != 0) { 239 /* Parent */ 240 struct lmk_procprio params; 241 /* wait for child to start and get ready */ 242 wait_for_state(ssync, STATE_CHILD_READY); 243 params.pid = pid; 244 params.uid = uid; 245 params.oomadj = data->oomadj; 246 ASSERT_FALSE(lmkd_register_proc(sock, ¶ms) < 0) 247 << "Failed to communicate with lmkd, err=" << strerror(errno); 248 // signal the child it can proceed 249 signal_state(ssync, STATE_PARENT_READY); 250 waitpid(pid, NULL, 0); 251 if (data->finished) { 252 GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " 253 << data->allocated / ONE_MB << "MB"; 254 } else { 255 GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " 256 << data->allocated / ONE_MB 257 << "MB before being killed"; 258 } 259 data->oomadj -= OOM_ADJ_STEP; 260 } else { 261 /* Child */ 262 pid = getpid(); 263 GTEST_LOG_(INFO) << "Child [pid=" << pid 264 << "] is running at oomadj=" 265 << data->oomadj; 266 data->allocated = 0; 267 data->finished = false; 268 ASSERT_FALSE(create_memcg(uid, pid) != 0) 269 << "Child [pid=" << pid << "] failed to create a cgroup"; 270 signal_state(ssync, STATE_CHILD_READY); 271 wait_for_state(ssync, STATE_PARENT_READY); 272 add_pressure(data); 273 /* should not reach here, child should be killed by OOM/LMK */ 274 FAIL() << "Child [pid=" << pid << "] was not killed"; 275 break; 276 } 277 } 278 destroy_state_sync_obj(ssync); 279 munmap(data, sizeof(struct shared_data)); 280 close(sock); 281 } 282 283 TEST(lmkd, check_for_oom) { 284 // test requirements 285 // userdebug build 286 if (!__android_log_is_debuggable()) { 287 GTEST_LOG_(INFO) << "Must be userdebug build, terminating test"; 288 return; 289 } 290 // check if in-kernel LMK driver is present 291 if (!access(INKERNEL_MINFREE_PATH, W_OK)) { 292 GTEST_LOG_(INFO) << "Must not have kernel lowmemorykiller driver," 293 << " terminating test"; 294 return; 295 } 296 297 // if respawned test process then run the test and exit (no analysis) 298 if (getenv(LMKDTEST_RESPAWN_FLAG) != NULL) { 299 runMemStressTest(); 300 return; 301 } 302 303 // Main test process 304 // mark the beginning of the test 305 std::string marker = StringPrintf( 306 "LMKD test start %lu\n", static_cast<unsigned long>(time(nullptr))); 307 ASSERT_TRUE(writeKmsg(marker)); 308 309 // get executable complete path 310 std::string test_path; 311 ASSERT_TRUE(getExecPath(test_path)); 312 313 std::string test_output; 314 if (getuid() != static_cast<unsigned>(AID_ROOT)) { 315 // if not root respawn itself as root and capture output 316 std::string command = StringPrintf( 317 "%s=true su root %s 2>&1", LMKDTEST_RESPAWN_FLAG, 318 test_path.c_str()); 319 std::string test_output = readCommand(command); 320 GTEST_LOG_(INFO) << test_output; 321 } else { 322 // main test process is root, run the test 323 runMemStressTest(); 324 } 325 326 // Analyze results 327 // capture logcat containind kernel logs 328 std::string logcat_out = readLogcat(marker); 329 330 // 1. extract LMKD kills from logcat output, count kills 331 std::stringstream kill_logs; 332 int hit_count = 0; 333 size_t pos = 0; 334 marker = StringPrintf(LMKD_KILL_MARKER_TEMPLATE, test_path.c_str()); 335 336 while (true) { 337 if ((pos = logcat_out.find(marker, pos)) != std::string::npos) { 338 kill_logs << getTextAround(logcat_out, pos, 0, 1); 339 pos += marker.length(); 340 hit_count++; 341 } else { 342 break; 343 } 344 } 345 GTEST_LOG_(INFO) << "====Logged kills====" << std::endl 346 << kill_logs.str(); 347 EXPECT_TRUE(hit_count == STEP_COUNT) << "Number of kills " << hit_count 348 << " is less than expected " 349 << STEP_COUNT; 350 351 // 2. check kernel logs for OOM kills 352 pos = logcat_out.find(OOM_MARKER); 353 bool oom_detected = (pos != std::string::npos); 354 bool oom_kill_detected = (oom_detected && 355 logcat_out.find(OOM_KILL_MARKER, pos) != std::string::npos); 356 357 EXPECT_FALSE(oom_kill_detected) << "OOM kill is detected!"; 358 if (oom_detected || oom_kill_detected) { 359 // capture logcat with logs around all OOMs 360 pos = 0; 361 while ((pos = logcat_out.find(OOM_MARKER, pos)) != std::string::npos) { 362 GTEST_LOG_(INFO) << "====Logs around OOM====" << std::endl 363 << getTextAround(logcat_out, pos, 364 MIN_LOG_SIZE / 2, MIN_LOG_SIZE / 2); 365 pos += strlen(OOM_MARKER); 366 } 367 } 368 369 // output complete logcat with kernel (might get truncated) 370 GTEST_LOG_(INFO) << "====Complete logcat output====" << std::endl 371 << logcat_out; 372 } 373 374