| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 "chrome/browser/private_working_set_snapshot.h" | |
| 6 | |
| 7 #include <pdh.h> | |
| 8 #include <pdhmsg.h> | |
| 9 | |
| 10 #include <algorithm> | |
| 11 | |
| 12 #include "base/numerics/safe_conversions.h" | |
| 13 #include "base/win/windows_version.h" | |
| 14 | |
| 15 // Link pdh.lib (import library for pdh.dll) whenever this file is linked in. | |
| 16 #pragma comment(lib, "pdh.lib") | |
| 17 | |
| 18 PrivateWorkingSetSnapshot::PrivateWorkingSetSnapshot() { | |
| 19 // The Pdh APIs are supported on Windows XP, but the "Working Set - Private" | |
| 20 // counter that PrivateWorkingSetSnapshot depends on is not defined until | |
| 21 // Windows Vista. Early-out to avoid wasted effort. All queries will return | |
| 22 // zero and will have to use the fallback calculations. | |
| 23 if (base::win::GetVersion() < base::win::VERSION_VISTA) | |
| 24 return; | |
| 25 | |
| 26 // Create a Pdh query | |
| 27 PDH_HQUERY query_handle; | |
| 28 if (PdhOpenQuery(NULL, NULL, &query_handle) != ERROR_SUCCESS) { | |
| 29 return; | |
| 30 } | |
| 31 | |
| 32 query_handle_.Set(query_handle); | |
| 33 } | |
| 34 | |
| 35 PrivateWorkingSetSnapshot::~PrivateWorkingSetSnapshot() { | |
| 36 } | |
| 37 | |
| 38 void PrivateWorkingSetSnapshot::AddToMonitorList( | |
| 39 const std::string& process_name) { | |
| 40 if (!query_handle_.IsValid()) | |
| 41 return; | |
| 42 | |
| 43 // Create the magic strings that will return a list of process IDs and a list | |
| 44 // of private working sets. The 'process_name' variable should be something | |
| 45 // like "chrome". The '*' character indicates that we want records for all | |
| 46 // processes whose names start with process_name - all chrome processes, but | |
| 47 // also all 'chrome_editor.exe' processes or other matching names. The excess | |
| 48 // information is unavoidable but harmless. | |
| 49 std::string process_id_query = "\\Process(" + process_name + "*)\\ID Process"; | |
| 50 std::string private_ws_query = | |
| 51 "\\Process(" + process_name + "*)\\Working Set - Private"; | |
| 52 | |
| 53 // Add the two counters to the query. | |
| 54 PdhCounterPair new_counters; | |
| 55 if (PdhAddCounterA(query_handle_.Get(), process_id_query.c_str(), NULL, | |
| 56 &new_counters.process_id_handle) != ERROR_SUCCESS) { | |
| 57 return; | |
| 58 } | |
| 59 | |
| 60 // If adding the second counter fails then we should remove the first one. | |
| 61 if (PdhAddCounterA(query_handle_.Get(), private_ws_query.c_str(), NULL, | |
| 62 &new_counters.private_ws_handle) != ERROR_SUCCESS) { | |
| 63 PdhRemoveCounter(new_counters.process_id_handle); | |
| 64 } | |
| 65 | |
| 66 // Record the pair of counter query handles so that we can query them later. | |
| 67 counter_pairs_.push_back(new_counters); | |
| 68 } | |
| 69 | |
| 70 void PrivateWorkingSetSnapshot::Sample() { | |
| 71 if (counter_pairs_.empty()) | |
| 72 return; | |
| 73 | |
| 74 // Destroy all previous data. | |
| 75 records_.resize(0); | |
| 76 // Record the requested data into PDH's internal buffers. | |
| 77 if (PdhCollectQueryData(query_handle_.Get()) != ERROR_SUCCESS) | |
| 78 return; | |
| 79 | |
| 80 for (auto& counter_pair : counter_pairs_) { | |
| 81 // Find out how much space is required for the two counter arrays. | |
| 82 // A return code of PDH_MORE_DATA indicates that we should call again with | |
| 83 // the buffer size returned. | |
| 84 DWORD buffer_size1 = 0; | |
| 85 DWORD item_count1 = 0; | |
| 86 // Process IDs should be retrieved as PDH_FMT_LONG | |
| 87 if (PdhGetFormattedCounterArray(counter_pair.process_id_handle, | |
| 88 PDH_FMT_LONG, &buffer_size1, &item_count1, | |
| 89 nullptr) != PDH_MORE_DATA) | |
| 90 continue; | |
| 91 if (buffer_size1 == 0 || item_count1 == 0) | |
| 92 continue; | |
| 93 | |
| 94 DWORD buffer_size2 = 0; | |
| 95 DWORD item_count2 = 0; | |
| 96 // Working sets should be retrieved as PDH_FMT_LARGE (LONGLONG) | |
| 97 // Note that if this second call to PdhGetFormattedCounterArray with the | |
| 98 // buffer size and count variables being zero is omitted then the PID and | |
| 99 // working-set results are not reliably correlated. | |
| 100 if (PdhGetFormattedCounterArray(counter_pair.private_ws_handle, | |
| 101 PDH_FMT_LARGE, &buffer_size2, &item_count2, | |
| 102 nullptr) != PDH_MORE_DATA) | |
| 103 continue; | |
| 104 | |
| 105 // It is not clear whether Pdh guarantees that the two counters in the same | |
| 106 // query will execute atomically - if they will see the same set of | |
| 107 // processes. If they do not then the correspondence between "ID Process" | |
| 108 // and "Working Set - Private" is lost and we have to discard these results. | |
| 109 // In testing these values have always matched. If this check fails then | |
| 110 // the old per-process memory calculations will be used instead. | |
| 111 if (buffer_size1 != buffer_size2 || item_count1 != item_count2) | |
| 112 continue; | |
| 113 | |
| 114 // Allocate enough space for the results of both queries. | |
| 115 std::vector<char> buffer(buffer_size1 * 2); | |
| 116 // Retrieve the process ID data. | |
| 117 auto process_id_data = | |
| 118 reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM*>(&buffer[0]); | |
| 119 if (PdhGetFormattedCounterArray(counter_pair.process_id_handle, | |
| 120 PDH_FMT_LONG, &buffer_size1, &item_count1, | |
| 121 process_id_data) != ERROR_SUCCESS) | |
| 122 continue; | |
| 123 // Retrieve the private working set data. | |
| 124 auto private_ws_data = | |
| 125 reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM*>(&buffer[buffer_size1]); | |
| 126 if (PdhGetFormattedCounterArray(counter_pair.private_ws_handle, | |
| 127 PDH_FMT_LARGE, &buffer_size1, &item_count1, | |
| 128 private_ws_data) != ERROR_SUCCESS) | |
| 129 continue; | |
| 130 | |
| 131 // Make room for the new set of records. | |
| 132 size_t start_offset = records_.size(); | |
| 133 records_.resize(start_offset + item_count1); | |
| 134 | |
| 135 for (DWORD i = 0; i < item_count1; ++i) { | |
| 136 records_[start_offset + i].process_id = | |
| 137 process_id_data[i].FmtValue.longValue; | |
| 138 // Integer overflow can happen here if a 32-bit process is monitoring a | |
| 139 // 64-bit process so we do a saturated_cast. | |
| 140 records_[start_offset + i].private_ws = | |
| 141 base::saturated_cast<size_t>(private_ws_data[i].FmtValue.largeValue); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // The results will include all processes that match the passed in name, | |
| 146 // regardless of whether they are spawned by the calling process. | |
| 147 // The results must be sorted by process ID for efficient lookup. | |
| 148 std::sort(records_.begin(), records_.end()); | |
| 149 } | |
| 150 | |
| 151 size_t PrivateWorkingSetSnapshot::GetPrivateWorkingSet( | |
| 152 base::ProcessId process_id) const { | |
| 153 // Do a binary search for the requested process ID and return the working set | |
| 154 // if found. | |
| 155 auto p = std::lower_bound(records_.begin(), records_.end(), process_id); | |
| 156 if (p != records_.end() && p->process_id == process_id) | |
| 157 return p->private_ws; | |
| 158 | |
| 159 return 0; | |
| 160 } | |
| OLD | NEW |