Chromium Code Reviews| 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<int64, 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 static const char kExample[] = "7fe94ae7d000-7fe94bbd0000 r--p"; | |
|
bulach
2013/05/30 11:47:07
nit: instead of strlen below, perhaps:
static cons
Philippe
2013/05/30 13:48:43
Done. I got rid of this and addressed when I addre
| |
| 70 do { | |
| 71 tokens->clear(); | |
| 72 if (line.length() < strlen(kExample)) | |
| 73 break; | |
| 74 base::SplitString(line, ' ', tokens); | |
| 75 if (tokens->size() < 2) | |
| 76 break; | |
| 77 const std::string& addr_range = tokens->at(0); | |
| 78 int64 tmp; | |
| 79 if (!base::HexStringToInt64( | |
| 80 base::StringPiece(addr_range.begin(), addr_range.begin() + 8), | |
| 81 &tmp)) { | |
| 82 break; | |
| 83 } | |
| 84 memory_map->start_address = static_cast<uint>(tmp); | |
| 85 if (!base::HexStringToInt64( | |
| 86 base::StringPiece(addr_range.begin() + 9, | |
| 87 addr_range.begin() + 9 + 8), &tmp)) { | |
| 88 break; | |
| 89 } | |
| 90 memory_map->end_address = static_cast<uint>(tmp); | |
| 91 const int kModeLength = strlen("rwxp"); | |
| 92 if (tokens->at(1).size() < kModeLength) | |
| 93 break; | |
| 94 memory_map->flags.swap(tokens->at(1)); | |
| 95 if (tokens->size() >= 6) { | |
| 96 for (std::vector<std::string>::const_iterator it = tokens->begin() + 5; | |
| 97 it != tokens->end(); ++it) { | |
| 98 if (!it->empty()) { | |
| 99 if (!memory_map->name.empty()) | |
| 100 memory_map->name.append(" "); | |
| 101 memory_map->name.append(*it); | |
| 102 } | |
| 103 } | |
| 104 } | |
| 105 return true; | |
| 106 } while (false); | |
|
bulach
2013/05/30 11:47:07
nit: perhaps split into a TokenizeMemoryMapLine()
Philippe
2013/05/30 13:48:43
Good idea. I just moved the error message to the c
| |
| 107 | |
| 108 LOG(ERROR) << "Could not parse line: " << line; | |
| 109 return false; | |
| 110 } | |
| 111 | |
| 112 // Reads sizeof(*value) bytes from file |fd| at |offset|. | |
|
bulach
2013/05/30 11:47:07
sizeof(T), and in 119 as well?
Philippe
2013/05/30 13:48:43
I made the change here but kept line 119 unchanged
| |
| 113 template <typename T> | |
| 114 bool ReadFromFileAtOffset(int fd, off_t offset, T* value) { | |
|
qsr
2013/05/30 10:30:25
You might want to use mmap instead of opening your
digit
2013/05/30 10:34:09
From past experience, most files in /proc cannot b
| |
| 115 if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) { | |
| 116 PLOG(ERROR) << "lseek"; | |
| 117 return false; | |
| 118 } | |
| 119 ssize_t bytes = read(fd, value, sizeof(*value)); | |
| 120 if (bytes < 0) { | |
|
qsr
2013/05/30 10:30:25
You could replace 0 by sizeof(*value) if you do no
Philippe
2013/05/30 13:48:43
Done.
| |
| 121 PLOG(ERROR) << "read"; | |
| 122 return false; | |
| 123 } | |
| 124 return true; | |
| 125 } | |
| 126 | |
| 127 // Fills |process_maps| in with the process memory maps identified by |pid|. | |
| 128 bool GetProcessMaps(pid_t pid, std::vector<MemoryMap>* process_maps) { | |
| 129 std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str()); | |
| 130 if (!maps_file.good()) { | |
| 131 PLOG(ERROR) << "open"; | |
| 132 return false; | |
| 133 } | |
| 134 std::string line; | |
| 135 std::vector<std::string> tokens; | |
| 136 while (std::getline(maps_file, line) && !line.empty()) { | |
| 137 MemoryMap memory_map = {}; | |
| 138 if (!ParseMemoryMapLine(line, &tokens, &memory_map)) | |
| 139 return false; | |
| 140 process_maps->push_back(memory_map); | |
| 141 } | |
| 142 return true; | |
| 143 } | |
| 144 | |
| 145 // Fills |committed_pages| in with the set of committed pages contained in the | |
| 146 // provided memory map. | |
| 147 bool GetPagesForMemoryMap(int pagemap_fd, | |
| 148 const MemoryMap& memory_map, | |
| 149 std::vector<PageInfo>* committed_pages) { | |
| 150 for (uint addr = memory_map.start_address; addr < memory_map.end_address; | |
| 151 addr += PAGE_SIZE) { | |
| 152 DCHECK_EQ(0, addr % PAGE_SIZE); | |
| 153 PageMapEntry page_map_entry = {}; | |
| 154 COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size); | |
| 155 const off64_t offset = addr / PAGE_SIZE; | |
| 156 if (!ReadFromFileAtOffset(pagemap_fd, offset, &page_map_entry)) | |
| 157 return false; | |
| 158 if (page_map_entry.present) { // Ignore non-committed pages. | |
| 159 if (page_map_entry.page_frame_number == 0) | |
| 160 continue; | |
| 161 PageInfo page_info = {}; | |
| 162 page_info.page_frame_number = page_map_entry.page_frame_number; | |
| 163 committed_pages->push_back(page_info); | |
| 164 } | |
| 165 } | |
| 166 return true; | |
| 167 } | |
| 168 | |
| 169 bool SetTimesMapped(int pagecount_fd, std::vector<PageInfo>* pages) { | |
| 170 for (std::vector<PageInfo>::iterator it = pages->begin(); | |
| 171 it != pages->end(); ++it) { | |
| 172 PageInfo* const page_info = &*it; | |
| 173 int64 times_mapped; | |
| 174 if (!ReadFromFileAtOffset( | |
| 175 pagecount_fd, page_info->page_frame_number, ×_mapped)) { | |
| 176 return false; | |
| 177 } | |
| 178 page_info->times_mapped = times_mapped; | |
| 179 } | |
| 180 return true; | |
| 181 } | |
| 182 | |
| 183 // Fills in the provided vector of Page Frame Number maps. This lets | |
| 184 // ClassifyPages() know how many times each page is mapped in the processes. | |
| 185 void FillPFNMaps(const std::vector<ProcessMemory>& processes_memory, | |
| 186 std::vector<PFNMap>* pfn_maps) { | |
| 187 int current_process_index = 0; | |
| 188 for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin(); | |
| 189 it != processes_memory.end(); ++it, ++current_process_index) { | |
| 190 const std::vector<MemoryMap>& memory_maps = it->memory_maps; | |
| 191 for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin(); | |
| 192 it != memory_maps.end(); ++it) { | |
| 193 const std::vector<PageInfo>& pages = it->committed_pages; | |
| 194 for (std::vector<PageInfo>::const_iterator it = pages.begin(); | |
| 195 it != pages.end(); ++it) { | |
| 196 const PageInfo& page_info = *it; | |
| 197 PFNMap* const pfn_map = &(*pfn_maps)[current_process_index]; | |
| 198 const std::pair<PFNMap::iterator, bool> result = pfn_map->insert( | |
| 199 std::make_pair(page_info.page_frame_number, 0)); | |
| 200 ++result.first->second; | |
| 201 } | |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 // Sets the private_count/app_shared_count/other_shared_count fields of the | |
| 207 // provided memory maps for each process. | |
| 208 void ClassifyPages(std::vector<ProcessMemory>* processes_memory) { | |
| 209 std::vector<PFNMap> pfn_maps(processes_memory->size()); | |
| 210 FillPFNMaps(*processes_memory, &pfn_maps); | |
| 211 for (std::vector<ProcessMemory>::iterator it = processes_memory->begin(); | |
| 212 it != processes_memory->end(); ++it) { | |
| 213 std::vector<MemoryMap>* const memory_maps = &it->memory_maps; | |
| 214 for (std::vector<MemoryMap>::iterator it = memory_maps->begin(); | |
| 215 it != memory_maps->end(); ++it) { | |
| 216 MemoryMap* const memory_map = &*it; | |
| 217 const std::vector<PageInfo>& pages = memory_map->committed_pages; | |
| 218 for (std::vector<PageInfo>::const_iterator it = pages.begin(); | |
| 219 it != pages.end(); ++it) { | |
| 220 const PageInfo& page_info = *it; | |
| 221 if (page_info.times_mapped == 1) { | |
| 222 ++memory_map->private_count; | |
| 223 continue; | |
| 224 } | |
| 225 bool mapped_in_multiple_processes = false; | |
| 226 int times_mapped = 0; | |
| 227 // See if the current physical page is also mapped in the processes that | |
| 228 // are being analyzed. | |
| 229 for (std::vector<PFNMap>::const_iterator it = pfn_maps.begin(); | |
| 230 it != pfn_maps.end(); ++it) { | |
| 231 const PFNMap& pfn_map = *it; | |
| 232 const PFNMap::const_iterator found_it = pfn_map.find( | |
| 233 page_info.page_frame_number); | |
| 234 if (found_it == pfn_map.end()) | |
| 235 continue; | |
| 236 if (times_mapped) | |
| 237 mapped_in_multiple_processes = true; | |
| 238 times_mapped += found_it->second; | |
| 239 } | |
| 240 if (times_mapped == page_info.times_mapped) { | |
| 241 // The physical page is only mapped in the processes that are being | |
| 242 // analyzed. | |
| 243 if (mapped_in_multiple_processes) | |
| 244 ++memory_map->app_shared_count; | |
| 245 else // Mapped multiple times in the same process. | |
| 246 ++memory_map->private_count; | |
| 247 } else { | |
| 248 ++memory_map->other_shared_count; | |
| 249 } | |
| 250 } | |
| 251 } | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 void DumpProcessesMemoryMaps( | |
| 256 const std::vector<ProcessMemory>& processes_memory) { | |
| 257 std::string buf; | |
| 258 for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin(); | |
| 259 it != processes_memory.end(); ++it) { | |
| 260 const ProcessMemory& process_memory = *it; | |
| 261 std::cout << "[ PID=" << process_memory.pid << "]" << '\n'; | |
| 262 const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps; | |
| 263 for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin(); | |
| 264 it != memory_maps.end(); ++it) { | |
| 265 const MemoryMap& memory_map = *it; | |
| 266 base::SStringPrintf( | |
| 267 &buf, "%x-%x %s %d %d %d %s\n", memory_map.start_address, | |
| 268 memory_map.end_address, memory_map.flags.c_str(), | |
| 269 memory_map.private_count * PAGE_SIZE, | |
| 270 memory_map.app_shared_count * PAGE_SIZE, | |
| 271 memory_map.other_shared_count * PAGE_SIZE, | |
| 272 memory_map.name.c_str()); | |
| 273 std::cout << buf; | |
| 274 } | |
| 275 } | |
| 276 } | |
| 277 | |
| 278 void ResumeProcesses(const std::vector<pid_t>& pids) { | |
| 279 for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end(); | |
| 280 ++it) { | |
| 281 kill(*it, SIGCONT); | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 } // namespace | |
| 286 | |
| 287 int main(int argc, char** argv) { | |
| 288 if (argc == 1) { | |
| 289 LOG(ERROR) << "Usage: " << argv[0] << " <PID1>... <PIDN>"; | |
| 290 return EXIT_FAILURE; | |
| 291 } | |
| 292 std::vector<pid_t> pids; | |
| 293 for (const char* const* ptr = argv + 1; *ptr; ++ptr) { | |
| 294 pid_t pid; | |
| 295 if (!base::StringToInt(*ptr, &pid)) | |
| 296 return EXIT_FAILURE; | |
| 297 pids.push_back(pid); | |
| 298 } | |
| 299 int page_count_fd = open("/proc/kpagecount", O_RDONLY); | |
| 300 if (page_count_fd < 0) { | |
| 301 PLOG(ERROR) << "open"; | |
| 302 return EXIT_FAILURE; | |
| 303 } | |
| 304 file_util::ScopedFD page_count_fd_closer(&page_count_fd); | |
| 305 | |
| 306 base::ScopedClosureRunner auto_resume_processes( | |
| 307 base::Bind(&ResumeProcesses, pids)); | |
|
bulach
2013/05/30 11:47:07
s/ResumeProcess/KillAll/, pass the signal in, and
Philippe
2013/05/30 13:48:43
Great idea! I should have thought about it.
| |
| 308 std::vector<ProcessMemory> processes_memory(pids.size()); | |
| 309 for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end(); | |
| 310 ++it) { | |
| 311 kill(*it, SIGSTOP); | |
| 312 } | |
| 313 for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end(); | |
| 314 ++it) { | |
| 315 const pid_t pid = *it; | |
|
qsr
2013/05/30 10:30:26
You could extract this content in another method.
Philippe
2013/05/30 13:48:43
Done.
| |
| 316 int pagemap_fd = open( | |
| 317 base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY); | |
| 318 if (pagemap_fd < 0) { | |
| 319 PLOG(ERROR) << "open"; | |
| 320 return EXIT_FAILURE; | |
| 321 } | |
| 322 file_util::ScopedFD auto_closer(&pagemap_fd); | |
| 323 ProcessMemory* const process_memory = &processes_memory[it - pids.begin()]; | |
| 324 process_memory->pid = pid; | |
| 325 std::vector<MemoryMap>* const process_maps = &process_memory->memory_maps; | |
| 326 if (!GetProcessMaps(pid, process_maps)) | |
| 327 return EXIT_FAILURE; | |
| 328 for (std::vector<MemoryMap>::iterator it = process_maps->begin(); | |
| 329 it != process_maps->end(); ++it) { | |
| 330 std::vector<PageInfo>* const committed_pages = | |
| 331 &it->committed_pages; | |
| 332 GetPagesForMemoryMap(pagemap_fd, *it, committed_pages); | |
| 333 SetTimesMapped(page_count_fd, committed_pages); | |
| 334 } | |
| 335 } | |
| 336 ResumeProcesses(pids); | |
| 337 auto_resume_processes.Release(); | |
|
qsr
2013/05/30 10:30:26
instead of this, you could have it in a scope, and
bulach
2013/05/30 11:47:07
or parhaps:
delete 332, and here:
auto_resume_proc
Philippe
2013/05/30 13:48:43
I followed Benjamin's suggestion. To me it looks l
| |
| 338 ClassifyPages(&processes_memory); | |
| 339 DumpProcessesMemoryMaps(processes_memory); | |
| 340 return EXIT_SUCCESS; | |
| 341 } | |
| OLD | NEW |