| OLD | NEW |
| (Empty) | |
| 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 <string> |
| 15 #include <utility> |
| 16 #include <vector> |
| 17 |
| 18 #include "base/basictypes.h" |
| 19 #include "base/bind.h" |
| 20 #include "base/bind_helpers.h" |
| 21 #include "base/file_util.h" |
| 22 #include "base/hash_tables.h" |
| 23 #include "base/logging.h" |
| 24 #include "base/memory/scoped_ptr.h" |
| 25 #include "base/string_number_conversions.h" |
| 26 #include "base/stringprintf.h" |
| 27 #include "base/strings/string_piece.h" |
| 28 #include "base/strings/string_split.h" |
| 29 |
| 30 namespace { |
| 31 |
| 32 // An entry in /proc/<pid>/pagemap. |
| 33 struct PageMapEntry { |
| 34 uint64 page_frame_number : 55; |
| 35 uint unused : 8; |
| 36 uint present : 1; |
| 37 }; |
| 38 |
| 39 // Describes a virtual page. |
| 40 struct PageInfo { |
| 41 int64 page_frame_number : 55; // Physical page id, also known as PFN. |
| 42 int64 times_mapped : 9 + 32; |
| 43 }; |
| 44 |
| 45 struct MemoryMap { |
| 46 std::string name; |
| 47 std::string flags; |
| 48 uint start_address; |
| 49 uint end_address; |
| 50 int private_count; |
| 51 int app_shared_count; |
| 52 int other_shared_count; |
| 53 std::vector<PageInfo> committed_pages; |
| 54 }; |
| 55 |
| 56 struct ProcessMemory { |
| 57 pid_t pid; |
| 58 std::vector<MemoryMap> memory_maps; |
| 59 }; |
| 60 |
| 61 // Number of times a physical page is mapped in a process. |
| 62 typedef base::hash_map<uint64, int> PFNMap; |
| 63 |
| 64 // Parses lines from /proc/<PID>/maps, e.g.: |
| 65 // 401e7000-401f5000 r-xp 00000000 103:02 158 /system/bin/linker |
| 66 bool ParseMemoryMapLine(const std::string& line, |
| 67 std::vector<std::string>* tokens, |
| 68 MemoryMap* memory_map) { |
| 69 tokens->clear(); |
| 70 base::SplitString(line, ' ', tokens); |
| 71 if (tokens->size() < 2) |
| 72 return false; |
| 73 const int addr_len = 8; |
| 74 const std::string& addr_range = tokens->at(0); |
| 75 if (addr_range.length() != addr_len + 1 + addr_len) |
| 76 return false; |
| 77 uint64 tmp = 0; |
| 78 if (!base::HexStringToUInt64( |
| 79 base::StringPiece( |
| 80 addr_range.begin(), addr_range.begin() + addr_len), |
| 81 &tmp)) { |
| 82 return false; |
| 83 } |
| 84 memory_map->start_address = static_cast<uint>(tmp); |
| 85 const int end_addr_start_pos = addr_len + 1; |
| 86 if (!base::HexStringToUInt64( |
| 87 base::StringPiece( |
| 88 addr_range.begin() + end_addr_start_pos, |
| 89 addr_range.begin() + end_addr_start_pos + addr_len), |
| 90 &tmp)) { |
| 91 return false; |
| 92 } |
| 93 memory_map->end_address = static_cast<uint>(tmp); |
| 94 if (tokens->at(1).size() != strlen("rwxp")) |
| 95 return false; |
| 96 memory_map->flags.swap(tokens->at(1)); |
| 97 const int map_name_index = 5; |
| 98 if (tokens->size() >= map_name_index + 1) { |
| 99 for (std::vector<std::string>::const_iterator it = |
| 100 tokens->begin() + map_name_index; it != tokens->end(); ++it) { |
| 101 if (!it->empty()) { |
| 102 if (!memory_map->name.empty()) |
| 103 memory_map->name.append(" "); |
| 104 memory_map->name.append(*it); |
| 105 } |
| 106 } |
| 107 } |
| 108 return true; |
| 109 } |
| 110 |
| 111 // Reads sizeof(T) bytes from file |fd| at |offset|. |
| 112 template <typename T> |
| 113 bool ReadFromFileAtOffset(int fd, off_t offset, T* value) { |
| 114 if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) { |
| 115 PLOG(ERROR) << "lseek"; |
| 116 return false; |
| 117 } |
| 118 ssize_t bytes = read(fd, value, sizeof(*value)); |
| 119 if (bytes != sizeof(*value) && bytes != 0) { |
| 120 PLOG(ERROR) << "read"; |
| 121 return false; |
| 122 } |
| 123 return true; |
| 124 } |
| 125 |
| 126 // Fills |process_maps| in with the process memory maps identified by |pid|. |
| 127 bool GetProcessMaps(pid_t pid, std::vector<MemoryMap>* process_maps) { |
| 128 std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str()); |
| 129 if (!maps_file.good()) { |
| 130 PLOG(ERROR) << "open"; |
| 131 return false; |
| 132 } |
| 133 std::string line; |
| 134 std::vector<std::string> tokens; |
| 135 while (std::getline(maps_file, line) && !line.empty()) { |
| 136 MemoryMap memory_map = {}; |
| 137 if (!ParseMemoryMapLine(line, &tokens, &memory_map)) { |
| 138 LOG(ERROR) << "Could not parse line: " << line; |
| 139 return false; |
| 140 } |
| 141 process_maps->push_back(memory_map); |
| 142 } |
| 143 return true; |
| 144 } |
| 145 |
| 146 // Fills |committed_pages| in with the set of committed pages contained in the |
| 147 // provided memory map. |
| 148 bool GetPagesForMemoryMap(int pagemap_fd, |
| 149 const MemoryMap& memory_map, |
| 150 std::vector<PageInfo>* committed_pages) { |
| 151 for (uint addr = memory_map.start_address; addr < memory_map.end_address; |
| 152 addr += PAGE_SIZE) { |
| 153 DCHECK_EQ(0, addr % PAGE_SIZE); |
| 154 PageMapEntry page_map_entry = {}; |
| 155 COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size); |
| 156 const off64_t offset = addr / PAGE_SIZE; |
| 157 if (!ReadFromFileAtOffset(pagemap_fd, offset, &page_map_entry)) |
| 158 return false; |
| 159 if (page_map_entry.present) { // Ignore non-committed pages. |
| 160 if (page_map_entry.page_frame_number == 0) |
| 161 continue; |
| 162 PageInfo page_info = {}; |
| 163 page_info.page_frame_number = page_map_entry.page_frame_number; |
| 164 committed_pages->push_back(page_info); |
| 165 } |
| 166 } |
| 167 return true; |
| 168 } |
| 169 |
| 170 bool SetTimesMapped(int pagecount_fd, std::vector<PageInfo>* pages) { |
| 171 for (std::vector<PageInfo>::iterator it = pages->begin(); |
| 172 it != pages->end(); ++it) { |
| 173 PageInfo* const page_info = &*it; |
| 174 int64 times_mapped; |
| 175 if (!ReadFromFileAtOffset( |
| 176 pagecount_fd, page_info->page_frame_number, ×_mapped)) { |
| 177 return false; |
| 178 } |
| 179 page_info->times_mapped = times_mapped; |
| 180 } |
| 181 return true; |
| 182 } |
| 183 |
| 184 // Fills in the provided vector of Page Frame Number maps. This lets |
| 185 // ClassifyPages() know how many times each page is mapped in the processes. |
| 186 void FillPFNMaps(const std::vector<ProcessMemory>& processes_memory, |
| 187 std::vector<PFNMap>* pfn_maps) { |
| 188 int current_process_index = 0; |
| 189 for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin(); |
| 190 it != processes_memory.end(); ++it, ++current_process_index) { |
| 191 const std::vector<MemoryMap>& memory_maps = it->memory_maps; |
| 192 for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin(); |
| 193 it != memory_maps.end(); ++it) { |
| 194 const std::vector<PageInfo>& pages = it->committed_pages; |
| 195 for (std::vector<PageInfo>::const_iterator it = pages.begin(); |
| 196 it != pages.end(); ++it) { |
| 197 const PageInfo& page_info = *it; |
| 198 PFNMap* const pfn_map = &(*pfn_maps)[current_process_index]; |
| 199 const std::pair<PFNMap::iterator, bool> result = pfn_map->insert( |
| 200 std::make_pair(page_info.page_frame_number, 0)); |
| 201 ++result.first->second; |
| 202 } |
| 203 } |
| 204 } |
| 205 } |
| 206 |
| 207 // Sets the private_count/app_shared_count/other_shared_count fields of the |
| 208 // provided memory maps for each process. |
| 209 void ClassifyPages(std::vector<ProcessMemory>* processes_memory) { |
| 210 std::vector<PFNMap> pfn_maps(processes_memory->size()); |
| 211 FillPFNMaps(*processes_memory, &pfn_maps); |
| 212 // Hash set keeping track of the physical pages mapped in a single process so |
| 213 // that they can be counted only once. |
| 214 std::hash_set<uint64> physical_pages_mapped_in_process; |
| 215 |
| 216 for (std::vector<ProcessMemory>::iterator it = processes_memory->begin(); |
| 217 it != processes_memory->end(); ++it) { |
| 218 std::vector<MemoryMap>* const memory_maps = &it->memory_maps; |
| 219 physical_pages_mapped_in_process.clear(); |
| 220 for (std::vector<MemoryMap>::iterator it = memory_maps->begin(); |
| 221 it != memory_maps->end(); ++it) { |
| 222 MemoryMap* const memory_map = &*it; |
| 223 const std::vector<PageInfo>& pages = memory_map->committed_pages; |
| 224 for (std::vector<PageInfo>::const_iterator it = pages.begin(); |
| 225 it != pages.end(); ++it) { |
| 226 const PageInfo& page_info = *it; |
| 227 if (page_info.times_mapped == 1) { |
| 228 ++memory_map->private_count; |
| 229 continue; |
| 230 } |
| 231 const uint64 page_frame_number = page_info.page_frame_number; |
| 232 const std::pair<std::hash_set<uint64>::iterator, bool> result = |
| 233 physical_pages_mapped_in_process.insert(page_frame_number); |
| 234 const bool did_insert = result.second; |
| 235 if (!did_insert) { |
| 236 // This physical page (mapped multiple times in the same process) was |
| 237 // already counted. |
| 238 continue; |
| 239 } |
| 240 // See if the current physical page is also mapped in the other |
| 241 // processes that are being analyzed. |
| 242 int times_mapped = 0; |
| 243 bool mapped_in_multiple_processes = false; |
| 244 for (std::vector<PFNMap>::const_iterator it = pfn_maps.begin(); |
| 245 it != pfn_maps.end(); ++it) { |
| 246 const PFNMap& pfn_map = *it; |
| 247 const PFNMap::const_iterator found_it = pfn_map.find( |
| 248 page_frame_number); |
| 249 if (found_it == pfn_map.end()) |
| 250 continue; |
| 251 if (times_mapped) |
| 252 mapped_in_multiple_processes = true; |
| 253 times_mapped += found_it->second; |
| 254 } |
| 255 if (times_mapped == page_info.times_mapped) { |
| 256 // The physical page is only mapped in the processes that are being |
| 257 // analyzed. |
| 258 if (mapped_in_multiple_processes) { |
| 259 ++memory_map->app_shared_count; |
| 260 } else { |
| 261 // The physical page is mapped multiple times in the same process. |
| 262 ++memory_map->private_count; |
| 263 } |
| 264 } else { |
| 265 ++memory_map->other_shared_count; |
| 266 } |
| 267 } |
| 268 } |
| 269 } |
| 270 } |
| 271 |
| 272 void DumpProcessesMemoryMaps( |
| 273 const std::vector<ProcessMemory>& processes_memory) { |
| 274 std::string buf; |
| 275 for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin(); |
| 276 it != processes_memory.end(); ++it) { |
| 277 const ProcessMemory& process_memory = *it; |
| 278 std::cout << "[ PID=" << process_memory.pid << "]" << '\n'; |
| 279 const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps; |
| 280 for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin(); |
| 281 it != memory_maps.end(); ++it) { |
| 282 const MemoryMap& memory_map = *it; |
| 283 base::SStringPrintf( |
| 284 &buf, "%x-%x %s private=%d shared_app=%d shared_other=%d %s\n", |
| 285 memory_map.start_address, |
| 286 memory_map.end_address, memory_map.flags.c_str(), |
| 287 memory_map.private_count * PAGE_SIZE, |
| 288 memory_map.app_shared_count * PAGE_SIZE, |
| 289 memory_map.other_shared_count * PAGE_SIZE, |
| 290 memory_map.name.c_str()); |
| 291 std::cout << buf; |
| 292 } |
| 293 } |
| 294 } |
| 295 |
| 296 bool CollectProcessMemoryInformation(int page_count_fd, |
| 297 ProcessMemory* process_memory) { |
| 298 const pid_t pid = process_memory->pid; |
| 299 int pagemap_fd = open( |
| 300 base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY); |
| 301 if (pagemap_fd < 0) { |
| 302 PLOG(ERROR) << "open"; |
| 303 return false; |
| 304 } |
| 305 file_util::ScopedFD auto_closer(&pagemap_fd); |
| 306 std::vector<MemoryMap>* const process_maps = &process_memory->memory_maps; |
| 307 if (!GetProcessMaps(pid, process_maps)) |
| 308 return false; |
| 309 for (std::vector<MemoryMap>::iterator it = process_maps->begin(); |
| 310 it != process_maps->end(); ++it) { |
| 311 std::vector<PageInfo>* const committed_pages = &it->committed_pages; |
| 312 GetPagesForMemoryMap(pagemap_fd, *it, committed_pages); |
| 313 SetTimesMapped(page_count_fd, committed_pages); |
| 314 } |
| 315 return true; |
| 316 } |
| 317 |
| 318 void KillAll(const std::vector<pid_t>& pids, int signal_number) { |
| 319 for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end(); |
| 320 ++it) { |
| 321 kill(*it, signal_number); |
| 322 } |
| 323 } |
| 324 |
| 325 } // namespace |
| 326 |
| 327 int main(int argc, char** argv) { |
| 328 if (argc == 1) { |
| 329 LOG(ERROR) << "Usage: " << argv[0] << " <PID1>... <PIDN>"; |
| 330 return EXIT_FAILURE; |
| 331 } |
| 332 std::vector<pid_t> pids; |
| 333 for (const char* const* ptr = argv + 1; *ptr; ++ptr) { |
| 334 pid_t pid; |
| 335 if (!base::StringToInt(*ptr, &pid)) |
| 336 return EXIT_FAILURE; |
| 337 pids.push_back(pid); |
| 338 } |
| 339 std::vector<ProcessMemory> processes_memory(pids.size()); |
| 340 { |
| 341 int page_count_fd = open("/proc/kpagecount", O_RDONLY); |
| 342 if (page_count_fd < 0) { |
| 343 PLOG(ERROR) << "open"; |
| 344 return EXIT_FAILURE; |
| 345 } |
| 346 file_util::ScopedFD page_count_fd_closer(&page_count_fd); |
| 347 base::ScopedClosureRunner auto_resume_processes( |
| 348 base::Bind(&KillAll, pids, SIGCONT)); |
| 349 KillAll(pids, SIGSTOP); |
| 350 for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end(); |
| 351 ++it) { |
| 352 ProcessMemory* const process_memory = |
| 353 &processes_memory[it - pids.begin()]; |
| 354 process_memory->pid = *it; |
| 355 if (!CollectProcessMemoryInformation(page_count_fd, process_memory)) |
| 356 return EXIT_FAILURE; |
| 357 } |
| 358 } |
| 359 ClassifyPages(&processes_memory); |
| 360 DumpProcessesMemoryMaps(processes_memory); |
| 361 return EXIT_SUCCESS; |
| 362 } |
| OLD | NEW |