- add sources.
[platform/framework/web/crosswalk.git] / src / tools / android / memdump / memdump.cc
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <fcntl.h>
6 #include <signal.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9
10 #include <algorithm>
11 #include <cstring>
12 #include <fstream>
13 #include <iostream>
14 #include <limits>
15 #include <string>
16 #include <utility>
17 #include <vector>
18
19 #include "base/base64.h"
20 #include "base/basictypes.h"
21 #include "base/bind.h"
22 #include "base/callback_helpers.h"
23 #include "base/containers/hash_tables.h"
24 #include "base/file_util.h"
25 #include "base/logging.h"
26 #include "base/memory/scoped_ptr.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/string_piece.h"
29 #include "base/strings/string_split.h"
30 #include "base/strings/stringprintf.h"
31
32 namespace {
33
34 class BitSet {
35  public:
36   void resize(size_t nbits) {
37     data_.resize((nbits + 7) / 8);
38   }
39
40   void set(uint32 bit) {
41     const uint32 byte_idx = bit / 8;
42     CHECK(byte_idx < data_.size());
43     data_[byte_idx] |= (1 << (bit & 7));
44   }
45
46   std::string AsB64String() const {
47     std::string bits(&data_[0], data_.size());
48     std::string b64_string;
49     base::Base64Encode(bits, &b64_string);
50     return b64_string;
51   }
52
53  private:
54   std::vector<char> data_;
55 };
56
57 // An entry in /proc/<pid>/pagemap.
58 struct PageMapEntry {
59   uint64 page_frame_number : 55;
60   uint unused : 8;
61   uint present : 1;
62 };
63
64 // Describes a memory page.
65 struct PageInfo {
66   int64 page_frame_number; // Physical page id, also known as PFN.
67   int64 flags;
68   int32 times_mapped;
69 };
70
71 struct PageCount {
72   PageCount() : total_count(0), unevictable_count(0) {}
73
74   int total_count;
75   int unevictable_count;
76 };
77
78 struct MemoryMap {
79   std::string name;
80   std::string flags;
81   uint start_address;
82   uint end_address;
83   uint offset;
84   PageCount private_pages;
85   // app_shared_pages[i] contains the number of pages mapped in i+2 processes
86   // (only among the processes that are being analyzed).
87   std::vector<PageCount> app_shared_pages;
88   PageCount other_shared_pages;
89   std::vector<PageInfo> committed_pages;
90   // committed_pages_bits is a bitset reflecting the present bit for all the
91   // virtual pages of the mapping.
92   BitSet committed_pages_bits;
93 };
94
95 struct ProcessMemory {
96   pid_t pid;
97   std::vector<MemoryMap> memory_maps;
98 };
99
100 bool PageIsUnevictable(const PageInfo& page_info) {
101   // These constants are taken from kernel-page-flags.h.
102   const int KPF_DIRTY = 4; // Note that only file-mapped pages can be DIRTY.
103   const int KPF_ANON = 12; // Anonymous pages are dirty per definition.
104   const int KPF_UNEVICTABLE = 18;
105   const int KPF_MLOCKED = 33;
106
107   return (page_info.flags & ((1ll << KPF_DIRTY) |
108                              (1ll << KPF_ANON) |
109                              (1ll << KPF_UNEVICTABLE) |
110                              (1ll << KPF_MLOCKED))) ?
111                              true : false;
112 }
113
114 // Number of times a physical page is mapped in a process.
115 typedef base::hash_map<uint64, int> PFNMap;
116
117 // Parses lines from /proc/<PID>/maps, e.g.:
118 // 401e7000-401f5000 r-xp 00000000 103:02 158       /system/bin/linker
119 bool ParseMemoryMapLine(const std::string& line,
120                         std::vector<std::string>* tokens,
121                         MemoryMap* memory_map) {
122   tokens->clear();
123   base::SplitString(line, ' ', tokens);
124   if (tokens->size() < 2)
125     return false;
126   const int addr_len = 8;
127   const std::string& addr_range = tokens->at(0);
128   if (addr_range.length() != addr_len + 1 + addr_len)
129     return false;
130   uint64 tmp = 0;
131   if (!base::HexStringToUInt64(
132           base::StringPiece(
133               addr_range.begin(), addr_range.begin() + addr_len),
134           &tmp)) {
135     return false;
136   }
137   memory_map->start_address = static_cast<uint>(tmp);
138   const int end_addr_start_pos = addr_len + 1;
139   if (!base::HexStringToUInt64(
140           base::StringPiece(
141               addr_range.begin() + end_addr_start_pos,
142               addr_range.begin() + end_addr_start_pos + addr_len),
143           &tmp)) {
144     return false;
145   }
146   memory_map->end_address = static_cast<uint>(tmp);
147   if (tokens->at(1).size() != strlen("rwxp"))
148     return false;
149   memory_map->flags.swap(tokens->at(1));
150   if (!base::HexStringToUInt64(tokens->at(2), &tmp))
151     return false;
152   memory_map->offset = static_cast<uint>(tmp);
153   memory_map->committed_pages_bits.resize(
154       (memory_map->end_address - memory_map->start_address) / PAGE_SIZE);
155   const int map_name_index = 5;
156   if (tokens->size() >= map_name_index + 1) {
157     for (std::vector<std::string>::const_iterator it =
158              tokens->begin() + map_name_index; it != tokens->end(); ++it) {
159       if (!it->empty()) {
160         if (!memory_map->name.empty())
161           memory_map->name.append(" ");
162         memory_map->name.append(*it);
163       }
164     }
165   }
166   return true;
167 }
168
169 // Reads sizeof(T) bytes from file |fd| at |offset|.
170 template <typename T>
171 bool ReadFromFileAtOffset(int fd, off_t offset, T* value) {
172   if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) {
173     PLOG(ERROR) << "lseek";
174     return false;
175   }
176   ssize_t bytes = read(fd, value, sizeof(*value));
177   if (bytes != sizeof(*value) && bytes != 0) {
178     PLOG(ERROR) << "read";
179     return false;
180   }
181   return true;
182 }
183
184 // Fills |process_maps| in with the process memory maps identified by |pid|.
185 bool GetProcessMaps(pid_t pid, std::vector<MemoryMap>* process_maps) {
186   std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str());
187   if (!maps_file.good()) {
188     PLOG(ERROR) << "open";
189     return false;
190   }
191   std::string line;
192   std::vector<std::string> tokens;
193   while (std::getline(maps_file, line) && !line.empty()) {
194     MemoryMap memory_map = {};
195     if (!ParseMemoryMapLine(line, &tokens, &memory_map)) {
196       LOG(ERROR) << "Could not parse line: " << line;
197       return false;
198     }
199     process_maps->push_back(memory_map);
200   }
201   return true;
202 }
203
204 // Fills |committed_pages| in with the set of committed pages contained in the
205 // provided memory map.
206 bool GetPagesForMemoryMap(int pagemap_fd,
207                           const MemoryMap& memory_map,
208                           std::vector<PageInfo>* committed_pages,
209                           BitSet* committed_pages_bits) {
210   for (uint addr = memory_map.start_address, page_index = 0;
211        addr < memory_map.end_address;
212        addr += PAGE_SIZE, ++page_index) {
213     DCHECK_EQ(0, addr % PAGE_SIZE);
214     PageMapEntry page_map_entry = {};
215     COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size);
216     const off64_t offset = addr / PAGE_SIZE;
217     if (!ReadFromFileAtOffset(pagemap_fd, offset, &page_map_entry))
218       return false;
219     if (page_map_entry.present) {  // Ignore non-committed pages.
220       if (page_map_entry.page_frame_number == 0)
221         continue;
222       PageInfo page_info = {};
223       page_info.page_frame_number = page_map_entry.page_frame_number;
224       committed_pages->push_back(page_info);
225       committed_pages_bits->set(page_index);
226     }
227   }
228   return true;
229 }
230
231 // Fills |committed_pages| with mapping count and flags information gathered
232 // looking-up /proc/kpagecount and /proc/kpageflags.
233 bool SetPagesInfo(int pagecount_fd,
234                   int pageflags_fd,
235                   std::vector<PageInfo>* pages) {
236   for (std::vector<PageInfo>::iterator it = pages->begin();
237        it != pages->end(); ++it) {
238     PageInfo* const page_info = &*it;
239     int64 times_mapped;
240     if (!ReadFromFileAtOffset(
241             pagecount_fd, page_info->page_frame_number, &times_mapped)) {
242       return false;
243     }
244     DCHECK(times_mapped <= std::numeric_limits<int32_t>::max());
245     page_info->times_mapped = static_cast<int32>(times_mapped);
246
247     int64 page_flags;
248     if (!ReadFromFileAtOffset(
249             pageflags_fd, page_info->page_frame_number, &page_flags)) {
250       return false;
251     }
252     page_info->flags = page_flags;
253   }
254   return true;
255 }
256
257 // Fills in the provided vector of Page Frame Number maps. This lets
258 // ClassifyPages() know how many times each page is mapped in the processes.
259 void FillPFNMaps(const std::vector<ProcessMemory>& processes_memory,
260                  std::vector<PFNMap>* pfn_maps) {
261   int current_process_index = 0;
262   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
263        it != processes_memory.end(); ++it, ++current_process_index) {
264     const std::vector<MemoryMap>& memory_maps = it->memory_maps;
265     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
266          it != memory_maps.end(); ++it) {
267       const std::vector<PageInfo>& pages = it->committed_pages;
268       for (std::vector<PageInfo>::const_iterator it = pages.begin();
269            it != pages.end(); ++it) {
270         const PageInfo& page_info = *it;
271         PFNMap* const pfn_map = &(*pfn_maps)[current_process_index];
272         const std::pair<PFNMap::iterator, bool> result = pfn_map->insert(
273             std::make_pair(page_info.page_frame_number, 0));
274         ++result.first->second;
275       }
276     }
277   }
278 }
279
280 // Sets the private_count/app_shared_pages/other_shared_count fields of the
281 // provided memory maps for each process.
282 void ClassifyPages(std::vector<ProcessMemory>* processes_memory) {
283   std::vector<PFNMap> pfn_maps(processes_memory->size());
284   FillPFNMaps(*processes_memory, &pfn_maps);
285   // Hash set keeping track of the physical pages mapped in a single process so
286   // that they can be counted only once.
287   std::hash_set<uint64> physical_pages_mapped_in_process;
288
289   for (std::vector<ProcessMemory>::iterator it = processes_memory->begin();
290        it != processes_memory->end(); ++it) {
291     std::vector<MemoryMap>* const memory_maps = &it->memory_maps;
292     physical_pages_mapped_in_process.clear();
293     for (std::vector<MemoryMap>::iterator it = memory_maps->begin();
294          it != memory_maps->end(); ++it) {
295       MemoryMap* const memory_map = &*it;
296       const size_t processes_count = processes_memory->size();
297       memory_map->app_shared_pages.resize(processes_count - 1);
298       const std::vector<PageInfo>& pages = memory_map->committed_pages;
299       for (std::vector<PageInfo>::const_iterator it = pages.begin();
300            it != pages.end(); ++it) {
301         const PageInfo& page_info = *it;
302         if (page_info.times_mapped == 1) {
303           ++memory_map->private_pages.total_count;
304           if (PageIsUnevictable(page_info))
305             ++memory_map->private_pages.unevictable_count;
306           continue;
307         }
308         const uint64 page_frame_number = page_info.page_frame_number;
309         const std::pair<std::hash_set<uint64>::iterator, bool> result =
310             physical_pages_mapped_in_process.insert(page_frame_number);
311         const bool did_insert = result.second;
312         if (!did_insert) {
313           // This physical page (mapped multiple times in the same process) was
314           // already counted.
315           continue;
316         }
317         // See if the current physical page is also mapped in the other
318         // processes that are being analyzed.
319         int times_mapped = 0;
320         int mapped_in_processes_count = 0;
321         for (std::vector<PFNMap>::const_iterator it = pfn_maps.begin();
322              it != pfn_maps.end(); ++it) {
323           const PFNMap& pfn_map = *it;
324           const PFNMap::const_iterator found_it = pfn_map.find(
325               page_frame_number);
326           if (found_it == pfn_map.end())
327             continue;
328           ++mapped_in_processes_count;
329           times_mapped += found_it->second;
330         }
331         PageCount* page_count_to_update = NULL;
332         if (times_mapped == page_info.times_mapped) {
333           // The physical page is only mapped in the processes that are being
334           // analyzed.
335           if (mapped_in_processes_count > 1) {
336             // The physical page is mapped in multiple processes.
337             page_count_to_update =
338                 &memory_map->app_shared_pages[mapped_in_processes_count - 2];
339           } else {
340             // The physical page is mapped multiple times in the same process.
341             page_count_to_update = &memory_map->private_pages;
342           }
343         } else {
344           page_count_to_update = &memory_map->other_shared_pages;
345         }
346         ++page_count_to_update->total_count;
347         if (PageIsUnevictable(page_info))
348           ++page_count_to_update->unevictable_count;
349       }
350     }
351   }
352 }
353
354 void AppendAppSharedField(const std::vector<PageCount>& app_shared_pages,
355                           std::string* out) {
356   out->append("[");
357   for (std::vector<PageCount>::const_iterator it = app_shared_pages.begin();
358        it != app_shared_pages.end(); ++it) {
359     out->append(base::IntToString(it->total_count * PAGE_SIZE));
360     out->append(":");
361     out->append(base::IntToString(it->unevictable_count * PAGE_SIZE));
362     if (it + 1 != app_shared_pages.end())
363       out->append(",");
364   }
365   out->append("]");
366 }
367
368 void DumpProcessesMemoryMapsInShortFormat(
369     const std::vector<ProcessMemory>& processes_memory) {
370   const int KB_PER_PAGE = PAGE_SIZE >> 10;
371   std::vector<int> totals_app_shared(processes_memory.size());
372   std::string buf;
373   std::cout << "pid\tprivate\t\tshared_app\tshared_other (KB)\n";
374   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
375        it != processes_memory.end(); ++it) {
376     const ProcessMemory& process_memory = *it;
377     std::fill(totals_app_shared.begin(), totals_app_shared.end(), 0);
378     int total_private = 0, total_other_shared = 0;
379     const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
380     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
381          it != memory_maps.end(); ++it) {
382       const MemoryMap& memory_map = *it;
383       total_private += memory_map.private_pages.total_count;
384       for (size_t i = 0; i < memory_map.app_shared_pages.size(); ++i)
385         totals_app_shared[i] += memory_map.app_shared_pages[i].total_count;
386       total_other_shared += memory_map.other_shared_pages.total_count;
387     }
388     double total_app_shared = 0;
389     for (size_t i = 0; i < totals_app_shared.size(); ++i)
390       total_app_shared += static_cast<double>(totals_app_shared[i]) / (i + 2);
391     base::SStringPrintf(
392         &buf, "%d\t%d\t\t%d\t\t%d\n",
393         process_memory.pid,
394         total_private * KB_PER_PAGE,
395         static_cast<int>(total_app_shared) * KB_PER_PAGE,
396         total_other_shared * KB_PER_PAGE);
397     std::cout << buf;
398   }
399 }
400
401 void DumpProcessesMemoryMapsInExtendedFormat(
402     const std::vector<ProcessMemory>& processes_memory) {
403   std::string buf;
404   std::string app_shared_buf;
405   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
406        it != processes_memory.end(); ++it) {
407     const ProcessMemory& process_memory = *it;
408     std::cout << "[ PID=" << process_memory.pid << "]" << '\n';
409     const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
410     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
411          it != memory_maps.end(); ++it) {
412       const MemoryMap& memory_map = *it;
413       app_shared_buf.clear();
414       AppendAppSharedField(memory_map.app_shared_pages, &app_shared_buf);
415       base::SStringPrintf(
416           &buf,
417           "%x-%x %s %x private_unevictable=%d private=%d shared_app=%s "
418           "shared_other_unevictable=%d shared_other=%d \"%s\" [%s]\n",
419           memory_map.start_address,
420           memory_map.end_address,
421           memory_map.flags.c_str(),
422           memory_map.offset,
423           memory_map.private_pages.unevictable_count * PAGE_SIZE,
424           memory_map.private_pages.total_count * PAGE_SIZE,
425           app_shared_buf.c_str(),
426           memory_map.other_shared_pages.unevictable_count * PAGE_SIZE,
427           memory_map.other_shared_pages.total_count * PAGE_SIZE,
428           memory_map.name.c_str(),
429           memory_map.committed_pages_bits.AsB64String().c_str());
430       std::cout << buf;
431     }
432   }
433 }
434
435 bool CollectProcessMemoryInformation(int page_count_fd,
436                                      int page_flags_fd,
437                                      ProcessMemory* process_memory) {
438   const pid_t pid = process_memory->pid;
439   int pagemap_fd = open(
440       base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY);
441   if (pagemap_fd < 0) {
442     PLOG(ERROR) << "open";
443     return false;
444   }
445   file_util::ScopedFD auto_closer(&pagemap_fd);
446   std::vector<MemoryMap>* const process_maps = &process_memory->memory_maps;
447   if (!GetProcessMaps(pid, process_maps))
448     return false;
449   for (std::vector<MemoryMap>::iterator it = process_maps->begin();
450        it != process_maps->end(); ++it) {
451     std::vector<PageInfo>* const committed_pages = &it->committed_pages;
452     BitSet* const pages_bits = &it->committed_pages_bits;
453     GetPagesForMemoryMap(pagemap_fd, *it, committed_pages, pages_bits);
454     SetPagesInfo(page_count_fd, page_flags_fd, committed_pages);
455   }
456   return true;
457 }
458
459 void KillAll(const std::vector<pid_t>& pids, int signal_number) {
460   for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
461        ++it) {
462     kill(*it, signal_number);
463   }
464 }
465
466 void ExitWithUsage() {
467   LOG(ERROR) << "Usage: memdump [-a] <PID1>... <PIDN>";
468   exit(EXIT_FAILURE);
469 }
470
471 }  // namespace
472
473 int main(int argc, char** argv) {
474   if (argc == 1)
475     ExitWithUsage();
476   const bool short_output = !strncmp(argv[1], "-a", 2);
477   if (short_output) {
478     if (argc == 2)
479       ExitWithUsage();
480     ++argv;
481   }
482   std::vector<pid_t> pids;
483   for (const char* const* ptr = argv + 1; *ptr; ++ptr) {
484     pid_t pid;
485     if (!base::StringToInt(*ptr, &pid))
486       return EXIT_FAILURE;
487     pids.push_back(pid);
488   }
489
490   std::vector<ProcessMemory> processes_memory(pids.size());
491   {
492     int page_count_fd = open("/proc/kpagecount", O_RDONLY);
493     if (page_count_fd < 0) {
494       PLOG(ERROR) << "open /proc/kpagecount";
495       return EXIT_FAILURE;
496     }
497
498     int page_flags_fd = open("/proc/kpageflags", O_RDONLY);
499     if (page_flags_fd < 0) {
500       PLOG(ERROR) << "open /proc/kpageflags";
501       return EXIT_FAILURE;
502     }
503
504     file_util::ScopedFD page_count_fd_closer(&page_count_fd);
505     file_util::ScopedFD page_flags_fd_closer(&page_flags_fd);
506
507     base::ScopedClosureRunner auto_resume_processes(
508         base::Bind(&KillAll, pids, SIGCONT));
509     KillAll(pids, SIGSTOP);
510     for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
511          ++it) {
512       ProcessMemory* const process_memory =
513           &processes_memory[it - pids.begin()];
514       process_memory->pid = *it;
515       if (!CollectProcessMemoryInformation(
516               page_count_fd, page_flags_fd, process_memory)) {
517         return EXIT_FAILURE;
518       }
519     }
520   }
521
522   ClassifyPages(&processes_memory);
523   if (short_output)
524     DumpProcessesMemoryMapsInShortFormat(processes_memory);
525   else
526     DumpProcessesMemoryMapsInExtendedFormat(processes_memory);
527   return EXIT_SUCCESS;
528 }