| /* |
| * Copyright 2013 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 "memtrack.h" |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cutils/log.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #ifdef LOG_TAG |
| #undef LOG_TAG |
| #endif |
| #define LOG_TAG "MemTracker" |
| |
| FileData::FileData(char *filename, char *buffer, size_t buffer_len) |
| : data_(buffer), max_(buffer_len), cur_idx_(0), len_(0), |
| read_complete_(false) { |
| fd_ = open(filename, O_RDONLY); |
| if (fd_ < 0) { |
| read_complete_ = true; |
| } |
| } |
| |
| FileData::~FileData() { |
| if (fd_ >= 0) { |
| close(fd_); |
| } |
| } |
| |
| bool FileData::isAvail(size_t bytes_needed) { |
| if (cur_idx_ + bytes_needed < len_) { |
| return true; |
| } |
| |
| if (read_complete_) { |
| return false; |
| } |
| |
| if (cur_idx_ != len_) { |
| // Copy the leftover to the front of the buffer. |
| len_ = len_ - cur_idx_; |
| memcpy(data_, data_ + cur_idx_, len_); |
| } |
| |
| ssize_t bytes; |
| cur_idx_ = 0; |
| while (cur_idx_ + bytes_needed >= len_) { |
| bytes = read(fd_, data_ + len_, max_ - len_); |
| if (bytes == 0 || bytes == -1) { |
| read_complete_; |
| break; |
| } |
| len_ += bytes; |
| } |
| |
| return cur_idx_ + bytes_needed < len_; |
| } |
| |
| bool FileData::getPss(size_t *pss) { |
| size_t value; |
| while (true) { |
| if (!isAvail(4)) { |
| return false; |
| } |
| |
| if (data_[cur_idx_] != 'P' || data_[cur_idx_+1] != 's' || |
| data_[cur_idx_+2] != 's' || data_[cur_idx_+3] != ':') { |
| // Consume the rest of the line. |
| while (isAvail(1) && data_[cur_idx_++] != '\n'); |
| } else { |
| cur_idx_ += 4; |
| while (isAvail(1) && isspace(data_[cur_idx_])) { |
| cur_idx_++; |
| } |
| |
| value = 0; |
| while (isAvail(1) && isdigit(data_[cur_idx_])) { |
| value = value * 10 + data_[cur_idx_] - '0'; |
| cur_idx_++; |
| } |
| *pss = value; |
| |
| // Consume the rest of the line. |
| while (isAvail(1) && data_[cur_idx_++] != '\n'); |
| |
| return true; |
| } |
| } |
| } |
| |
| const char *ProcessInfo::kProc = "/proc/"; |
| const char *ProcessInfo::kCmdline = "/cmdline"; |
| const char *ProcessInfo::kSmaps = "/smaps"; |
| |
| ProcessInfo::ProcessInfo() { |
| memcpy(proc_file_, kProc, kProcLen); |
| } |
| |
| ProcessInfo::~ProcessInfo() { |
| } |
| |
| bool ProcessInfo::getInformation(int pid, char *pid_str, size_t pid_str_len) { |
| memcpy(proc_file_ + kProcLen, pid_str, pid_str_len); |
| memcpy(proc_file_ + kProcLen + pid_str_len, kCmdline, kCmdlineLen); |
| |
| // Read the cmdline for the process. |
| int fd = open(proc_file_, O_RDONLY); |
| if (fd < 0) { |
| return false; |
| } |
| |
| ssize_t bytes = read(fd, cmd_name_, sizeof(cmd_name_)); |
| close(fd); |
| if (bytes == -1 || bytes == 0) { |
| return false; |
| } |
| |
| memcpy(proc_file_ + kProcLen + pid_str_len, kSmaps, kSmapsLen); |
| FileData smaps(proc_file_, buffer_, sizeof(buffer_)); |
| |
| cur_process_info_t process_info; |
| size_t pss_kb; |
| process_info.pss_kb = 0; |
| while (smaps.getPss(&pss_kb)) { |
| process_info.pss_kb += pss_kb; |
| } |
| |
| if (cur_.count(cmd_name_) == 0) { |
| cur_[cmd_name_] = process_info; |
| } else { |
| cur_[cmd_name_].pss_kb += process_info.pss_kb; |
| } |
| cur_[cmd_name_].pids.push_back(pid); |
| |
| return true; |
| } |
| |
| void ProcessInfo::scan() { |
| DIR *proc_dir = opendir(kProc); |
| if (proc_dir == NULL) { |
| perror("Cannot open directory.\n"); |
| exit(1); |
| } |
| |
| // Clear any current pids. |
| for (processes_t::iterator it = all_.begin(); it != all_.end(); ++it) { |
| it->second.pids.clear(); |
| } |
| |
| struct dirent *dir_data; |
| int len; |
| bool is_pid; |
| size_t pid; |
| cur_.clear(); |
| while ((dir_data = readdir(proc_dir))) { |
| // Check if the directory entry represents a pid. |
| len = strlen(dir_data->d_name); |
| is_pid = true; |
| pid = 0; |
| for (int i = 0; i < len; i++) { |
| if (!isdigit(dir_data->d_name[i])) { |
| is_pid = false; |
| break; |
| } |
| pid = pid * 10 + dir_data->d_name[i] - '0'; |
| } |
| if (is_pid) { |
| getInformation(pid, dir_data->d_name, len); |
| } |
| } |
| closedir(proc_dir); |
| |
| // Loop through the current processes and add them into our real list. |
| for (cur_processes_t::const_iterator it = cur_.begin(); |
| it != cur_.end(); ++it) { |
| |
| if (all_.count(it->first) == 0) { |
| // Initialize all of the variables. |
| all_[it->first].num_samples = 0; |
| all_[it->first].name = it->first; |
| all_[it->first].avg_pss_kb = 0; |
| all_[it->first].min_pss_kb = 0; |
| all_[it->first].max_pss_kb = 0; |
| } |
| |
| if (it->second.pids.size() > all_[it->first].max_num_pids) { |
| all_[it->first].max_num_pids = it->second.pids.size(); |
| } |
| |
| all_[it->first].pids = it->second.pids; |
| |
| if (it->second.pss_kb > all_[it->first].max_pss_kb) { |
| all_[it->first].max_pss_kb = it->second.pss_kb; |
| } |
| |
| if (all_[it->first].min_pss_kb == 0 || |
| it->second.pss_kb < all_[it->first].min_pss_kb) { |
| all_[it->first].min_pss_kb = it->second.pss_kb; |
| } |
| |
| all_[it->first].last_pss_kb = it->second.pss_kb; |
| |
| computeAvg(&all_[it->first].avg_pss_kb, it->second.pss_kb, |
| all_[it->first].num_samples); |
| all_[it->first].num_samples++; |
| } |
| } |
| |
| bool comparePss(const process_info_t *first, const process_info_t *second) { |
| return first->max_pss_kb > second->max_pss_kb; |
| } |
| |
| void ProcessInfo::dumpToLog() { |
| list_.clear(); |
| for (processes_t::const_iterator it = all_.begin(); it != all_.end(); ++it) { |
| list_.push_back(&it->second); |
| } |
| |
| // Now sort the list. |
| std::sort(list_.begin(), list_.end(), comparePss); |
| |
| ALOGI("Dumping process list"); |
| for (std::vector<const process_info_t *>::const_iterator it = list_.begin(); |
| it != list_.end(); ++it) { |
| ALOGI(" Name: %s", (*it)->name.c_str()); |
| ALOGI(" Max running processes: %d", (*it)->max_num_pids); |
| if ((*it)->pids.size() > 0) { |
| ALOGI(" Currently running pids:"); |
| for (std::vector<int>::const_iterator pid_it = (*it)->pids.begin(); |
| pid_it != (*it)->pids.end(); ++pid_it) { |
| ALOGI(" %d", *pid_it); |
| } |
| } |
| |
| ALOGI(" Min PSS %0.4fM", (*it)->min_pss_kb/1024.0); |
| ALOGI(" Avg PSS %0.4fM", (*it)->avg_pss_kb/1024.0); |
| ALOGI(" Max PSS %0.4fM", (*it)->max_pss_kb/1024.0); |
| ALOGI(" Last PSS %0.4fM", (*it)->last_pss_kb/1024.0); |
| } |
| } |
| |
| void usage() { |
| printf("Usage: memtrack [--verbose | --quiet] [--scan_delay TIME_SECS]\n"); |
| printf(" --scan_delay TIME_SECS\n"); |
| printf(" The amount of delay in seconds between scans.\n"); |
| printf(" --verbose\n"); |
| printf(" Print information about the scans to stdout only.\n"); |
| printf(" --quiet\n"); |
| printf(" Nothing will be printed to stdout.\n"); |
| printf(" All scan data is dumped to the android log using the tag %s\n", |
| LOG_TAG); |
| } |
| |
| int SignalReceived = 0; |
| |
| int SignalsToHandle[] = { |
| SIGTSTP, |
| SIGINT, |
| SIGHUP, |
| SIGPIPE, |
| SIGUSR1, |
| }; |
| |
| void handleSignal(int signo) { |
| if (SignalReceived == 0) { |
| SignalReceived = signo; |
| } |
| } |
| |
| int main(int argc, char **argv) { |
| if (geteuid() != 0) { |
| printf("Must be run as root.\n"); |
| exit(1); |
| } |
| |
| bool verbose = false; |
| bool quiet = false; |
| unsigned int scan_delay_sec = DEFAULT_SLEEP_DELAY_SECONDS; |
| for (int i = 1; i < argc; i++) { |
| if (strcmp(argv[i], "--verbose") == 0) { |
| verbose = true; |
| } else if (strcmp(argv[i], "--quiet") == 0) { |
| quiet = true; |
| } else if (strcmp(argv[i], "--scan_delay") == 0) { |
| if (i+1 == argc) { |
| printf("The %s options requires a single argument.\n", argv[i]); |
| usage(); |
| exit(1); |
| } |
| scan_delay_sec = atoi(argv[++i]); |
| } else { |
| printf("Unknown option %s\n", argv[i]); |
| usage(); |
| exit(1); |
| } |
| } |
| if (quiet && verbose) { |
| printf("Both --quiet and --verbose cannot be specified.\n"); |
| usage(); |
| exit(1); |
| } |
| |
| // Set up the signal handlers. |
| for (size_t i = 0; i < sizeof(SignalsToHandle)/sizeof(int); i++) { |
| if (signal(SignalsToHandle[i], handleSignal) == SIG_ERR) { |
| printf("Unable to handle signal %d\n", SignalsToHandle[i]); |
| exit(1); |
| } |
| } |
| |
| ProcessInfo proc_info; |
| |
| if (!quiet) { |
| printf("Hit Ctrl-Z or send SIGUSR1 to pid %d to print the current list of\n", |
| getpid()); |
| printf("processes.\n"); |
| printf("Hit Ctrl-C to print the list of processes and terminate.\n"); |
| } |
| |
| struct timespec t; |
| unsigned long long nsecs; |
| while (true) { |
| if (verbose) { |
| memset(&t, 0, sizeof(t)); |
| clock_gettime(CLOCK_MONOTONIC, &t); |
| nsecs = (unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec; |
| } |
| proc_info.scan(); |
| if (verbose) { |
| memset(&t, 0, sizeof(t)); |
| clock_gettime(CLOCK_MONOTONIC, &t); |
| nsecs = ((unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec) - nsecs; |
| printf("Scan Time %0.4f\n", ((double)nsecs)/NS_PER_SEC); |
| } |
| |
| if (SignalReceived != 0) { |
| proc_info.dumpToLog(); |
| if (SignalReceived != SIGUSR1 && SignalReceived != SIGTSTP) { |
| if (!quiet) { |
| printf("Terminating...\n"); |
| } |
| exit(1); |
| } |
| SignalReceived = 0; |
| } |
| sleep(scan_delay_sec); |
| } |
| } |