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

Compatibility Improvements:
- Ensure consistent `fmt::formatter` availability across fmt library versions
- Deprecate two `simple_backtrace` constructors previously added in
  095a07b, as they are no longer needed with fmt::join
- Maintain robust formatting approach independent of library version

Impact:
This change enhances system resilience by enabling backtrace logging even
under significant memory pressure, providing more reliable post-mortem
debugging information.

Signed-off-by: Kefu Chai <[email protected]>
  • Loading branch information
tchaikov authored and avikivity committed Dec 10, 2024
1 parent be29a2f commit 3133ecd
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 30 deletions.
36 changes: 25 additions & 11 deletions include/seastar/util/backtrace.hh
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,20 @@ public:
using vector_type = boost::container::static_vector<frame, 64>;
private:
vector_type _frames;
size_t _hash;
char _delimeter;
size_t _hash = 0;
char _delimeter = ' ';
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 delimeter) : _frames(std::move(f)), _hash(calculate_hash()), _delimeter(delimeter) {}
[[deprecated]] simple_backtrace(char delimeter) : _delimeter(delimeter) {}

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 +118,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 @@ -151,7 +153,7 @@ public:
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 +184,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 3133ecd

Please sign in to comment.