sapling/eden/fs/utils/ProcUtil.cpp
Victor Zverovich 3285a8f909 Replace Folly Format with fmt in logger to reduce binary size
Summary:
Now that fmt is available in Folly builds (D14813810), use it to reduce binary code size in Folly Logger. This is done by moving most of the formatting logic behind the type-erased `vformat` API. Previously it was instantiated for all combinations of formatting argument types used in calls to `FB_LOGF` and `XLOGF` in a program.

The effect of this change can be illustrated by looking at symbol sizes as given by `nm -S -td` for the following test function:

```
void test_log() {
  FB_LOGF(logger, WARN, "num events: {:06d}, duration: {:6.3f}", 1234, 5.6789);
}
```
compiled in `opt` mode.

`nm` before:

```
0000000004236736 0000000000000231 T test_log()
0000000004236992 0000000000001002 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > folly::LogStreamProcessor::formatLogString<int, double>(folly::Range<char const*>, int const&, double const&)
```

`nm` after:

```
0000000004237536 0000000000000231 T test_log()
0000000004237792 0000000000000251 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > folly::LogStreamProcessor::formatLogString<int, double>(folly::Range<char const*>, int const&, double const&)
0000000004238048 0000000000000740 W folly::LogStreamProcessor::vformatLogString[abi:cxx11](folly::Range<char const*>, fmt::v5::format_args, bool&)
```

Before we had one 1002 byte instantiation of `formatLogString<int, double>`. With this change it was reduced 4x to 251 bytes and non-template function `vformatLogString` was added which is shared among all logging calls. The size of `test_log` remained unchanged. There are even bigger savings from Folly Formatter instantiations which are no longer needed, e.g.

```
0000000004238032 0000000000001363 W _ZNK5folly13BaseFormatterINS_9FormatterILb0EJRKiRKdEEELb0EJS3_S5_EEclIZNKS7_8appendToINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEENSt9enable_ifIXsr12IsSomeStringIT_EE5valueEvE4typeERSH_EUlNS_5RangeIPKcEEE_EEvSK_
```

So in total this change results in ~5x per-call/instantiation binary size. It is possible to reduce binary size even further but it is not done in the current diff to keep it manageable.

In addition to binary size improvements, switching to fmt will potentially

