374 lines
9.4 KiB
C++
374 lines
9.4 KiB
C++
/*
|
|
* 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 <stdio.h>
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
|
|
#include <cutils/log.h>
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include "memtrack.h"
|
|
|
|
#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);
|
|
}
|
|
}
|