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