Skip to content

Commit

Permalink
util/backtrace: Optimize formatter to reduce memory allocation overhead
Browse files Browse the repository at this point in the history
This commit addresses a critical memory allocation issue in backtrace
formatting by directly specializing fmt::formatter for backtrace types,
eliminating dependency on iostream-based formatting.

Problem:
When Seastar applications experience high memory pressure, logging
backtrace information could fail due to additional memory allocation
required by iostream formatting. This resulted in errors like:

```
ERROR 2024-12-10 01:59:16,905 [shard 0:main] seastar_memory - seastar/src/core/memory.cc:2126 @void seastar::memory::maybe_dump_memory_diagnostics(size_t, bool): failed to log message: fmt='Failed to allocate {} bytes at {}': std::__ios_failure (error iostream:1, basic_ios::clear: iostream error)"
```

Solution:
- Implement direct fmt::formatter specialization for backtrace
- Remove reliance on operator<< for string representation
- Reduce memory allocation pressure during out-of-memory scenarios
- Improve reliability of backtrace logging under extreme memory constraints

Additional Compatibility Note:
Due to changes in fmt library versioning, this implementation ensures
fmt::formatter is always defined for backtrace types, even when using
fmt versions earlier than 9.0. Previously, these formatters were
conditionally defined based on the fmt version. Now, the formatters
are consistently available, with operator<< implemented using the
new fmt::formatter specialization.

This change ensures that backtrace logging can proceed even when the
system is under significant memory pressure, providing more reliable
post-mortem debugging information.

Signed-off-by: Kefu Chai <[email protected]>
  • Loading branch information
tchaikov committed Dec 10, 2024
1 parent ca5f653 commit dd9c2b1
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 32 deletions.
37 changes: 24 additions & 13 deletions include/seastar/util/backtrace.hh
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,18 @@ public:
using vector_type = boost::container::static_vector<frame, 64>;
private:
vector_type _frames;
size_t _hash;
char _delimeter;
size_t _hash = 0;
private:
size_t calculate_hash() const noexcept;
public:
simple_backtrace(vector_type f, char delimeter = ' ') noexcept : _frames(std::move(f)), _hash(calculate_hash()), _delimeter(delimeter) {}
simple_backtrace(char delimeter = ' ') noexcept : simple_backtrace({}, delimeter) {}
simple_backtrace(vector_type f) noexcept : _frames(std::move(f)), _hash(calculate_hash()) {}
simple_backtrace() noexcept = default;
[[deprecated]] simple_backtrace(vector_type f, char) : simple_backtrace(std::move(f)) {}
[[deprecated]] simple_backtrace(char) {}

size_t hash() const noexcept { return _hash; }
char delimeter() const noexcept { return _delimeter; }

friend std::ostream& operator<<(std::ostream& out, const simple_backtrace&);
friend fmt::formatter<simple_backtrace>;

bool operator==(const simple_backtrace& o) const noexcept {
return _hash == o._hash && _frames == o._frames;
Expand All @@ -116,7 +116,7 @@ public:
: _task_type(&ti)
{ }

friend std::ostream& operator<<(std::ostream& out, const task_entry&);
friend fmt::formatter<task_entry>;

bool operator==(const task_entry& o) const noexcept {
return *_task_type == *o._task_type;
Expand Down Expand Up @@ -149,9 +149,8 @@ public:
~tasktrace();

size_t hash() const noexcept { return _hash; }
char delimeter() const noexcept { return _main.delimeter(); }

friend std::ostream& operator<<(std::ostream& out, const tasktrace&);
friend fmt::formatter<tasktrace>;

bool operator==(const tasktrace& o) const noexcept;

Expand Down Expand Up @@ -182,10 +181,22 @@ struct hash<seastar::tasktrace> {

}

#if FMT_VERSION >= 90000
template <> struct fmt::formatter<seastar::tasktrace> : fmt::ostream_formatter {};
template <> struct fmt::formatter<seastar::simple_backtrace> : fmt::ostream_formatter {};
#endif
template <> struct fmt::formatter<seastar::frame> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const seastar::frame&, fmt::format_context& ctx) const -> decltype(ctx.out());
};
template <> struct fmt::formatter<seastar::simple_backtrace> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const seastar::simple_backtrace&, fmt::format_context& ctx) const -> decltype(ctx.out());
};
template <> struct fmt::formatter<seastar::tasktrace> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const seastar::tasktrace&, fmt::format_context& ctx) const -> decltype(ctx.out());
};
template <> struct fmt::formatter<seastar::task_entry> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const seastar::task_entry&, fmt::format_context& ctx) const -> decltype(ctx.out());
};

namespace seastar {

Expand Down
65 changes: 46 additions & 19 deletions src/util/backtrace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module;
#include <variant>
#include <vector>
#include <boost/container/static_vector.hpp>
#include <fmt/ostream.h>

#ifdef SEASTAR_MODULE
module seastar;
Expand Down Expand Up @@ -111,37 +112,23 @@ size_t simple_backtrace::calculate_hash() const noexcept {
}

std::ostream& operator<<(std::ostream& out, const frame& f) {
if (!f.so->name.empty()) {
out << f.so->name << "+";
}
out << format("0x{:x}", f.addr);
fmt::print(out, "{}", f);
return out;
}

std::ostream& operator<<(std::ostream& out, const simple_backtrace& b) {
char delim[2] = {'\0', '\0'};
for (auto f : b._frames) {
out << delim << f;
delim[0] = b.delimeter();
}
fmt::print(out, "{}", b);
return out;
}

std::ostream& operator<<(std::ostream& out, const tasktrace& b) {
out << b._main;
for (auto&& e : b._prev) {
out << "\n --------";
std::visit(make_visitor([&] (const shared_backtrace& sb) {
out << '\n' << sb;
}, [&] (const task_entry& f) {
out << "\n " << f;
}), e);
}
fmt::print(out, "{}", b);
return out;
}

std::ostream& operator<<(std::ostream& out, const task_entry& e) {
return out << seastar::pretty_type_name(*e._task_type);
fmt::print(out, "{}", e);
return out;
}

tasktrace current_tasktrace() noexcept {
Expand Down Expand Up @@ -195,3 +182,43 @@ bool tasktrace::operator==(const tasktrace& o) const noexcept {
tasktrace::~tasktrace() {}

} // namespace seastar

namespace fmt {

auto formatter<seastar::frame>::format(const seastar::frame& f, format_context& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
if (!f.so->name.empty()) {
out = fmt::format_to(out, "{}+", f.so->name);
}
return fmt::format_to(out, "0x{:x}", f.addr);
}

auto formatter<seastar::simple_backtrace>::format(const seastar::simple_backtrace& b, format_context& ctx) const
-> decltype(ctx.out()) {
return fmt::format_to(ctx.out(), "{}", fmt::join(b._frames, " "));
}

auto formatter<seastar::tasktrace>::format(const seastar::tasktrace& b, format_context& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = fmt::format_to(out, "{}", b._main);
for (auto&& e : b._prev) {
out = fmt::format_to(out, "\n --------");
out = std::visit(seastar::make_visitor(
[&] (const seastar::shared_backtrace& sb) {
return fmt::format_to(out, "\n{}", sb);
},
[&] (const seastar::task_entry& f) {
return fmt::format_to(out, "\n {}", f);
}), e);
}
return out;
}

auto formatter<seastar::task_entry>::format(const seastar::task_entry& e, format_context& ctx) const
-> decltype(ctx.out()) {
return fmt::format_to(ctx.out(), "{}", seastar::pretty_type_name(*e._task_type));
}

}

0 comments on commit dd9c2b1

Please sign in to comment.