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