1 /*
2  * Copyright 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "memtrack.h"
18 
19 #include <ctype.h>
20 #include <dirent.h>
21 #include <fcntl.h>
22 #include <limits.h>
23 #include <signal.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 
30 #include <cutils/log.h>
31 
32 #include <algorithm>
33 #include <vector>
34 
35 #ifdef LOG_TAG
36 #undef LOG_TAG
37 #endif
38 #define LOG_TAG "MemTracker"
39 
FileData(char * filename,char * buffer,size_t buffer_len)40 FileData::FileData(char *filename, char *buffer, size_t buffer_len)
41     : data_(buffer), max_(buffer_len), cur_idx_(0), len_(0),
42       read_complete_(false) {
43   fd_ = open(filename, O_RDONLY);
44   if (fd_ < 0) {
45     read_complete_ = true;
46   }
47 }
48 
~FileData()49 FileData::~FileData() {
50   if (fd_ >= 0) {
51     close(fd_);
52   }
53 }
54 
isAvail(size_t bytes_needed)55 bool FileData::isAvail(size_t bytes_needed) {
56   if (cur_idx_ + bytes_needed < len_) {
57     return true;
58   }
59 
60   if (read_complete_) {
61     return false;
62   }
63 
64   if (cur_idx_ != len_) {
65     // Copy the leftover to the front of the buffer.
66     len_ = len_ - cur_idx_;
67     memcpy(data_, data_ + cur_idx_, len_);
68   }
69 
70   ssize_t bytes;
71   cur_idx_ = 0;
72   while (cur_idx_ + bytes_needed >= len_) {
73     bytes = read(fd_, data_ + len_, max_ - len_);
74     if (bytes == 0 || bytes == -1) {
75       read_complete_;
76       break;
77     }
78     len_ += bytes;
79   }
80 
81   return cur_idx_ + bytes_needed < len_;
82 }
83 
getPss(size_t * pss)84 bool FileData::getPss(size_t *pss) {
85   size_t value;
86   while (true) {
87     if (!isAvail(4)) {
88       return false;
89     }
90 
91     if (data_[cur_idx_] != 'P' || data_[cur_idx_+1] != 's' ||
92         data_[cur_idx_+2] != 's' || data_[cur_idx_+3] != ':') {
93       // Consume the rest of the line.
94       while (isAvail(1) && data_[cur_idx_++] != '\n');
95     } else {
96       cur_idx_ += 4;
97       while (isAvail(1) && isspace(data_[cur_idx_])) {
98         cur_idx_++;
99       }
100 
101       value = 0;
102       while (isAvail(1) && isdigit(data_[cur_idx_])) {
103         value = value * 10 + data_[cur_idx_] - '0';
104         cur_idx_++;
105       }
106       *pss = value;
107 
108       // Consume the rest of the line.
109       while (isAvail(1) && data_[cur_idx_++] != '\n');
110 
111       return true;
112     }
113   }
114 }
115 
116 const char *ProcessInfo::kProc = "/proc/";
117 const char *ProcessInfo::kCmdline = "/cmdline";
118 const char *ProcessInfo::kSmaps = "/smaps";
119 
ProcessInfo()120 ProcessInfo::ProcessInfo() {
121   memcpy(proc_file_, kProc, kProcLen);
122 }
123 
~ProcessInfo()124 ProcessInfo::~ProcessInfo() {
125 }
126 
getInformation(int pid,char * pid_str,size_t pid_str_len)127 bool ProcessInfo::getInformation(int pid, char *pid_str, size_t pid_str_len) {
128   memcpy(proc_file_ + kProcLen, pid_str, pid_str_len);
129   memcpy(proc_file_ + kProcLen + pid_str_len, kCmdline, kCmdlineLen);
130 
131   // Read the cmdline for the process.
132   int fd = open(proc_file_, O_RDONLY);
133   if (fd < 0) {
134     return false;
135   }
136 
137   ssize_t bytes = read(fd, cmd_name_, sizeof(cmd_name_));
138   close(fd);
139   if (bytes == -1 || bytes == 0) {
140     return false;
141   }
142 
143   memcpy(proc_file_ + kProcLen + pid_str_len, kSmaps, kSmapsLen);
144   FileData smaps(proc_file_, buffer_, sizeof(buffer_));
145 
146   cur_process_info_t process_info;
147   size_t pss_kb;
148   process_info.pss_kb = 0;
149   while (smaps.getPss(&pss_kb)) {
150     process_info.pss_kb += pss_kb;
151   }
152 
153   if (cur_.count(cmd_name_) == 0) {
154     cur_[cmd_name_] = process_info;
155   } else {
156     cur_[cmd_name_].pss_kb += process_info.pss_kb;
157   }
158   cur_[cmd_name_].pids.push_back(pid);
159 
160   return true;
161 }
162 
scan()163 void ProcessInfo::scan() {
164   DIR *proc_dir = opendir(kProc);
165   if (proc_dir == NULL) {
166     perror("Cannot open directory.\n");
167     exit(1);
168   }
169 
170   // Clear any current pids.
171   for (processes_t::iterator it = all_.begin(); it != all_.end(); ++it) {
172     it->second.pids.clear();
173   }
174 
175   struct dirent *dir_data;
176   int len;
177   bool is_pid;
178   size_t pid;
179   cur_.clear();
180   while ((dir_data = readdir(proc_dir))) {
181     // Check if the directory entry represents a pid.
182     len = strlen(dir_data->d_name);
183     is_pid = true;
184     pid = 0;
185     for (int i = 0; i < len; i++) {
186       if (!isdigit(dir_data->d_name[i])) {
187         is_pid = false;
188         break;
189       }
190       pid = pid * 10 + dir_data->d_name[i] - '0';
191     }
192     if (is_pid) {
193       getInformation(pid, dir_data->d_name, len);
194     }
195   }
196   closedir(proc_dir);
197 
198   // Loop through the current processes and add them into our real list.
199   for (cur_processes_t::const_iterator it = cur_.begin();
200        it != cur_.end(); ++it) {
201 
202     if (all_.count(it->first) == 0) {
203       // Initialize all of the variables.
204       all_[it->first].num_samples = 0;
205       all_[it->first].name = it->first;
206       all_[it->first].avg_pss_kb = 0;
207       all_[it->first].min_pss_kb = 0;
208       all_[it->first].max_pss_kb = 0;
209     }
210 
211     if (it->second.pids.size() > all_[it->first].max_num_pids) {
212       all_[it->first].max_num_pids = it->second.pids.size();
213     }
214 
215     all_[it->first].pids = it->second.pids;
216 
217     if (it->second.pss_kb > all_[it->first].max_pss_kb) {
218       all_[it->first].max_pss_kb = it->second.pss_kb;
219     }
220 
221     if (all_[it->first].min_pss_kb == 0 ||
222         it->second.pss_kb < all_[it->first].min_pss_kb) {
223       all_[it->first].min_pss_kb = it->second.pss_kb;
224     }
225 
226     all_[it->first].last_pss_kb = it->second.pss_kb;
227 
228     computeAvg(&all_[it->first].avg_pss_kb, it->second.pss_kb,
229                all_[it->first].num_samples);
230     all_[it->first].num_samples++;
231   }
232 }
233 
comparePss(const process_info_t * first,const process_info_t * second)234 bool comparePss(const process_info_t *first, const process_info_t *second) {
235   return first->max_pss_kb > second->max_pss_kb;
236 }
237 
dumpToLog()238 void ProcessInfo::dumpToLog() {
239   list_.clear();
240   for (processes_t::const_iterator it = all_.begin(); it != all_.end(); ++it) {
241     list_.push_back(&it->second);
242   }
243 
244   // Now sort the list.
245   std::sort(list_.begin(), list_.end(), comparePss);
246 
247   ALOGI("Dumping process list");
248   for (std::vector<const process_info_t *>::const_iterator it = list_.begin();
249        it != list_.end(); ++it) {
250     ALOGI("  Name: %s", (*it)->name.c_str());
251     ALOGI("    Max running processes: %d", (*it)->max_num_pids);
252     if ((*it)->pids.size() > 0) {
253       ALOGI("    Currently running pids:");
254       for (std::vector<int>::const_iterator pid_it = (*it)->pids.begin();
255            pid_it != (*it)->pids.end(); ++pid_it) {
256         ALOGI("      %d", *pid_it);
257       }
258     }
259 
260     ALOGI("    Min  PSS %0.4fM", (*it)->min_pss_kb/1024.0);
261     ALOGI("    Avg  PSS %0.4fM", (*it)->avg_pss_kb/1024.0);
262     ALOGI("    Max  PSS %0.4fM", (*it)->max_pss_kb/1024.0);
263     ALOGI("    Last PSS %0.4fM", (*it)->last_pss_kb/1024.0);
264   }
265 }
266 
usage()267 void usage() {
268   printf("Usage: memtrack [--verbose | --quiet] [--scan_delay TIME_SECS]\n");
269   printf("  --scan_delay TIME_SECS\n");
270   printf("    The amount of delay in seconds between scans.\n");
271   printf("  --verbose\n");
272   printf("    Print information about the scans to stdout only.\n");
273   printf("  --quiet\n");
274   printf("    Nothing will be printed to stdout.\n");
275   printf("  All scan data is dumped to the android log using the tag %s\n",
276          LOG_TAG);
277 }
278 
279 int SignalReceived = 0;
280 
281 int SignalsToHandle[] = {
282   SIGTSTP,
283   SIGINT,
284   SIGHUP,
285   SIGPIPE,
286   SIGUSR1,
287 };
288 
handleSignal(int signo)289 void handleSignal(int signo) {
290   if (SignalReceived == 0) {
291     SignalReceived = signo;
292   }
293 }
294 
main(int argc,char ** argv)295 int main(int argc, char **argv) {
296   if (geteuid() != 0) {
297     printf("Must be run as root.\n");
298     exit(1);
299   }
300 
301   bool verbose = false;
302   bool quiet = false;
303   unsigned int scan_delay_sec = DEFAULT_SLEEP_DELAY_SECONDS;
304   for (int i = 1; i < argc; i++) {
305     if (strcmp(argv[i], "--verbose") == 0) {
306       verbose = true;
307     } else if (strcmp(argv[i], "--quiet") == 0) {
308       quiet = true;
309     } else if (strcmp(argv[i], "--scan_delay") == 0) {
310       if (i+1 == argc) {
311         printf("The %s options requires a single argument.\n", argv[i]);
312         usage();
313         exit(1);
314       }
315       scan_delay_sec = atoi(argv[++i]);
316     } else {
317       printf("Unknown option %s\n", argv[i]);
318       usage();
319       exit(1);
320     }
321   }
322   if (quiet && verbose) {
323     printf("Both --quiet and --verbose cannot be specified.\n");
324     usage();
325     exit(1);
326   }
327 
328   // Set up the signal handlers.
329   for (size_t i = 0; i < sizeof(SignalsToHandle)/sizeof(int); i++) {
330     if (signal(SignalsToHandle[i], handleSignal) == SIG_ERR) {
331       printf("Unable to handle signal %d\n", SignalsToHandle[i]);
332       exit(1);
333     }
334   }
335 
336   ProcessInfo proc_info;
337 
338   if (!quiet) {
339     printf("Hit Ctrl-Z or send SIGUSR1 to pid %d to print the current list of\n",
340            getpid());
341     printf("processes.\n");
342     printf("Hit Ctrl-C to print the list of processes and terminate.\n");
343   }
344 
345   struct timespec t;
346   unsigned long long nsecs;
347   while (true) {
348     if (verbose) {
349       memset(&t, 0, sizeof(t));
350       clock_gettime(CLOCK_MONOTONIC, &t);
351       nsecs = (unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec;
352     }
353     proc_info.scan();
354     if (verbose) {
355       memset(&t, 0, sizeof(t));
356       clock_gettime(CLOCK_MONOTONIC, &t);
357       nsecs = ((unsigned long long)t.tv_sec*NS_PER_SEC + t.tv_nsec) - nsecs;
358       printf("Scan Time %0.4f\n", ((double)nsecs)/NS_PER_SEC);
359     }
360 
361     if (SignalReceived != 0) {
362       proc_info.dumpToLog();
363       if (SignalReceived != SIGUSR1 && SignalReceived != SIGTSTP) {
364         if (!quiet) {
365           printf("Terminating...\n");
366         }
367         exit(1);
368       }
369       SignalReceived = 0;
370     }
371     sleep(scan_delay_sec);
372   }
373 }
374