File record_logger.hpp
File List > illixr > record_logger.hpp
Go to the documentation of this file
#pragma once
#include "phonebook.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef NDEBUG
# include
# include
#endif
namespace ILLIXR {
class record_header {
public:
record_header(const std::string& name, std::vector<std::pair<std::string, const std::type_info&>> columns_)
: id_{std::hash<std::string>{}(name)}
, name_{name}
, columns_{std::move(columns_)} { }
bool operator==(const record_header& other) const {
// Check pointer first
if (this == &other) {
return true;
}
if (name_ != other.name_ || columns_.size() != other.columns_.size() || id_ != other.id_) {
return false;
}
for (std::size_t i = 0; i < columns_.size(); ++i) {
if (columns_[i] != other.columns_[i]) {
return false;
}
}
return true;
}
bool operator!=(const record_header& other) const {
return !(*this == other);
}
[[nodiscard]] std::size_t get_id() const {
return id_;
}
[[nodiscard]] const std::string& get_name() const {
return name_;
}
[[nodiscard]] const std::string& get_column_name(unsigned column) const {
return columns_[column].first;
}
[[nodiscard]] const std::type_info& get_column_type(unsigned column) const {
return columns_[column].second;
}
[[nodiscard]] unsigned get_columns() const {
return columns_.size();
}
[[nodiscard]] std::string to_string() const {
std::string ret = std::string{"record_header "} + name_ + std::string{" { "};
for (const auto& pair : columns_) {
ret += std::string{pair.second.name()} + std::string{" "} + pair.first + std::string{"; "};
}
ret.erase(ret.size() - 2);
ret += std::string{" }"};
return ret;
}
private:
std::size_t id_;
std::string name_;
const std::vector<std::pair<std::string, const std::type_info&>> columns_;
};
class data_use_indicator {
public:
data_use_indicator()
: used_{false} { }
data_use_indicator(const data_use_indicator& other)
: used_{false} {
other.used_ = true;
}
data_use_indicator& operator=(const data_use_indicator& other) {
if (&other != this) {
other.used_ = true;
used_ = false;
}
return *this;
}
/*
copy constructors are just as efficient as move constructors would be,
so I won't define move constructors. C++ will invoke copy instead (for no loss).
*/
bool is_used() const {
return used_;
}
void mark_used() const {
used_ = true;
}
[[maybe_unused]] void mark_unused() const {
used_ = false;
}
private:
mutable bool used_;
};
class record {
public:
record(const record_header& rh, std::vector<std::any> values)
: record_header_{rh}
, values_(std::move(values)) {
#ifndef NDEBUG
assert(record_header_);
if (values_.size() != record_header_->get().get_columns()) {
spdlog::get("illixr")->error("[record_logger] {} elements passed, but rh for {} only specifies {}.", values_.size(),
record_header_->get().get_name(), record_header_->get().get_columns());
abort();
}
for (auto column = 0; column < values_.size(); ++column) {
if (values_[column].type() != record_header_->get().get_column_type(column)) {
spdlog::get("illixr")->error("[record_logger] Caller got wrong type for column {} of {}.", column,
record_header_->get().get_name());
spdlog::get("illixr")->error("[record_logger] Caller passed: {}; record_header specifies: {}",
values_[column].type().name(),
record_header_->get().get_column_type(column).name());
abort();
}
}
#endif
}
record() = default;
~record() {
#ifndef NDEBUG
if (record_header_ && !data_use_indicator_.is_used()) {
spdlog::get("illixr")->error("[record_logger] Record was deleted without being logged.");
abort();
}
#endif
}
template<typename T>
T get_value(unsigned column) const {
#ifndef NDEBUG
assert(record_header_);
data_use_indicator_.mark_used();
if (record_header_->get().get_column_type(column) != typeid(T)) {
std::ostringstream ss;
ss << "Caller column type for " << column << " of " << record_header_->get().get_name() << ". "
<< "Caller passed: " << typeid(T).name() << "; "
<< "record_header specifies: " << record_header_->get().get_column_type(column).name() << ". ";
throw std::runtime_error{ss.str()};
}
#endif
return std::any_cast<T>(values_[column]);
}
const record_header& get_record_header() const {
assert(record_header_);
return record_header_->get();
}
[[maybe_unused]] void mark_used() const {
#ifndef NDEBUG
assert(record_header_);
data_use_indicator_.mark_used();
#endif
}
private:
// Holding a pointer to a record_header is more efficient than
// requiring each record to hold a list of its column names
// and table name_. This is just one pointer.
std::optional<std::reference_wrapper<const record_header>> record_header_;
std::vector<std::any> values_;
#ifndef NDEBUG
data_use_indicator data_use_indicator_;
#endif
};
class record_logger : public phonebook::service {
public:
~record_logger() override = default;
virtual void log(const record& r) = 0;
virtual void log(const std::vector<record>& rs) {
for (const record& r : rs) {
log(r);
}
}
};
static std::chrono::milliseconds LOG_BUFFER_DELAY{1000};
class record_coalescer {
public:
explicit record_coalescer(std::shared_ptr<record_logger> logger_)
: logger_{std::move(logger_)}
, last_log_{std::chrono::high_resolution_clock::now()} { }
~record_coalescer() {
flush();
}
void log(const record& r) {
if (logger_) {
buffer_.push_back(r);
// Log coalescer should only be used with
// In the common case, they will be the same pointer, quickly check the pointers.
// In the less common case, we check for object-structural equality.
#ifndef NDEBUG
if (&r.get_record_header() != &buffer_[0].get_record_header() &&
r.get_record_header() == buffer_[0].get_record_header()) {
spdlog::get("illixr")->error("[record_logger] Tried to push a record of type {} to a record logger for type {}",
r.get_record_header().to_string(), buffer_[0].get_record_header().to_string());
abort();
}
#endif
maybe_flush();
}
}
void maybe_flush() {
if (std::chrono::high_resolution_clock::now() > last_log_ + LOG_BUFFER_DELAY) {
flush();
}
}
void flush() {
if (logger_) {
std::vector<record> buffer2;
buffer_.swap(buffer2);
logger_->log(buffer2);
last_log_ = std::chrono::high_resolution_clock::now();
}
}
explicit operator bool() const {
return bool(logger_);
}
private:
std::shared_ptr<record_logger> logger_;
std::chrono::time_point<std::chrono::high_resolution_clock> last_log_;
std::vector<record> buffer_;
};
} // namespace ILLIXR