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