// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/private_working_set_snapshot.h" #include <pdh.h> #include <pdhmsg.h> #include <algorithm> #include "base/numerics/safe_conversions.h" #include "base/win/windows_version.h" // Link pdh.lib (import library for pdh.dll) whenever this file is linked in. #pragma comment(lib, "pdh.lib") PrivateWorkingSetSnapshot::PrivateWorkingSetSnapshot() {} PrivateWorkingSetSnapshot::~PrivateWorkingSetSnapshot() {} void PrivateWorkingSetSnapshot::Initialize() { DCHECK(!initialized_); // Set this to true whether initialization succeeds or not. That is, only try // once. initialized_ = true; // The Pdh APIs are supported on Windows XP and above, but the "Working Set - // Private" counter that PrivateWorkingSetSnapshot depends on is not defined // until Windows Vista and is not reliable until Windows 7. Early-out to avoid // wasted effort. All queries will return zero and will have to use the // fallback calculations. if (base::win::GetVersion() <= base::win::VERSION_VISTA) { process_names_.clear(); return; } // Pdh.dll has a format-string bug that causes crashes on Windows 8 and 8.1. // This was patched (around October 2014) but the broken versions are still on // a few machines. CreateFileVersionInfoForModule touches the disk so it // be used on the main thread. If this call gets moved off of the main thread // then pdh.dll version checking can be found in the history of // https://codereview.chromium.org/1269223005 // Create a Pdh query PDH_HQUERY query_handle; if (PdhOpenQuery(NULL, NULL, &query_handle) != ERROR_SUCCESS) { process_names_.clear(); return; } query_handle_.Set(query_handle); for (const auto& process_name : process_names_) { AddToMonitorList(process_name); } process_names_.clear(); } void PrivateWorkingSetSnapshot::AddToMonitorList( const std::string& process_name) { if (!query_handle_.IsValid()) { // Save the name for later. if (!initialized_) process_names_.push_back(process_name); return; } // Create the magic strings that will return a list of process IDs and a list // of private working sets. The 'process_name' variable should be something // like "chrome". The '*' character indicates that we want records for all // processes whose names start with process_name - all chrome processes, but // also all 'chrome_editor.exe' processes or other matching names. The excess // information is unavoidable but harmless. std::string process_id_query = "\\Process(" + process_name + "*)\\ID Process"; std::string private_ws_query = "\\Process(" + process_name + "*)\\Working Set - Private"; // Add the two counters to the query. PdhCounterPair new_counters; if (PdhAddCounterA(query_handle_.Get(), process_id_query.c_str(), NULL, &new_counters.process_id_handle) != ERROR_SUCCESS) { return; } // If adding the second counter fails then we should remove the first one. if (PdhAddCounterA(query_handle_.Get(), private_ws_query.c_str(), NULL, &new_counters.private_ws_handle) != ERROR_SUCCESS) { PdhRemoveCounter(new_counters.process_id_handle); } // Record the pair of counter query handles so that we can query them later. counter_pairs_.push_back(new_counters); } void PrivateWorkingSetSnapshot::Sample() { // Make sure this is called once. if (!initialized_) Initialize(); if (counter_pairs_.empty()) return; // Destroy all previous data. records_.resize(0); // Record the requested data into PDH's internal buffers. if (PdhCollectQueryData(query_handle_.Get()) != ERROR_SUCCESS) return; for (auto& counter_pair : counter_pairs_) { // Find out how much space is required for the two counter arrays. // A return code of PDH_MORE_DATA indicates that we should call again with // the buffer size returned. DWORD buffer_size1 = 0; DWORD item_count1 = 0; // Process IDs should be retrieved as PDH_FMT_LONG if (PdhGetFormattedCounterArray(counter_pair.process_id_handle, PDH_FMT_LONG, &buffer_size1, &item_count1, nullptr) != PDH_MORE_DATA) continue; if (buffer_size1 == 0 || item_count1 == 0) continue; DWORD buffer_size2 = 0; DWORD item_count2 = 0; // Working sets should be retrieved as PDH_FMT_LARGE (LONGLONG) // Note that if this second call to PdhGetFormattedCounterArray with the // buffer size and count variables being zero is omitted then the PID and // working-set results are not reliably correlated. if (PdhGetFormattedCounterArray(counter_pair.private_ws_handle, PDH_FMT_LARGE, &buffer_size2, &item_count2, nullptr) != PDH_MORE_DATA) continue; // It is not clear whether Pdh guarantees that the two counters in the same // query will execute atomically - if they will see the same set of // processes. If they do not then the correspondence between "ID Process" // and "Working Set - Private" is lost and we have to discard these results. // In testing these values have always matched. If this check fails then // the old per-process memory calculations will be used instead. if (buffer_size1 != buffer_size2 || item_count1 != item_count2) continue; // Allocate enough space for the results of both queries. std::vector<char> buffer(buffer_size1 * 2); // Retrieve the process ID data. auto process_id_data = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM*>(&buffer[0]); if (PdhGetFormattedCounterArray(counter_pair.process_id_handle, PDH_FMT_LONG, &buffer_size1, &item_count1, process_id_data) != ERROR_SUCCESS) continue; // Retrieve the private working set data. auto private_ws_data = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM*>(&buffer[buffer_size1]); if (PdhGetFormattedCounterArray(counter_pair.private_ws_handle, PDH_FMT_LARGE, &buffer_size1, &item_count1, private_ws_data) != ERROR_SUCCESS) continue; // Make room for the new set of records. size_t start_offset = records_.size(); records_.resize(start_offset + item_count1); for (DWORD i = 0; i < item_count1; ++i) { records_[start_offset + i].process_id = process_id_data[i].FmtValue.longValue; // Integer overflow can happen here if a 32-bit process is monitoring a // 64-bit process so we do a saturated_cast. records_[start_offset + i].private_ws = base::saturated_cast<size_t>(private_ws_data[i].FmtValue.largeValue); } } // The results will include all processes that match the passed in name, // regardless of whether they are spawned by the calling process. // The results must be sorted by process ID for efficient lookup. std::sort(records_.begin(), records_.end()); } size_t PrivateWorkingSetSnapshot::GetPrivateWorkingSet( base::ProcessId process_id) const { // Do a binary search for the requested process ID and return the working set // if found. auto p = std::lower_bound(records_.begin(), records_.end(), process_id); if (p != records_.end() && p->process_id == process_id) return p->private_ws; return 0; }