added networking components, hosts are next
This commit is contained in:
56
src/core/CMakeLists.txt
Normal file
56
src/core/CMakeLists.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
add_library(dofs_core STATIC)
|
||||
|
||||
# Propagate include dir via dofs_config to anyone linking dofs_core
|
||||
target_link_libraries(dofs_core PUBLIC dofs_config)
|
||||
|
||||
target_include_directories(dofs_core
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
if(TARGET yaml-cpp)
|
||||
target_link_libraries(dofs_core PUBLIC yaml-cpp)
|
||||
endif()
|
||||
|
||||
if(DOFS_GLOB_SOURCES)
|
||||
file(GLOB CORE_CC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
|
||||
target_sources(dofs_core PRIVATE ${CORE_CC})
|
||||
else()
|
||||
# Prefer explicit lists for reproducibility
|
||||
target_sources(dofs_core
|
||||
PUBLIC
|
||||
time.h
|
||||
timer.h
|
||||
types.h
|
||||
error.h
|
||||
rng.h
|
||||
logger.h
|
||||
simulator.h
|
||||
node.h
|
||||
host.h
|
||||
PRIVATE
|
||||
error.cc
|
||||
logger.cc
|
||||
simulator.cc
|
||||
node.cc
|
||||
host.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(dofs::core ALIAS dofs_core)
|
||||
|
||||
# --- Tooling-only unity TU for headers (core) ---
|
||||
# Generate per-header stubs with the SAME basename (time.h -> time.cpp)
|
||||
file(GLOB CORE_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
|
||||
set(CORE_STUBS)
|
||||
foreach(h ${CORE_HEADERS})
|
||||
get_filename_component(_base "${h}" NAME_WE)
|
||||
set(_stub "${CMAKE_CURRENT_BINARY_DIR}/${_base}.cpp")
|
||||
file(WRITE "${_stub}" "// tooling stub for ${_base}.h\n#include \"core/${_base}.h\"\n")
|
||||
list(APPEND CORE_STUBS "${_stub}")
|
||||
endforeach()
|
||||
|
||||
add_library(dofs_core_headers_tooling STATIC ${CORE_STUBS})
|
||||
target_link_libraries(dofs_core_headers_tooling PRIVATE dofs_core)
|
||||
# This target exists purely for compile_commands; no need to link it elsewhere.
|
||||
34
src/core/error.cc
Normal file
34
src/core/error.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "error.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
static std::mutex g_error_mutex;
|
||||
|
||||
std::mutex &error_mutex() noexcept {
|
||||
return g_error_mutex;
|
||||
}
|
||||
|
||||
static inline void emit_error_line(std::string_view type,
|
||||
std::optional<uint64_t> ts,
|
||||
std::string_view message) noexcept {
|
||||
std::lock_guard<std::mutex> lock(g_error_mutex);
|
||||
std::cerr << '[' << type << ']';
|
||||
|
||||
if (ts.has_value()) {
|
||||
std::cerr << '[' << *ts << ']';
|
||||
}
|
||||
|
||||
std::cerr << ": " << message << '\n';
|
||||
}
|
||||
|
||||
void log_error(std::string_view type,
|
||||
std::string_view message,
|
||||
std::optional<uint64_t> timestamp) noexcept {
|
||||
emit_error_line(type, timestamp, message);
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
54
src/core/error.h
Normal file
54
src/core/error.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef CORE_ERROR_H
|
||||
#define CORE_ERROR_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <format>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
void log_error(std::string_view type,
|
||||
std::string_view message,
|
||||
std::optional<uint64_t> timestamp = std::nullopt) noexcept;
|
||||
|
||||
std::mutex &error_mutex() noexcept;
|
||||
|
||||
template <class T>
|
||||
inline T&& show(std::string_view name, T&& value) noexcept {
|
||||
std::lock_guard<std::mutex> lock(error_mutex());
|
||||
std::cerr << name << '=' << value << '\n';
|
||||
return std::forward<T>(value);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline T&& eval_and_show(std::string_view expr, T&& value) noexcept {
|
||||
std::lock_guard<std::mutex> lock(error_mutex());
|
||||
std::cerr << expr << '=' << value << '\n';
|
||||
return std::forward<T>(value);
|
||||
}
|
||||
|
||||
#define DOFS_ERROR(TYPE, MSG) \
|
||||
::dofs::log_error((TYPE), (MSG))
|
||||
|
||||
#define DOFS_ERROR_T(TYPE, MSG, TS) \
|
||||
::dofs::log_error((TYPE), (MSG), (TS))
|
||||
|
||||
#define DOFS_ERROR_ST(TYPE, SRC, MSG, TS) \
|
||||
::dofs::log_error((TYPE), (SRC), (MSG), (TS))
|
||||
|
||||
#define DOFS_SHOW(VAR) \
|
||||
(::dofs::show(#VAR, (VAR)))
|
||||
|
||||
#define DOFS_EVAL(EXPR) \
|
||||
(::dofs::eval_and_show(#EXPR, (EXPR)))
|
||||
|
||||
#define FORMAT(VAR) \
|
||||
(std::format("{}={}", #VAR, VAR))
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_ERROR_H
|
||||
29
src/core/host.cc
Normal file
29
src/core/host.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "core/host.h"
|
||||
#include "core/error.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
Host::Host(Simulator *const sim, NodeId id) noexcept
|
||||
: Node(sim, id, NodeType::HOST), _nic(nullptr) {
|
||||
// Hosts start in OK unless caller overrides later.
|
||||
Node::set_status(NodeStatus::OK);
|
||||
}
|
||||
|
||||
void Host::attach_nic(NetworkNic* nic) noexcept {
|
||||
if (_nic && _nic != nic) {
|
||||
log_error("ERROR", "Host::attach_nic called while NIC already attached");
|
||||
}
|
||||
|
||||
_nic = nic;
|
||||
}
|
||||
|
||||
void Host::detach_nic(NetworkNic* nic) noexcept {
|
||||
if (_nic && _nic != nic) {
|
||||
log_error("ERROR", "Host::detach_nic called with non-matching NIC pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
_nic = nullptr;
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
45
src/core/host.h
Normal file
45
src/core/host.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef CORE_HOST_H
|
||||
#define CORE_HOST_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/node.h"
|
||||
#include "core/types.h"
|
||||
#include "network/packet.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class NetworkNic;
|
||||
|
||||
class Host : public Node {
|
||||
public:
|
||||
Host(Simulator *const sim, NodeId id) noexcept;
|
||||
virtual ~Host() = default;
|
||||
|
||||
// Access to the attached NIC (may be null if detached)
|
||||
NetworkNic *nic() const noexcept {
|
||||
return _nic;
|
||||
}
|
||||
|
||||
// NIC lifecycle hooks (called by the NIC)
|
||||
void attach_nic(NetworkNic* nic) noexcept;
|
||||
void detach_nic(NetworkNic* nic) noexcept;
|
||||
|
||||
// Data-plane completion: a whole flow has arrived.
|
||||
virtual void recv_flow(NodeId src,
|
||||
FlowPriority priority,
|
||||
Bytes flow_size) = 0;
|
||||
|
||||
// Control/telemetry interrupt: ACK/NACK/TRIM_BACK, etc.
|
||||
virtual void recv_frame(const Packet& frame) = 0;
|
||||
|
||||
Host(const Host &) = delete;
|
||||
Host &operator=(const Host &) = delete;
|
||||
|
||||
private:
|
||||
NetworkNic *_nic{nullptr};
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_HOST_H
|
||||
67
src/core/logger.cc
Normal file
67
src/core/logger.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "logger.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
Logger::Logger(std::string_view path, bool append) noexcept
|
||||
: _path(path) {
|
||||
const std::ios::openmode mode = append ? std::ios::app : std::ios::trunc;
|
||||
(void)open(mode);
|
||||
}
|
||||
|
||||
bool Logger::open(std::ios::openmode mode) noexcept {
|
||||
_stream.exceptions(std::ios::goodbit);
|
||||
_stream.open(_path, std::ios::out | mode);
|
||||
_is_open = _stream.is_open();
|
||||
|
||||
if (!_is_open) {
|
||||
DOFS_ERROR("Logger", ("failed to open file: " + _path).c_str());
|
||||
}
|
||||
|
||||
return _is_open;
|
||||
}
|
||||
|
||||
Logger::~Logger() noexcept {
|
||||
close();
|
||||
}
|
||||
|
||||
void Logger::flush() noexcept {
|
||||
std::lock_guard<std::mutex> _lk(_mutex);
|
||||
|
||||
if (_is_open) {
|
||||
_stream.flush();
|
||||
|
||||
if (!_stream) {
|
||||
DOFS_ERROR("Logger", ("flush failed for: " + _path).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::close() noexcept {
|
||||
std::lock_guard<std::mutex> _lk(_mutex);
|
||||
|
||||
if (_is_open) {
|
||||
_stream.flush();
|
||||
_stream.close();
|
||||
_is_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::write_line(std::string_view line) noexcept {
|
||||
std::lock_guard<std::mutex> _lk(_mutex);
|
||||
|
||||
if (!_is_open) {
|
||||
DOFS_ERROR("Logger", ("write on closed file: " + _path).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
_stream << line << '\n';
|
||||
|
||||
if (!_stream) {
|
||||
DOFS_ERROR("Logger", ("write failed for: " + _path).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
53
src/core/logger.h
Normal file
53
src/core/logger.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef CORE_LOGGER_H
|
||||
#define CORE_LOGGER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "core/error.h"
|
||||
#include "core/types.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Logger final {
|
||||
public:
|
||||
Logger(std::string_view path, bool append) noexcept;
|
||||
~Logger() noexcept;
|
||||
|
||||
Logger(const Logger &) = delete;
|
||||
Logger &operator=(const Logger &) = delete;
|
||||
Logger(Logger &&) = delete;
|
||||
Logger &operator=(Logger &&) = delete;
|
||||
|
||||
bool is_open() const noexcept {
|
||||
return _is_open;
|
||||
}
|
||||
|
||||
void write_line(std::string_view line) noexcept;
|
||||
|
||||
void flush() noexcept;
|
||||
void close() noexcept;
|
||||
|
||||
std::string_view path() const noexcept {
|
||||
return _path;
|
||||
}
|
||||
|
||||
private:
|
||||
bool open(std::ios::openmode mode) noexcept;
|
||||
|
||||
std::string _path;
|
||||
std::ofstream _stream;
|
||||
bool _is_open{false};
|
||||
std::mutex _mutex;
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_LOGGER_H
|
||||
67
src/core/node.cc
Normal file
67
src/core/node.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "core/node.h"
|
||||
|
||||
#include "core/simulator.h"
|
||||
#include "core/error.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
Node::Node(Simulator *const sim, NodeId id, NodeType type) noexcept
|
||||
: _sim(sim), _id(id), _status(NodeStatus::OK), _type(type) {}
|
||||
|
||||
// Accessors
|
||||
NodeId Node::id() const noexcept {
|
||||
return _id;
|
||||
}
|
||||
NodeStatus Node::status() const noexcept {
|
||||
return _status;
|
||||
}
|
||||
NodeType Node::type() const noexcept {
|
||||
return _type;
|
||||
}
|
||||
|
||||
void Node::set_status(NodeStatus s) noexcept {
|
||||
_status = s;
|
||||
}
|
||||
|
||||
static inline bool schedule_status_ok_after(Simulator *const sim, Time delay_ns,
|
||||
Node* self) {
|
||||
if (!sim) {
|
||||
log_error("ERROR", "Node: simulator instance not found for boot/reboot");
|
||||
return false;
|
||||
}
|
||||
|
||||
sim->schedule_after(delay_ns, [self]() {
|
||||
self->set_status(NodeStatus::OK);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void Node::boot(Time boottime_ns) {
|
||||
if (_status != NodeStatus::DOWN) {
|
||||
log_error("ERROR", "boot() ignored: node not in DOWN state");
|
||||
return;
|
||||
}
|
||||
|
||||
_status = NodeStatus::BOOTING;
|
||||
|
||||
if (!schedule_status_ok_after(_sim, boottime_ns, this)) {
|
||||
// Intentionally left BOOTING; caller can handle recovery/logs.
|
||||
}
|
||||
}
|
||||
|
||||
void Node::reboot(Time boottime_ns) {
|
||||
if (_status != NodeStatus::OK && _status != NodeStatus::THROTTLING) {
|
||||
log_error("ERROR", "reboot() ignored: node not in OK/THROTTLING state");
|
||||
return;
|
||||
}
|
||||
|
||||
_status = NodeStatus::BOOTING;
|
||||
|
||||
if (!schedule_status_ok_after(_sim, boottime_ns, this)) {
|
||||
// Intentionally left BOOTING; caller can handle recovery/logs.
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
39
src/core/node.h
Normal file
39
src/core/node.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef CORE_NODE_H
|
||||
#define CORE_NODE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/simulator.h"
|
||||
#include "core/time.h"
|
||||
#include "core/types.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Node(Simulator *const sim, NodeId id, NodeType type) noexcept;
|
||||
virtual ~Node() = default;
|
||||
|
||||
NodeId id() const noexcept;
|
||||
NodeStatus status() const noexcept;
|
||||
NodeType type() const noexcept;
|
||||
|
||||
void set_status(NodeStatus s) noexcept;
|
||||
|
||||
void boot(Time boottime_ns);
|
||||
void reboot(Time boottime_ns);
|
||||
|
||||
Node(const Node &) = delete;
|
||||
Node &operator=(const Node &) = delete;
|
||||
|
||||
protected:
|
||||
Simulator *const _sim;
|
||||
|
||||
NodeId _id;
|
||||
NodeStatus _status;
|
||||
NodeType _type;
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_NODE_H
|
||||
131
src/core/rng.h
Normal file
131
src/core/rng.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#ifndef CORE_RNG_H
|
||||
#define CORE_RNG_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Rng final {
|
||||
public:
|
||||
using engine_type = std::mt19937_64;
|
||||
using seed_type = engine_type::result_type;
|
||||
|
||||
explicit Rng(seed_type seed = default_seed()) noexcept
|
||||
: _eng(seed) {}
|
||||
|
||||
void seed(seed_type s) noexcept {
|
||||
_eng.seed(s);
|
||||
}
|
||||
|
||||
// Real in [0, 1)
|
||||
double uniform01() {
|
||||
return std::uniform_real_distribution<double> {}(_eng);
|
||||
}
|
||||
|
||||
// Integer in [lo, hi) — lo inclusive, hi exclusive
|
||||
template <typename Int,
|
||||
typename = std::enable_if_t<std::is_integral<Int>::value>>
|
||||
Int uniform_range(Int lo_inclusive, Int hi_exclusive) {
|
||||
return std::uniform_int_distribution<Int>(lo_inclusive, hi_exclusive - 1)(_eng);
|
||||
}
|
||||
|
||||
// Integer in [0, hi) — convenience overload
|
||||
template <typename Int,
|
||||
typename = std::enable_if_t<std::is_integral<Int>::value>>
|
||||
Int uniform_range(Int hi_exclusive) {
|
||||
return uniform_range<Int>(0, hi_exclusive);
|
||||
}
|
||||
|
||||
double uniform_range(double lo_inclusive, double hi_exclusive) {
|
||||
|
||||
return lo_inclusive + (hi_exclusive - lo_inclusive) * uniform01();
|
||||
}
|
||||
|
||||
std::uint64_t poisson(double lambda) {
|
||||
return static_cast<std::uint64_t>(std::poisson_distribution<std::uint64_t>
|
||||
(lambda)(_eng));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T choose_weighted(const std::vector<std::pair<double, T>>& items) {
|
||||
return choose_weighted_impl(items.begin(), items.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T choose_weighted(std::initializer_list<std::pair<double, T>> items) {
|
||||
return choose_weighted_impl(items.begin(), items.end());
|
||||
}
|
||||
|
||||
private:
|
||||
engine_type _eng;
|
||||
|
||||
static constexpr seed_type default_seed() noexcept {
|
||||
return 0xC0FFEEULL ^ 0x9E3779B97F4A7C15ULL;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
auto choose_weighted_impl(Iter first,
|
||||
Iter last) -> typename std::iterator_traits<Iter>::value_type::second_type {
|
||||
using Pair = typename std::iterator_traits<Iter>::value_type;
|
||||
using T = typename Pair::second_type;
|
||||
|
||||
double total = 0.0;
|
||||
|
||||
for (auto it = first; it != last; ++it) {
|
||||
const double w = it->first;
|
||||
|
||||
if (w > 0.0 && std::isfinite(w))
|
||||
total += w;
|
||||
}
|
||||
|
||||
if (!(total > 0.0)) {
|
||||
std::size_t n = 0;
|
||||
|
||||
for (auto it = first; it != last; ++it)
|
||||
++n;
|
||||
|
||||
if (n == 0)
|
||||
return T{};
|
||||
|
||||
std::size_t idx = static_cast<std::size_t>(uniform_range<std::uint64_t>(0,
|
||||
static_cast<std::uint64_t>(n)));
|
||||
|
||||
auto it = first;
|
||||
|
||||
while (idx--)
|
||||
++it;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
double u = uniform01() * total;
|
||||
|
||||
for (auto it = first; it != last; ++it) {
|
||||
const double w = (it->first > 0.0 &&
|
||||
std::isfinite(it->first)) ? it->first : 0.0;
|
||||
|
||||
if (u < w)
|
||||
return it->second;
|
||||
|
||||
u -= w;
|
||||
}
|
||||
|
||||
auto it = last;
|
||||
|
||||
do {
|
||||
--it;
|
||||
} while (it->first <= 0.0 || !std::isfinite(it->first));
|
||||
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_RNG_H
|
||||
157
src/core/simulator.cc
Normal file
157
src/core/simulator.cc
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "core/simulator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/rng.h"
|
||||
#include "network/link.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
bool Simulator::Cmp::operator()(const Item& a, const Item& b) const noexcept {
|
||||
if (a.when != b.when)
|
||||
return a.when > b.when;
|
||||
|
||||
return a.id > b.id;
|
||||
}
|
||||
|
||||
Time Simulator::now() const noexcept {
|
||||
return _now;
|
||||
}
|
||||
|
||||
bool Simulator::cancel(EventId id) {
|
||||
return _cancelled.insert(id).second;
|
||||
}
|
||||
|
||||
bool Simulator::run_next() {
|
||||
while (!_event_pq.empty()) {
|
||||
Item it = _event_pq.top();
|
||||
_event_pq.pop();
|
||||
|
||||
if (_cancelled.erase(it.id) > 0)
|
||||
continue;
|
||||
|
||||
_now = it.when;
|
||||
it.fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Simulator::run_until(Time end_time) {
|
||||
while (!_event_pq.empty()) {
|
||||
const Item& top = _event_pq.top();
|
||||
|
||||
if (end_time < top.when)
|
||||
break;
|
||||
|
||||
run_next();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Simulator>>& Simulator::registry_() {
|
||||
static std::vector<std::unique_ptr<Simulator>> reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
std::pair<InstanceId, Simulator *> Simulator::create_simulator(InstanceId id) {
|
||||
if (id == INVALID_INSTANCE_ID) {
|
||||
return {INVALID_INSTANCE_ID, nullptr};
|
||||
}
|
||||
|
||||
auto& reg = registry_();
|
||||
|
||||
if (static_cast<size_t>(id) >= reg.size()) {
|
||||
reg.resize(static_cast<size_t>(id) + 1);
|
||||
}
|
||||
|
||||
if (!reg[static_cast<size_t>(id)]) {
|
||||
auto sim = std::make_unique<Simulator>();
|
||||
Simulator* ptr = sim.get();
|
||||
reg[static_cast<size_t>(id)] = std::move(sim);
|
||||
return {id, ptr};
|
||||
}
|
||||
|
||||
return {id, reg[static_cast<size_t>(id)].get()};
|
||||
}
|
||||
|
||||
Simulator * Simulator::get_simulator(InstanceId id) noexcept {
|
||||
if (id == INVALID_INSTANCE_ID)
|
||||
return nullptr;
|
||||
|
||||
auto& reg = registry_();
|
||||
const size_t idx = static_cast<size_t>(id);
|
||||
|
||||
if (idx >= reg.size())
|
||||
return nullptr;
|
||||
|
||||
return reg[idx].get();
|
||||
}
|
||||
|
||||
Rng * Simulator::create_rng(std::uint64_t seed) {
|
||||
if (_rng)
|
||||
return nullptr;
|
||||
|
||||
_rng = std::make_unique<Rng>(seed);
|
||||
return _rng.get();
|
||||
}
|
||||
Rng * Simulator::get_rng() noexcept {
|
||||
return _rng.get();
|
||||
}
|
||||
|
||||
Rng const * Simulator::get_rng() const noexcept {
|
||||
return _rng.get();
|
||||
}
|
||||
|
||||
std::pair<LinkId, Link *> Simulator::create_link(
|
||||
NetworkNode* a, PortId a_port,
|
||||
NetworkNode* b, PortId b_port,
|
||||
Time latency,
|
||||
double bandwidth_gbps) {
|
||||
|
||||
if (!a || !b)
|
||||
return {INVALID_LINK_ID, nullptr};
|
||||
|
||||
if (bandwidth_gbps <= 0.0)
|
||||
return {INVALID_LINK_ID, nullptr};
|
||||
|
||||
if (latency < Time{0})
|
||||
return {INVALID_LINK_ID, nullptr};
|
||||
|
||||
const LinkId id = static_cast<LinkId>(_links.size());
|
||||
auto up =
|
||||
std::unique_ptr<Link>(new Link(this, id,
|
||||
a, a_port, b, b_port,
|
||||
latency,
|
||||
bandwidth_gbps));
|
||||
|
||||
Link* raw = up.get();
|
||||
_links.push_back(std::move(up));
|
||||
return {id, raw};
|
||||
}
|
||||
|
||||
Link * Simulator::get_link(LinkId id) noexcept {
|
||||
if (id == INVALID_LINK_ID)
|
||||
return nullptr;
|
||||
|
||||
const size_t idx = static_cast<size_t>(id);
|
||||
|
||||
if (idx >= _links.size())
|
||||
return nullptr;
|
||||
|
||||
return _links[idx].get();
|
||||
}
|
||||
|
||||
Link const * Simulator::get_link(LinkId id) const noexcept {
|
||||
if (id == INVALID_LINK_ID)
|
||||
return nullptr;
|
||||
|
||||
const size_t idx = static_cast<size_t>(id);
|
||||
|
||||
if (idx >= _links.size())
|
||||
return nullptr;
|
||||
|
||||
return _links[idx].get();
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
120
src/core/simulator.h
Normal file
120
src/core/simulator.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef CORE_SIMULATOR_H
|
||||
#define CORE_SIMULATOR_H
|
||||
|
||||
// TODO: implement concrete node methods for different nodes, store them all uniformly in vector<u_ptr<Node>> and return a get function
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "core/time.h"
|
||||
#include "core/types.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Rng;
|
||||
class Logger;
|
||||
|
||||
class Node;
|
||||
class NetworkNode;
|
||||
class Link;
|
||||
|
||||
constexpr EventId NULL_EVENT = 0;
|
||||
|
||||
constexpr InstanceId INVALID_INSTANCE_ID =
|
||||
std::numeric_limits<InstanceId>::max();
|
||||
constexpr LinkId INVALID_LINK_ID =
|
||||
std::numeric_limits<LinkId>::max();
|
||||
|
||||
class Simulator final {
|
||||
private:
|
||||
struct Item {
|
||||
Time when;
|
||||
EventId id;
|
||||
std::function<void()> fn;
|
||||
};
|
||||
|
||||
struct Cmp {
|
||||
bool operator()(const Item& a, const Item& b) const noexcept;
|
||||
};
|
||||
|
||||
std::priority_queue<Item, std::vector<Item>, Cmp> _event_pq;
|
||||
std::unordered_set<EventId> _cancelled;
|
||||
Time _now{};
|
||||
EventId _next_id{1};
|
||||
|
||||
std::unique_ptr<Rng> _rng;
|
||||
|
||||
std::vector<std::unique_ptr<Node>> _nodes;
|
||||
std::vector<std::unique_ptr<Link>> _links;
|
||||
|
||||
public:
|
||||
Simulator() = default;
|
||||
|
||||
static std::pair<InstanceId, Simulator *> create_simulator(InstanceId id);
|
||||
static Simulator *get_simulator(InstanceId id) noexcept;
|
||||
|
||||
Time now() const noexcept;
|
||||
|
||||
template <class F, class... Args>
|
||||
EventId schedule_at(Time abs_time, F&& f, Args&&... args) {
|
||||
if (abs_time < _now)
|
||||
return NULL_EVENT;
|
||||
|
||||
const EventId eid = _next_id++;
|
||||
Item it{
|
||||
abs_time,
|
||||
eid,
|
||||
make_callable(std::forward<F>(f), std::forward<Args>(args)...)
|
||||
};
|
||||
_event_pq.push(std::move(it));
|
||||
return eid;
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
EventId schedule_after(Time delay, F&& f, Args&&... args) {
|
||||
return schedule_at(_now + delay, std::forward<F>(f),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
bool cancel(EventId id);
|
||||
bool run_next();
|
||||
void run_until(Time end_time);
|
||||
|
||||
// ---------- Object management ----------
|
||||
Rng* create_rng(std::uint64_t seed);
|
||||
Rng* get_rng() noexcept;
|
||||
Rng const* get_rng() const noexcept;
|
||||
|
||||
std::pair<LinkId, Link *> create_link(NetworkNode* a, PortId a_port,
|
||||
NetworkNode* b, PortId b_port,
|
||||
Time latency,
|
||||
double bandwidth_gbps);
|
||||
|
||||
Link* get_link(LinkId id) noexcept;
|
||||
Link const* get_link(LinkId id) const noexcept;
|
||||
|
||||
private:
|
||||
template <class F, class... Args>
|
||||
static auto make_callable(F&& f, Args&&... args) {
|
||||
using Fn = std::decay_t<F>;
|
||||
using Tup = std::tuple<std::decay_t<Args>...>;
|
||||
return [fn = Fn(std::forward<F>(f)),
|
||||
tup = Tup(std::forward<Args>(args)...)]() mutable {
|
||||
std::apply(fn, std::move(tup));
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::unique_ptr<Simulator>>& registry_();
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_SIMULATOR_H
|
||||
129
src/core/time.h
Normal file
129
src/core/time.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef CORE_TIME_H
|
||||
#define CORE_TIME_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Time final {
|
||||
public:
|
||||
using rep = uint64_t;
|
||||
|
||||
// ----- Constructors -----
|
||||
constexpr Time() : _nsec(0) {}
|
||||
explicit constexpr Time(rep ns) : _nsec(ns) {}
|
||||
|
||||
// ----- Factories -----
|
||||
static constexpr Time from_ns(rep ns) noexcept {
|
||||
return Time(ns);
|
||||
}
|
||||
static constexpr Time from_us(rep us) noexcept {
|
||||
return Time(us * 1000ULL);
|
||||
}
|
||||
static constexpr Time from_ms(rep ms) noexcept {
|
||||
return Time(ms * 1000ULL * 1000ULL);
|
||||
}
|
||||
static constexpr Time from_s (rep s ) noexcept {
|
||||
return Time(s * 1000ULL * 1000ULL * 1000ULL);
|
||||
}
|
||||
|
||||
// ----- Accessors -----
|
||||
constexpr rep ns() const noexcept {
|
||||
return _nsec;
|
||||
}
|
||||
constexpr rep count() const noexcept {
|
||||
return _nsec;
|
||||
}
|
||||
|
||||
// ----- Converters (plain uint64_t) -----
|
||||
static constexpr rep us_to_ns(rep us) noexcept {
|
||||
return us * 1000ULL;
|
||||
}
|
||||
static constexpr rep ms_to_ns(rep ms) noexcept {
|
||||
return ms * 1000ULL * 1000ULL;
|
||||
}
|
||||
|
||||
// ----- Comparisons -----
|
||||
friend constexpr bool operator==(Time a, Time b) noexcept {
|
||||
return a._nsec == b._nsec;
|
||||
}
|
||||
|
||||
friend constexpr bool operator!=(Time a, Time b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
friend constexpr bool operator< (Time a, Time b) noexcept {
|
||||
return a._nsec < b._nsec;
|
||||
}
|
||||
|
||||
friend constexpr bool operator> (Time a, Time b) noexcept {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
friend constexpr bool operator<=(Time a, Time b) noexcept {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
friend constexpr bool operator>=(Time a, Time b) noexcept {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
// ----- Arithmetic -----
|
||||
constexpr Time &operator+=(Time rhs) noexcept {
|
||||
_nsec += rhs._nsec;
|
||||
return *this;
|
||||
}
|
||||
constexpr Time &operator-=(Time rhs) noexcept {
|
||||
if (_nsec < rhs._nsec)
|
||||
_nsec = 0;
|
||||
else
|
||||
_nsec -= rhs._nsec;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend constexpr Time operator+(Time a, Time b) noexcept {
|
||||
return Time(a._nsec + b._nsec);
|
||||
}
|
||||
|
||||
friend constexpr Time operator*(Time a, Time b) noexcept {
|
||||
return Time(a._nsec * b._nsec);
|
||||
}
|
||||
|
||||
friend constexpr std::optional<Time> operator-(Time a, Time b) noexcept {
|
||||
return safe_sub(a, b);
|
||||
}
|
||||
|
||||
friend constexpr std::optional<Time> safe_sub(Time a, Time b) noexcept {
|
||||
if (a._nsec < b._nsec) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Time(a._nsec - b._nsec);
|
||||
}
|
||||
|
||||
constexpr Time unsafe_sub(Time t) const noexcept {
|
||||
return Time(this->_nsec - t._nsec);
|
||||
}
|
||||
|
||||
private:
|
||||
rep _nsec;
|
||||
};
|
||||
|
||||
constexpr Time operator""_ns(unsigned long long v) noexcept {
|
||||
return Time::from_ns(static_cast<Time::rep>(v));
|
||||
}
|
||||
constexpr Time operator""_us(unsigned long long v) noexcept {
|
||||
return Time::from_us(static_cast<Time::rep>(v));
|
||||
}
|
||||
constexpr Time operator""_ms(unsigned long long v) noexcept {
|
||||
return Time::from_ms(static_cast<Time::rep>(v));
|
||||
}
|
||||
constexpr Time operator""_s (unsigned long long v) noexcept {
|
||||
return Time::from_s (static_cast<Time::rep>(v));
|
||||
}
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_TIME_H
|
||||
56
src/core/timer.h
Normal file
56
src/core/timer.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef CORE_TIMER_H
|
||||
#define CORE_TIMER_H
|
||||
|
||||
#include <chrono>
|
||||
#include "core/time.h"
|
||||
|
||||
namespace dofs {
|
||||
|
||||
class Timer final {
|
||||
public:
|
||||
Timer() {
|
||||
init();
|
||||
}
|
||||
|
||||
void init() noexcept {
|
||||
_start = clock::now();
|
||||
}
|
||||
|
||||
Time start() const noexcept {
|
||||
using namespace std;
|
||||
|
||||
auto tp = _start.time_since_epoch();
|
||||
return Time::from_ns(
|
||||
static_cast<Time::rep>(
|
||||
chrono::duration_cast<chrono::nanoseconds>(tp).count()
|
||||
));
|
||||
}
|
||||
|
||||
Time now() const noexcept {
|
||||
using namespace std;
|
||||
|
||||
auto tp = clock::now().time_since_epoch();
|
||||
return Time::from_ns(
|
||||
static_cast<Time::rep>(
|
||||
chrono::duration_cast<chrono::nanoseconds>(tp).count()
|
||||
));
|
||||
}
|
||||
|
||||
Time elapsed() const noexcept {
|
||||
using namespace std;
|
||||
|
||||
auto dur = clock::now() - _start;
|
||||
return Time::from_ns(
|
||||
static_cast<Time::rep>(
|
||||
chrono::duration_cast<chrono::nanoseconds>(dur).count()
|
||||
));
|
||||
}
|
||||
|
||||
private:
|
||||
using clock = std::chrono::steady_clock;
|
||||
clock::time_point _start;
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_TIMER_H
|
||||
70
src/core/types.h
Normal file
70
src/core/types.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef CORE_TYPES_H
|
||||
#define CORE_TYPES_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace dofs {
|
||||
|
||||
using uint128_t = __uint128_t;
|
||||
using PacketGroups = uint128_t;
|
||||
|
||||
using EventId = uint64_t;
|
||||
using InstanceId = uint8_t;
|
||||
|
||||
using NodeId = uint16_t;
|
||||
enum class NodeStatus : uint8_t {
|
||||
OK,
|
||||
THROTTLING,
|
||||
DOWN,
|
||||
BOOTING
|
||||
};
|
||||
|
||||
enum class NodeType : uint8_t {
|
||||
HOST,
|
||||
SWITCH,
|
||||
NIC
|
||||
};
|
||||
|
||||
using PortId = uint16_t;
|
||||
|
||||
using IPv4Addr = uint32_t;
|
||||
inline IPv4Addr ipv4(NodeId n, PortId p) noexcept {
|
||||
return (static_cast<uint32_t>(n) << 16) | static_cast<uint32_t>(p);
|
||||
}
|
||||
|
||||
using LinkId = uint32_t;
|
||||
enum class LinkStatus : uint8_t { OK, DOWN, THROTTLING };
|
||||
|
||||
using Bytes = uint32_t;
|
||||
using PacketSeq = uint16_t;
|
||||
using FlowId = uint16_t;
|
||||
|
||||
enum class PacketProtocol : uint8_t {
|
||||
DATA,
|
||||
ACK,
|
||||
NACK,
|
||||
HEADER_TRIM,
|
||||
HEADER_TRIM_BACK
|
||||
};
|
||||
|
||||
enum class SwitchBufferType : uint8_t { SHARED = 0, DEDICATED = 1 };
|
||||
|
||||
enum class FlowPriority : uint8_t {
|
||||
CTRL = 0,
|
||||
MICE = 1,
|
||||
ELEPHANT = 2,
|
||||
AUTO
|
||||
};
|
||||
|
||||
enum class CCType : uint8_t {
|
||||
DCQCN,
|
||||
NSCC
|
||||
};
|
||||
|
||||
enum class LBType : uint8_t {
|
||||
RANDOM_PACKET_SPRAYING
|
||||
};
|
||||
|
||||
} // namespace dofs
|
||||
|
||||
#endif // CORE_TYPES_H
|
||||
Reference in New Issue
Block a user