mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
Merge pull request #11300 from blinkov/sentry
Opt-in support for sending crash reports
This commit is contained in:
commit
a34032cace
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@
|
|||||||
/build
|
/build
|
||||||
/build_*
|
/build_*
|
||||||
/build-*
|
/build-*
|
||||||
|
/tests/venv
|
||||||
|
|
||||||
/docs/build
|
/docs/build
|
||||||
/docs/publish
|
/docs/publish
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -168,3 +168,6 @@
|
|||||||
[submodule "contrib/fmtlib"]
|
[submodule "contrib/fmtlib"]
|
||||||
path = contrib/fmtlib
|
path = contrib/fmtlib
|
||||||
url = https://github.com/fmtlib/fmt.git
|
url = https://github.com/fmtlib/fmt.git
|
||||||
|
[submodule "contrib/sentry-native"]
|
||||||
|
path = contrib/sentry-native
|
||||||
|
url = https://github.com/getsentry/sentry-native.git
|
||||||
|
@ -362,6 +362,7 @@ include (cmake/find/orc.cmake)
|
|||||||
include (cmake/find/avro.cmake)
|
include (cmake/find/avro.cmake)
|
||||||
include (cmake/find/msgpack.cmake)
|
include (cmake/find/msgpack.cmake)
|
||||||
include (cmake/find/cassandra.cmake)
|
include (cmake/find/cassandra.cmake)
|
||||||
|
include (cmake/find/sentry.cmake)
|
||||||
|
|
||||||
find_contrib_lib(cityhash)
|
find_contrib_lib(cityhash)
|
||||||
find_contrib_lib(farmhash)
|
find_contrib_lib(farmhash)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <daemon/BaseDaemon.h>
|
#include <daemon/BaseDaemon.h>
|
||||||
|
#include <daemon/SentryWriter.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@ -288,7 +289,7 @@ private:
|
|||||||
std::stringstream bare_stacktrace;
|
std::stringstream bare_stacktrace;
|
||||||
bare_stacktrace << "Stack trace:";
|
bare_stacktrace << "Stack trace:";
|
||||||
for (size_t i = stack_trace.getOffset(); i < stack_trace.getSize(); ++i)
|
for (size_t i = stack_trace.getOffset(); i < stack_trace.getSize(); ++i)
|
||||||
bare_stacktrace << ' ' << stack_trace.getFrames()[i];
|
bare_stacktrace << ' ' << stack_trace.getFramePointers()[i];
|
||||||
|
|
||||||
LOG_FATAL(log, bare_stacktrace.str());
|
LOG_FATAL(log, bare_stacktrace.str());
|
||||||
}
|
}
|
||||||
@ -296,9 +297,20 @@ private:
|
|||||||
/// Write symbolized stack trace line by line for better grep-ability.
|
/// Write symbolized stack trace line by line for better grep-ability.
|
||||||
stack_trace.toStringEveryLine([&](const std::string & s) { LOG_FATAL(log, s); });
|
stack_trace.toStringEveryLine([&](const std::string & s) { LOG_FATAL(log, s); });
|
||||||
|
|
||||||
|
/// Send crash report to developers (if configured)
|
||||||
|
|
||||||
|
#if defined(__ELF__) && !defined(__FreeBSD__)
|
||||||
|
const String & build_id_hex = DB::SymbolIndex::instance().getBuildIDHex();
|
||||||
|
#else
|
||||||
|
String build_id_hex{};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SentryWriter::onFault(sig, info, context, stack_trace, build_id_hex);
|
||||||
|
|
||||||
/// When everything is done, we will try to send these error messages to client.
|
/// When everything is done, we will try to send these error messages to client.
|
||||||
if (thread_ptr)
|
if (thread_ptr)
|
||||||
thread_ptr->onFatalError();
|
thread_ptr->onFatalError();
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -330,7 +342,7 @@ static void sanitizerDeathCallback()
|
|||||||
std::stringstream bare_stacktrace;
|
std::stringstream bare_stacktrace;
|
||||||
bare_stacktrace << "Stack trace:";
|
bare_stacktrace << "Stack trace:";
|
||||||
for (size_t i = stack_trace.getOffset(); i < stack_trace.getSize(); ++i)
|
for (size_t i = stack_trace.getOffset(); i < stack_trace.getSize(); ++i)
|
||||||
bare_stacktrace << ' ' << stack_trace.getFrames()[i];
|
bare_stacktrace << ' ' << stack_trace.getFramePointers()[i];
|
||||||
|
|
||||||
LOG_FATAL(log, bare_stacktrace.str());
|
LOG_FATAL(log, bare_stacktrace.str());
|
||||||
}
|
}
|
||||||
@ -529,6 +541,7 @@ void debugIncreaseOOMScore() {}
|
|||||||
void BaseDaemon::initialize(Application & self)
|
void BaseDaemon::initialize(Application & self)
|
||||||
{
|
{
|
||||||
closeFDs();
|
closeFDs();
|
||||||
|
|
||||||
task_manager = std::make_unique<Poco::TaskManager>();
|
task_manager = std::make_unique<Poco::TaskManager>();
|
||||||
ServerApplication::initialize(self);
|
ServerApplication::initialize(self);
|
||||||
|
|
||||||
@ -536,7 +549,6 @@ void BaseDaemon::initialize(Application & self)
|
|||||||
argsToConfig(argv(), config(), PRIO_APPLICATION - 100);
|
argsToConfig(argv(), config(), PRIO_APPLICATION - 100);
|
||||||
|
|
||||||
bool is_daemon = config().getBool("application.runAsDaemon", false);
|
bool is_daemon = config().getBool("application.runAsDaemon", false);
|
||||||
|
|
||||||
if (is_daemon)
|
if (is_daemon)
|
||||||
{
|
{
|
||||||
/** When creating pid file and looking for config, will search for paths relative to the working path of the program when started.
|
/** When creating pid file and looking for config, will search for paths relative to the working path of the program when started.
|
||||||
@ -672,6 +684,7 @@ void BaseDaemon::initialize(Application & self)
|
|||||||
|
|
||||||
void BaseDaemon::initializeTerminationAndSignalProcessing()
|
void BaseDaemon::initializeTerminationAndSignalProcessing()
|
||||||
{
|
{
|
||||||
|
SentryWriter::initialize(config());
|
||||||
std::set_terminate(terminate_handler);
|
std::set_terminate(terminate_handler);
|
||||||
|
|
||||||
/// We want to avoid SIGPIPE when working with sockets and pipes, and just handle return value/errno instead.
|
/// We want to avoid SIGPIPE when working with sockets and pipes, and just handle return value/errno instead.
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
add_library (daemon
|
add_library (daemon
|
||||||
BaseDaemon.cpp
|
BaseDaemon.cpp
|
||||||
GraphiteWriter.cpp
|
GraphiteWriter.cpp
|
||||||
|
SentryWriter.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories (daemon PUBLIC ..)
|
target_include_directories (daemon PUBLIC ..)
|
||||||
target_link_libraries (daemon PUBLIC loggers PRIVATE clickhouse_common_io clickhouse_common_config common ${EXECINFO_LIBRARIES})
|
target_link_libraries (daemon PUBLIC loggers PRIVATE clickhouse_common_io clickhouse_common_config common ${EXECINFO_LIBRARIES})
|
||||||
|
|
||||||
|
if (USE_SENTRY)
|
||||||
|
target_link_libraries (daemon PRIVATE curl)
|
||||||
|
target_link_libraries (daemon PRIVATE ${SENTRY_LIBRARY})
|
||||||
|
endif ()
|
||||||
|
250
base/daemon/SentryWriter.cpp
Normal file
250
base/daemon/SentryWriter.cpp
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
#include <daemon/SentryWriter.h>
|
||||||
|
|
||||||
|
#include <Poco/File.h>
|
||||||
|
#include <Poco/Util/Application.h>
|
||||||
|
|
||||||
|
#include <common/defines.h>
|
||||||
|
#include <common/getFQDNOrHostName.h>
|
||||||
|
#include <common/logger_useful.h>
|
||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include "Common/config_version.h"
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SENTRY
|
||||||
|
# include <sentry.h> // Y_IGNORE
|
||||||
|
# include <stdio.h>
|
||||||
|
# include <filesystem>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if USE_SENTRY
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
bool anonymize = false;
|
||||||
|
|
||||||
|
void setExtras()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!anonymize)
|
||||||
|
{
|
||||||
|
sentry_set_extra("server_name", sentry_value_new_string(getFQDNOrHostName().c_str()));
|
||||||
|
}
|
||||||
|
sentry_set_tag("version", VERSION_STRING);
|
||||||
|
sentry_set_extra("version_githash", sentry_value_new_string(VERSION_GITHASH));
|
||||||
|
sentry_set_extra("version_describe", sentry_value_new_string(VERSION_DESCRIBE));
|
||||||
|
sentry_set_extra("version_integer", sentry_value_new_int32(VERSION_INTEGER));
|
||||||
|
sentry_set_extra("version_revision", sentry_value_new_int32(VERSION_REVISION));
|
||||||
|
sentry_set_extra("version_major", sentry_value_new_int32(VERSION_MAJOR));
|
||||||
|
sentry_set_extra("version_minor", sentry_value_new_int32(VERSION_MINOR));
|
||||||
|
sentry_set_extra("version_patch", sentry_value_new_int32(VERSION_PATCH));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sentry_logger(sentry_level_t level, const char * message, va_list args)
|
||||||
|
{
|
||||||
|
auto * logger = &Poco::Logger::get("SentryWriter");
|
||||||
|
size_t size = 1024;
|
||||||
|
char buffer[size];
|
||||||
|
#ifdef __clang__
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
||||||
|
#endif
|
||||||
|
if (vsnprintf(buffer, size, message, args) >= 0)
|
||||||
|
{
|
||||||
|
#ifdef __clang__
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case SENTRY_LEVEL_DEBUG:
|
||||||
|
logger->debug(buffer);
|
||||||
|
break;
|
||||||
|
case SENTRY_LEVEL_INFO:
|
||||||
|
logger->information(buffer);
|
||||||
|
break;
|
||||||
|
case SENTRY_LEVEL_WARNING:
|
||||||
|
logger->warning(buffer);
|
||||||
|
break;
|
||||||
|
case SENTRY_LEVEL_ERROR:
|
||||||
|
logger->error(buffer);
|
||||||
|
break;
|
||||||
|
case SENTRY_LEVEL_FATAL:
|
||||||
|
logger->fatal(buffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void SentryWriter::initialize(Poco::Util::LayeredConfiguration & config)
|
||||||
|
{
|
||||||
|
#if USE_SENTRY
|
||||||
|
bool enabled = false;
|
||||||
|
bool debug = config.getBool("send_crash_reports.debug", false);
|
||||||
|
auto * logger = &Poco::Logger::get("SentryWriter");
|
||||||
|
if (config.getBool("send_crash_reports.enabled", false))
|
||||||
|
{
|
||||||
|
if (debug || (strlen(VERSION_OFFICIAL) > 0))
|
||||||
|
{
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
const std::filesystem::path & default_tmp_path = std::filesystem::path(config.getString("tmp_path", Poco::Path::temp())) / "sentry";
|
||||||
|
const std::string & endpoint
|
||||||
|
= config.getString("send_crash_reports.endpoint");
|
||||||
|
const std::string & temp_folder_path
|
||||||
|
= config.getString("send_crash_reports.tmp_path", default_tmp_path);
|
||||||
|
Poco::File(temp_folder_path).createDirectories();
|
||||||
|
|
||||||
|
sentry_options_t * options = sentry_options_new(); /// will be freed by sentry_init or sentry_shutdown
|
||||||
|
sentry_options_set_release(options, VERSION_STRING_SHORT);
|
||||||
|
sentry_options_set_logger(options, &sentry_logger);
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
sentry_options_set_debug(options, 1);
|
||||||
|
}
|
||||||
|
sentry_options_set_dsn(options, endpoint.c_str());
|
||||||
|
sentry_options_set_database_path(options, temp_folder_path.c_str());
|
||||||
|
if (strstr(VERSION_DESCRIBE, "-stable") || strstr(VERSION_DESCRIBE, "-lts"))
|
||||||
|
{
|
||||||
|
sentry_options_set_environment(options, "prod");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sentry_options_set_environment(options, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string & http_proxy = config.getString("send_crash_reports.http_proxy", "");
|
||||||
|
if (!http_proxy.empty())
|
||||||
|
{
|
||||||
|
sentry_options_set_http_proxy(options, http_proxy.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int init_status = sentry_init(options);
|
||||||
|
if (!init_status)
|
||||||
|
{
|
||||||
|
initialized = true;
|
||||||
|
anonymize = config.getBool("send_crash_reports.anonymize", false);
|
||||||
|
LOG_INFO(
|
||||||
|
logger,
|
||||||
|
"Sending crash reports is initialized with {} endpoint and {} temp folder{}",
|
||||||
|
endpoint,
|
||||||
|
temp_folder_path,
|
||||||
|
anonymize ? " (anonymized)" : "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_WARNING(logger, "Sending crash reports failed to initialize with {} status", init_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_INFO(logger, "Sending crash reports is disabled");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
UNUSED(config);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentryWriter::shutdown()
|
||||||
|
{
|
||||||
|
#if USE_SENTRY
|
||||||
|
if (initialized)
|
||||||
|
{
|
||||||
|
sentry_shutdown();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentryWriter::onFault(int sig, const siginfo_t & info, const ucontext_t & context, const StackTrace & stack_trace, const String & build_id_hex)
|
||||||
|
{
|
||||||
|
#if USE_SENTRY
|
||||||
|
auto * logger = &Poco::Logger::get("SentryWriter");
|
||||||
|
if (initialized)
|
||||||
|
{
|
||||||
|
const std::string & error_message = signalToErrorMessage(sig, info, context);
|
||||||
|
sentry_value_t event = sentry_value_new_message_event(SENTRY_LEVEL_FATAL, "fault", error_message.c_str());
|
||||||
|
sentry_set_tag("signal", strsignal(sig));
|
||||||
|
sentry_set_extra("signal_number", sentry_value_new_int32(sig));
|
||||||
|
if (!build_id_hex.empty())
|
||||||
|
{
|
||||||
|
sentry_set_tag("build_id", build_id_hex.c_str());
|
||||||
|
}
|
||||||
|
setExtras();
|
||||||
|
|
||||||
|
/// Prepare data for https://develop.sentry.dev/sdk/event-payloads/stacktrace/
|
||||||
|
sentry_value_t sentry_frames = sentry_value_new_list();
|
||||||
|
size_t stack_size = stack_trace.getSize();
|
||||||
|
if (stack_size > 0)
|
||||||
|
{
|
||||||
|
ssize_t offset = stack_trace.getOffset();
|
||||||
|
char instruction_addr[100];
|
||||||
|
StackTrace::Frames frames;
|
||||||
|
StackTrace::symbolize(stack_trace.getFramePointers(), offset, stack_size, frames);
|
||||||
|
for (ssize_t i = stack_size - 1; i >= offset; --i)
|
||||||
|
{
|
||||||
|
const StackTrace::Frame & current_frame = frames[i];
|
||||||
|
sentry_value_t sentry_frame = sentry_value_new_object();
|
||||||
|
UInt64 frame_ptr = reinterpret_cast<UInt64>(current_frame.virtual_addr);
|
||||||
|
|
||||||
|
if (std::snprintf(instruction_addr, sizeof(instruction_addr), "0x%" PRIx64, frame_ptr) >= 0)
|
||||||
|
{
|
||||||
|
sentry_value_set_by_key(sentry_frame, "instruction_addr", sentry_value_new_string(instruction_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_frame.symbol.has_value())
|
||||||
|
{
|
||||||
|
sentry_value_set_by_key(sentry_frame, "function", sentry_value_new_string(current_frame.symbol.value().c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_frame.file.has_value())
|
||||||
|
{
|
||||||
|
sentry_value_set_by_key(sentry_frame, "filename", sentry_value_new_string(current_frame.file.value().c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_frame.line.has_value())
|
||||||
|
{
|
||||||
|
sentry_value_set_by_key(sentry_frame, "lineno", sentry_value_new_int32(current_frame.line.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sentry_value_append(sentry_frames, sentry_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare data for https://develop.sentry.dev/sdk/event-payloads/threads/
|
||||||
|
/// Stacktrace is filled only for a single thread that failed
|
||||||
|
sentry_value_t stacktrace = sentry_value_new_object();
|
||||||
|
sentry_value_set_by_key(stacktrace, "frames", sentry_frames);
|
||||||
|
|
||||||
|
sentry_value_t thread = sentry_value_new_object();
|
||||||
|
sentry_value_set_by_key(thread, "stacktrace", stacktrace);
|
||||||
|
|
||||||
|
sentry_value_t values = sentry_value_new_list();
|
||||||
|
sentry_value_append(values, thread);
|
||||||
|
|
||||||
|
sentry_value_t threads = sentry_value_new_object();
|
||||||
|
sentry_value_set_by_key(threads, "values", values);
|
||||||
|
|
||||||
|
sentry_value_set_by_key(event, "threads", threads);
|
||||||
|
|
||||||
|
LOG_INFO(logger, "Sending crash report");
|
||||||
|
sentry_capture_event(event);
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_INFO(logger, "Not sending crash report");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
UNUSED(sig);
|
||||||
|
UNUSED(info);
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(stack_trace);
|
||||||
|
UNUSED(build_id_hex);
|
||||||
|
#endif
|
||||||
|
}
|
33
base/daemon/SentryWriter.h
Normal file
33
base/daemon/SentryWriter.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <common/types.h>
|
||||||
|
#include <Common/StackTrace.h>
|
||||||
|
|
||||||
|
#include <Poco/Util/LayeredConfiguration.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/// \brief Sends crash reports to ClickHouse core developer team via https://sentry.io
|
||||||
|
///
|
||||||
|
/// This feature can enabled with "send_crash_reports.enabled" server setting,
|
||||||
|
/// in this case reports are sent only for official ClickHouse builds.
|
||||||
|
///
|
||||||
|
/// It is possible to send those reports to your own sentry account or account of consulting company you hired
|
||||||
|
/// by overriding "send_crash_reports.endpoint" setting. "send_crash_reports.debug" setting will allow to do that for
|
||||||
|
class SentryWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SentryWriter() = delete;
|
||||||
|
|
||||||
|
static void initialize(Poco::Util::LayeredConfiguration & config);
|
||||||
|
static void shutdown();
|
||||||
|
|
||||||
|
/// Not signal safe and can't be called from a signal handler
|
||||||
|
static void onFault(
|
||||||
|
int sig,
|
||||||
|
const siginfo_t & info,
|
||||||
|
const ucontext_t & context,
|
||||||
|
const StackTrace & stack_trace,
|
||||||
|
const String & build_id_hex
|
||||||
|
);
|
||||||
|
};
|
@ -9,6 +9,7 @@ PEERDIR(
|
|||||||
SRCS(
|
SRCS(
|
||||||
BaseDaemon.cpp
|
BaseDaemon.cpp
|
||||||
GraphiteWriter.cpp
|
GraphiteWriter.cpp
|
||||||
|
SentryWriter.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
END()
|
END()
|
||||||
|
21
cmake/find/sentry.cmake
Normal file
21
cmake/find/sentry.cmake
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
set (SENTRY_LIBRARY "sentry")
|
||||||
|
set (SENTRY_INCLUDE_DIR "${ClickHouse_SOURCE_DIR}/contrib/sentry-native/include")
|
||||||
|
if (NOT EXISTS "${SENTRY_INCLUDE_DIR}/sentry.h")
|
||||||
|
message (WARNING "submodule contrib/sentry-native is missing. to fix try run: \n git submodule update --init --recursive")
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT OS_FREEBSD AND NOT SPLIT_SHARED_LIBRARIES AND NOT_UNBUNDLED AND NOT (OS_DARWIN AND COMPILER_CLANG))
|
||||||
|
option (USE_SENTRY "Use Sentry" ON)
|
||||||
|
set (CURL_LIBRARY ${ClickHouse_SOURCE_DIR}/contrib/curl/lib)
|
||||||
|
set (CURL_INCLUDE_DIR ${ClickHouse_SOURCE_DIR}/contrib/curl/include)
|
||||||
|
set (SENTRY_TRANSPORT "curl" CACHE STRING "")
|
||||||
|
set (SENTRY_BACKEND "none" CACHE STRING "")
|
||||||
|
set (SENTRY_EXPORT_SYMBOLS OFF CACHE BOOL "")
|
||||||
|
set (SENTRY_LINK_PTHREAD OFF CACHE BOOL "")
|
||||||
|
set (SENTRY_PIC OFF CACHE BOOL "")
|
||||||
|
set (BUILD_SHARED_LIBS OFF)
|
||||||
|
message (STATUS "Using sentry=${USE_SENTRY}: ${SENTRY_LIBRARY}")
|
||||||
|
|
||||||
|
include_directories("${SENTRY_INCLUDE_DIR}")
|
||||||
|
endif ()
|
@ -14,6 +14,7 @@ endif ()
|
|||||||
set (VERSION_NAME "${PROJECT_NAME}")
|
set (VERSION_NAME "${PROJECT_NAME}")
|
||||||
set (VERSION_FULL "${VERSION_NAME} ${VERSION_STRING}")
|
set (VERSION_FULL "${VERSION_NAME} ${VERSION_STRING}")
|
||||||
set (VERSION_SO "${VERSION_STRING}")
|
set (VERSION_SO "${VERSION_STRING}")
|
||||||
|
set (VERSION_STRING_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}")
|
||||||
|
|
||||||
math (EXPR VERSION_INTEGER "${VERSION_PATCH} + ${VERSION_MINOR}*1000 + ${VERSION_MAJOR}*1000000")
|
math (EXPR VERSION_INTEGER "${VERSION_PATCH} + ${VERSION_MINOR}*1000 + ${VERSION_MAJOR}*1000000")
|
||||||
|
|
||||||
|
14
contrib/CMakeLists.txt
vendored
14
contrib/CMakeLists.txt
vendored
@ -263,7 +263,7 @@ if (USE_INTERNAL_GRPC_LIBRARY)
|
|||||||
add_subdirectory(grpc-cmake)
|
add_subdirectory(grpc-cmake)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (USE_INTERNAL_AWS_S3_LIBRARY)
|
if (USE_INTERNAL_AWS_S3_LIBRARY OR USE_SENTRY)
|
||||||
set (save_CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
|
set (save_CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
|
||||||
set (save_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
|
set (save_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
|
||||||
set (save_CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES})
|
set (save_CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES})
|
||||||
@ -275,12 +275,18 @@ if (USE_INTERNAL_AWS_S3_LIBRARY)
|
|||||||
set (CMAKE_CMAKE_REQUIRED_INCLUDES ${save_CMAKE_REQUIRED_INCLUDES})
|
set (CMAKE_CMAKE_REQUIRED_INCLUDES ${save_CMAKE_REQUIRED_INCLUDES})
|
||||||
set (CMAKE_REQUIRED_FLAGS ${save_CMAKE_REQUIRED_FLAGS})
|
set (CMAKE_REQUIRED_FLAGS ${save_CMAKE_REQUIRED_FLAGS})
|
||||||
set (CMAKE_CMAKE_MODULE_PATH ${save_CMAKE_MODULE_PATH})
|
set (CMAKE_CMAKE_MODULE_PATH ${save_CMAKE_MODULE_PATH})
|
||||||
|
|
||||||
|
# The library is large - avoid bloat.
|
||||||
|
target_compile_options (curl PRIVATE -g0)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (USE_INTERNAL_AWS_S3_LIBRARY)
|
||||||
add_subdirectory(aws-s3-cmake)
|
add_subdirectory(aws-s3-cmake)
|
||||||
|
|
||||||
# The library is large - avoid bloat.
|
# The library is large - avoid bloat.
|
||||||
target_compile_options (aws_s3 PRIVATE -g0)
|
target_compile_options (aws_s3 PRIVATE -g0)
|
||||||
target_compile_options (aws_s3_checksums PRIVATE -g0)
|
target_compile_options (aws_s3_checksums PRIVATE -g0)
|
||||||
target_compile_options (curl PRIVATE -g0)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (USE_BASE64)
|
if (USE_BASE64)
|
||||||
@ -300,5 +306,9 @@ if (USE_CASSANDRA)
|
|||||||
add_subdirectory (cassandra)
|
add_subdirectory (cassandra)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (USE_SENTRY)
|
||||||
|
add_subdirectory (sentry-native)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory (fmtlib-cmake)
|
add_subdirectory (fmtlib-cmake)
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
set (CURL_DIR ${ClickHouse_SOURCE_DIR}/contrib/curl)
|
set (CURL_DIR ${ClickHouse_SOURCE_DIR}/contrib/curl)
|
||||||
|
set (CURL_LIBRARY ${ClickHouse_SOURCE_DIR}/contrib/curl/lib)
|
||||||
|
set (CURL_INCLUDE_DIR ${ClickHouse_SOURCE_DIR}/contrib/curl/include)
|
||||||
|
|
||||||
set (SRCS
|
set (SRCS
|
||||||
${CURL_DIR}/lib/file.c
|
${CURL_DIR}/lib/file.c
|
||||||
|
1
contrib/sentry-native
vendored
Submodule
1
contrib/sentry-native
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit f91ed3f95b5653f247189d720ab00765b4899d6f
|
@ -307,11 +307,11 @@ Logging settings.
|
|||||||
|
|
||||||
Keys:
|
Keys:
|
||||||
|
|
||||||
- level – Logging level. Acceptable values: `trace`, `debug`, `information`, `warning`, `error`.
|
- `level` – Logging level. Acceptable values: `trace`, `debug`, `information`, `warning`, `error`.
|
||||||
- log – The log file. Contains all the entries according to `level`.
|
- `log` – The log file. Contains all the entries according to `level`.
|
||||||
- errorlog – Error log file.
|
- `errorlog` – Error log file.
|
||||||
- size – Size of the file. Applies to `log`and`errorlog`. Once the file reaches `size`, ClickHouse archives and renames it, and creates a new log file in its place.
|
- `size` – Size of the file. Applies to `log`and`errorlog`. Once the file reaches `size`, ClickHouse archives and renames it, and creates a new log file in its place.
|
||||||
- count – The number of archived log files that ClickHouse stores.
|
- `count` – The number of archived log files that ClickHouse stores.
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
@ -348,6 +348,30 @@ Keys:
|
|||||||
Default value: `LOG_USER` if `address` is specified, `LOG_DAEMON otherwise.`
|
Default value: `LOG_USER` if `address` is specified, `LOG_DAEMON otherwise.`
|
||||||
- format – Message format. Possible values: `bsd` and `syslog.`
|
- format – Message format. Possible values: `bsd` and `syslog.`
|
||||||
|
|
||||||
|
## send_crash_reports {#server_configuration_parameters-logger}
|
||||||
|
|
||||||
|
Settings for opt-in sending crash reports to the ClickHouse core developers team via [Sentry](https://sentry.io).
|
||||||
|
Enabling it, especially in pre-production environments, is greatly appreciated.
|
||||||
|
|
||||||
|
The server will need an access to public Internet via IPv4 (at the time of writing IPv6 is not supported by Sentry) for this feature to be functioning properly.
|
||||||
|
|
||||||
|
Keys:
|
||||||
|
|
||||||
|
- `enabled` – Boolean flag to enable the feature. Set to `true` to allow sending crash reports.
|
||||||
|
- `endpoint` – Overrides the Sentry endpoint.
|
||||||
|
- `anonymize` - Avoid attaching the server hostname to crash report.
|
||||||
|
- `http_proxy` - Configure HTTP proxy for sending crash reports.
|
||||||
|
- `debug` - Sets the Sentry client into debug mode.
|
||||||
|
- `tmp_path` - Filesystem path for temporary crash report state.
|
||||||
|
|
||||||
|
**Recommended way to use**
|
||||||
|
|
||||||
|
``` xml
|
||||||
|
<send_crash_reports>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
## macros {#macros}
|
## macros {#macros}
|
||||||
|
|
||||||
Parameter substitutions for replicated tables.
|
Parameter substitutions for replicated tables.
|
||||||
|
@ -10,7 +10,6 @@ set (CLICKHOUSE_ODBC_BRIDGE_SOURCES
|
|||||||
PingHandler.cpp
|
PingHandler.cpp
|
||||||
validateODBCConnectionString.cpp
|
validateODBCConnectionString.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (CLICKHOUSE_ODBC_BRIDGE_LINK
|
set (CLICKHOUSE_ODBC_BRIDGE_LINK
|
||||||
PRIVATE
|
PRIVATE
|
||||||
clickhouse_parsers
|
clickhouse_parsers
|
||||||
|
@ -45,6 +45,18 @@
|
|||||||
-->
|
-->
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<send_crash_reports>
|
||||||
|
<!-- Changing <enabled> to true allows sending crash reports to -->
|
||||||
|
<!-- the ClickHouse core developers team via Sentry https://sentry.io -->
|
||||||
|
<!-- Doing so at least in pre-production environments is highly appreciated -->
|
||||||
|
<enabled>false</enabled>
|
||||||
|
<!-- Change <anonymize> to true if you don't feel comfortable attaching the server hostname to the crash report -->
|
||||||
|
<anonymize>false</anonymize>
|
||||||
|
<!-- Default endpoint should be changed to different Sentry DSN only if you have -->
|
||||||
|
<!-- some in-house engineers or hired consultants who're going to debug ClickHouse issues for you -->
|
||||||
|
<endpoint>https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277</endpoint>
|
||||||
|
</send_crash_reports>
|
||||||
|
|
||||||
<!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
|
<!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
|
||||||
<http_port>8123</http_port>
|
<http_port>8123</http_port>
|
||||||
<tcp_port>9000</tcp_port>
|
<tcp_port>9000</tcp_port>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#include <Common/StackTrace.h>
|
#include <Common/StackTrace.h>
|
||||||
|
|
||||||
|
#include <Core/Defines.h>
|
||||||
#include <Common/Dwarf.h>
|
#include <Common/Dwarf.h>
|
||||||
#include <Common/Elf.h>
|
#include <Common/Elf.h>
|
||||||
#include <Common/SymbolIndex.h>
|
#include <Common/SymbolIndex.h>
|
||||||
#include <Common/MemorySanitizer.h>
|
#include <Common/MemorySanitizer.h>
|
||||||
#include <common/SimpleCache.h>
|
#include <common/SimpleCache.h>
|
||||||
#include <common/demangle.h>
|
#include <common/demangle.h>
|
||||||
#include <Core/Defines.h>
|
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@ -176,13 +176,13 @@ static void * getCallerAddress(const ucontext_t & context)
|
|||||||
{
|
{
|
||||||
#if defined(__x86_64__)
|
#if defined(__x86_64__)
|
||||||
/// Get the address at the time the signal was raised from the RIP (x86-64)
|
/// Get the address at the time the signal was raised from the RIP (x86-64)
|
||||||
#if defined(__FreeBSD__)
|
# if defined(__FreeBSD__)
|
||||||
return reinterpret_cast<void *>(context.uc_mcontext.mc_rip);
|
return reinterpret_cast<void *>(context.uc_mcontext.mc_rip);
|
||||||
#elif defined(__APPLE__)
|
# elif defined(__APPLE__)
|
||||||
return reinterpret_cast<void *>(context.uc_mcontext->__ss.__rip);
|
return reinterpret_cast<void *>(context.uc_mcontext->__ss.__rip);
|
||||||
#else
|
# else
|
||||||
return reinterpret_cast<void *>(context.uc_mcontext.gregs[REG_RIP]);
|
return reinterpret_cast<void *>(context.uc_mcontext.gregs[REG_RIP]);
|
||||||
#endif
|
# endif
|
||||||
#elif defined(__aarch64__)
|
#elif defined(__aarch64__)
|
||||||
return reinterpret_cast<void *>(context.uc_mcontext.pc);
|
return reinterpret_cast<void *>(context.uc_mcontext.pc);
|
||||||
#else
|
#else
|
||||||
@ -190,6 +190,66 @@ static void * getCallerAddress(const ucontext_t & context)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StackTrace::symbolize(const StackTrace::FramePointers & frame_pointers, size_t offset, size_t size, StackTrace::Frames & frames)
|
||||||
|
{
|
||||||
|
#if defined(__ELF__) && !defined(__FreeBSD__) && !defined(ARCADIA_BUILD)
|
||||||
|
|
||||||
|
const DB::SymbolIndex & symbol_index = DB::SymbolIndex::instance();
|
||||||
|
std::unordered_map<std::string, DB::Dwarf> dwarfs;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < offset; ++i)
|
||||||
|
{
|
||||||
|
frames[i].virtual_addr = frame_pointers[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = offset; i < size; ++i)
|
||||||
|
{
|
||||||
|
StackTrace::Frame & current_frame = frames[i];
|
||||||
|
current_frame.virtual_addr = frame_pointers[i];
|
||||||
|
const auto * object = symbol_index.findObject(current_frame.virtual_addr);
|
||||||
|
uintptr_t virtual_offset = object ? uintptr_t(object->address_begin) : 0;
|
||||||
|
current_frame.physical_addr = reinterpret_cast<void *>(uintptr_t(current_frame.virtual_addr) - virtual_offset);
|
||||||
|
|
||||||
|
if (object)
|
||||||
|
{
|
||||||
|
current_frame.object = object->name;
|
||||||
|
if (std::filesystem::exists(current_frame.object.value()))
|
||||||
|
{
|
||||||
|
auto dwarf_it = dwarfs.try_emplace(object->name, *object->elf).first;
|
||||||
|
|
||||||
|
DB::Dwarf::LocationInfo location;
|
||||||
|
if (dwarf_it->second.findAddress(uintptr_t(current_frame.physical_addr), location, DB::Dwarf::LocationInfoMode::FAST))
|
||||||
|
{
|
||||||
|
current_frame.file = location.file.toString();
|
||||||
|
current_frame.line = location.line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_frame.object = "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto * symbol = symbol_index.findSymbol(current_frame.virtual_addr);
|
||||||
|
if (symbol)
|
||||||
|
{
|
||||||
|
int status = 0;
|
||||||
|
current_frame.symbol = demangle(symbol->name, status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_frame.symbol = "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
frames[i].virtual_addr = frame_pointers[i];
|
||||||
|
}
|
||||||
|
UNUSED(offset);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
StackTrace::StackTrace()
|
StackTrace::StackTrace()
|
||||||
{
|
{
|
||||||
tryCapture();
|
tryCapture();
|
||||||
@ -203,16 +263,15 @@ StackTrace::StackTrace(const ucontext_t & signal_context)
|
|||||||
|
|
||||||
if (size == 0 && caller_address)
|
if (size == 0 && caller_address)
|
||||||
{
|
{
|
||||||
frames[0] = caller_address;
|
frame_pointers[0] = caller_address;
|
||||||
size = 1;
|
size = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/// Skip excessive stack frames that we have created while finding stack trace.
|
/// Skip excessive stack frames that we have created while finding stack trace.
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
for (size_t i = 0; i < size; ++i)
|
||||||
{
|
{
|
||||||
if (frames[i] == caller_address)
|
if (frame_pointers[i] == caller_address)
|
||||||
{
|
{
|
||||||
offset = i;
|
offset = i;
|
||||||
break;
|
break;
|
||||||
@ -229,8 +288,8 @@ void StackTrace::tryCapture()
|
|||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
#if USE_UNWIND
|
#if USE_UNWIND
|
||||||
size = unw_backtrace(frames.data(), capacity);
|
size = unw_backtrace(frame_pointers.data(), capacity);
|
||||||
__msan_unpoison(frames.data(), size * sizeof(frames[0]));
|
__msan_unpoison(frame_pointers.data(), size * sizeof(frame_pointers[0]));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,13 +303,12 @@ size_t StackTrace::getOffset() const
|
|||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StackTrace::Frames & StackTrace::getFrames() const
|
const StackTrace::FramePointers & StackTrace::getFramePointers() const
|
||||||
{
|
{
|
||||||
return frames;
|
return frame_pointers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void toStringEveryLineImpl(const StackTrace::FramePointers & frame_pointers, size_t offset, size_t size, std::function<void(const std::string &)> callback)
|
||||||
static void toStringEveryLineImpl(const StackTrace::Frames & frames, size_t offset, size_t size, std::function<void(const std::string &)> callback)
|
|
||||||
{
|
{
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return callback("<Empty trace>");
|
return callback("<Empty trace>");
|
||||||
@ -263,7 +321,7 @@ static void toStringEveryLineImpl(const StackTrace::Frames & frames, size_t offs
|
|||||||
|
|
||||||
for (size_t i = offset; i < size; ++i)
|
for (size_t i = offset; i < size; ++i)
|
||||||
{
|
{
|
||||||
const void * virtual_addr = frames[i];
|
const void * virtual_addr = frame_pointers[i];
|
||||||
const auto * object = symbol_index.findObject(virtual_addr);
|
const auto * object = symbol_index.findObject(virtual_addr);
|
||||||
uintptr_t virtual_offset = object ? uintptr_t(object->address_begin) : 0;
|
uintptr_t virtual_offset = object ? uintptr_t(object->address_begin) : 0;
|
||||||
const void * physical_addr = reinterpret_cast<const void *>(uintptr_t(virtual_addr) - virtual_offset);
|
const void * physical_addr = reinterpret_cast<const void *>(uintptr_t(virtual_addr) - virtual_offset);
|
||||||
@ -302,7 +360,7 @@ static void toStringEveryLineImpl(const StackTrace::Frames & frames, size_t offs
|
|||||||
|
|
||||||
for (size_t i = offset; i < size; ++i)
|
for (size_t i = offset; i < size; ++i)
|
||||||
{
|
{
|
||||||
const void * addr = frames[i];
|
const void * addr = frame_pointers[i];
|
||||||
out << i << ". " << addr;
|
out << i << ". " << addr;
|
||||||
|
|
||||||
callback(out.str());
|
callback(out.str());
|
||||||
@ -311,35 +369,36 @@ static void toStringEveryLineImpl(const StackTrace::Frames & frames, size_t offs
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string toStringImpl(const StackTrace::Frames & frames, size_t offset, size_t size)
|
static std::string toStringImpl(const StackTrace::FramePointers & frame_pointers, size_t offset, size_t size)
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
toStringEveryLineImpl(frames, offset, size, [&](const std::string & str) { out << str << '\n'; });
|
toStringEveryLineImpl(frame_pointers, offset, size, [&](const std::string & str) { out << str << '\n'; });
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackTrace::toStringEveryLine(std::function<void(const std::string &)> callback) const
|
void StackTrace::toStringEveryLine(std::function<void(const std::string &)> callback) const
|
||||||
{
|
{
|
||||||
toStringEveryLineImpl(frames, offset, size, std::move(callback));
|
toStringEveryLineImpl(frame_pointers, offset, size, std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string StackTrace::toString() const
|
std::string StackTrace::toString() const
|
||||||
{
|
{
|
||||||
/// Calculation of stack trace text is extremely slow.
|
/// Calculation of stack trace text is extremely slow.
|
||||||
/// We use simple cache because otherwise the server could be overloaded by trash queries.
|
/// We use simple cache because otherwise the server could be overloaded by trash queries.
|
||||||
|
|
||||||
static SimpleCache<decltype(toStringImpl), &toStringImpl> func_cached;
|
static SimpleCache<decltype(toStringImpl), &toStringImpl> func_cached;
|
||||||
return func_cached(frames, offset, size);
|
return func_cached(frame_pointers, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string StackTrace::toString(void ** frames_, size_t offset, size_t size)
|
std::string StackTrace::toString(void ** frame_pointers_, size_t offset, size_t size)
|
||||||
{
|
{
|
||||||
__msan_unpoison(frames_, size * sizeof(*frames_));
|
__msan_unpoison(frame_pointers_, size * sizeof(*frame_pointers_));
|
||||||
|
|
||||||
StackTrace::Frames frames_copy{};
|
StackTrace::FramePointers frame_pointers_copy{};
|
||||||
for (size_t i = 0; i < size; ++i)
|
for (size_t i = 0; i < size; ++i)
|
||||||
frames_copy[i] = frames_[i];
|
frame_pointers_copy[i] = frame_pointers_[i];
|
||||||
|
|
||||||
static SimpleCache<decltype(toStringImpl), &toStringImpl> func_cached;
|
static SimpleCache<decltype(toStringImpl), &toStringImpl> func_cached;
|
||||||
return func_cached(frames_copy, offset, size);
|
return func_cached(frame_pointers_copy, offset, size);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <common/types.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <array>
|
#include <array>
|
||||||
@ -23,8 +25,18 @@ struct NoCapture
|
|||||||
class StackTrace
|
class StackTrace
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
struct Frame
|
||||||
|
{
|
||||||
|
const void * virtual_addr = nullptr;
|
||||||
|
void * physical_addr = nullptr;
|
||||||
|
std::optional<std::string> symbol;
|
||||||
|
std::optional<std::string> object;
|
||||||
|
std::optional<std::string> file;
|
||||||
|
std::optional<UInt64> line;
|
||||||
|
};
|
||||||
static constexpr size_t capacity = 32;
|
static constexpr size_t capacity = 32;
|
||||||
using Frames = std::array<void *, capacity>;
|
using FramePointers = std::array<void *, capacity>;
|
||||||
|
using Frames = std::array<Frame, capacity>;
|
||||||
|
|
||||||
/// Tries to capture stack trace
|
/// Tries to capture stack trace
|
||||||
StackTrace();
|
StackTrace();
|
||||||
@ -38,19 +50,19 @@ public:
|
|||||||
|
|
||||||
size_t getSize() const;
|
size_t getSize() const;
|
||||||
size_t getOffset() const;
|
size_t getOffset() const;
|
||||||
const Frames & getFrames() const;
|
const FramePointers & getFramePointers() const;
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
static std::string toString(void ** frames, size_t offset, size_t size);
|
static std::string toString(void ** frame_pointers, size_t offset, size_t size);
|
||||||
|
static void symbolize(const FramePointers & frame_pointers, size_t offset, size_t size, StackTrace::Frames & frames);
|
||||||
|
|
||||||
void toStringEveryLine(std::function<void(const std::string &)> callback) const;
|
void toStringEveryLine(std::function<void(const std::string &)> callback) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tryCapture();
|
void tryCapture();
|
||||||
|
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
size_t offset = 0; /// How many frames to skip while displaying.
|
size_t offset = 0; /// How many frames to skip while displaying.
|
||||||
Frames frames{};
|
FramePointers frame_pointers{};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string signalToErrorMessage(int sig, const siginfo_t & info, const ucontext_t & context);
|
std::string signalToErrorMessage(int sig, const siginfo_t & info, const ucontext_t & context);
|
||||||
|
@ -81,7 +81,7 @@ void TraceCollector::collect(TraceType trace_type, const StackTrace & stack_trac
|
|||||||
size_t stack_trace_offset = stack_trace.getOffset();
|
size_t stack_trace_offset = stack_trace.getOffset();
|
||||||
writeIntBinary(UInt8(stack_trace_size - stack_trace_offset), out);
|
writeIntBinary(UInt8(stack_trace_size - stack_trace_offset), out);
|
||||||
for (size_t i = stack_trace_offset; i < stack_trace_size; ++i)
|
for (size_t i = stack_trace_offset; i < stack_trace_size; ++i)
|
||||||
writePODBinary(stack_trace.getFrames()[i], out);
|
writePODBinary(stack_trace.getFramePointers()[i], out);
|
||||||
|
|
||||||
writePODBinary(trace_type, out);
|
writePODBinary(trace_type, out);
|
||||||
writePODBinary(thread_id, out);
|
writePODBinary(thread_id, out);
|
||||||
|
@ -10,5 +10,6 @@
|
|||||||
#cmakedefine01 USE_UNWIND
|
#cmakedefine01 USE_UNWIND
|
||||||
#cmakedefine01 USE_OPENCL
|
#cmakedefine01 USE_OPENCL
|
||||||
#cmakedefine01 USE_CASSANDRA
|
#cmakedefine01 USE_CASSANDRA
|
||||||
|
#cmakedefine01 USE_SENTRY
|
||||||
#cmakedefine01 USE_GRPC
|
#cmakedefine01 USE_GRPC
|
||||||
#cmakedefine01 CLICKHOUSE_SPLIT_BINARY
|
#cmakedefine01 CLICKHOUSE_SPLIT_BINARY
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#cmakedefine VERSION_MINOR @VERSION_MINOR@
|
#cmakedefine VERSION_MINOR @VERSION_MINOR@
|
||||||
#cmakedefine VERSION_PATCH @VERSION_PATCH@
|
#cmakedefine VERSION_PATCH @VERSION_PATCH@
|
||||||
#cmakedefine VERSION_STRING "@VERSION_STRING@"
|
#cmakedefine VERSION_STRING "@VERSION_STRING@"
|
||||||
|
#cmakedefine VERSION_STRING_SHORT "@VERSION_STRING_SHORT@"
|
||||||
#cmakedefine VERSION_OFFICIAL "@VERSION_OFFICIAL@"
|
#cmakedefine VERSION_OFFICIAL "@VERSION_OFFICIAL@"
|
||||||
#cmakedefine VERSION_FULL "@VERSION_FULL@"
|
#cmakedefine VERSION_FULL "@VERSION_FULL@"
|
||||||
#cmakedefine VERSION_DESCRIBE "@VERSION_DESCRIBE@"
|
#cmakedefine VERSION_DESCRIBE "@VERSION_DESCRIBE@"
|
||||||
|
@ -198,7 +198,7 @@ void StorageSystemStackTrace::fillData(MutableColumns & res_columns, const Conte
|
|||||||
Array arr;
|
Array arr;
|
||||||
arr.reserve(stack_trace_size - stack_trace_offset);
|
arr.reserve(stack_trace_size - stack_trace_offset);
|
||||||
for (size_t i = stack_trace_offset; i < stack_trace_size; ++i)
|
for (size_t i = stack_trace_offset; i < stack_trace_size; ++i)
|
||||||
arr.emplace_back(reinterpret_cast<intptr_t>(stack_trace->getFrames()[i]));
|
arr.emplace_back(reinterpret_cast<intptr_t>(stack_trace->getFramePointers()[i]));
|
||||||
|
|
||||||
res_columns[0]->insert(tid);
|
res_columns[0]->insert(tid);
|
||||||
res_columns[1]->insertData(query_id_data, query_id_size);
|
res_columns[1]->insertData(query_id_data, query_id_size);
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<yandex>
|
||||||
|
<send_crash_reports>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<debug>true</debug>
|
||||||
|
<endpoint>http://6f33034cfe684dd7a3ab9875e57b1c8d@localhost:9500/5226277</endpoint>
|
||||||
|
</send_crash_reports>
|
||||||
|
</yandex>
|
@ -0,0 +1,43 @@
|
|||||||
|
import BaseHTTPServer
|
||||||
|
|
||||||
|
RESULT_PATH = '/result.txt'
|
||||||
|
|
||||||
|
class SentryHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
post_data = self.__read_and_decode_post_data()
|
||||||
|
with open(RESULT_PATH, 'w') as f:
|
||||||
|
if self.headers.get("content-type") != "application/x-sentry-envelope":
|
||||||
|
f.write("INCORRECT_CONTENT_TYPE")
|
||||||
|
elif self.headers.get("content-length") < 3000:
|
||||||
|
f.write("INCORRECT_CONTENT_LENGTH")
|
||||||
|
elif '"http://6f33034cfe684dd7a3ab9875e57b1c8d@localhost:9500/5226277"' not in post_data:
|
||||||
|
f.write('INCORRECT_POST_DATA')
|
||||||
|
else:
|
||||||
|
f.write("OK")
|
||||||
|
self.send_response(200)
|
||||||
|
|
||||||
|
def __read_and_decode_post_data(self):
|
||||||
|
transfer_encoding = self.headers.get("transfer-Encoding")
|
||||||
|
decoded = ""
|
||||||
|
if transfer_encoding == "chunked":
|
||||||
|
while True:
|
||||||
|
s = self.rfile.readline()
|
||||||
|
chunk_length = int(s, 16)
|
||||||
|
if not chunk_length:
|
||||||
|
break
|
||||||
|
decoded += self.rfile.read(chunk_length)
|
||||||
|
self.rfile.readline()
|
||||||
|
else:
|
||||||
|
content_length = int(self.headers.get("content-length", 0))
|
||||||
|
decoded = self.rfile.read(content_length)
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with open(RESULT_PATH, 'w') as f:
|
||||||
|
f.write("INITIAL_STATE")
|
||||||
|
httpd = BaseHTTPServer.HTTPServer(("localhost", 9500,), SentryHandler)
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
finally:
|
||||||
|
httpd.server_close()
|
44
tests/integration/test_send_crash_reports/test.py
Normal file
44
tests/integration/test_send_crash_reports/test.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import helpers.cluster
|
||||||
|
import helpers.test_tools
|
||||||
|
import fake_sentry_server
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def started_node():
|
||||||
|
cluster = helpers.cluster.ClickHouseCluster(__file__)
|
||||||
|
try:
|
||||||
|
node = cluster.add_instance("node", main_configs=[
|
||||||
|
os.path.join(SCRIPT_DIR, "configs", "config_send_crash_reports.xml")
|
||||||
|
])
|
||||||
|
cluster.start()
|
||||||
|
yield node
|
||||||
|
finally:
|
||||||
|
cluster.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_segfault(started_node,):
|
||||||
|
started_node.copy_file_to_container(os.path.join(SCRIPT_DIR, "fake_sentry_server.py"), "/fake_sentry_server.py")
|
||||||
|
started_node.exec_in_container(["bash", "-c", "python2 /fake_sentry_server.py"], detach=True, user="root")
|
||||||
|
time.sleep(0.5)
|
||||||
|
started_node.exec_in_container(["bash", "-c", "pkill -11 clickhouse"], user="root")
|
||||||
|
|
||||||
|
result = None
|
||||||
|
for attempt in range(1, 6):
|
||||||
|
time.sleep(0.25 * attempt)
|
||||||
|
result = started_node.exec_in_container(['cat', fake_sentry_server.RESULT_PATH], user='root')
|
||||||
|
if result == 'OK':
|
||||||
|
break
|
||||||
|
elif result == 'INITIAL_STATE':
|
||||||
|
continue
|
||||||
|
elif result:
|
||||||
|
assert False, 'Unexpected state: ' + result
|
||||||
|
|
||||||
|
assert result == 'OK', 'Crash report not sent'
|
@ -36,5 +36,5 @@ wait
|
|||||||
${CLICKHOUSE_CLIENT} --query_id=42 --query='SELECT 3, count() FROM system.numbers' 2>&1 | grep -cF 'was cancelled' &
|
${CLICKHOUSE_CLIENT} --query_id=42 --query='SELECT 3, count() FROM system.numbers' 2>&1 | grep -cF 'was cancelled' &
|
||||||
wait_for_query_to_start '42'
|
wait_for_query_to_start '42'
|
||||||
${CLICKHOUSE_CLIENT} --query_id=42 --replace_running_query=1 --replace_running_query_max_wait_ms=500 --query='SELECT 43' 2>&1 | grep -F "can't be stopped" > /dev/null
|
${CLICKHOUSE_CLIENT} --query_id=42 --replace_running_query=1 --replace_running_query_max_wait_ms=500 --query='SELECT 43' 2>&1 | grep -F "can't be stopped" > /dev/null
|
||||||
${CLICKHOUSE_CLIENT} --query_id=42 --replace_running_query=1 --query='SELECT 44'
|
|
||||||
wait
|
wait
|
||||||
|
${CLICKHOUSE_CLIENT} --query_id=42 --replace_running_query=1 --query='SELECT 44'
|
||||||
|
@ -59,6 +59,7 @@ inc="-I. \
|
|||||||
-I./contrib/lz4/lib \
|
-I./contrib/lz4/lib \
|
||||||
-I./contrib/hyperscan/src \
|
-I./contrib/hyperscan/src \
|
||||||
-I./contrib/simdjson/include \
|
-I./contrib/simdjson/include \
|
||||||
|
-I./contrib/sentry-native/include \
|
||||||
-I./src \
|
-I./src \
|
||||||
-I${BUILD_DIR}/src"
|
-I${BUILD_DIR}/src"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user