Christopher Ferris | 91f4410 | 2013-05-20 17:24:15 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2013 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 | |
Dan Albert | dc847e6 | 2014-11-15 18:50:10 -0800 | [diff] [blame^] | 17 | #include "memtrack.h" |
Christopher Ferris | 91f4410 | 2013-05-20 17:24:15 -0700 | [diff] [blame] | 18 | |
Dan Albert | dc847e6 | 2014-11-15 18:50:10 -0800 | [diff] [blame^] | 19 | #include <ctype.h> |
Christopher Ferris | 91f4410 | 2013-05-20 17:24:15 -0700 | [diff] [blame] | 20 | #include <dirent.h> |
Dan Albert | dc847e6 | 2014-11-15 18:50:10 -0800 | [diff] [blame^] | 21 | #include <fcntl.h> |
| 22 | #include <limits.h> |
| 23 | #include <signal.h> |
| 24 | #include <stdio.h> |
| 25 | #include <stdlib.h> |
| 26 | #include <sys/stat.h> |
| 27 | #include <sys/types.h> |
| 28 | #include <unistd.h> |
Christopher Ferris | 91f4410 | 2013-05-20 17:24:15 -0700 | [diff] [blame] | 29 | |
| 30 | #include <cutils/log.h> |
| 31 | |
| 32 | #include <algorithm> |
| 33 | #include <vector> |
| 34 | |
Christopher Ferris | 91f4410 | 2013-05-20 17:24:15 -0700 | [diff] [blame] | 35 | #ifdef LOG_TAG |
| 36 | #undef LOG_TAG |
| 37 | #endif |
| 38 | #define LOG_TAG "MemTracker" |
| 39 | |
| 40 | FileData::FileData(char *filename, char *buffer, size_t buffer_len) |
| 41 | : data_(buffer), max_(buffer_len), cur_idx_(0), len_(0), |
| 42 | read_complete_(false) { |
| 43 | fd_ = open(filename, O_RDONLY); |
| 44 | if (fd_ < 0) { |
| 45 | read_complete_ = true; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | FileData::~FileData() { |
| 50 | if (fd_ >= 0) { |
| 51 | close(fd_); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | bool FileData::isAvail(size_t bytes_needed) { |
| 56 | if (cur_idx_ + bytes_needed < len_) { |
| 57 | return true; |
| 58 | } |
| 59 | |
| 60 | if (read_complete_) { |
| 61 | return false; |
| 62 | } |
| 63 | |
| 64 | if (cur_idx_ != len_) { |
| 65 | // Copy the leftover to the front of the buffer. |
| 66 | len_ = len_ - cur_idx_; |
| 67 | memcpy(data_, data_ + cur_idx_, len_); |
| 68 | } |
| 69 | |
| 70 | ssize_t bytes; |
| 71 | cur_idx_ = 0; |
| 72 | while (cur_idx_ + bytes_needed >= len_) { |
| 73 | bytes = read(fd_, data_ + len_, max_ - len_); |
| 74 | if (bytes == 0 || bytes == -1) { |
| 75 | read_complete_; |
| 76 | break; |
| 77 | } |
| 78 | len_ += bytes; |
| 79 | } |
| 80 | |
| 81 | return cur_idx_ + bytes_needed < len_; |
| 82 | } |
| 83 | |
| 84 | bool FileData::getPss(size_t *pss) { |
| 85 | size_t value; |
| 86 | while (true) { |
| 87 | if (!isAvail(4)) { |
| 88 | return false; |
| 89 | } |
| 90 | |
| 91 | if (data_[cur_idx_] != 'P' || data_[cur_idx_+1] != 's' || |
| 92 | data_[cur_idx_+2] != 's' || data_[cur_idx_+3] != ':') { |
| 93 | // Consume the rest of the line. |
| 94 | while (isAvail(1) && data_[cur_idx_++] != '\n'); |
| 95 | } else { |
| 96 | cur_idx_ += 4; |
| 97 | while (isAvail(1) && isspace(data_[cur_idx_])) { |
| 98 | cur_idx_++; |
| 99 | } |
| 100 | |
| 101 | value = 0; |
| 102 | while (isAvail(1) && isdigit(data_[cur_idx_])) { |
| 103 | value = value * 10 + data_[cur_idx_] - '0'; |
| 104 | cur_idx_++; |
| 105 | } |
| 106 | *pss = value; |
| 107 | |
| 108 | // Consume the rest of the line. |
| 109 | while (isAvail(1) && data_[cur_idx_++] != '\n'); |
| 110 | |
| 111 | return true; |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | const char *ProcessInfo::kProc = "/proc/"; |
| 117 | const char *ProcessInfo::kCmdline = "/cmdline"; |
| 118 | const char *ProcessInfo::kSmaps = "/smaps"; |
| 119 | |
| 120 | ProcessInfo::ProcessInfo() { |
| 121 | memcpy(proc_file_, kProc, kProcLen); |
| 122 | } |
| 123 | |
| 124 | ProcessInfo::~ProcessInfo() { |
| 125 | } |
| 126 | |
| 127 | bool ProcessInfo::getInformation(int pid, char *pid_str, size_t pid_str_len) { |
| 128 | memcpy(proc_file_ + kProcLen, pid_str, pid_str_len); |
| 129 | memcpy(proc_file_ + kProcLen + pid_str_len, kCmdline, kCmdlineLen); |
| 130 | |
| 131 | // Read the cmdline for the process. |
| 132 | int fd = open(proc_file_, O_RDONLY); |
| 133 | if (fd < 0) { |
| 134 | return false; |
| 135 | } |
| 136 | |
| 137 | ssize_t bytes = read(fd, cmd_name_, sizeof(cmd_name_)); |
| 138 | close(fd); |
| 139 | if (bytes == -1 || bytes == 0) { |
| 140 | return false; |
| 141 | } |
| 142 | |
| 143 | memcpy(proc_file_ + kProcLen + pid_str_len, kSmaps, kSmapsLen); |
| 144 | FileData smaps(proc_file_, buffer_, sizeof(buffer_)); |
| 145 | |
| 146 | cur_process_info_t process_info; |
| 147 | size_t pss_kb; |
| 148 | process_info.pss_kb = 0; |
| 149 | while (smaps.getPss(&pss_kb)) { |
| 150 | process_info.pss_kb += pss_kb; |
| 151 | } |
| 152 | |
| 153 | if (cur_.count(cmd_name_) == 0) { |
| 154 | cur_[cmd_name_] = process_info; |
| 155 | } else { |
| 156 | cur_[cmd_name_].pss_kb += process_info.pss_kb; |
| 157 | } |
| 158 | cur_[cmd_name_].pids.push_back(pid); |
| 159 | |
| 160 | return true; |
| 161 | } |
| 162 | |
| 163 | void ProcessInfo::scan() { |
| 164 | DIR *proc_dir = opendir(kProc); |
| 165 | if (proc_dir == NULL) { |
| 166 | perror("Cannot open directory.\n"); |
| 167 | exit(1); |
| 168 | } |
| 169 | |
| 170 | // Clear any current pids. |
| 171 | for (processes_t::iterator it = all_.begin(); it != all_.end(); ++it) { |
| 172 | it->second.pids.clear(); |
| 173 | } |
| 174 | |
| 175 | struct dirent *dir_data; |
| 176 | int len; |
| 177 | bool is_pid; |
| 178 | size_t pid; |
| 179 | cur_.clear(); |
| 180 | while ((dir_data = readdir(proc_dir))) { |
| 181 | // Check if the directory entry represents a pid. |
| 182 | len = strlen(dir_data->d_name); |
| 183 | is_pid = true; |
| 184 | pid = 0; |
| 185 | for (int i = 0; i < len; i++) { |
| 186 | if (!isdigit(dir_data->d_name[i])) { |
| 187 | is_pid = false; |
| 188 | break; |
| 189 | } |
| 190 | pid = pid * 10 + dir_data->d_name[i] - '0'; |
| 191 | } |
| 192 | if (is_pid) { |
| 193 | getInformation(pid, dir_data->d_name, len); |
| 194 | } |
| 195 | } |
| 196 | closedir(proc_dir); |
| 197 | |
| 198 | // Loop through the current processes and add them into our real list. |
| 199 | for (cur_processes_t::const_iterator it = cur_.begin(); |
| 200 | it != cur_.end(); ++it) { |
| 201 | |
| 202 | if (all_.count(it->first) == 0) { |
| 203 | // Initialize all of the variables. |
| 204 | all_[it->first].num_samples = 0; |
| 205 | all_[it->first].name = it->first; |
| 206 | all_[it->first].avg_pss_kb = 0; |
| 207 | all_[it->first].min_pss_kb = 0; |
| 208 | all_[it->first].max_pss_kb = 0; |
| 209 | } |
| 210 | |
| 211 | if (it->second.pids.size() > all_[it->first].max_num_pids) { |
| 212 | all_[it->first].max_num_pids = it->second.pids.size(); |
| 213 | } |
| 214 | |
| 215 | all_[it->first].pids = it->second.pids; |
| 216 | |
| 217 | if (it->second.pss_kb > all_[it->first].max_pss_kb) { |
| 218 | all_[it->first].max_pss_kb = it->second.pss_kb; |
| 219 | } |
| 220 | |
| 221 | if (all_[it->first].min_pss_kb == 0 || |
| 222 | it->second.pss_kb < all_[it->first].min_pss_kb) { |
| 223 | all_[it->first].min_pss_kb = it->second.pss_kb; |
| 224 | } |
| 225 | |
| 226 | all_[it->first].last_pss_kb = it->second.pss_kb; |
| 227 | |
| 228 | computeAvg(&all_[it->first].avg_pss_kb, it->second.pss_kb, |
| 229 | all_[it->first].num_samples); |
| 230 | all_[it->first].num_samples++; |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | bool comparePss(const process_info_t *first, const process_info_t *second) { |
| 235 | return first->max_pss_kb > second->max_pss_kb; |
| 236 | } |
| 237 | |
| 238 | void ProcessInfo::dumpToLog() { |
| 239 | list_.clear(); |
| 240 | for (processes_t::const_iterator it = all_.begin(); it != all_.end(); ++it) { |
| 241 | list_.push_back(&it->second); |
| 242 | } |
| 243 | |
| 244 | // Now sort the list. |
| 245 | std::sort(list_.begin(), list_.end(), comparePss); |
| 246 | |
| 247 | ALOGI("Dumping process list"); |
| 248 | for (std::vector<const process_info_t *>::const_iterator it = list_.begin(); |
| 249 | it != list_.end(); ++it) { |
| 250 | ALOGI(" Name: %s", (*it)->name.c_str()); |
| 251 | ALOGI(" Max running processes: %d", (*it)->max_num_pids); |
| 252 | if ((*it)->pids.size() > 0) { |
| 253 | ALOGI(" Currently running pids:"); |
| 254 | for (std::vector<int>::const_iterator pid_it = (*it)->pids.begin(); |
| 255 | pid_it != (*it)->pids.end(); ++pid_it) { |
| 256 | ALOGI(" %d", *pid_it); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | ALOGI(" Min PSS %0.4fM", (*it)->min_pss_kb/1024.0); |
| 261 | ALOGI(" Avg PSS %0.4fM", (*it)->avg_pss_kb/1024.0); |
| 262 | ALOGI(" Max PSS %0.4fM", (*it)->max_pss_kb/1024.0); |
| 263 | ALOGI(" Last PSS %0.4fM", (*it)->last_pss_kb/1024.0); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | void usage() { |
| 268 | printf("Usage: memtrack [--verbose | --quiet] [--scan_delay TIME_SECS]\n"); |
| 269 | printf(" --scan_delay TIME_SECS\n"); |
| 270 | printf(" The amount of delay in seconds between scans.\n"); |
| 271 | printf(" --verbose\n"); |
| 272 | printf(" Print information about the scans to stdout only.\n"); |
| 273 | printf(" --quiet\n"); |
| 274 | printf(" Nothing will be printed to stdout.\n"); |
| 275 | printf(" All scan data is dumped to the android log using the tag %s\n", |
| 276 | LOG_TAG); |
| 277 | } |
| 278 | |
| 279 | int SignalReceived = 0; |
| 280 | |
| 281 | int SignalsToHandle[] = { |
| 282 | SIGTSTP, |
| 283 | SIGINT, |
| 284 | SIGHUP, |
| 285 | SIGPIPE, |
| 286 | SIGUSR1, |
| 287 | }; |
| 288 | |
| 289 | void handleSignal(int signo) { |
| 290 | if (SignalReceived == 0) { |
| 291 | SignalReceived = signo; |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | int main(int argc, char **argv) { |
| 296 | if (geteuid() != 0) { |
| 297 | printf("Must be run as root.\n"); |
| 298 | exit(1); |
| 299 | } |
| 300 | |
| 301 | bool verbose = false; |
| 302 | bool quiet = false; |
| 303 | unsigned int scan_delay_sec = DEFAULT_SLEEP_DELAY_SECONDS; |
| 304 | for (int i = 1; i < argc; i++) { |
| 305 | if (strcmp(argv[i], "--verbose") == 0) { |
| 306 | verbose = true; |
| 307 | } else if (strcmp(argv[i], "--quiet") == 0) { |
| 308 | quiet = true; |
| 309 | } else if (strcmp(argv[i], "--scan_delay") == 0) { |
| 310 | if (i+1 == argc) { |
| 311 | printf("The %s options requires a single argument.\n", argv[i]); |
| 312 | usage(); |
| 313 | exit(1); |
| 314 | } |
| 315 | scan_delay_sec = atoi(argv[++i]); |
| 316 | } else { |
| 317 | printf("Unknown option %s\n", argv[i]); |
| 318 | usage(); |
| 319 | exit(1); |
| 320 | } |
| 321 | } |
| 322 | if (quiet && verbose) { |
| 323 | printf("Both --quiet and --verbose cannot be specified.\n"); |
| 324 | usage(); |
| 325 | exit(1); |
| 326 | } |
| 327 | |
| 328 | // Set up the signal handlers. |
| 329 | for (size_t i = 0; i < sizeof(SignalsToHandle)/sizeof(int); i++) { |
| 330 | if (signal(SignalsToHandle[i], handleSignal) == SIG_ERR) { |
| 331 | printf("Unable to handle signal %d\n", SignalsToHandle[i]); |
| 332 | exit(1); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | ProcessInfo proc_info; |
| 337 | |
| 338 | if (!quiet) { |
| 339 | printf("Hit Ctrl-Z or send SIGUSR1 to pid %d to print the current list of\n", |
| 340 | getpid()); |
| 341 | printf("processes.\n"); |
| 342 | printf("Hit Ctrl-C to print the list of processes and terminate.\n"); |
| 343 | } |
| 344 | |
| 345 | struct timespec t; |
| 346 | unsigned long long nsecs; |
| 347 | while (true) { |
| 348 | if (verbose) { |
| 349 | memset(&t, 0, sizeof(t)); |
| 350 | clock_gettime(CLOCK_MONOTONIC, &t); |
| 351 | nsecs = (unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec; |
| 352 | } |
| 353 | proc_info.scan(); |
| 354 | if (verbose) { |
| 355 | memset(&t, 0, sizeof(t)); |
| 356 | clock_gettime(CLOCK_MONOTONIC, &t); |
| 357 | nsecs = ((unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec) - nsecs; |
| 358 | printf("Scan Time %0.4f\n", ((double)nsecs)/NS_PER_SEC); |
| 359 | } |
| 360 | |
| 361 | if (SignalReceived != 0) { |
| 362 | proc_info.dumpToLog(); |
| 363 | if (SignalReceived != SIGUSR1 && SignalReceived != SIGTSTP) { |
| 364 | if (!quiet) { |
| 365 | printf("Terminating...\n"); |
| 366 | } |
| 367 | exit(1); |
| 368 | } |
| 369 | SignalReceived = 0; |
| 370 | } |
| 371 | sleep(scan_delay_sec); |
| 372 | } |
| 373 | } |