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.
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/files/file_util.h"
25 #include "base/files/scoped_file.h"
26 #include "base/format_macros.h"
27 #include "base/logging.h"
28 #include "base/strings/string_number_conversions.h"
29 #include "base/strings/string_split.h"
30 #include "base/strings/stringprintf.h"
32 const unsigned int kPageSize
= getpagesize();
38 void resize(size_t nbits
) {
39 data_
.resize((nbits
+ 7) / 8);
42 void set(uint32 bit
) {
43 const uint32 byte_idx
= bit
/ 8;
44 CHECK(byte_idx
< data_
.size());
45 data_
[byte_idx
] |= (1 << (bit
& 7));
48 std::string
AsB64String() const {
49 // Simple optimization: strip trailing zero bytes from the bitmap.
50 // For instance, if a region has 32 pages but only the first 9 are resident,
51 // The full bitmap would be 0xff 0x01 0x00 0x00, the stripped one 0xff 0x01.
52 // It can save up to some seconds when printing large mmaps, in particular
53 // in presence of large virtual address space reservations (where none of
54 // the pages are resident).
55 size_t end
= data_
.size();
56 while (end
> 0 && data_
[end
- 1] == '\0')
58 std::string
bits(&data_
[0], end
);
59 std::string b64_string
;
60 base::Base64Encode(bits
, &b64_string
);
65 std::vector
<char> data_
;
68 // An entry in /proc/<pid>/pagemap.
70 uint64 page_frame_number
: 55;
75 // Describes a memory page.
77 int64 page_frame_number
; // Physical page id, also known as PFN.
83 PageCount() : total_count(0), unevictable_count(0) {}
86 int unevictable_count
;
95 PageCount private_pages
;
96 // app_shared_pages[i] contains the number of pages mapped in i+2 processes
97 // (only among the processes that are being analyzed).
98 std::vector
<PageCount
> app_shared_pages
;
99 PageCount other_shared_pages
;
100 std::vector
<PageInfo
> committed_pages
;
101 // committed_pages_bits is a bitset reflecting the present bit for all the
102 // virtual pages of the mapping.
103 BitSet committed_pages_bits
;
106 struct ProcessMemory
{
108 std::vector
<MemoryMap
> memory_maps
;
111 bool PageIsUnevictable(const PageInfo
& page_info
) {
112 // These constants are taken from kernel-page-flags.h.
113 const int KPF_DIRTY
= 4; // Note that only file-mapped pages can be DIRTY.
114 const int KPF_ANON
= 12; // Anonymous pages are dirty per definition.
115 const int KPF_UNEVICTABLE
= 18;
116 const int KPF_MLOCKED
= 33;
118 return (page_info
.flags
& ((1ll << KPF_DIRTY
) |
120 (1ll << KPF_UNEVICTABLE
) |
121 (1ll << KPF_MLOCKED
))) ?
125 // Number of times a physical page is mapped in a process.
126 typedef base::hash_map
<uint64
, int> PFNMap
;
128 // Parses lines from /proc/<PID>/maps, e.g.:
129 // 401e7000-401f5000 r-xp 00000000 103:02 158 /system/bin/linker
130 bool ParseMemoryMapLine(const std::string
& line
,
131 std::vector
<std::string
>* tokens
,
132 MemoryMap
* memory_map
) {
134 base::SplitString(line
, ' ', tokens
);
135 if (tokens
->size() < 2)
137 const std::string
& addr_range
= tokens
->at(0);
138 std::vector
<std::string
> range_tokens
;
139 base::SplitString(addr_range
, '-', &range_tokens
);
140 const std::string
& start_address_token
= range_tokens
.at(0);
141 if (!base::HexStringToUInt64(start_address_token
,
142 &memory_map
->start_address
)) {
145 const std::string
& end_address_token
= range_tokens
.at(1);
146 if (!base::HexStringToUInt64(end_address_token
, &memory_map
->end_address
)) {
149 if (tokens
->at(1).size() != strlen("rwxp"))
151 memory_map
->flags
.swap(tokens
->at(1));
152 if (!base::HexStringToUInt64(tokens
->at(2), &memory_map
->offset
))
154 memory_map
->committed_pages_bits
.resize(
155 (memory_map
->end_address
- memory_map
->start_address
) / kPageSize
);
156 const int map_name_index
= 5;
157 if (tokens
->size() >= map_name_index
+ 1) {
158 for (std::vector
<std::string
>::const_iterator it
=
159 tokens
->begin() + map_name_index
; it
!= tokens
->end(); ++it
) {
161 if (!memory_map
->name
.empty())
162 memory_map
->name
.append(" ");
163 memory_map
->name
.append(*it
);
170 // Reads sizeof(T) bytes from file |fd| at |offset|.
171 template <typename T
>
172 bool ReadFromFileAtOffset(int fd
, off_t offset
, T
* value
) {
173 if (lseek64(fd
, offset
* sizeof(*value
), SEEK_SET
) < 0) {
174 PLOG(ERROR
) << "lseek";
177 ssize_t bytes
= read(fd
, value
, sizeof(*value
));
178 if (bytes
!= sizeof(*value
) && bytes
!= 0) {
179 PLOG(ERROR
) << "read";
185 // Fills |process_maps| in with the process memory maps identified by |pid|.
186 bool GetProcessMaps(pid_t pid
, std::vector
<MemoryMap
>* process_maps
) {
187 std::ifstream
maps_file(base::StringPrintf("/proc/%d/maps", pid
).c_str());
188 if (!maps_file
.good()) {
189 PLOG(ERROR
) << "open";
193 std::vector
<std::string
> tokens
;
194 while (std::getline(maps_file
, line
) && !line
.empty()) {
195 MemoryMap memory_map
= {};
196 if (!ParseMemoryMapLine(line
, &tokens
, &memory_map
)) {
197 LOG(ERROR
) << "Could not parse line: " << line
;
200 process_maps
->push_back(memory_map
);
205 // Fills |committed_pages| in with the set of committed pages contained in the
206 // provided memory map.
207 bool GetPagesForMemoryMap(int pagemap_fd
,
208 const MemoryMap
& memory_map
,
209 std::vector
<PageInfo
>* committed_pages
,
210 BitSet
* committed_pages_bits
) {
211 const off64_t offset
= memory_map
.start_address
/ kPageSize
;
212 if (lseek64(pagemap_fd
, offset
* sizeof(PageMapEntry
), SEEK_SET
) < 0) {
213 PLOG(ERROR
) << "lseek";
216 for (uint64 addr
= memory_map
.start_address
, page_index
= 0;
217 addr
< memory_map
.end_address
;
218 addr
+= kPageSize
, ++page_index
) {
219 DCHECK_EQ(0, addr
% kPageSize
);
220 PageMapEntry page_map_entry
= {};
221 static_assert(sizeof(PageMapEntry
) == sizeof(uint64
), "unexpected size");
222 ssize_t bytes
= read(pagemap_fd
, &page_map_entry
, sizeof(page_map_entry
));
223 if (bytes
!= sizeof(PageMapEntry
) && bytes
!= 0) {
224 PLOG(ERROR
) << "read";
227 if (page_map_entry
.present
) { // Ignore non-committed pages.
228 if (page_map_entry
.page_frame_number
== 0)
230 PageInfo page_info
= {};
231 page_info
.page_frame_number
= page_map_entry
.page_frame_number
;
232 committed_pages
->push_back(page_info
);
233 committed_pages_bits
->set(page_index
);
239 // Fills |committed_pages| with mapping count and flags information gathered
240 // looking-up /proc/kpagecount and /proc/kpageflags.
241 bool SetPagesInfo(int pagecount_fd
,
243 std::vector
<PageInfo
>* pages
) {
244 for (std::vector
<PageInfo
>::iterator it
= pages
->begin();
245 it
!= pages
->end(); ++it
) {
246 PageInfo
* const page_info
= &*it
;
248 if (!ReadFromFileAtOffset(
249 pagecount_fd
, page_info
->page_frame_number
, ×_mapped
)) {
252 DCHECK(times_mapped
<= std::numeric_limits
<int32_t>::max());
253 page_info
->times_mapped
= static_cast<int32
>(times_mapped
);
256 if (!ReadFromFileAtOffset(
257 pageflags_fd
, page_info
->page_frame_number
, &page_flags
)) {
260 page_info
->flags
= page_flags
;
265 // Fills in the provided vector of Page Frame Number maps. This lets
266 // ClassifyPages() know how many times each page is mapped in the processes.
267 void FillPFNMaps(const std::vector
<ProcessMemory
>& processes_memory
,
268 std::vector
<PFNMap
>* pfn_maps
) {
269 int current_process_index
= 0;
270 for (std::vector
<ProcessMemory
>::const_iterator it
= processes_memory
.begin();
271 it
!= processes_memory
.end(); ++it
, ++current_process_index
) {
272 const std::vector
<MemoryMap
>& memory_maps
= it
->memory_maps
;
273 for (std::vector
<MemoryMap
>::const_iterator it
= memory_maps
.begin();
274 it
!= memory_maps
.end(); ++it
) {
275 const std::vector
<PageInfo
>& pages
= it
->committed_pages
;
276 for (std::vector
<PageInfo
>::const_iterator it
= pages
.begin();
277 it
!= pages
.end(); ++it
) {
278 const PageInfo
& page_info
= *it
;
279 PFNMap
* const pfn_map
= &(*pfn_maps
)[current_process_index
];
280 const std::pair
<PFNMap::iterator
, bool> result
= pfn_map
->insert(
281 std::make_pair(page_info
.page_frame_number
, 0));
282 ++result
.first
->second
;
288 // Sets the private_count/app_shared_pages/other_shared_count fields of the
289 // provided memory maps for each process.
290 void ClassifyPages(std::vector
<ProcessMemory
>* processes_memory
) {
291 std::vector
<PFNMap
> pfn_maps(processes_memory
->size());
292 FillPFNMaps(*processes_memory
, &pfn_maps
);
293 // Hash set keeping track of the physical pages mapped in a single process so
294 // that they can be counted only once.
295 base::hash_set
<uint64
> physical_pages_mapped_in_process
;
297 for (std::vector
<ProcessMemory
>::iterator it
= processes_memory
->begin();
298 it
!= processes_memory
->end(); ++it
) {
299 std::vector
<MemoryMap
>* const memory_maps
= &it
->memory_maps
;
300 physical_pages_mapped_in_process
.clear();
301 for (std::vector
<MemoryMap
>::iterator it
= memory_maps
->begin();
302 it
!= memory_maps
->end(); ++it
) {
303 MemoryMap
* const memory_map
= &*it
;
304 const size_t processes_count
= processes_memory
->size();
305 memory_map
->app_shared_pages
.resize(processes_count
- 1);
306 const std::vector
<PageInfo
>& pages
= memory_map
->committed_pages
;
307 for (std::vector
<PageInfo
>::const_iterator it
= pages
.begin();
308 it
!= pages
.end(); ++it
) {
309 const PageInfo
& page_info
= *it
;
310 if (page_info
.times_mapped
== 1) {
311 ++memory_map
->private_pages
.total_count
;
312 if (PageIsUnevictable(page_info
))
313 ++memory_map
->private_pages
.unevictable_count
;
316 const uint64 page_frame_number
= page_info
.page_frame_number
;
317 const std::pair
<base::hash_set
<uint64
>::iterator
, bool> result
=
318 physical_pages_mapped_in_process
.insert(page_frame_number
);
319 const bool did_insert
= result
.second
;
321 // This physical page (mapped multiple times in the same process) was
325 // See if the current physical page is also mapped in the other
326 // processes that are being analyzed.
327 int times_mapped
= 0;
328 int mapped_in_processes_count
= 0;
329 for (std::vector
<PFNMap
>::const_iterator it
= pfn_maps
.begin();
330 it
!= pfn_maps
.end(); ++it
) {
331 const PFNMap
& pfn_map
= *it
;
332 const PFNMap::const_iterator found_it
= pfn_map
.find(
334 if (found_it
== pfn_map
.end())
336 ++mapped_in_processes_count
;
337 times_mapped
+= found_it
->second
;
339 PageCount
* page_count_to_update
= NULL
;
340 if (times_mapped
== page_info
.times_mapped
) {
341 // The physical page is only mapped in the processes that are being
343 if (mapped_in_processes_count
> 1) {
344 // The physical page is mapped in multiple processes.
345 page_count_to_update
=
346 &memory_map
->app_shared_pages
[mapped_in_processes_count
- 2];
348 // The physical page is mapped multiple times in the same process.
349 page_count_to_update
= &memory_map
->private_pages
;
352 page_count_to_update
= &memory_map
->other_shared_pages
;
354 ++page_count_to_update
->total_count
;
355 if (PageIsUnevictable(page_info
))
356 ++page_count_to_update
->unevictable_count
;
362 void AppendAppSharedField(const std::vector
<PageCount
>& app_shared_pages
,
365 for (std::vector
<PageCount
>::const_iterator it
= app_shared_pages
.begin();
366 it
!= app_shared_pages
.end(); ++it
) {
367 out
->append(base::IntToString(it
->total_count
* kPageSize
));
369 out
->append(base::IntToString(it
->unevictable_count
* kPageSize
));
370 if (it
+ 1 != app_shared_pages
.end())
376 void DumpProcessesMemoryMapsInShortFormat(
377 const std::vector
<ProcessMemory
>& processes_memory
) {
378 const int KB_PER_PAGE
= kPageSize
>> 10;
379 std::vector
<int> totals_app_shared(processes_memory
.size());
381 std::cout
<< "pid\tprivate\t\tshared_app\tshared_other (KB)\n";
382 for (std::vector
<ProcessMemory
>::const_iterator it
= processes_memory
.begin();
383 it
!= processes_memory
.end(); ++it
) {
384 const ProcessMemory
& process_memory
= *it
;
385 std::fill(totals_app_shared
.begin(), totals_app_shared
.end(), 0);
386 int total_private
= 0, total_other_shared
= 0;
387 const std::vector
<MemoryMap
>& memory_maps
= process_memory
.memory_maps
;
388 for (std::vector
<MemoryMap
>::const_iterator it
= memory_maps
.begin();
389 it
!= memory_maps
.end(); ++it
) {
390 const MemoryMap
& memory_map
= *it
;
391 total_private
+= memory_map
.private_pages
.total_count
;
392 for (size_t i
= 0; i
< memory_map
.app_shared_pages
.size(); ++i
)
393 totals_app_shared
[i
] += memory_map
.app_shared_pages
[i
].total_count
;
394 total_other_shared
+= memory_map
.other_shared_pages
.total_count
;
396 double total_app_shared
= 0;
397 for (size_t i
= 0; i
< totals_app_shared
.size(); ++i
)
398 total_app_shared
+= static_cast<double>(totals_app_shared
[i
]) / (i
+ 2);
400 &buf
, "%d\t%d\t\t%d\t\t%d\n",
402 total_private
* KB_PER_PAGE
,
403 static_cast<int>(total_app_shared
) * KB_PER_PAGE
,
404 total_other_shared
* KB_PER_PAGE
);
409 void DumpProcessesMemoryMapsInExtendedFormat(
410 const std::vector
<ProcessMemory
>& processes_memory
) {
412 std::string app_shared_buf
;
413 for (std::vector
<ProcessMemory
>::const_iterator it
= processes_memory
.begin();
414 it
!= processes_memory
.end(); ++it
) {
415 const ProcessMemory
& process_memory
= *it
;
416 std::cout
<< "[ PID=" << process_memory
.pid
<< "]" << '\n';
417 const std::vector
<MemoryMap
>& memory_maps
= process_memory
.memory_maps
;
418 for (std::vector
<MemoryMap
>::const_iterator it
= memory_maps
.begin();
419 it
!= memory_maps
.end(); ++it
) {
420 const MemoryMap
& memory_map
= *it
;
421 app_shared_buf
.clear();
422 AppendAppSharedField(memory_map
.app_shared_pages
, &app_shared_buf
);
425 "%"PRIx64
"-%"PRIx64
" %s %"PRIx64
" private_unevictable=%d private=%d "
426 "shared_app=%s shared_other_unevictable=%d shared_other=%d "
428 memory_map
.start_address
,
429 memory_map
.end_address
,
430 memory_map
.flags
.c_str(),
432 memory_map
.private_pages
.unevictable_count
* kPageSize
,
433 memory_map
.private_pages
.total_count
* kPageSize
,
434 app_shared_buf
.c_str(),
435 memory_map
.other_shared_pages
.unevictable_count
* kPageSize
,
436 memory_map
.other_shared_pages
.total_count
* kPageSize
,
437 memory_map
.name
.c_str(),
438 memory_map
.committed_pages_bits
.AsB64String().c_str());
444 bool CollectProcessMemoryInformation(int page_count_fd
,
446 ProcessMemory
* process_memory
) {
447 const pid_t pid
= process_memory
->pid
;
448 base::ScopedFD
pagemap_fd(HANDLE_EINTR(open(
449 base::StringPrintf("/proc/%d/pagemap", pid
).c_str(), O_RDONLY
)));
450 if (!pagemap_fd
.is_valid()) {
451 PLOG(ERROR
) << "open";
454 std::vector
<MemoryMap
>* const process_maps
= &process_memory
->memory_maps
;
455 if (!GetProcessMaps(pid
, process_maps
))
457 for (std::vector
<MemoryMap
>::iterator it
= process_maps
->begin();
458 it
!= process_maps
->end(); ++it
) {
459 std::vector
<PageInfo
>* const committed_pages
= &it
->committed_pages
;
460 BitSet
* const pages_bits
= &it
->committed_pages_bits
;
461 GetPagesForMemoryMap(pagemap_fd
.get(), *it
, committed_pages
, pages_bits
);
462 SetPagesInfo(page_count_fd
, page_flags_fd
, committed_pages
);
467 void KillAll(const std::vector
<pid_t
>& pids
, int signal_number
) {
468 for (std::vector
<pid_t
>::const_iterator it
= pids
.begin(); it
!= pids
.end();
470 kill(*it
, signal_number
);
474 void ExitWithUsage() {
475 LOG(ERROR
) << "Usage: memdump [-a] <PID1>... <PIDN>";
481 int main(int argc
, char** argv
) {
484 const bool short_output
= !strncmp(argv
[1], "-a", 2);
490 std::vector
<pid_t
> pids
;
491 for (const char* const* ptr
= argv
+ 1; *ptr
; ++ptr
) {
493 if (!base::StringToInt(*ptr
, &pid
))
498 std::vector
<ProcessMemory
> processes_memory(pids
.size());
500 base::ScopedFD
page_count_fd(
501 HANDLE_EINTR(open("/proc/kpagecount", O_RDONLY
)));
502 if (!page_count_fd
.is_valid()) {
503 PLOG(ERROR
) << "open /proc/kpagecount";
507 base::ScopedFD
page_flags_fd(open("/proc/kpageflags", O_RDONLY
));
508 if (!page_flags_fd
.is_valid()) {
509 PLOG(ERROR
) << "open /proc/kpageflags";
513 base::ScopedClosureRunner
auto_resume_processes(
514 base::Bind(&KillAll
, pids
, SIGCONT
));
515 KillAll(pids
, SIGSTOP
);
516 for (std::vector
<pid_t
>::const_iterator it
= pids
.begin(); it
!= pids
.end();
518 ProcessMemory
* const process_memory
=
519 &processes_memory
[it
- pids
.begin()];
520 process_memory
->pid
= *it
;
521 if (!CollectProcessMemoryInformation(
522 page_count_fd
.get(), page_flags_fd
.get(), process_memory
)) {
528 ClassifyPages(&processes_memory
);
530 DumpProcessesMemoryMapsInShortFormat(processes_memory
);
532 DumpProcessesMemoryMapsInExtendedFormat(processes_memory
);