diff --git a/LICENSE b/LICENSE index 5ca50b6f30..0e53635111 100644 --- a/LICENSE +++ b/LICENSE @@ -262,6 +262,8 @@ Meta Velox ./cpp/scripts/setup-helper-functions.sh ./cpp/celeborn/utils/ProcessBase.h ./cpp/celeborn/utils/ProcessBase.cpp +./cpp/celeborn/utils/StackTrace.h +./cpp/celeborn/utils/StackTrace.cpp ------------------------------------------------------------------------------------ diff --git a/cpp/celeborn/utils/CMakeLists.txt b/cpp/celeborn/utils/CMakeLists.txt index 9a98d67489..f473fb5e6a 100644 --- a/cpp/celeborn/utils/CMakeLists.txt +++ b/cpp/celeborn/utils/CMakeLists.txt @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -add_library(utils ProcessBase.cpp) +add_library(utils ProcessBase.cpp StackTrace.cpp) target_link_libraries( utils diff --git a/cpp/celeborn/utils/StackTrace.cpp b/cpp/celeborn/utils/StackTrace.cpp new file mode 100644 index 0000000000..de40851f7c --- /dev/null +++ b/cpp/celeborn/utils/StackTrace.cpp @@ -0,0 +1,192 @@ +/* + * Based on StackTrace.cpp from Facebook Velox + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "celeborn/utils/StackTrace.h" + +// Symbolizer requires folly to be compiled with libelf and libdwarf support +// (also currently only works in Linux). +#if __linux__ && FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF +#define CELEBORN_HAS_SYMBOLIZER 1 +#else +#define CELEBORN_HAS_SYMBOLIZER 0 +#endif + +#include +#include + +#include +#include +#include +#include + +#include "utils/ProcessBase.h" + +#ifdef __linux__ +#include // @manual +#include // @manual +#endif + +namespace celeborn::utils { + +StackTrace::StackTrace(int32_t skipFrames) { + create(skipFrames); +} + +StackTrace::StackTrace(const StackTrace& other) { + bt_pointers_ = other.bt_pointers_; + if (folly::test_once(other.bt_vector_flag_)) { + bt_vector_ = other.bt_vector_; + folly::call_once(bt_vector_flag_, [] {}); // Set the flag. + } + if (folly::test_once(other.bt_flag_)) { + bt_ = other.bt_; + folly::call_once(bt_flag_, [] {}); // Set the flag. + } +} + +StackTrace& StackTrace::operator=(const StackTrace& other) { + if (this != &other) { + this->~StackTrace(); + new (this) StackTrace(other); + } + return *this; +} + +void StackTrace::create(int32_t skipFrames) { + const int32_t kDefaultSkipFrameAdjust = 2; // ::create(), ::StackTrace() + const int32_t kMaxFrames = 75; + + bt_pointers_.clear(); + uintptr_t btpointers[kMaxFrames]; + ssize_t framecount = folly::symbolizer::getStackTrace(btpointers, kMaxFrames); + if (framecount <= 0) { + return; + } + + framecount = std::min(framecount, static_cast(kMaxFrames)); + skipFrames = std::max(skipFrames + kDefaultSkipFrameAdjust, 0); + + bt_pointers_.reserve(framecount - skipFrames); + for (int32_t i = skipFrames; i < framecount; i++) { + bt_pointers_.push_back(reinterpret_cast(btpointers[i])); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// reporting functions + +const std::vector& StackTrace::toStrVector() const { + folly::call_once(bt_vector_flag_, [&] { + size_t frame = 0; + static folly::Indestructible myname{ + folly::demangle(typeid(decltype(*this))) + "::"}; + bt_vector_.reserve(bt_pointers_.size()); + for (auto ptr : bt_pointers_) { + auto framename = translateFrame(ptr); + if (folly::StringPiece(framename).startsWith(*myname)) { + continue; // ignore frames in the StackTrace class + } + bt_vector_.push_back(fmt::format("# {:<2d} {}", frame++, framename)); + } + }); + return bt_vector_; +} + +const std::string& StackTrace::toString() const { + folly::call_once(bt_flag_, [&] { + const auto& vec = toStrVector(); + size_t needed = 0; + for (const auto& frame : vec) { + needed += frame.size() + 1; + } + bt_.reserve(needed); + for (const auto& frame_title : vec) { + bt_ += frame_title; + bt_ += '\n'; + } + }); + return bt_; +} + +std::string StackTrace::log( + const char* errorType, + std::string* out /* = NULL */) const { + std::string pid = folly::to(getProcessId()); + + std::string msg; + msg += "Host: " + getHostName(); + msg += "\nProcessID: " + pid; + msg += "\nThreadID: " + + folly::to(reinterpret_cast(getThreadId())); + msg += "\nName: " + getAppName(); + msg += "\nType: "; + if (errorType) { + msg += errorType; + } else { + msg += "(unknown error)"; + } + msg += "\n\n"; + msg += toString(); + msg += "\n"; + + std::string tracefn = "/tmp/stacktrace." + pid + ".log"; + std::ofstream f(tracefn.c_str()); + if (f) { + f << msg; + f.close(); + } + + if (out) { + *out = msg; + } + return tracefn; +} + +#if CELEBORN_HAS_SYMBOLIZER +namespace { +inline std::string translateFrameImpl(void* addressPtr) { + // TODO: lineNumbers has been disabled since 2009. + using namespace folly::symbolizer; + + std::uintptr_t address = reinterpret_cast(addressPtr); + Symbolizer symbolizer(LocationInfoMode::DISABLED); + SymbolizedFrame frame; + symbolizer.symbolize(address, frame); + + StringSymbolizePrinter printer(SymbolizePrinter::TERSE); + printer.print(frame); + return printer.str(); +} +} // namespace +#endif + +std::string StackTrace::translateFrame(void* addressPtr, bool /*lineNumbers*/) { +#if CELEBORN_HAS_SYMBOLIZER + return folly::fibers::runInMainContext( + [addressPtr]() { return translateFrameImpl(addressPtr); }); +#else + (void)addressPtr; + return std::string{}; +#endif +} + +std::string StackTrace::demangle(const char* mangled) { + return folly::demangle(mangled).toStdString(); +} + +} // namespace celeborn::utils diff --git a/cpp/celeborn/utils/StackTrace.h b/cpp/celeborn/utils/StackTrace.h new file mode 100644 index 0000000000..9c9b980216 --- /dev/null +++ b/cpp/celeborn/utils/StackTrace.h @@ -0,0 +1,93 @@ +/* + * Based on StackTrace.h from Facebook Velox + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +namespace celeborn::utils { + +/////////////////////////////////////////////////////////////////////////////// + +// TODO: Deprecate in favor of folly::symbolizer. +class StackTrace { + public: + /** + * Translate a frame pointer to file name and line number pair. + */ + static std::string translateFrame(void* framePtr, bool lineNumbers = true); + + /** + * Demangle a function name. + */ + static std::string demangle(const char* mangled); + + public: + /** + * Constructor -- saves the current stack trace. By default, we skip the + * frames for StackTrace::StackTrace. If you want those, you can pass + * '-2' to skipFrames. + */ + explicit StackTrace(int32_t skipFrames = 0); + + StackTrace(const StackTrace& other); + StackTrace& operator=(const StackTrace& other); + + /** + * Generate an output of the written stack trace. + */ + const std::string& toString() const; + + /** + * Generate a vector that for each position has the title of the frame. + */ + const std::vector& toStrVector() const; + + /** + * Return the raw stack pointers. + */ + const std::vector& getStack() const { + return bt_pointers_; + } + + /** + * Log stacktrace into a file under /tmp. If "out" is not null, + * also store translated stack trace into the variable. + * Returns the name of the generated file. + */ + std::string log(const char* errorType, std::string* out = nullptr) const; + + private: + /** + * Record bt pointers. + */ + void create(int32_t skipFrames); + + private: + std::vector bt_pointers_; + mutable folly::once_flag bt_vector_flag_; + mutable std::vector bt_vector_; + mutable folly::once_flag bt_flag_; + mutable std::string bt_; +}; + +/////////////////////////////////////////////////////////////////////////////// +} // namespace celeborn::utils