diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2020-03-16 14:40:06 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-03-17 18:42:37 +0000 |
commit | 97e0731610f18576229c322c3a4186202c77c521 (patch) | |
tree | 4e43370d293655e08b8ac1ae216d5ee9d5e48bfd /src/mongo | |
parent | b3c675b592d33cfe14f7df59407fae442687ed9b (diff) | |
download | mongo-97e0731610f18576229c322c3a4186202c77c521.tar.gz |
SERVER-46024 Collect /proc/vmstat swap statistics in FTDC
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/ftdc/ftdc_system_stats_linux.cpp | 18 | ||||
-rw-r--r-- | src/mongo/util/procparser.cpp | 89 | ||||
-rw-r--r-- | src/mongo/util/procparser.h | 19 | ||||
-rw-r--r-- | src/mongo/util/procparser_test.cpp | 73 |
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 35537d34360..c350ca0bb22 100644 --- a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp +++ b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp @@ -74,6 +74,16 @@ static const std::vector<StringData> kNetstatKeys{ "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. */ @@ -127,6 +137,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 45c8effbf4f..a48208cb8ad 100644 --- a/src/mongo/util/procparser.cpp +++ b/src/mongo/util/procparser.cpp @@ -722,5 +722,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 (!NumberParser{}(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 8fd39d0fd35..5c2d106665b 100644 --- a/src/mongo/util/procparser.h +++ b/src/mongo/util/procparser.h @@ -135,5 +135,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 4db5ff0b745..53336cab451 100644 --- a/src/mongo/util/procparser_test.cpp +++ b/src/mongo/util/procparser_test.cpp @@ -96,6 +96,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) { @@ -649,5 +654,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 |