diff options
author | Bruce Lucas <bruce.lucas@10gen.com> | 2018-05-08 07:40:02 -0400 |
---|---|---|
committer | Bruce Lucas <bruce.lucas@mongodb.com> | 2018-05-22 11:07:25 -0400 |
commit | 0768d9842baea52df153c84c627e63fd3de3683a (patch) | |
tree | 079ac0c8ccd69dd5a1248e52590e0acdc7c82865 | |
parent | f2e995b3b8b6a688c97720e6fdb286e2e538180c (diff) | |
download | mongo-0768d9842baea52df153c84c627e63fd3de3683a.tar.gz |
SERVER-31400 Record netstat metrics in ftdc
(cherry picked from commit 68aaf285c35b379a4c81231d86903c78e97d1e76)
-rw-r--r-- | src/mongo/db/ftdc/ftdc_system_stats_linux.cpp | 15 | ||||
-rw-r--r-- | src/mongo/util/procparser.cpp | 95 | ||||
-rw-r--r-- | src/mongo/util/procparser.h | 18 | ||||
-rw-r--r-- | src/mongo/util/procparser_test.cpp | 132 |
4 files changed, 260 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 9c93718c171..fe730879834 100644 --- a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp +++ b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp @@ -66,6 +66,10 @@ static const std::vector<StringData> kMemKeys{ "Inactive(file)"_sd, }; +static const std::vector<StringData> kNetstatKeys{ + "Tcp:"_sd, "Ip:"_sd, "TcpExt:"_sd, "IpExt:"_sd, +}; + /** * Collect metrics from the Linux /proc file system. */ @@ -99,6 +103,17 @@ public: subObjBuilder.doneFast(); } + { + BSONObjBuilder subObjBuilder(builder.subobjStart("netstat"_sd)); + processStatusErrors(procparser::parseProcNetstatFile( + kNetstatKeys, "/proc/net/netstat"_sd, &subObjBuilder), + &subObjBuilder); + processStatusErrors( + procparser::parseProcNetstatFile(kNetstatKeys, "/proc/net/snmp"_sd, &subObjBuilder), + &subObjBuilder); + subObjBuilder.doneFast(); + } + // Skip the disks section if we could not find any disks. // This can happen when we do not have permission to /sys/block for instance. if (!_disksStringData.empty()) { diff --git a/src/mongo/util/procparser.cpp b/src/mongo/util/procparser.cpp index 36f2ae0254e..af7a81fc7a2 100644 --- a/src/mongo/util/procparser.cpp +++ b/src/mongo/util/procparser.cpp @@ -406,6 +406,101 @@ Status parseProcMemInfoFile(StringData filename, return parseProcMemInfo(keys, swString.getValue(), builder); } +// +// Here is an example of the type of string it supports (long lines elided for clarity). +// > cat /proc/net/netstat +// TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed ... +// TcpExt: 3437 5938 13368 ... +// IpExt: InNoRoutes InTruncatedPkts InMcastPkts ... +// IpExt: 999 1 4819969 ... +// +// Parser assumes file consists of alternating lines of keys and values +// key and value lines consist of space-separated tokens +// first token is a key prefix that is prepended in the output to each key +// all prefixed keys and corresponding values are copied to output as-is +// + +Status parseProcNetstat(const std::vector<StringData>& keys, + StringData data, + BSONObjBuilder* builder) { + + using string_split_iterator = boost::split_iterator<StringData::const_iterator>; + + string_split_iterator keysIt; + bool foundKeys = false; + + // Split the file by lines. + uint32_t lineNum = 0; + 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, ++lineNum) { + + if (lineNum % 2 == 0) { + + // even numbered lines are keys + keysIt = string_split_iterator( + (*lineIt).begin(), + (*lineIt).end(), + boost::token_finder([](char c) { return c == ' '; }, boost::token_compress_on)); + + } else { + + // odd numbered lines are values + string_split_iterator valuesIt = string_split_iterator( + (*lineIt).begin(), + (*lineIt).end(), + boost::token_finder([](char c) { return c == ' '; }, boost::token_compress_on)); + + StringData prefix; + + // iterate over the keys and values in parallel + for (uint32_t keyNum = 0; + keysIt != string_split_iterator() && valuesIt != string_split_iterator(); + ++keysIt, ++valuesIt, ++keyNum) { + + if (keyNum == 0) { + + // first token is a prefix to be applied to remaining keys + prefix = StringData((*keysIt).begin(), (*keysIt).end()); + + // ignore line if prefix isn't in requested list + if (!keys.empty() && std::find(keys.begin(), keys.end(), prefix) == keys.end()) + break; + + } else { + + // remaining tokens are key/value pairs + StringData key((*keysIt).begin(), (*keysIt).end()); + StringData stringValue((*valuesIt).begin(), (*valuesIt).end()); + uint64_t value; + if (parseNumberFromString(stringValue, &value).isOK()) { + builder->appendNumber(prefix.toString() + key.toString(), value); + foundKeys = true; + } + } + } + } + } + + return foundKeys ? Status::OK() + : Status(ErrorCodes::NoSuchKey, "Failed to find any keys in netstats string"); +} + +Status parseProcNetstatFile(const std::vector<StringData>& keys, + StringData filename, + BSONObjBuilder* builder) { + auto swString = readFileAsString(filename); + if (!swString.isOK()) { + return swString.getStatus(); + } + return parseProcNetstat(keys, swString.getValue(), builder); +} + + // Here is an example of the type of string it supports: // // For more information, see: diff --git a/src/mongo/util/procparser.h b/src/mongo/util/procparser.h index 2f9e97e343c..bbaf543802a 100644 --- a/src/mongo/util/procparser.h +++ b/src/mongo/util/procparser.h @@ -88,6 +88,24 @@ Status parseProcMemInfoFile(StringData filename, BSONObjBuilder* builder); /** + * Read a string matching /proc/net/netstat format, and write the keys + * found in that string into builder. + * + * data - string to parse + * builder - BSON output + */ +Status parseProcNetstat(const std::vector<StringData>& keys, + StringData data, + BSONObjBuilder* builder); + +/** + * Read from file, and write the keys found in that file into builder. + */ +Status parseProcNetstatFile(const std::vector<StringData>& keys, + StringData filename, + BSONObjBuilder* builder); + +/** * Read a string matching /proc/diskstats format, and write the specified list of disks in builder. * * disks - vector of block devices to include in output. For each disk selected, 11 fields are diff --git a/src/mongo/util/procparser_test.cpp b/src/mongo/util/procparser_test.cpp index 81bf17ac8df..ff620b8ac61 100644 --- a/src/mongo/util/procparser_test.cpp +++ b/src/mongo/util/procparser_test.cpp @@ -85,6 +85,11 @@ StringMap toNestedStringMap(BSONObj& obj) { ASSERT_OK(procparser::parseProcMemInfo(_keys, _x, &builder)); \ auto obj = builder.obj(); \ auto stringMap = toStringMap(obj); +#define ASSERT_PARSE_NETSTAT(_keys, _x) \ + BSONObjBuilder builder; \ + ASSERT_OK(procparser::parseProcNetstat(_keys, _x, &builder)); \ + auto obj = builder.obj(); \ + auto stringMap = toStringMap(obj); #define ASSERT_PARSE_DISKSTATS(_disks, _x) \ BSONObjBuilder builder; \ ASSERT_OK(procparser::parseProcDiskStats(_disks, _x, &builder)); \ @@ -368,6 +373,133 @@ TEST(FTDCProcMemInfo, TestLocalNonExistentMemInfo) { ASSERT_NOT_OK(procparser::parseProcMemInfoFile("/proc/does_not_exist", keys, &builder)); } +TEST(FTDCProcNetstat, TestNetstat) { + + // test keys + std::vector<StringData> keys{"pfx1", "pfx2", "pfx3"}; + + // Normal case + { + ASSERT_PARSE_NETSTAT(keys, + "pfx1 key1 key2 key3\n" + "pfx1 1 2 3\n" + "pfxX key1 key2\n" + "pfxX key1 key2\n" + "pfx2 key4 key5\n" + "pfx2 4 5\n"); + ASSERT_KEY_AND_VALUE("pfx1key1", 1UL); + ASSERT_KEY_AND_VALUE("pfx1key2", 2UL); + ASSERT_NO_KEY("pfxXkey1"); + ASSERT_NO_KEY("pfxXkey2"); + ASSERT_KEY_AND_VALUE("pfx1key3", 3UL) + ASSERT_KEY_AND_VALUE("pfx2key4", 4UL); + ASSERT_KEY_AND_VALUE("pfx2key5", 5UL); + } + + // Mismatched keys and values + { + ASSERT_PARSE_NETSTAT(keys, + "pfx1 key1 key2 key3\n" + "pfx1 1 2 3 4\n" + "pfx2 key4 key5\n" + "pfx2 4\n" + "pfx3 key6 key7\n"); + ASSERT_KEY_AND_VALUE("pfx1key1", 1UL); + ASSERT_KEY_AND_VALUE("pfx1key2", 2UL); + ASSERT_KEY_AND_VALUE("pfx1key3", 3UL); + ASSERT_NO_KEY("pfx1key4"); + ASSERT_KEY_AND_VALUE("pfx2key4", 4UL); + ASSERT_NO_KEY("pfx2key5"); + ASSERT_NO_KEY("pfx3key6"); + ASSERT_NO_KEY("pfx3key7"); + } + + // Non-numeric value + { + ASSERT_PARSE_NETSTAT(keys, + "pfx1 key1 key2 key3\n" + "pfx1 1 foo 3\n"); + ASSERT_KEY_AND_VALUE("pfx1key1", 1UL); + ASSERT_NO_KEY("pfx1key2"); + ASSERT_KEY_AND_VALUE("pfx1key3", 3UL) + } + + // No newline + { + ASSERT_PARSE_NETSTAT(keys, + "pfx1 key1 key2 key3\n" + "pfx1 1 2 3\n" + "pfx2 key4 key5\n" + "pfx2 4 5"); + ASSERT_KEY_AND_VALUE("pfx1key1", 1UL); + ASSERT_KEY_AND_VALUE("pfx1key2", 2UL); + ASSERT_KEY_AND_VALUE("pfx1key3", 3UL) + ASSERT_KEY_AND_VALUE("pfx2key4", 4UL); + ASSERT_KEY_AND_VALUE("pfx2key5", 5UL); + } + + // Single line only + { + BSONObjBuilder builder; + ASSERT_NOT_OK(procparser::parseProcNetstat(keys, "pfx1 key1 key2 key3\n", &builder)); + } + + // Empty string + { + BSONObjBuilder builder; + ASSERT_NOT_OK(procparser::parseProcNetstat(keys, "", &builder)); + } +} + +// Test we can parse the /proc/net/netstat on this machine and assert we have some expected fields +// Some keys can vary between distros, so we test only for the existence of a few basic ones +TEST(FTDCProcNetstat, TestLocalNetstat) { + + BSONObjBuilder builder; + + std::vector<StringData> keys{"TcpExt:"_sd, "IpExt:"_sd}; + + ASSERT_OK(procparser::parseProcNetstatFile(keys, "/proc/net/netstat", &builder)); + + BSONObj obj = builder.obj(); + auto stringMap = toStringMap(obj); + log() << "OBJ:" << obj; + ASSERT_KEY("TcpExt:TCPTimeouts"); + ASSERT_KEY("TcpExt:TCPPureAcks"); + ASSERT_KEY("TcpExt:TCPAbortOnTimeout"); + ASSERT_KEY("TcpExt:EmbryonicRsts"); + ASSERT_KEY("TcpExt:ListenDrops"); + ASSERT_KEY("TcpExt:ListenOverflows"); + ASSERT_KEY("TcpExt:DelayedACKs"); + ASSERT_KEY("IpExt:OutOctets"); + ASSERT_KEY("IpExt:InOctets"); +} + +// Test we can parse the /proc/net/snmp on this machine and assert we have some expected fields +// Some keys can vary between distros, so we test only for the existence of a few basic ones +TEST(FTDCProcNetstat, TestLocalNetSnmp) { + + BSONObjBuilder builder; + + std::vector<StringData> keys{"Tcp:"_sd, "Ip:"_sd}; + + ASSERT_OK(procparser::parseProcNetstatFile(keys, "/proc/net/snmp", &builder)); + + BSONObj obj = builder.obj(); + auto stringMap = toStringMap(obj); + log() << "OBJ:" << obj; + ASSERT_KEY("Ip:InReceives"); + ASSERT_KEY("Ip:OutRequests"); + ASSERT_KEY("Tcp:InSegs"); + ASSERT_KEY("Tcp:OutSegs"); +} + +TEST(FTDCProcNetstat, TestLocalNonExistentNetstat) { + std::vector<StringData> keys{}; + BSONObjBuilder builder; + + ASSERT_NOT_OK(procparser::parseProcNetstatFile(keys, "/proc/does_not_exist", &builder)); +} TEST(FTDCProcDiskStats, TestDiskStats) { |