Logging to with a ringbuffer. Much faster.

This commit is contained in:
Ben Vanik
2015-08-29 18:06:30 -07:00
parent 8dd59d07ac
commit b7203c2989
25 changed files with 387 additions and 312 deletions

View File

@@ -34,12 +34,12 @@ std::string CanonicalizePath(const std::string& original_path) {
pos_n = std::string::npos;
break;
case 1:
// Duplicate separators
// Duplicate separators.
path.erase(pos, 1);
pos_n -= 1;
break;
case 2:
// Potential marker for current directory
// Potential marker for current directory.
if (path[pos + 1] == '.') {
path.erase(pos, 2);
pos_n -= 2;
@@ -48,10 +48,10 @@ std::string CanonicalizePath(const std::string& original_path) {
}
break;
case 3:
// Potential marker for parent directory
// Potential marker for parent directory.
if (path[pos + 1] == '.' && path[pos + 2] == '.') {
if (path_breaks.empty()) {
// Ensure we don't override the device name
// Ensure we don't override the device name.
std::string::size_type loc(path.find_first_of(':'));
auto req(pos + 3);
if (loc == std::string::npos || loc > req) {
@@ -66,7 +66,7 @@ std::string CanonicalizePath(const std::string& original_path) {
auto last_diff((pos + 3) - last);
path.erase(last, last_diff);
pos_n = last;
// Also remove path reference
// Also remove path reference.
path_breaks.erase(path_breaks.end() - 1);
}
} else {
@@ -82,12 +82,12 @@ std::string CanonicalizePath(const std::string& original_path) {
pos = pos_n;
}
// Remove trailing seperator
// Remove trailing seperator.
if (!path.empty() && path.back() == path_sep) {
path.erase(path.size() - 1);
}
// Final sanity check for dead paths
// Final sanity check for dead paths.
if ((path.size() == 1 && (path[0] == '.' || path[0] == path_sep)) ||
(path.size() == 2 && path[0] == '.' && path[1] == '.')) {
return "";
@@ -96,6 +96,16 @@ std::string CanonicalizePath(const std::string& original_path) {
return path;
}
bool CreateParentFolder(const std::wstring& path) {
auto fixed_path = xe::fix_path_separators(path, '/');
auto base_path = xe::find_base_path(fixed_path, '/');
if (!PathExists(base_path)) {
return CreateFolder(base_path);
} else {
return true;
}
}
WildcardFlags WildcardFlags::FIRST(true, false);
WildcardFlags WildcardFlags::LAST(false, true);

View File

@@ -24,6 +24,7 @@ std::string CanonicalizePath(const std::string& original_path);
bool PathExists(const std::wstring& path);
bool CreateParentFolder(const std::wstring& path);
bool CreateFolder(const std::wstring& path);
bool DeleteFolder(const std::wstring& path);
bool IsFolder(const std::wstring& path);

View File

@@ -11,12 +11,15 @@
#include <gflags/gflags.h>
#include <atomic>
#include <cstdarg>
#include <mutex>
#include <vector>
#include "xenia/base/filesystem.h"
#include "xenia/base/main.h"
#include "xenia/base/math.h"
#include "xenia/base/ring_buffer.h"
#include "xenia/base/threading.h"
// For MessageBox:
@@ -25,91 +28,150 @@
#include "xenia/base/platform_win.h"
#endif // XE_PLATFORM_WIN32
DEFINE_bool(fast_stdout, false,
"Don't lock around stdout/stderr. May introduce weirdness.");
DEFINE_bool(flush_stdout, true, "Flush stdout after each log line.");
DEFINE_string(log_file, "",
"Logs are written to the given file instead of the default.");
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.");
namespace xe {
std::mutex log_lock;
thread_local std::vector<char> log_format_buffer_(64 * 1024);
thread_local std::vector<char> log_buffer(16 * 1024);
void format_log_line(char* buffer, size_t buffer_capacity,
const char level_char, const char* fmt, va_list args) {
char* buffer_ptr;
buffer_ptr = buffer;
*(buffer_ptr++) = level_char;
*(buffer_ptr++) = '>';
*(buffer_ptr++) = ' ';
buffer_ptr +=
std::snprintf(buffer_ptr, buffer_capacity - (buffer_ptr - buffer), "%.4X",
xe::threading::current_thread_id());
*(buffer_ptr++) = ' ';
// Scribble args into the print buffer.
size_t remaining_capacity = buffer_capacity - (buffer_ptr - buffer) - 3;
size_t chars_written = vsnprintf(buffer_ptr, remaining_capacity, fmt, args);
if (chars_written >= remaining_capacity) {
buffer_ptr += remaining_capacity - 1;
} else {
buffer_ptr += chars_written;
class Logger {
public:
Logger() : ring_buffer_(buffer_, kBufferSize), running_(true) {
flush_event_ = xe::threading::Event::CreateAutoResetEvent(false);
write_thread_ =
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
write_thread_->set_name("xe::FileLogSink Writer");
}
// Add a trailing newline.
if (buffer_ptr[-1] != '\n') {
buffer_ptr[0] = '\n';
buffer_ptr[1] = 0;
~Logger() {
running_ = false;
flush_event_->Set();
xe::threading::Wait(write_thread_.get(), true);
fflush(file_);
fclose(file_);
}
void Initialize(const std::wstring& app_name) {
if (!FLAGS_log_file.empty()) {
auto file_path = xe::to_wstring(FLAGS_log_file.c_str());
xe::filesystem::CreateParentFolder(file_path);
file_ = xe::filesystem::OpenFile(file_path, "wt");
} else {
auto file_path = app_name + L".log";
file_ = xe::filesystem::OpenFile(file_path, "wt");
}
}
void AppendLine(uint32_t thread_id, const char level_char, const char* buffer,
size_t buffer_length) {
LogLine line;
line.thread_id = thread_id;
line.level_char = level_char;
line.buffer_length = buffer_length;
while (true) {
mutex_.lock();
if (ring_buffer_.write_count() < sizeof(line) + buffer_length) {
// Buffer is full. Stall.
mutex_.unlock();
xe::threading::MaybeYield();
continue;
}
ring_buffer_.Write(&line, sizeof(LogLine));
ring_buffer_.Write(buffer, buffer_length);
mutex_.unlock();
break;
}
flush_event_->Set();
}
private:
static const size_t kBufferSize = 32 * 1024 * 1024;
struct LogLine {
uint32_t thread_id;
char level_char;
size_t buffer_length;
};
void WriteThread() {
while (running_) {
mutex_.lock();
bool did_write = false;
while (!ring_buffer_.empty()) {
did_write = true;
LogLine line;
ring_buffer_.Read(&line, sizeof(line));
ring_buffer_.Read(log_format_buffer_.data(), line.buffer_length);
const char prefix[3] = {line.level_char, '>', ' '};
fwrite(prefix, 1, sizeof(prefix), file_);
fwrite(log_format_buffer_.data(), 1, line.buffer_length, file_);
if (log_format_buffer_[line.buffer_length - 1] != '\n') {
const char suffix[1] = {'\n'};
fwrite(suffix, 1, sizeof(suffix), file_);
}
}
mutex_.unlock();
if (did_write) {
if (FLAGS_flush_log) {
fflush(file_);
}
}
xe::threading::Wait(flush_event_.get(), true);
}
}
FILE* file_ = nullptr;
uint8_t buffer_[kBufferSize];
RingBuffer ring_buffer_;
std::mutex mutex_;
std::atomic<bool> running_;
std::unique_ptr<xe::threading::Event> flush_event_;
std::unique_ptr<xe::threading::Thread> write_thread_;
};
Logger logger_;
void InitializeLogging(const std::wstring& app_name) {
logger_.Initialize(app_name);
}
void log_line(const char level_char, const char* fmt, ...) {
// SCOPE_profile_cpu_i("emu", "log_line");
void LogLineFormat(const char level_char, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
format_log_line(log_buffer.data(), log_buffer.capacity(), level_char, fmt,
args);
size_t chars_written = vsnprintf(log_format_buffer_.data(),
log_format_buffer_.capacity(), fmt, args);
va_end(args);
if (!FLAGS_fast_stdout) {
log_lock.lock();
}
#if 0 // defined(OutputDebugString)
OutputDebugStringA(log_buffer.data());
#else
fprintf(stdout, "%s", log_buffer.data());
if (FLAGS_flush_stdout) {
fflush(stdout);
}
#endif // OutputDebugString
if (!FLAGS_fast_stdout) {
log_lock.unlock();
}
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
log_format_buffer_.data(), chars_written);
}
void handle_fatal(const char* fmt, ...) {
void LogLineVarargs(const char level_char, const char* fmt, va_list args) {
size_t chars_written = vsnprintf(log_format_buffer_.data(),
log_format_buffer_.capacity(), fmt, args);
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
log_format_buffer_.data(), chars_written);
}
void LogLine(const char level_char, const std::string& str) {
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
str.c_str(), str.length());
}
void FatalError(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
format_log_line(log_buffer.data(), log_buffer.capacity(), 'X', fmt, args);
LogLineVarargs('X', fmt, args);
va_end(args);
if (!FLAGS_fast_stdout) {
log_lock.lock();
}
#if defined(OutputDebugString)
OutputDebugStringA(log_buffer.data());
#else
fprintf(stderr, "%s", log_buffer.data());
fflush(stderr);
#endif // OutputDebugString
if (!FLAGS_fast_stdout) {
log_lock.unlock();
}
#if XE_PLATFORM_WIN32
if (!xe::has_console_attached()) {
MessageBoxA(NULL, log_buffer.data(), "Xenia Error",
va_start(args, fmt);
vsnprintf(log_format_buffer_.data(), log_format_buffer_.capacity(), fmt,
args);
va_end(args);
MessageBoxA(NULL, log_format_buffer_.data(), "Xenia Error",
MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
}
#endif // WIN32
@@ -117,4 +179,6 @@ void handle_fatal(const char* fmt, ...) {
exit(1);
}
void FatalError(const std::string& str) { FatalError(str.c_str()); }
} // namespace xe

View File

@@ -11,86 +11,47 @@
#define XENIA_BASE_LOGGING_H_
#include <cstdint>
#include <string>
#include "xenia/base/string.h"
namespace xe {
#define XE_OPTION_ENABLE_LOGGING 1
#define XE_OPTION_LOG_ERROR 1
#define XE_OPTION_LOG_WARNING 1
#define XE_OPTION_LOG_INFO 1
#define XE_OPTION_LOG_DEBUG 1
#define XE_OPTION_LOG_CPU 1
#define XE_OPTION_LOG_APU 1
#define XE_OPTION_LOG_GPU 1
#define XE_OPTION_LOG_KERNEL 1
#define XE_OPTION_LOG_FS 1
#define XE_EMPTY_MACRO \
do { \
} while (false)
// Initializes the logging system and any outputs requested.
// Must be called on startup.
void InitializeLogging(const std::wstring& app_name);
void log_line(const char level_char, const char* fmt, ...);
void handle_fatal(const char* fmt, ...);
// Appends a line to the log with printf-style formatting.
void LogLineFormat(const char level_char, const char* fmt, ...);
void LogLineVarargs(const char level_char, const char* fmt, va_list args);
// Appends a line to the log.
void LogLine(const char level_char, const std::string& str);
// Logs a fatal error with printf-style formatting and aborts the program.
void FatalError(const char* fmt, ...);
// Logs a fatal error and aborts the program.
void FatalError(const std::string& str);
#if XE_OPTION_ENABLE_LOGGING
#define XELOGCORE(level, fmt, ...) xe::log_line(level, fmt, ##__VA_ARGS__)
#define XELOGCORE(level, fmt, ...) xe::LogLineFormat(level, fmt, ##__VA_ARGS__)
#else
#define XELOGCORE(level, fmt, ...) XE_EMPTY_MACRO
#define XELOGCORE(level, fmt, ...) \
do { \
} while (false)
#endif // ENABLE_LOGGING
#define XEFATAL(fmt, ...) \
do { \
xe::handle_fatal(fmt, ##__VA_ARGS__); \
} while (false)
#if XE_OPTION_LOG_ERROR
#define XELOGE(fmt, ...) XELOGCORE('!', fmt, ##__VA_ARGS__)
#else
#define XELOGE(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_WARNING
#define XELOGW(fmt, ...) XELOGCORE('w', fmt, ##__VA_ARGS__)
#else
#define XELOGW(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_INFO
#define XELOGI(fmt, ...) XELOGCORE('i', fmt, ##__VA_ARGS__)
#else
#define XELOGI(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_DEBUG
#define XELOGD(fmt, ...) XELOGCORE('d', fmt, ##__VA_ARGS__)
#else
#define XELOGD(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_CPU
#define XELOGCPU(fmt, ...) XELOGCORE('C', fmt, ##__VA_ARGS__)
#else
#define XELOGCPU(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_APU
#define XELOGAPU(fmt, ...) XELOGCORE('A', fmt, ##__VA_ARGS__)
#else
#define XELOGAPU(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_GPU
#define XELOGGPU(fmt, ...) XELOGCORE('G', fmt, ##__VA_ARGS__)
#else
#define XELOGGPU(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_KERNEL
#define XELOGKERNEL(fmt, ...) XELOGCORE('K', fmt, ##__VA_ARGS__)
#else
#define XELOGKERNEL(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION_LOG_FS
#define XELOGFS(fmt, ...) XELOGCORE('F', fmt, ##__VA_ARGS__)
#else
#define XELOGFS(fmt, ...) XE_EMPTY_MACRO
#endif
} // namespace xe

View File

@@ -11,6 +11,7 @@
#include <gflags/gflags.h>
#include "xenia/base/logging.h"
#include "xenia/base/string.h"
namespace xe {
@@ -32,6 +33,9 @@ extern "C" int main(int argc, char** argv) {
args.push_back(xe::to_wstring(argv[n]));
}
// Initialize logging. Needs parsed FLAGS.
xe::InitializeLogging(entry_info.name);
// Call app-provided entry point.
int result = entry_info.entry_point(args);

View File

@@ -13,6 +13,7 @@
#include <gflags/gflags.h>
#include <io.h>
#include "xenia/base/logging.h"
#include "xenia/base/platform_win.h"
#include "xenia/base/string.h"
@@ -84,6 +85,9 @@ int Main() {
// NOTE: this may fail if COM has already been initialized - that's OK.
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
// Initialize logging. Needs parsed FLAGS.
xe::InitializeLogging(entry_info.name);
// Call app-provided entry point.
int result = entry_info.entry_point(args);

View File

@@ -15,7 +15,7 @@
namespace xe {
RingBuffer::RingBuffer(uint8_t* buffer, size_t capacity)
: buffer_(buffer), capacity_(capacity), read_offset_(0), write_offset_(0) {}
: buffer_(buffer), capacity_(capacity) {}
size_t RingBuffer::Read(uint8_t* buffer, size_t count) {
count = std::min(count, capacity_);
@@ -37,7 +37,7 @@ size_t RingBuffer::Read(uint8_t* buffer, size_t count) {
return count;
}
size_t RingBuffer::Write(uint8_t* buffer, size_t count) {
size_t RingBuffer::Write(const uint8_t* buffer, size_t count) {
count = std::min(count, capacity_);
if (!count) {
return 0;

View File

@@ -20,14 +20,13 @@ class RingBuffer {
public:
RingBuffer(uint8_t* buffer, size_t capacity);
size_t Read(uint8_t* buffer, size_t count);
size_t Write(uint8_t* buffer, size_t count);
uint8_t* buffer() const { return buffer_; }
size_t capacity() const { return capacity_; }
bool empty() const { return read_offset_ == write_offset_; }
uint8_t* buffer() { return buffer_; }
size_t capacity() { return capacity_; }
size_t read_offset() { return read_offset_; }
size_t read_count() {
size_t read_offset() const { return read_offset_; }
void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }
size_t read_count() const {
if (read_offset_ == write_offset_) {
return 0;
} else if (read_offset_ < write_offset_) {
@@ -37,8 +36,9 @@ class RingBuffer {
}
}
size_t write_offset() { return write_offset_; }
size_t write_count() {
size_t write_offset() const { return write_offset_; }
void set_write_offset(size_t offset) { write_offset_ = offset % capacity_; }
size_t write_count() const {
if (read_offset_ == write_offset_) {
return capacity_;
} else if (write_offset_ < read_offset_) {
@@ -48,15 +48,23 @@ class RingBuffer {
}
}
void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }
size_t Read(uint8_t* buffer, size_t count);
template <typename T>
size_t Read(T* buffer, size_t count) {
return Read(reinterpret_cast<uint8_t*>(buffer), count);
}
void set_write_offset(size_t offset) { write_offset_ = offset % capacity_; }
size_t Write(const uint8_t* buffer, size_t count);
template <typename T>
size_t Write(const T* buffer, size_t count) {
return Write(reinterpret_cast<const uint8_t*>(buffer), count);
}
private:
uint8_t* buffer_;
size_t capacity_;
size_t read_offset_;
size_t write_offset_;
uint8_t* buffer_ = nullptr;
size_t capacity_ = 0;
size_t read_offset_ = 0;
size_t write_offset_ = 0;
};
} // namespace xe

View File

@@ -155,14 +155,14 @@ std::string fix_path_separators(const std::string& source, char new_sep) {
return dest;
}
std::string find_name_from_path(const std::string& path) {
std::string find_name_from_path(const std::string& path, char sep) {
std::string name(path);
if (!path.empty()) {
std::string::size_type from(std::string::npos);
if (path.back() == '\\') {
if (path.back() == sep) {
from = path.size() - 2;
}
auto pos(path.find_last_of('\\', from));
auto pos(path.find_last_of(sep, from));
if (pos != std::string::npos) {
if (from == std::string::npos) {
name = path.substr(pos + 1);
@@ -175,14 +175,14 @@ std::string find_name_from_path(const std::string& path) {
return name;
}
std::wstring find_name_from_path(const std::wstring& path) {
std::wstring find_name_from_path(const std::wstring& path, wchar_t sep) {
std::wstring name(path);
if (!path.empty()) {
std::wstring::size_type from(std::wstring::npos);
if (path.back() == '\\') {
if (path.back() == sep) {
from = path.size() - 2;
}
auto pos(path.find_last_of('\\', from));
auto pos(path.find_last_of(sep, from));
if (pos != std::wstring::npos) {
if (from == std::wstring::npos) {
name = path.substr(pos + 1);
@@ -195,12 +195,12 @@ std::wstring find_name_from_path(const std::wstring& path) {
return name;
}
std::string find_base_path(const std::string& path) {
auto last_slash = path.find_last_of('\\');
std::string find_base_path(const std::string& path, char sep) {
auto last_slash = path.find_last_of(sep);
if (last_slash == std::string::npos) {
return path;
} else if (last_slash == path.length() - 1) {
auto prev_slash = path.find_last_of('\\', last_slash - 1);
auto prev_slash = path.find_last_of(sep, last_slash - 1);
if (prev_slash == std::string::npos) {
return "";
} else {
@@ -211,12 +211,12 @@ std::string find_base_path(const std::string& path) {
}
}
std::wstring find_base_path(const std::wstring& path) {
auto last_slash = path.find_last_of('\\');
std::wstring find_base_path(const std::wstring& path, wchar_t sep) {
auto last_slash = path.find_last_of(sep);
if (last_slash == std::wstring::npos) {
return path;
} else if (last_slash == path.length() - 1) {
auto prev_slash = path.find_last_of('\\', last_slash - 1);
auto prev_slash = path.find_last_of(sep, last_slash - 1);
if (prev_slash == std::wstring::npos) {
return L"";
} else {

View File

@@ -45,12 +45,16 @@ std::string fix_path_separators(const std::string& source,
char new_sep = xe::kPathSeparator);
// Find the top directory name or filename from a path.
std::string find_name_from_path(const std::string& path);
std::wstring find_name_from_path(const std::wstring& path);
std::string find_name_from_path(const std::string& path,
char sep = xe::kPathSeparator);
std::wstring find_name_from_path(const std::wstring& path,
wchar_t sep = xe::kPathSeparator);
// Get parent path of the given directory or filename.
std::string find_base_path(const std::string& path);
std::wstring find_base_path(const std::wstring& path);
std::string find_base_path(const std::string& path,
char sep = xe::kPathSeparator);
std::wstring find_base_path(const std::wstring& path,
wchar_t sep = xe::kPathSeparator);
} // namespace xe