summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2020-03-16 14:40:06 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-03-19 17:23:48 +0000
commit997a51ce1592e3f048858b128a525e97f03407ea (patch)
tree99782f4b13c0981162018f4fa762ca5862360e2f
parent33cf85587979600c884f36841f8267cf1a7a7eb1 (diff)
downloadmongo-997a51ce1592e3f048858b128a525e97f03407ea.tar.gz
SERVER-46024 Collect /proc/vmstat swap statistics in FTDC
(cherry picked from commit 97e0731610f18576229c322c3a4186202c77c521)
-rw-r--r--src/mongo/db/ftdc/ftdc_system_stats_linux.cpp18
-rw-r--r--src/mongo/util/procparser.cpp89
-rw-r--r--src/mongo/util/procparser.h19
-rw-r--r--src/mongo/util/procparser_test.cpp73
4 files changed, 199 insertions, 0 deletions
diff --git a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp
index 21c0138cfde..70ab7d0c240 100644
--- a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp
+++ b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp
@@ -72,6 +72,16 @@ static const std::vector<StringData> kNetstatKeys{
"Tcp:"_sd, "Ip:"_sd, "TcpExt:"_sd, "IpExt:"_sd,
};
+static const std::vector<StringData> kVMKeys{
+ "balloon_deflate"_sd,
+ "balloon_inflate"_sd,
+ "nr_mlock"_sd,
+ "pgfault"_sd,
+ "pgmajfault"_sd,
+ "pswpin"_sd,
+ "pswpout"_sd,
+};
+
/**
* Collect metrics from the Linux /proc file system.
*/
@@ -125,6 +135,14 @@ public:
&subObjBuilder);
subObjBuilder.doneFast();
}
+
+ {
+ BSONObjBuilder subObjBuilder(builder.subobjStart("vmstat"_sd));
+ processStatusErrors(
+ procparser::parseProcVMStatFile("/proc/vmstat"_sd, kVMKeys, &subObjBuilder),
+ &subObjBuilder);
+ subObjBuilder.doneFast();
+ }
}
private:
diff --git a/src/mongo/util/procparser.cpp b/src/mongo/util/procparser.cpp
index 625b8b7466a..e03006da07c 100644
--- a/src/mongo/util/procparser.cpp
+++ b/src/mongo/util/procparser.cpp
@@ -714,5 +714,94 @@ std::vector<std::string> findPhysicalDisks(StringData sysBlockPath) {
return files;
}
+// Here is an example of the type of string it supports:
+// Note: output has been trimmed
+//
+// For more information, see:
+// proc(5) man page
+//
+// > cat /proc/vmstat
+// nr_free_pages 2732282
+// nr_zone_inactive_anon 686253
+// nr_zone_active_anon 4975441
+// nr_zone_inactive_file 2332485
+// nr_zone_active_file 4791149
+// nr_zone_unevictable 0
+// nr_zone_write_pending 0
+// nr_mlock 0
+//
+Status parseProcVMStat(const std::vector<StringData>& keys,
+ StringData data,
+ BSONObjBuilder* builder) {
+ bool foundKeys = false;
+
+ using string_split_iterator = boost::split_iterator<StringData::const_iterator>;
+
+ // Split the file by lines.
+ // token_compress_on means the iterator skips over consecutive '\n'. This should not be a
+ // problem in normal /proc/vmstat output.
+ for (string_split_iterator lineIt = string_split_iterator(
+ data.begin(),
+ data.end(),
+ boost::token_finder([](char c) { return c == '\n'; }, boost::token_compress_on));
+ lineIt != string_split_iterator();
+ ++lineIt) {
+
+ StringData line(lineIt->begin(), lineIt->end());
+
+ // Split the line by spaces since this the delimiters for vmstat files.
+ // token_compress_on means the iterator skips over consecutive ' '. This is needed for
+ // every line.
+ string_split_iterator partIt = string_split_iterator(
+ line.begin(),
+ line.end(),
+ boost::token_finder([](char c) { return c == ' '; }, boost::token_compress_on));
+
+ // Skip processing this line if we do not have a key.
+ if (partIt == string_split_iterator()) {
+ continue;
+ }
+
+ StringData key(partIt->begin(), partIt->end());
+
+ ++partIt;
+
+ // Skip processing this line if we only have a key, and no number.
+ if (partIt == string_split_iterator()) {
+ continue;
+ }
+
+ // Check if the key is in the list. /proc/vmstat will have extra keys, and may not have the
+ // keys we want.
+ if (keys.empty() || std::find(keys.begin(), keys.end(), key) != keys.end()) {
+ foundKeys = true;
+
+ StringData stringValue(partIt->begin(), partIt->end());
+
+ uint64_t value;
+
+ if (!parseNumberFromString(stringValue, &value).isOK()) {
+ value = 0;
+ }
+
+ builder->appendNumber(key, static_cast<long long>(value));
+ }
+ }
+
+ return foundKeys ? Status::OK()
+ : Status(ErrorCodes::NoSuchKey, "Failed to find any keys in vmstat string");
+}
+
+Status parseProcVMStatFile(StringData filename,
+ const std::vector<StringData>& keys,
+ BSONObjBuilder* builder) {
+ auto swString = readFileAsString(filename);
+ if (!swString.isOK()) {
+ return swString.getStatus();
+ }
+
+ return parseProcVMStat(keys, swString.getValue(), builder);
+}
+
} // namespace procparser
} // namespace mongo
diff --git a/src/mongo/util/procparser.h b/src/mongo/util/procparser.h
index 0db4fce3617..578fd68a3ab 100644
--- a/src/mongo/util/procparser.h
+++ b/src/mongo/util/procparser.h
@@ -136,5 +136,24 @@ Status parseProcDiskStatsFile(StringData filename,
*/
std::vector<std::string> findPhysicalDisks(StringData directory);
+/**
+ * Read a string matching /proc/vmstat format, and write the specified list of keys in builder.
+ *
+ * keys - list of keys to output in BSON. If keys is empty, all keys are outputed.
+ * data - string to parsee
+ * builder - BSON output
+ */
+Status parseProcVMStat(const std::vector<StringData>& keys,
+ StringData data,
+ BSONObjBuilder* builder);
+
+/**
+ * Read from file, and write the specified list of keys in builder.
+ */
+Status parseProcVMStatFile(StringData filename,
+ const std::vector<StringData>& keys,
+ BSONObjBuilder* builder);
+
+
} // namespace procparser
} // namespace mongo
diff --git a/src/mongo/util/procparser_test.cpp b/src/mongo/util/procparser_test.cpp
index 1f6324543cb..d047cc4ab2b 100644
--- a/src/mongo/util/procparser_test.cpp
+++ b/src/mongo/util/procparser_test.cpp
@@ -97,6 +97,11 @@ StringMap toNestedStringMap(BSONObj& obj) {
ASSERT_OK(procparser::parseProcDiskStats(_disks, _x, &builder)); \
auto obj = builder.obj(); \
auto stringMap = toNestedStringMap(obj);
+#define ASSERT_PARSE_VMSTAT(_keys, _x) \
+ BSONObjBuilder builder; \
+ ASSERT_OK(procparser::parseProcVMStat(_keys, _x, &builder)); \
+ auto obj = builder.obj(); \
+ auto stringMap = toStringMap(obj);
TEST(FTDCProcStat, TestStat) {
@@ -640,5 +645,73 @@ TEST(FTDCProcDiskStats, TestLocalDiskStats) {
}
}
+
+TEST(FTDCProcVMStat, TestVMStat) {
+
+ std::vector<StringData> keys{"Key1", "Key2", "Key3"};
+
+ // Normal case
+ {
+ ASSERT_PARSE_VMSTAT(keys, "Key1 123\nKey2 456");
+ ASSERT_KEY_AND_VALUE("Key1", 123UL);
+ ASSERT_KEY_AND_VALUE("Key2", 456UL);
+ }
+
+ // No newline
+ {
+ ASSERT_PARSE_VMSTAT(keys, "Key1 123 Key2 456");
+ ASSERT_KEY_AND_VALUE("Key1", 123UL);
+ ASSERT_NO_KEY("Key2");
+ }
+
+ // Key without value
+ {
+ ASSERT_PARSE_VMSTAT(keys, "Key1 123\nKey2");
+ ASSERT_KEY_AND_VALUE("Key1", 123UL);
+ ASSERT_NO_KEY("Key2");
+ }
+
+ // Empty string
+ {
+ BSONObjBuilder builder;
+ ASSERT_NOT_OK(procparser::parseProcVMStat(keys, "", &builder));
+ }
+}
+
+// Test we can parse the /proc/vmstat on this machine. Also assert we have the expected fields
+// This tests is designed to exercise our parsing code on various Linuxes and fail
+// Normally when run in the FTDC loop we return a non-fatal error so we may not notice the failure
+// otherwise.
+TEST(FTDCProcVMStat, TestLocalVMStat) {
+ std::vector<StringData> keys{
+ "balloon_deflate"_sd,
+ "balloon_inflate"_sd,
+ "nr_mlock"_sd,
+ "pgfault"_sd,
+ "pgmajfault"_sd,
+ "pswpin"_sd,
+ "pswpout"_sd,
+ };
+
+ BSONObjBuilder builder;
+
+ ASSERT_OK(procparser::parseProcVMStatFile("/proc/vmstat", keys, &builder));
+
+ BSONObj obj = builder.obj();
+ auto stringMap = toStringMap(obj);
+ ASSERT_KEY("nr_mlock");
+ ASSERT_KEY("pgmajfault");
+ ASSERT_KEY("pswpin");
+ ASSERT_KEY("pswpout");
+}
+
+
+TEST(FTDCProcVMStat, TestLocalNonExistentVMStat) {
+ std::vector<StringData> keys{};
+ BSONObjBuilder builder;
+
+ ASSERT_NOT_OK(procparser::parseProcVMStatFile("/proc/does_not_exist", keys, &builder));
+}
+
} // namespace
} // namespace mongo