From 91f4410f49f8f701f9001c447b5bc6162c348f6b Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Mon, 20 May 2013 17:24:15 -0700 Subject: Add the memtrack utility. This utility attempts to track the PSS usage of all of the processes in the system. It will keep track of the min/max/avg/last PSS for every process it has ever seen and dump that information when the program is terminated or when a USR1 or TSTP signal is sent to the process. Change-Id: Id9364d5121b70f80b8335c379a241bee2fbdb019 --- memtrack/Android.mk | 55 ++++++++ memtrack/memtrack.cpp | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++ memtrack/memtrack.h | 123 +++++++++++++++++ 3 files changed, 551 insertions(+) create mode 100644 memtrack/Android.mk create mode 100644 memtrack/memtrack.cpp create mode 100644 memtrack/memtrack.h (limited to 'memtrack') diff --git a/memtrack/Android.mk b/memtrack/Android.mk new file mode 100644 index 00000000..66759bb5 --- /dev/null +++ b/memtrack/Android.mk @@ -0,0 +1,55 @@ +# Copyright (C) 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. + +LOCAL_PATH:= $(call my-dir) + +src_files := \ + memtrack.cpp + +includes := \ + bionic \ + external/stlport/stlport \ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(src_files) + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := memtrack_share + +LOCAL_C_INCLUDES += $(includes) +LOCAL_SHARED_LIBRARIES := \ + libc \ + libstlport \ + liblog \ + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(src_files) +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := memtrack + +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_C_INCLUDES += $(includes) +LOCAL_STATIC_LIBRARIES := \ + libc \ + libstdc++ \ + libstlport_static \ + liblog \ + +include $(BUILD_EXECUTABLE) diff --git a/memtrack/memtrack.cpp b/memtrack/memtrack.cpp new file mode 100644 index 00000000..ab45fd0c --- /dev/null +++ b/memtrack/memtrack.cpp @@ -0,0 +1,373 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#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_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::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); + } +} diff --git a/memtrack/memtrack.h b/memtrack/memtrack.h new file mode 100644 index 00000000..602fdb22 --- /dev/null +++ b/memtrack/memtrack.h @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#ifndef __MEMTRACK_H__ +#define __MEMTRACK_H__ + +#include + +#include +#include +#include + +#define DEFAULT_SLEEP_DELAY_SECONDS 5 +#define NS_PER_SEC 1000000000LL + +class FileData { +public: + FileData(char *filename, char *buffer, size_t buffer_len); + ~FileData(); + + // Get the PSS information from the file data. If there are no more + // PSS values to be found, return false. + bool getPss(size_t *pss); + + // Check if there is at least bytes available in the file data. + bool isAvail(size_t bytes); + +private: + int fd_; + char *data_; + size_t max_; + size_t cur_idx_; + size_t len_; + bool read_complete_; +}; + +typedef struct { + std::string name; + + size_t max_num_pids; + + size_t num_samples; + double avg_pss_kb; + size_t min_pss_kb; + size_t max_pss_kb; + size_t last_pss_kb; + + std::vector pids; +} process_info_t; +typedef std::map processes_t; + +typedef struct { + size_t pss_kb; + + std::vector pids; +} cur_process_info_t; +typedef std::map cur_processes_t; + +class ProcessInfo { +public: + ProcessInfo(); + ~ProcessInfo(); + + // Get the information about a single process. + bool getInformation(int pid, char *pid_str, size_t pid_str_len); + + // Scan all of the running processes. + void scan(); + + // Dump the information about all of the processes in the system to the log. + void dumpToLog(); + +private: + static const size_t kBufferLen = 4096; + static const size_t kCmdNameLen = 1024; + + static const char *kProc; + static const size_t kProcLen = 6; + + static const char *kCmdline; + static const size_t kCmdlineLen = 9; // Includes \0 at end of string. + + static const char *kSmaps; + static const size_t kSmapsLen = 7; // Includes \0 at end of string. + + static const char *kStatus; + static const size_t kStatusLen = 8; // Includes \0 at end of string. + + static const size_t kInitialEntries = 1000; + + char proc_file_[PATH_MAX]; + char buffer_[kBufferLen]; + + char cmd_name_[kCmdNameLen]; + + // Minimize a need for a lot of allocations by keeping our maps and + // lists in this object. + processes_t all_; + cur_processes_t cur_; + std::vector list_; + + // Compute a running average. + static inline void computeAvg(double *running_avg, size_t cur_avg, + size_t num_samples) { + *running_avg = (*running_avg/(num_samples+1))*num_samples + + (double)cur_avg/(num_samples+1); + } +}; + +#endif // __MEMTRACK_H__ -- cgit v1.2.3