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
diff --git a/memtrack/memtrack.cpp b/memtrack/memtrack.cpp
new file mode 100644
index 0000000..ab45fd0
--- /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);
+  }
+}