print extra info in exception message

This commit is contained in:
Alexander Tokmakov 2019-08-05 22:41:20 +03:00
parent 6625536236
commit 75d10f4160
3 changed files with 133 additions and 7 deletions

View File

@ -9,6 +9,9 @@
#include <IO/ReadBufferFromString.h>
#include <common/demangle.h>
#include <Common/config_version.h>
#include <Common/formatReadable.h>
#include <Storages/MergeTree/DiskSpaceMonitor.h>
#include <filesystem>
namespace DB
{
@ -68,7 +71,66 @@ void tryLogCurrentException(Poco::Logger * logger, const std::string & start_of_
}
}
std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded_stacktrace)
void getEnospcInfoMessage(std::filesystem::path path, std::string & msg)
{
/// It's possible to get ENOSPC for non existent file (e.g. if there are no free inodes and creat() fails)
/// So try to get info for existent parent directory.
while (!std::filesystem::exists(path) && path.has_relative_path())
path = path.parent_path();
/// Most likely path is invalid
if (!path.has_relative_path())
return;
auto fs = DiskSpaceMonitor::getStatvfs(path);
msg += "\nTotal space: " + formatReadableSizeWithBinarySuffix(fs.f_blocks * fs.f_bsize)
+ "\nAvailable space: " + formatReadableSizeWithBinarySuffix(fs.f_bavail * fs.f_bsize)
+ "\nTotal inodes: " + formatReadableQuantity(fs.f_files)
+ "\nAvailable inodes: " + formatReadableQuantity(fs.f_favail);
auto mount_point = DiskSpaceMonitor::getMountPoint(path).string();
msg += "\nMount point: " + mount_point;
msg += "\nFilesystem: " + DiskSpaceMonitor::getFilesystemName(mount_point);
}
std::string getAdditionalExceptionInfo(const std::exception & e)
{
String msg;
try
{
if (auto file_exception = dynamic_cast<const Poco::FileException *>(&e))
{
if (file_exception->code() == ENOSPC)
getEnospcInfoMessage(file_exception->message(), msg);
}
else if (auto errno_exception = dynamic_cast<const DB::ErrnoException *>(&e))
{
if (errno_exception->getErrno() == ENOSPC)
{
/// Try to extract path from text exception message. Most likely the exception was thrown by
/// DB::throwFromErrno("Some message" + filename, ...);
/// We suppose "Some message " does not contain '/' and filename is an absolute path starts with '/'.
/// throwFromErrno appends ", errno: ..." to the first argument.
/// It's ugly hack which may not work correctly. However, getEnospcInfoMessage(...) checks if path exists.
size_t likely_path_begin = errno_exception->message().find('/');
size_t likely_path_end = errno_exception->message().find(", errno: ", likely_path_begin);
if (likely_path_end != std::string::npos)
{
std::string supposed_to_be_path = errno_exception->message().substr(likely_path_begin,
likely_path_end - likely_path_begin);
getEnospcInfoMessage(supposed_to_be_path, msg);
}
}
}
}
catch (...) {
msg += "\nCannot print additional info: " + getCurrentExceptionMessage(false, false, false);
}
return msg;
}
std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded_stacktrace /*= false*/, bool with_additional_info /*= true*/)
{
std::stringstream stream;
@ -78,7 +140,9 @@ std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded
}
catch (const Exception & e)
{
stream << getExceptionMessage(e, with_stacktrace, check_embedded_stacktrace) << " (version " << VERSION_STRING << VERSION_OFFICIAL << ")";
stream << getExceptionMessage(e, with_stacktrace, check_embedded_stacktrace)
<< (with_additional_info ? getAdditionalExceptionInfo(e) : "")
<< " (version " << VERSION_STRING << VERSION_OFFICIAL << ")";
}
catch (const Poco::Exception & e)
{
@ -86,7 +150,8 @@ std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded
{
stream << "Poco::Exception. Code: " << ErrorCodes::POCO_EXCEPTION << ", e.code() = " << e.code()
<< ", e.displayText() = " << e.displayText()
<< " (version " << VERSION_STRING << VERSION_OFFICIAL << ")";
<< (with_additional_info ? getAdditionalExceptionInfo(e) : "")
<< " (version " << VERSION_STRING << VERSION_OFFICIAL;
}
catch (...) {}
}
@ -100,7 +165,9 @@ std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded
if (status)
name += " (demangling status: " + toString(status) + ")";
stream << "std::exception. Code: " << ErrorCodes::STD_EXCEPTION << ", type: " << name << ", e.what() = " << e.what() << ", version = " << VERSION_STRING << VERSION_OFFICIAL;
stream << "std::exception. Code: " << ErrorCodes::STD_EXCEPTION << ", type: " << name << ", e.what() = " << e.what()
<< (with_additional_info ? getAdditionalExceptionInfo(e) : "")
<< ", version = " << VERSION_STRING << VERSION_OFFICIAL;
}
catch (...) {}
}

