summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mongo/base/error_codes.err2
-rw-r--r--src/mongo/util/SConscript20
-rw-r--r--src/mongo/util/perfctr_collect.cpp506
-rw-r--r--src/mongo/util/perfctr_collect.h284
-rw-r--r--src/mongo/util/perfctr_collect_test.cpp422
5 files changed, 1234 insertions, 0 deletions
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 8b07c3ab8b6..e77c8dd2d94 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -175,6 +175,8 @@ error_code("QueryPlanKilled", 173)
error_code("FileOpenFailed", 174)
error_code("ZoneNotFound", 175)
error_code("RangeOverlapConflict", 176)
+error_code("WindowsPdhError", 177)
+error_code("BadPerfCounterPath", 178)
# Non-sequential error codes (for compatibility only)
error_code("SocketException", 9001)
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript
index 0a4864eea40..83516dcc9da 100644
--- a/src/mongo/util/SConscript
+++ b/src/mongo/util/SConscript
@@ -548,5 +548,25 @@ if env.TargetOSIs('linux'):
],
LIBDEPS=[
'procparser',
+ ])
+
+if env.TargetOSIs('windows'):
+ env.Library(
+ target='perfctr_collect',
+ source=[
+ "perfctr_collect.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/platform/platform',
],
)
+
+ env.CppUnitTest(
+ target='perfctr_collect_test',
+ source=[
+ 'perfctr_collect_test.cpp',
+ ],
+ LIBDEPS=[
+ 'perfctr_collect',
+ ])
diff --git a/src/mongo/util/perfctr_collect.cpp b/src/mongo/util/perfctr_collect.cpp
new file mode 100644
index 00000000000..da6667c9e77
--- /dev/null
+++ b/src/mongo/util/perfctr_collect.cpp
@@ -0,0 +1,506 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/util/perfctr_collect.h"
+
+#include "mongo/base/init.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/scopeguard.h"
+#include "mongo/util/text.h"
+
+namespace mongo {
+
+namespace {
+
+// Handle to the PDH library so that we can format error messages.
+HANDLE hPdhLibrary = nullptr;
+
+/**
+ * Load PDH.DLL for good error messages.
+ */
+MONGO_INITIALIZER(PdhInit)(InitializerContext* context) {
+
+ hPdhLibrary = LoadLibraryW(L"pdh.dll");
+ if (nullptr == hPdhLibrary) {
+ DWORD gle = GetLastError();
+ return {ErrorCodes::WindowsPdhError,
+ str::stream() << "LoadLibrary of pdh.dll failed with "
+ << errnoWithDescription(gle)};
+ }
+
+ return Status::OK();
+}
+
+/**
+ * Output an error message for ether PDH or the system.
+ */
+std::string errnoWithPdhDescription(PDH_STATUS status) {
+ LPWSTR errorText = nullptr;
+
+ if (!FormatMessageW(
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ hPdhLibrary,
+ status,
+ 0,
+ reinterpret_cast<LPWSTR>(
+ &errorText), // fudge the type so FormatMessageW uses it as an out parameter.
+ 0,
+ nullptr)) {
+ DWORD gle = GetLastError();
+ return str::stream() << "Format message failed with " << gle << " for status " << status;
+ }
+
+ ScopeGuard errorTextGuard = MakeGuard([errorText]() { LocalFree(errorText); });
+ std::string utf8ErrorText = toUtf8String(errorText);
+
+ auto size = utf8ErrorText.find_first_of("\r\n");
+ if (size == std::string::npos) {
+ size = utf8ErrorText.length();
+ }
+
+ return utf8ErrorText.substr(0, size);
+}
+
+/**
+ * Format an error message for a PDH function call failure.
+ */
+std::string formatFunctionCallError(StringData functionName, PDH_STATUS status) {
+ return str::stream() << functionName << " failed with '" << errnoWithPdhDescription(status)
+ << "'";
+}
+
+/**
+ * Transform a vector of string data into a vector of strings.
+ */
+void transformStringDataVector(const std::vector<StringData>& input,
+ std::vector<std::string>* output) {
+ output->reserve(input.size());
+ for (const auto& str : input) {
+ output->emplace_back(str.toString());
+ }
+}
+
+/**
+ * Check if a counter depends on system ticks per second to compute its value from raw values. This
+ * is basically any counter that does not use 100NS as a base. FYI, if we query raw count counters,
+ * we will get the system ticks as a time base.
+ */
+bool counterHasTickBasedTimeBase(uint32_t type) {
+ return ((type & PERF_TYPE_COUNTER) == PERF_TYPE_COUNTER) &&
+ ((type & PERF_TIMER_100NS) != PERF_TIMER_100NS);
+}
+
+} // namespace
+
+StatusWith<std::vector<std::string>> PerfCounterCollection::checkCounters(
+ StringData name, const std::vector<StringData>& paths) {
+
+ if (_counters.find(name.toString()) != _counters.end() ||
+ _nestedCounters.find(name.toString()) != _nestedCounters.end()) {
+ return Status(ErrorCodes::DuplicateKeyValue,
+ str::stream() << "Duplicate group name for " << name);
+ }
+
+ std::vector<std::string> stringPaths;
+ transformStringDataVector(paths, &stringPaths);
+
+ // While duplicate counter paths are not a problem for PDH, they are a waste of time.
+ std::sort(stringPaths.begin(), stringPaths.end());
+
+ if (std::unique(stringPaths.begin(), stringPaths.end()) != stringPaths.end()) {
+ return Status(ErrorCodes::DuplicateKeyValue,
+ str::stream() << "Duplicate counters in paths specified");
+ }
+
+ return {stringPaths};
+}
+
+Status PerfCounterCollection::addCountersGroup(StringData name,
+ const std::vector<StringData>& paths) {
+
+ auto swCounters = checkCounters(name, paths);
+ if (!swCounters.getStatus().isOK()) {
+ return swCounters.getStatus();
+ }
+
+ _counters.emplace(name.toString(), std::move(swCounters.getValue()));
+
+ return Status::OK();
+}
+
+Status PerfCounterCollection::addCountersGroupedByInstanceName(
+ StringData name, const std::vector<StringData>& paths) {
+
+ auto swCounters = checkCounters(name, paths);
+ if (!swCounters.getStatus().isOK()) {
+ return swCounters.getStatus();
+ }
+
+ _nestedCounters.emplace(name.toString(), std::move(swCounters.getValue()));
+
+ return Status::OK();
+}
+
+StatusWith<std::unique_ptr<PerfCounterCollector>> PerfCounterCollector::create(
+ PerfCounterCollection builder) {
+ auto pcc = std::unique_ptr<PerfCounterCollector>(new PerfCounterCollector());
+
+ Status s = pcc->open();
+ if (!s.isOK()) {
+ return s;
+ }
+
+ for (const auto& kvp : builder._counters) {
+ s = pcc->addCountersGroup(kvp.first, kvp.second);
+ if (!s.isOK()) {
+ return s;
+ }
+ }
+
+ // Sort to enforce predictable output in final document
+ std::sort(pcc->_counters.begin(),
+ pcc->_counters.end(),
+ [](const CounterGroup& a, const CounterGroup& b) { return a.name < b.name; });
+
+ for (const auto& kvp : builder._nestedCounters) {
+ s = pcc->addCountersGroupedByInstanceName(kvp.first, kvp.second);
+ if (!s.isOK()) {
+ return s;
+ }
+ }
+
+ std::sort(
+ pcc->_nestedCounters.begin(),
+ pcc->_nestedCounters.end(),
+ [](const NestedCounterGroup& a, const NestedCounterGroup& b) { return a.name < b.name; });
+
+ pcc->checkForTicksTimeBase();
+
+ return {std::move(pcc)};
+}
+
+PerfCounterCollector::~PerfCounterCollector() {
+ /*ignore*/ PdhCloseQuery(_query);
+}
+
+Status PerfCounterCollector::open() {
+
+ PDH_STATUS status = PdhOpenQueryW(nullptr, NULL, &_query);
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError, formatFunctionCallError("PdhOpenQueryW", status)};
+ }
+
+ return Status::OK();
+}
+
+StatusWith<PerfCounterCollector::CounterInfo> PerfCounterCollector::addCounter(StringData path) {
+
+ PDH_HCOUNTER counter{0};
+
+ PDH_STATUS status =
+ PdhAddCounterW(_query, toNativeString(path.toString().c_str()).c_str(), NULL, &counter);
+
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError, formatFunctionCallError("PdhAddCounterW", status)};
+ }
+
+ DWORD bufferSize = 0;
+
+ status = PdhGetCounterInfoW(counter, false, &bufferSize, nullptr);
+
+ if (status != PDH_MORE_DATA) {
+ return {ErrorCodes::WindowsPdhError, formatFunctionCallError("PdhGetCounterInfoW", status)};
+ }
+
+ auto buf = std::make_unique<char[]>(bufferSize);
+ auto counterInfo = reinterpret_cast<PPDH_COUNTER_INFO>(buf.get());
+
+ status = PdhGetCounterInfoW(counter, false, &bufferSize, counterInfo);
+
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError, formatFunctionCallError("PdhGetCounterInfoW", status)};
+ }
+
+ // A full qualified path is as such:
+ // "\\MYMACHINE\\Processor(0)\\% Idle Time"
+ // MachineName \\ Object Name (Instance Name) \\ CounterName
+ // Ex:
+ // MachineName: MYMACHINE
+ // Object Name: Processor
+ // InstanceName: 0
+ // CounterName: % Idle Time
+ // We do not want to use Machine Name, but sometimes we want InstanceName
+ //
+ std::string firstName = str::stream() << '\\' << toUtf8String(counterInfo->szObjectName) << '\\'
+ << toUtf8String(counterInfo->szCounterName);
+
+ // Compute a second name
+ std::string secondName(firstName);
+
+ bool hasSecondValue = false;
+
+ // Rate counters need time as a base
+ if ((counterInfo->dwType & PERF_COUNTER_DELTA) == PERF_COUNTER_DELTA ||
+ (counterInfo->dwType & PERF_COUNTER_FRACTION) == PERF_COUNTER_FRACTION ||
+ (counterInfo->dwType & PERF_ELAPSED_TIME) == PERF_ELAPSED_TIME) {
+ secondName += " Base";
+ hasSecondValue = true;
+ } else {
+ invariant(counterInfo->dwType == PERF_COUNTER_RAWCOUNT ||
+ counterInfo->dwType == PERF_COUNTER_LARGE_RAWCOUNT);
+ }
+
+ // InstanceName is null for counters without instance names
+ return {CounterInfo{std::move(firstName),
+ std::move(secondName),
+ hasSecondValue,
+ counterInfo->szInstanceName ? toUtf8String(counterInfo->szInstanceName)
+ : std::string(),
+ counterInfo->dwType,
+ counter}};
+}
+
+StatusWith<std::vector<PerfCounterCollector::CounterInfo>> PerfCounterCollector::addCounters(
+ StringData path) {
+ std::wstring pathWide = toNativeString(path.toString().c_str());
+ DWORD pathListLength = 0;
+ PDH_STATUS status = PdhExpandCounterPathW(pathWide.c_str(), nullptr, &pathListLength);
+
+ if (status != PDH_MORE_DATA) {
+ return {ErrorCodes::WindowsPdhError,
+ str::stream() << formatFunctionCallError("PdhExpandCounterPathW", status)
+ << " for counter '"
+ << path
+ << "'"};
+ }
+
+ auto buf = std::make_unique<wchar_t[]>(pathListLength);
+
+ status = PdhExpandCounterPathW(pathWide.c_str(), buf.get(), &pathListLength);
+
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError,
+ formatFunctionCallError("PdhExpandCounterPathW", status)};
+ }
+
+ std::vector<CounterInfo> counters;
+
+ // Windows' PdhExpandWildCardPathW returns a nullptr terminated array of nullptr separated
+ // strings.
+ std::vector<std::string> counterNames;
+
+ const wchar_t* ptr = buf.get();
+ while (ptr && *ptr) {
+ counterNames.emplace_back(toUtf8String(ptr));
+ ptr += wcslen(ptr) + 1;
+ }
+
+ // Sort to ensure we have a predictable ordering in the final BSON
+ std::sort(counterNames.begin(), counterNames.end());
+
+ for (const auto& name : counterNames) {
+
+ auto swCounterInfo = addCounter(name);
+ if (!swCounterInfo.isOK()) {
+ return swCounterInfo.getStatus();
+ }
+
+ counters.emplace_back(std::move(swCounterInfo.getValue()));
+ }
+
+ return {std::move(counters)};
+}
+
+Status PerfCounterCollector::addCountersGroup(StringData groupName,
+ const std::vector<std::string>& paths) {
+ CounterGroup group;
+ group.name = groupName.toString();
+
+ for (const auto& path : paths) {
+ auto swCounters = addCounters(path.c_str());
+ if (!swCounters.isOK()) {
+ return swCounters.getStatus();
+ }
+
+ auto newCounters = swCounters.getValue();
+
+ std::copy(newCounters.begin(), newCounters.end(), std::back_inserter(group.counters));
+ }
+
+ _counters.emplace_back(group);
+
+ return Status::OK();
+}
+
+Status PerfCounterCollector::addCountersGroupedByInstanceName(
+ StringData groupName, const std::vector<std::string>& paths) {
+ NestedCounterGroup group;
+ group.name = groupName.toString();
+
+ for (const auto& path : paths) {
+ auto swCounters = addCounters(path.c_str());
+ if (!swCounters.isOK()) {
+ return swCounters.getStatus();
+ }
+
+ auto newCounters = swCounters.getValue();
+
+ for (const auto& counter : newCounters) {
+ // Verify the counter has an instance name.
+ if (counter.instanceName.empty()) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Counter '" << counter.firstName
+ << "' must be an instance specific counter."};
+ }
+
+ // Skip counters in the _Total instance category.
+ if (counter.instanceName == "_Total") {
+ continue;
+ }
+
+ group.counters[counter.instanceName].emplace_back(std::move(counter));
+ }
+ }
+
+ _nestedCounters.emplace_back(group);
+
+ return Status::OK();
+}
+
+Status PerfCounterCollector::collectCounters(const std::vector<CounterInfo>& counters,
+ BSONObjBuilder* builder) {
+ for (const auto& counterInfo : counters) {
+
+ DWORD dwType = 0;
+ PDH_RAW_COUNTER rawCounter = {0};
+
+ PDH_STATUS status = PdhGetRawCounterValue(counterInfo.handle, &dwType, &rawCounter);
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError,
+ formatFunctionCallError("PdhGetRawCounterValue", status)};
+ }
+
+ if (counterInfo.hasSecondValue) {
+ // Delta, Rate, and Elapsed Time counters require the second value in the raw counter
+ // information
+ builder->append(counterInfo.firstName, rawCounter.FirstValue);
+ builder->append(counterInfo.secondName, rawCounter.SecondValue);
+ } else {
+ builder->append(counterInfo.firstName, rawCounter.FirstValue);
+ }
+ }
+
+ return Status::OK();
+}
+
+void PerfCounterCollector::checkForTicksTimeBase() {
+ for (const auto& counterGroup : _counters) {
+ for (const auto& counter : counterGroup.counters) {
+ if (counterHasTickBasedTimeBase(counter.type)) {
+ _timeBaseTicksCounter = &counter;
+ return;
+ }
+ }
+ }
+
+ for (const auto& counterGroup : _nestedCounters) {
+ for (const auto& instanceNamePair : counterGroup.counters) {
+ for (const auto& counter : instanceNamePair.second) {
+ if (counterHasTickBasedTimeBase(counter.type)) {
+ _timeBaseTicksCounter = &counter;
+ return;
+ }
+ }
+ }
+ }
+}
+
+Status PerfCounterCollector::collect(BSONObjBuilder* builder) {
+ // Ask PDH to collect the counters
+ PDH_STATUS status = PdhCollectQueryData(_query);
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError,
+ formatFunctionCallError("PdhCollectQueryData", status)};
+ }
+
+ // Output timebase
+ // Counters that are based on time either use 100NS or System Ticks Per Second.
+ // We only need to output system ticks per second once if any counter depends on it.
+ // This is typically 3320310.
+ if (_timeBaseTicksCounter) {
+ int64_t timebase;
+
+ status = PdhGetCounterTimeBase(_timeBaseTicksCounter->handle, &timebase);
+ if (status != ERROR_SUCCESS) {
+ return {ErrorCodes::WindowsPdhError,
+ formatFunctionCallError("PdhGetCounterTimeBase", status)};
+ }
+
+ builder->append("timebase", timebase);
+ }
+
+ // Retrieve all the values that PDH collected for us.
+ for (const auto& counterGroup : _counters) {
+ BSONObjBuilder subObjBuilder(builder->subobjStart(counterGroup.name));
+
+ Status s = collectCounters(counterGroup.counters, &subObjBuilder);
+ if (!s.isOK()) {
+ return s;
+ }
+
+ subObjBuilder.doneFast();
+ }
+
+ for (const auto& counterGroup : _nestedCounters) {
+ BSONObjBuilder subObjBuilder(builder->subobjStart(counterGroup.name));
+
+ for (const auto& instanceNamePair : counterGroup.counters) {
+ BSONObjBuilder instSubObjBuilder(builder->subobjStart(instanceNamePair.first));
+
+ Status s = collectCounters(instanceNamePair.second, &instSubObjBuilder);
+ if (!s.isOK()) {
+ return s;
+ }
+
+ instSubObjBuilder.doneFast();
+ }
+
+ subObjBuilder.doneFast();
+ }
+
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/util/perfctr_collect.h b/src/mongo/util/perfctr_collect.h
new file mode 100644
index 00000000000..72c74f9833a
--- /dev/null
+++ b/src/mongo/util/perfctr_collect.h
@@ -0,0 +1,284 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <pdh.h>
+#include <pdhmsg.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+class BSONObjBuilder;
+
+/**
+ * PerfCounterCollection contains a set of counters for PerfCounterCollector to collect. This class
+ * supports adding counters with wildcards. It also optionally supports grouping counters by
+ * instance name.
+ */
+class PerfCounterCollection {
+ MONGO_DISALLOW_COPYING(PerfCounterCollection);
+
+ friend class PerfCounterCollector;
+
+public:
+ PerfCounterCollection() = default;
+ PerfCounterCollection(PerfCounterCollection&&) = default;
+ PerfCounterCollection& operator=(PerfCounterCollection&&) = default;
+
+ /**
+ * Add vector of counters grouped under 'name'.
+ *
+ * group name - the name of the BSON document to add these counters into.
+ * paths - a vector of counter paths. These may contain wildcards.
+ *
+ * Errors if groupName duplicates an existing group or if paths has duplicate keys. Does not
+ * validate if the counters exist.
+ *
+ * Output document:
+ * For the following counters in "cpu":
+ * "\System\Processes"
+ * "\Processor(_Total)\% Idle Time"
+ *
+ * {
+ * "cpu" : {
+ * "\System\Processes" : 42,
+ * "\Processor\% Idle Time" : 12,
+ * "\Processor\% Idle Time Base" : 53,
+ * }
+ * }
+ */
+ Status addCountersGroup(StringData groupName, const std::vector<StringData>& paths);
+
+ /**
+ * Add vector of counters grouped under 'name', and grouped by instance name.
+ *
+ * group name - the name of the BSON document to add these counters into.
+ * paths - a vector of counter paths. These may contain wildcards. The '_Total' instance is
+ * automatically filtered since it can be computed by summing other instances.
+ *
+ * Errors if groupName duplicates an existing group or if paths has duplicate keys. Does not
+ * validate if the counters exist.
+ *
+ * Output document:
+ * For the following counters in "disks":
+ * "\PhysicalDisk(*)\% Disk Write Time"
+ *
+ * {
+ * "disks" : {
+ * "0 C:" : {
+ * "\PhysicalDisk\% Disk Write Time": 42,
+ * "\PhysicalDisk\% Disk Write Time Base": 32,
+ * },
+ * "1 D:" : {
+ * "\PhysicalDisk\% Disk Write Time": 43,
+ * "\PhysicalDisk\% Disk Write Time Base": 37,
+ * }
+ * }
+ * }
+ */
+ Status addCountersGroupedByInstanceName(StringData groupName,
+ const std::vector<StringData>& paths);
+
+private:
+ /**
+ * Check for duplicate group and counters.
+ */
+ StatusWith<std::vector<std::string>> checkCounters(StringData groupName,
+ const std::vector<StringData>& paths);
+
+private:
+ // Vector of counters which are not sub-grouped by instance name.
+ std::unordered_map<std::string, std::vector<std::string>> _counters;
+
+ // Vector of counters sub grouped by instance name.
+ std::unordered_map<std::string, std::vector<std::string>> _nestedCounters;
+};
+
+/**
+ * PerfCounterCollector collects a series of counters from a Performance Data Helper (PDH) Query and
+ * output the raw counter values to BSONObjBuilder.
+ */
+class PerfCounterCollector {
+ MONGO_DISALLOW_COPYING(PerfCounterCollector);
+
+public:
+ ~PerfCounterCollector();
+ PerfCounterCollector(PerfCounterCollector&&) = default;
+
+ /**
+ * Create a PerfCounterCollector to collect the performance counters in the specified
+ * PerfCounterCollection.
+ */
+ static StatusWith<std::unique_ptr<PerfCounterCollector>> create(PerfCounterCollection builder);
+
+ /**
+ * Collect the counters from PDH, and output their raw values into builder.
+ *
+ * For each counters, if the counter is a delta, rate, or fraction counter, the second value is
+ * output under the name "<counter> Base". Also, a single field is output called "timebase" if
+ * any counter depends on system ticks per second.
+ */
+ Status collect(BSONObjBuilder* builder);
+
+private:
+ /**
+ * Describes a counter by querying PDH, and contains the necessary information to retrieve a
+ * counter from PDH.
+ */
+ struct CounterInfo {
+ /**
+ * The name of the first value for a counter. This is output as:
+ * "\<Object Name>\<Counter Name>".
+ */
+ std::string firstName;
+
+ /**
+ * The name of the second value of a counter if the counter is a delta, rate, or fraction
+ * counter. This is output as: "\<Object Name>\<Counter Name> Base".
+ */
+ std::string secondName;
+
+ /**
+ * True if the counter is a delta, rate, or fraction counter, and its value should be output
+ * in the output BSON document.
+ */
+ bool hasSecondValue;
+
+ /**
+ * Instance name of the counter. Empty if the counter has no instance name.
+ */
+ std::string instanceName;
+
+ /**
+ * Counter Type. See PERF_* constants in winperf.h.
+ * https://technet.microsoft.com/en-us/library/cc785636(v=ws.10).aspx
+ */
+ uint32_t type;
+
+ /**
+ * Handle of counter to collect from.
+ */
+ PDH_HCOUNTER handle;
+ };
+
+ /**
+ * A set of counters that are part of "name" in the final bson document.
+ */
+ struct CounterGroup {
+ /**
+ * Name of the counter group.
+ */
+ std::string name;
+
+ /**
+ * Vector of counters in this group.
+ */
+ std::vector<CounterInfo> counters;
+ };
+
+ /**
+ * A set of counters that are part of "name" and "instanceName" in the final bson document.
+ */
+ struct NestedCounterGroup {
+ /**
+ * Name of the counter group.
+ */
+ std::string name;
+
+ /**
+ * A map of instance name to vector of counters to collect for each instance name.
+ * Ordered Map to ensure output is well-ordered.
+ */
+ std::map<std::string, std::vector<CounterInfo>> counters;
+ };
+
+private:
+ PerfCounterCollector() = default;
+
+ /**
+ * Open the PDH Query.
+ */
+ Status open();
+
+ /**
+ * Add the specified counter group to the PDH Query.
+ */
+ Status addCountersGroup(StringData groupName, const std::vector<std::string>& paths);
+
+ /**
+ * Add the specified counter group to the PDH Query grouped by instance name.
+ */
+ Status addCountersGroupedByInstanceName(StringData groupName,
+ const std::vector<std::string>& paths);
+
+ /**
+ * Add a counter to the PDH query and get a description of it.
+ */
+ StatusWith<CounterInfo> addCounter(StringData path);
+
+ /**
+ * Add a set of counters to the PDH query, and get descriptions of them.
+ */
+ StatusWith<std::vector<CounterInfo>> addCounters(StringData path);
+
+ /**
+ * Collect a vector of counters and output them to builder.
+ */
+ Status collectCounters(const std::vector<CounterInfo>& counters, BSONObjBuilder* builder);
+
+ /**
+ * Check if any of the counters we want depends on system ticks per second as a time base.
+ */
+ void checkForTicksTimeBase();
+
+private:
+ // PDH Query
+ HQUERY _query{INVALID_HANDLE_VALUE};
+
+ // Typically: CPU & Memory counters
+ std::vector<CounterGroup> _counters;
+
+ // Typically: Disks counters
+ std::vector<NestedCounterGroup> _nestedCounters;
+
+ // A counter that uses ticks as a timebase
+ const CounterInfo* _timeBaseTicksCounter{nullptr};
+};
+
+} // namespace mongo
diff --git a/src/mongo/util/perfctr_collect_test.cpp b/src/mongo/util/perfctr_collect_test.cpp
new file mode 100644
index 00000000000..f9d6b2fda09
--- /dev/null
+++ b/src/mongo/util/perfctr_collect_test.cpp
@@ -0,0 +1,422 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/util/perfctr_collect.h"
+
+#include <boost/filesystem.hpp>
+#include <map>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+using StringMap = std::map<std::string, uint64_t>;
+
+/**
+ * Convert BSON document nested up to 3 levels into a map where keys are dot separated paths to
+ * values.
+ */
+StringMap toNestedStringMap(BSONObj& obj) {
+ StringMap map;
+
+ for (const auto& parent : obj) {
+
+ if (parent.isABSONObj()) {
+ std::string parentNamePrefix = std::string(parent.fieldName()) + ".";
+
+ for (const auto& child : parent.Obj()) {
+
+ if (child.isABSONObj()) {
+ std::string childNamePrefix = parentNamePrefix + child.fieldName() + ".";
+
+ for (const auto& grandChild : child.Obj()) {
+ map[childNamePrefix + grandChild.fieldName()] = grandChild.numberLong();
+ }
+
+ } else {
+ map[parentNamePrefix + child.fieldName()] = child.numberLong();
+ }
+ }
+ } else {
+ map[parent.fieldName()] = parent.numberLong();
+ }
+ }
+
+ return map;
+}
+
+#define ASSERT_KEY(_key) ASSERT_TRUE(stringMap.find(_key) != stringMap.end());
+#define ASSERT_NO_KEY(_key) ASSERT_TRUE(stringMap.find(_key) == stringMap.end());
+
+#define ASSERT_TIMEBASE ASSERT_KEY("timebase");
+#define ASSERT_NO_TIMEBASE ASSERT_NO_KEY("timebase");
+
+#define ASSERT_GROUP_AND_RAW_COUNTER(g, c) \
+ ASSERT_KEY(g "." c); \
+ ASSERT_NO_KEY(g "." c " Base");
+
+#define ASSERT_GROUP_AND_NON_RAW_COUNTER(g, c) \
+ ASSERT_KEY(g "." c); \
+ ASSERT_KEY(g "." c " Base");
+
+#define ASSERT_NESTED_GROUP_AND_NON_RAW_COUNTER(g, p, c) \
+ ASSERT_KEY(g "." p "." c); \
+ ASSERT_KEY(g "." p "." c " Base");
+
+#define ASSERT_NO_NESTED_GROUP_AND_NON_RAW_COUNTER(g, p, c) \
+ ASSERT_NO_KEY(g "." p "." c); \
+ ASSERT_NO_KEY(g "." p "." c " Base");
+
+#define COLLECT_COUNTERS_VERBOSE \
+ BSONObjBuilder builder; \
+ ASSERT_OK(collector->collect(&builder)); \
+ auto obj = builder.obj(); \
+ log() << "OBJ:" << obj; \
+ auto stringMap = toNestedStringMap(obj); \
+ for (const auto& kvp : stringMap) { \
+ log() << "kvp " << kvp.first << " - " << kvp.second; \
+ }
+
+#define COLLECT_COUNTERS_QUIET \
+ BSONObjBuilder builder; \
+ ASSERT_OK(collector->collect(&builder)); \
+ auto obj = builder.obj(); \
+ auto stringMap = toNestedStringMap(obj);
+
+#define COLLECT_COUNTERS COLLECT_COUNTERS_QUIET
+
+size_t kDefaultCollectionCount = 2;
+
+// Simple verification test
+TEST(FTDCPerfCollector, TestSingleCounter) {
+
+ PerfCounterCollection collection;
+ // PERF_100NSEC_TIMER
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\Processor(0)\\% Idle Time"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+
+ ASSERT_NO_TIMEBASE;
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Idle Time");
+ }
+}
+
+
+// Simple verification test
+TEST(FTDCPerfCollector, TestSingleRawCounter) {
+
+ PerfCounterCollection collection;
+ // PERF_COUNTER_RAWCOUNT
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\System\\Processes"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+
+ ASSERT_NO_TIMEBASE;
+ ASSERT_GROUP_AND_RAW_COUNTER("cpu", "\\System\\Processes");
+ }
+}
+
+// Test negative cases for collection
+TEST(FTDCPerfCollector, TestBadCollectionInput) {
+
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\Processor(0)\\% Idle Time"}));
+
+ // Duplicate group
+ ASSERT_NOT_OK(collection.addCountersGroup("cpu", {"\\Processor(0)\\% Idle Time"}));
+
+ // Duplicate counter
+ ASSERT_NOT_OK(collection.addCountersGroup(
+ "cpu2",
+ {
+ "\\Processor(0)\\% Idle Time", "\\Processor(0)\\% Idle Time",
+ }));
+
+ // Duplicate group
+ ASSERT_NOT_OK(
+ collection.addCountersGroupedByInstanceName("cpu", {"\\Processor(0)\\% Idle Time"}));
+
+ // Duplicate counter
+ ASSERT_NOT_OK(collection.addCountersGroupedByInstanceName(
+ "cpu2",
+ {
+ "\\Processor(0)\\% Idle Time", "\\Processor(0)\\% Idle Time",
+ }));
+}
+
+// Test negative collector input
+TEST(FTDCPerfCollector, TestBadCollectorInput) {
+ // Bad counter name
+ {
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\Processor(0)\\DOES NOT EXIST"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_NOT_OK(swCollector.getStatus());
+ }
+
+ // Bad wild card
+ {
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\Processor(0)\\DOES*"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_NOT_OK(swCollector.getStatus());
+ }
+
+ // Use addCounterGroup with instance wildcard
+ {
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup("cpu", {"\\Processor(*)\\\\% Idle Time"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_NOT_OK(swCollector.getStatus());
+ }
+
+ // Use addCountersGroupedByInstanceName without instance name
+ {
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroupedByInstanceName("cpu", {"\\System\\Processes"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_NOT_OK(swCollector.getStatus());
+ }
+}
+
+// Test all the different counter types we use in the MongoDB code
+TEST(FTDCPerfCollector, TestCounterTypes) {
+
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup(
+ "misc",
+ {
+ "\\Processor(0)\\% Idle Time", // PERF_100NSEC_TIMER
+ "\\Processor(0)\\% Processor Time", // PERF_100NSEC_TIMER_INV
+ "\\System\\Processes", // PERF_COUNTER_RAWCOUNT
+ "\\System\\System Up Time", // PERF_ELAPSED_TIME
+ "\\Memory\\Available Bytes", // PERF_COUNTER_LARGE_RAWCOUNT
+ "\\PhysicalDisk(_Total)\\% Disk Write Time", // PERF_PRECISION_100NS_TIMER
+ "\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Write", // PERF_AVERAGE_BULK
+ "\\PhysicalDisk(_Total)\\Avg. Disk Read Queue Length", // PERF_COUNTER_LARGE_QUEUELEN_TYPE
+ "\\PhysicalDisk(_Total)\\Avg. Disk sec/Write", // PERF_AVERAGE_TIMER
+ "\\PhysicalDisk(_Total)\\Disk Write Bytes/sec", // PERF_COUNTER_BULK_COUNT
+ "\\PhysicalDisk(_Total)\\Disk Writes/sec", // PERF_COUNTER_COUNTER
+ }));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+
+ ASSERT_TIMEBASE
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\Processor\\% Idle Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\Processor\\% Processor Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\System\\System Up Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\% Disk Write Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\Avg. Disk Bytes/Write");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\Avg. Disk Read Queue Length");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\Avg. Disk sec/Write");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\Disk Write Bytes/sec");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("misc", "\\PhysicalDisk\\Disk Writes/sec");
+
+ ASSERT_GROUP_AND_RAW_COUNTER("misc", "\\System\\Processes");
+ }
+}
+
+// Test multiple counter groups
+TEST(FTDCPerfCollector, TestMultipleCounterGroups) {
+
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup(
+ "cpu", {"\\Processor(0)\\% Idle Time", "\\Processor(0)\\% Processor Time"}));
+ ASSERT_OK(
+ collection.addCountersGroup("sys", {"\\System\\Processes", "\\System\\System Up Time"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+
+ ASSERT_TIMEBASE
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Idle Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Processor Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("sys", "\\System\\System Up Time");
+
+ ASSERT_GROUP_AND_RAW_COUNTER("sys", "\\System\\Processes");
+ }
+}
+
+// Test multiple nested counter groups
+TEST(FTDCPerfCollector, TestMultipleNestedCounterGroups) {
+
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroupedByInstanceName(
+ "cpu", {"\\Processor(*)\\% Idle Time", "\\Processor(*)\\% Processor Time"}));
+ ASSERT_OK(
+ collection.addCountersGroup("sys", {"\\System\\Processes", "\\System\\System Up Time"}));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+ ASSERT_TIMEBASE
+
+ // We boldly assume that machines we test on have at least two processors
+ ASSERT_NESTED_GROUP_AND_NON_RAW_COUNTER("cpu", "0", "\\Processor\\% Idle Time");
+ ASSERT_NESTED_GROUP_AND_NON_RAW_COUNTER("cpu", "0", "\\Processor\\% Processor Time");
+
+ ASSERT_NESTED_GROUP_AND_NON_RAW_COUNTER("cpu", "1", "\\Processor\\% Idle Time");
+ ASSERT_NESTED_GROUP_AND_NON_RAW_COUNTER("cpu", "1", "\\Processor\\% Processor Time");
+
+ ASSERT_NO_NESTED_GROUP_AND_NON_RAW_COUNTER("cpu", "_Total", "\\Processor\\% Idle Time");
+ ASSERT_NO_NESTED_GROUP_AND_NON_RAW_COUNTER(
+ "cpu", "_Total", "\\Processor\\% Processor Time");
+
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("sys", "\\System\\System Up Time");
+ ASSERT_GROUP_AND_RAW_COUNTER("sys", "\\System\\Processes");
+ }
+}
+
+// Test Counters we use in MongoDB
+TEST(FTDCPerfCollector, TestLocalCounters) {
+
+ PerfCounterCollection collection;
+ ASSERT_OK(collection.addCountersGroup("cpu",
+ {
+ "\\Processor(_Total)\\% Idle Time",
+ "\\Processor(_Total)\\% Interrupt Time",
+ "\\Processor(_Total)\\% Privileged Time",
+ "\\Processor(_Total)\\% Processor Time",
+ "\\Processor(_Total)\\% User Time",
+ "\\Processor(_Total)\\Interrupts/sec",
+ "\\System\\Context Switches/sec",
+ "\\System\\Processes",
+ "\\System\\Processor Queue Length",
+ "\\System\\System Up Time",
+ "\\System\\Threads",
+ }));
+
+ // TODO: Should we capture the Heap Counters for the current process?
+ ASSERT_OK(collection.addCountersGroup("memory",
+ {
+ "\\Memory\\Available Bytes",
+ "\\Memory\\Cache Bytes",
+ "\\Memory\\Cache Faults/sec",
+ "\\Memory\\Committed Bytes",
+ "\\Memory\\Commit Limit",
+ "\\Memory\\Page Reads/sec",
+ "\\Memory\\Page Writes/sec",
+ "\\Memory\\Pages Input/sec",
+ "\\Memory\\Pages Output/sec",
+ "\\Memory\\Pool Nonpaged Bytes",
+ "\\Memory\\Pool Paged Bytes",
+ "\\Memory\\Pool Paged Resident Bytes",
+ "\\Memory\\System Cache Resident Bytes",
+ "\\Memory\\System Code Total Bytes",
+ }));
+
+ ASSERT_OK(collection.addCountersGroupedByInstanceName(
+ "disks",
+ {
+ "\\PhysicalDisk(*)\\% Disk Read Time",
+ "\\PhysicalDisk(*)\\% Disk Write Time",
+ "\\PhysicalDisk(*)\\Avg. Disk Bytes/Read",
+ "\\PhysicalDisk(*)\\Avg. Disk Bytes/Write",
+ "\\PhysicalDisk(*)\\Avg. Disk Read Queue Length",
+ "\\PhysicalDisk(*)\\Avg. Disk Write Queue Length",
+ "\\PhysicalDisk(*)\\Avg. Disk sec/Read",
+ "\\PhysicalDisk(*)\\Avg. Disk sec/Write",
+ "\\PhysicalDisk(*)\\Disk Read Bytes/sec",
+ "\\PhysicalDisk(*)\\Disk Write Bytes/sec",
+ "\\PhysicalDisk(*)\\Disk Reads/sec",
+ "\\PhysicalDisk(*)\\Disk Writes/sec",
+ "\\PhysicalDisk(*)\\Current Disk Queue Length",
+ }));
+
+ auto swCollector = PerfCounterCollector::create(std::move(collection));
+ ASSERT_OK(swCollector.getStatus());
+ auto collector = std::move(swCollector.getValue());
+
+ for (size_t i = 0; i < kDefaultCollectionCount; i++) {
+ COLLECT_COUNTERS;
+ ASSERT_TIMEBASE
+
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Idle Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Interrupt Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Privileged Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% Processor Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\% User Time");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\Processor\\Interrupts/sec");
+
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\System\\Context Switches/sec");
+ ASSERT_GROUP_AND_RAW_COUNTER("cpu", "\\System\\Processes");
+ ASSERT_GROUP_AND_RAW_COUNTER("cpu", "\\System\\Processor Queue Length");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("cpu", "\\System\\System Up Time");
+ ASSERT_GROUP_AND_RAW_COUNTER("cpu", "\\System\\Threads");
+
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\Available Bytes");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\Cache Bytes");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("memory", "\\Memory\\Cache Faults/sec");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\Commit Limit");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\Committed Bytes");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("memory", "\\Memory\\Page Reads/sec");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("memory", "\\Memory\\Page Writes/sec");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("memory", "\\Memory\\Pages Input/sec");
+ ASSERT_GROUP_AND_NON_RAW_COUNTER("memory", "\\Memory\\Pages Output/sec");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\Pool Paged Resident Bytes");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\System Cache Resident Bytes");
+ ASSERT_GROUP_AND_RAW_COUNTER("memory", "\\Memory\\System Code Total Bytes");
+ }
+}
+
+} // namespace
+} // namespace mongo