* allow catching errors in format strings at compile time,
* simplify future migration to C++20 [`std::format`](http://eel.is/c++draft/format).

Reviewed By: simpkins

Differential Revision: D15485589

fbshipit-source-id: 06db4436839f11c2c3dbed7b36658e2193343411
2019-11-18 05:53:08 -08:00

206 lines
5.6 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#include "eden/fs/utils/ProcUtil.h"
#include <array>
#include <fstream>
#include <vector>
#include <folly/Conv.h>
#include <folly/FileUtil.h>
#include <folly/String.h>
#include <folly/logging/xlog.h>
#include <folly/portability/Unistd.h>
#ifdef __APPLE__
#include <mach/mach_init.h> // @manual
#include <mach/task.h> // @manual
#include <mach/task_info.h> // @manual
#endif
using folly::StringPiece;
using std::optional;
namespace {
using namespace facebook::eden::proc_util;
#ifdef __APPLE__
optional<MemoryStats> readMemoryStatsApple() {
mach_task_basic_info_data_t taskinfo{};
mach_msg_type_number_t outCount = MACH_TASK_BASIC_INFO_COUNT;
auto result = task_info(
mach_task_self(),
MACH_TASK_BASIC_INFO,
reinterpret_cast<task_info_t>(&taskinfo),
&outCount);
if (result != KERN_SUCCESS) {
return std::nullopt;
}
MemoryStats ms;
ms.vsize = taskinfo.virtual_size;
ms.resident = taskinfo.resident_size;
return ms;
}
#endif
} // namespace
namespace facebook {
namespace eden {
namespace proc_util {
optional<MemoryStats> readMemoryStats() {
#ifdef __APPLE__
return readMemoryStatsApple();
#else
return readStatmFile("/proc/self/statm");
#endif
}
optional<MemoryStats> readStatmFile(const char* filename) {
std::string contents;
if (!folly::readFile(filename, contents)) {
return std::nullopt;
}
auto pageSize = sysconf(_SC_PAGESIZE);
if (pageSize == -1) {
return std::nullopt;
}
return parseStatmFile(contents, pageSize);
}
optional<MemoryStats> parseStatmFile(StringPiece data, size_t pageSize) {
std::array<size_t, 7> values;
for (size_t& value : values) {
auto parseResult = folly::parseTo(data, value);
if (parseResult.hasError()) {
return std::nullopt;
}
data = parseResult.value();
}
MemoryStats stats{};
stats.vsize = pageSize * values[0];
stats.resident = pageSize * values[1];
stats.shared = pageSize * values[2];
stats.text = pageSize * values[3];
// values[4] is always 0 since Linux 2.6
stats.data = pageSize * values[5];
// values[6] is always 0 since Linux 2.6
return stats;
}
std::string& trim(std::string& str, const std::string& delim) {
str.erase(0, str.find_first_not_of(delim));
str.erase(str.find_last_not_of(delim) + 1);
return str;
}
std::pair<std::string, std::string> getKeyValuePair(
const std::string& line,
const std::string& delim) {
std::vector<std::string> v;
std::pair<std::string, std::string> result;
folly::split(delim, line, v);
if (v.size() == 2) {
result.first = trim(v.front());
result.second = trim(v.back());
}
return result;
}
std::vector<std::unordered_map<std::string, std::string>> parseProcSmaps(
std::istream& input) {
std::vector<std::unordered_map<std::string, std::string>> entryList;
bool headerFound{false};
std::unordered_map<std::string, std::string> currentMap;
for (std::string line; getline(input, line);) {
if (line.find("-") != std::string::npos) {
if (!currentMap.empty()) {
entryList.push_back(currentMap);
currentMap.clear();
}
headerFound = true;
} else {
if (!headerFound) {
XLOG(WARN) << "Failed to parse smaps file ";
continue;
}
auto keyValue = getKeyValuePair(line, ":");
if (!keyValue.first.empty()) {
currentMap[keyValue.first] = keyValue.second;
} else {
XLOG(WARN) << "Failed to parse smaps field in smaps file ";
}
}
}
if (!currentMap.empty()) {
entryList.push_back(currentMap);
}
return entryList;
}
std::vector<std::unordered_map<std::string, std::string>> loadProcSmaps() {
return loadProcSmaps(kLinuxProcSmapsPath);
}
std::vector<std::unordered_map<std::string, std::string>> loadProcSmaps(
folly::StringPiece procSmapsPath) {
try {
std::ifstream input(procSmapsPath.data());
return parseProcSmaps(input);
} catch (const std::exception& ex) {
XLOG(WARN) << "Failed to parse memory usage: " << ex.what();
}
return std::vector<std::unordered_map<std::string, std::string>>();
}
std::optional<uint64_t> calculatePrivateBytes(
std::vector<std::unordered_map<std::string, std::string>> smapsListOfMaps) {
uint64_t count{0};
for (auto currentMap : smapsListOfMaps) {
auto iter = currentMap.find("Private_Dirty");
if (iter != currentMap.end()) {
auto& entry = iter->second;
if (entry.rfind(" kB") != std::string::npos) {
auto countString = entry.substr(0, entry.size() - 3);
try {
count += std::stoull(countString) * 1024;
} catch (const std::invalid_argument& ex) {
XLOG(WARN) << "Failed to extract long from /proc/smaps value ''"
<< countString << "' error: " << ex.what();
return std::nullopt;
} catch (const std::out_of_range& ex) {
XLOG(WARN) << "Failed to extract long from proc/status value ''"
<< countString << "' error: " << ex.what();
return std::nullopt;
}
} else {
XLOG(WARN) << "Failed to find Private_Dirty units in: "
<< kLinuxProcSmapsPath;
return std::nullopt;
}
}
}
return count;
}
std::optional<uint64_t> calculatePrivateBytes() {
try {
std::ifstream input(kLinuxProcSmapsPath.data());
return calculatePrivateBytes(parseProcSmaps(input));
} catch (const std::exception& ex) {
XLOG(WARN) << "Failed to parse file " << kLinuxProcSmapsPath << ex.what();
return std::nullopt;
}
}
} // namespace proc_util
} // namespace eden
} // namespace facebook