summaryrefslogtreecommitdiff
path: root/memtrack
diff options
context:
space:
mode:
authorChristopher Ferris <cferris@google.com>2013-05-20 17:24:15 -0700
committerChristopher Ferris <cferris@google.com>2013-05-22 14:16:19 -0700
commit91f4410f49f8f701f9001c447b5bc6162c348f6b (patch)
treee1bd8df553dec702595fced5a49df4728a667178 /memtrack
parent9e962bdfec55e63c0b1016ac57e5c7dbe23e65c0 (diff)
downloadextras-91f4410f49f8f701f9001c447b5bc6162c348f6b.tar.gz
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
Diffstat (limited to 'memtrack')
-rw-r--r--memtrack/Android.mk55
-rw-r--r--memtrack/memtrack.cpp373
-rw-r--r--memtrack/memtrack.h123
3 files changed, 551 insertions, 0 deletions
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 <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);
+ }
+}
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 <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#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<int> pids;
+} process_info_t;
+typedef std::map<std::string, process_info_t> processes_t;
+
+typedef struct {
+ size_t pss_kb;
+
+ std::vector<int> pids;
+} cur_process_info_t;
+typedef std::map<std::string, cur_process_info_t> 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<const process_info_t *> 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__