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