View File

@ -87,7 +87,8 @@ void tryLogCurrentException(Poco::Logger * logger, const std::string & start_of_
* check_embedded_stacktrace - if DB::Exception has embedded stacktrace then
* only this stack trace will be printed.
*/
std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded_stacktrace = false);
std::string getCurrentExceptionMessage(bool with_stacktrace, bool check_embedded_stacktrace = false,
bool with_additional_info = true);
/// Returns error code from ErrorCodes
int getCurrentExceptionCode();

View File

@ -2,7 +2,13 @@
#include <mutex>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdio>
#include <mntent.h>
#include <memory>
#include <filesystem>
#include <boost/noncopyable.hpp>
#include <common/logger_useful.h>
#include <Common/Exception.h>
@ -23,6 +29,7 @@ namespace ErrorCodes
{
extern const int CANNOT_STATVFS;
extern const int NOT_ENOUGH_SPACE;
extern const int SYSTEM_ERROR;
}
@ -96,12 +103,17 @@ public:
using ReservationPtr = std::unique_ptr<Reservation>;
static UInt64 getUnreservedFreeSpace(const std::string & path)
inline static struct statvfs getStatvfs(const std::string & path)
{
struct statvfs fs;
if (statvfs(path.c_str(), &fs) != 0)
throwFromErrno("Could not calculate available disk space (statvfs)", ErrorCodes::CANNOT_STATVFS);
return fs;
}
static UInt64 getUnreservedFreeSpace(const std::string & path)
{
struct statvfs fs = getStatvfs(path);
UInt64 res = fs.f_bfree * fs.f_bsize;
@ -140,6 +152,52 @@ public:
return std::make_unique<Reservation>(size);
}
/// Returns mount point of filesystem where absoulte_path (must exists) is located
static std::filesystem::path getMountPoint(std::filesystem::path absolute_path)
{
if (absolute_path.is_relative())
throw Exception("Path is relative. It's a bug.", ErrorCodes::LOGICAL_ERROR);
absolute_path = std::filesystem::canonical(absolute_path);
const auto get_device_id = [](const std::filesystem::path & p)
{
struct stat st;
if (stat(p.c_str(), &st))
throwFromErrno("Cannot stat " + p.string(), errno);
return st.st_dev;
};
/// If /some/path/to/dir/ and /some/path/to/ have different device id,
/// then device which contains /some/path/to/dir/filename is mounted to /some/path/to/dir/
auto device_id = get_device_id(absolute_path);
while (absolute_path.has_relative_path())
{
auto parent = absolute_path.parent_path();
auto parent_device_id = get_device_id(parent);
if (device_id != parent_device_id)
return absolute_path;
absolute_path = parent;
device_id = parent_device_id;
}
return absolute_path;
}
/// Returns name of filesystem mounted to mount_point
static std::string getFilesystemName(const std::string & mount_point)
{
auto mounted_filesystems = setmntent("/etc/mtab", "r");
mntent fs_info;
constexpr size_t buf_size = 4096; /// The same as buffer used for getmntent in glibc. It can happen that it's not enough
char buf[buf_size];
while (getmntent_r(mounted_filesystems, &fs_info, buf, buf_size) && fs_info.mnt_dir != mount_point);
endmntent(mounted_filesystems);
if (fs_info.mnt_dir != mount_point)
throw DB::Exception("Cannot find name of filesystem by mount point " + mount_point, ErrorCodes::SYSTEM_ERROR);
return fs_info.mnt_fsname;
}
private:
static UInt64 reserved_bytes;
static UInt64 reservation_count;