diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/client.cpp | 1553 | ||||
| -rw-r--r-- | core/common.c | 3 | ||||
| -rw-r--r-- | core/compress.c | 79 | ||||
| -rw-r--r-- | core/logger.c | 105 | ||||
| -rw-r--r-- | core/salis.c | 1139 | ||||
| -rw-r--r-- | core/server.c | 418 | ||||
| -rw-r--r-- | core/sql.c | 73 |
7 files changed, 3370 insertions, 0 deletions
diff --git a/core/client.cpp b/core/client.cpp new file mode 100644 index 0000000..771f626 --- /dev/null +++ b/core/client.cpp @@ -0,0 +1,1553 @@ +// index +// [section] includes +// [section] macros & enums +// [section] string comparator declaration +// [section] trace declarations +// [section] plot declarations +// [section] plots +// [section] global variables +// [section] string comparator definition +// [section] Trace (base) definition +// [section] TraceNamed definition +// [section] TraceHeatmap definition +// [section] Plot (base) definition +// [section] PlotLines definition +// [section] PlotStacked definition +// [section] PlotHeatmap definition +// [section] data functions +// [section] gui functions +// [section] main functions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include <arpa/inet.h> +#include <GLFW/glfw3.h> +#include <imgui_impl_glfw.h> +#include <imgui_impl_opengl3.h> +#include <implot.h> +#include <implot_internal.h> +#include <json-c/json.h> +#include <signal.h> +#include <threads.h> + +#include <algorithm> +#include <array> +#include <map> +#include <vector> + +#include "common.c" +#include "logger.c" + +// ---------------------------------------------------------------------------- +// [section] macros & enums +// ---------------------------------------------------------------------------- +#define COLOR_BLACK ImVec4(0.f, 0.f, 0.f, 1.f) +#define FONT_SIZE 12.f +#define GLSL_VERSION "#version 130" +#define PLOT_MIN_COLS 1 +#define PLOT_MAX_COLS 8 +#define PLOT_MIN_HEIGHT 100.f +#define PLOT_MAX_HEIGHT 800.f +#define PLOT_HEIGHT_INTERVAL 50.f +#define PLOT_SCROLL_MARGIN 28.f +#define HM_SCALE_POW_MIN -1.f +#define HM_SCALE_POW_MAX 64.f +#define HM_SCALE_POW_INTERVAL 1.f +#define HM_COLORSCALE_WIDTH 80.f + +#define IMGUI_WINDOW_FLAGS ( \ + ImGuiWindowFlags_NoBackground | \ + ImGuiWindowFlags_NoDecoration | \ + ImGuiWindowFlags_NoMove | \ + ImGuiWindowFlags_NoSavedSettings \ +) + +#define DATA_FETCH_INTERVAL 10 +#define DATA_FETCH_INTERVAL_SUBDIV 1000 + +#define DEFVAL_ENTRIES 0x800l +#define DEFVAL_NTH 1l +#define DEFVAL_X_AXIS 0 +#define DEFVAL_X_LOW 0l +#define DEFVAL_X_HIGH INT64_MAX +#define DEFVAL_HM_LEFT 0l + +enum Status { + STATUS_STOPPED, + STATUS_RUNNING, + STATUS_FETCHING, + STATUS_STOPPING, +}; + +// ---------------------------------------------------------------------------- +// [section] string comparator declaration +// ---------------------------------------------------------------------------- +struct StringComparator { + bool operator()(const char *a, const char *b) const; +}; + +// ---------------------------------------------------------------------------- +// [section] trace declarations +// ---------------------------------------------------------------------------- +template <class T> +struct Trace : public std::vector<T> { + Trace(); + virtual ~Trace(); + virtual size_t start_offset() const; + virtual void trim(); +#if !defined(NDEBUG) + virtual void validate() const; +#endif + void clear(); + void push_back(T value); + T *start(); + T &operator[](size_t n); + T operator[](size_t n) const; +protected: + void trim_spec(int64_t multiplier = 1); +}; + +template <class T> +struct TraceNamed : public Trace<T> { + TraceNamed(const char *name, const char *label); + const char *get_name() const; + const char *get_label() const; +private: + const char *m_name; + const char *m_label; +}; + +template <class T> +struct TraceHeatmap : public TraceNamed<T> { + TraceHeatmap(const char *name, const char *label); + size_t start_offset() const; + void trim(); +#if !defined(NDEBUG) + void validate() const; +#endif +}; + +// ---------------------------------------------------------------------------- +// [section] plot declarations +// ---------------------------------------------------------------------------- +typedef int (*AxisFormatter)(double value, char *buff, int size, void *data); + +struct Plot { + Plot(const char *name, const char *section); + virtual ~Plot(); + virtual void handle_input(int key, int mods); + virtual void flag_for_reset(); + const char *get_name() const; + const char *get_section() const; + void render(const ImVec2 &frame_size); + bool m_visible; +private: + static int hex_formatter(double value, char *buff, int size, void *data); + virtual AxisFormatter x_formatter() const; + virtual AxisFormatter y_formatter() const; + virtual float right_margin() const; + virtual int flags() const; + virtual void render_internal(const ImVec2 &frame_size) = 0; + virtual void render_post(const ImVec2 &frame_size); + const char *m_name; + const char *m_section; +}; + +struct PlotLines : public Plot { + PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys); +private: + void render_internal(const ImVec2 &frame_size); + std::vector<const char *> m_trace_keys; +}; + +struct PlotStacked : public Plot { + PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys); + void flag_for_reset(); +private: + static int percent_formatter(double value, char *buff, int size, void *data); + AxisFormatter y_formatter() const; + void render_internal(const ImVec2 &frame_size); + std::vector<const char *> m_trace_keys; + std::vector<bool> m_trace_states; + Trace<ImS64> m_trace_totals; + std::vector<Trace<double>> m_trace_normals; + bool m_needs_reset; +}; + +struct PlotHeatmap : public Plot { + PlotHeatmap(const char *name, const char *section, const char *trace_key); + void handle_input(int key, int mods); + void flag_for_reset(); +private: + float right_margin() const; + int flags() const; + void render_internal(const ImVec2 &frame_size); + void render_end(const ImVec2 &frame_size); + void render_post(const ImVec2 &frame_size); + const char *m_trace_key; + float m_tex_scale_pow; + float m_tex_scale_high; + size_t m_tex_rendered_last; + size_t m_rows_rendered; + GLuint m_tex_id; + std::vector<uint8_t> m_tex_render; + bool m_needs_reset; +}; + +// ---------------------------------------------------------------------------- +// [section] plots +// ---------------------------------------------------------------------------- +#include "plots.cpp" + +std::array g_core_traces = std::to_array<TraceNamed<ImS64>>({ + {"rowid", "rowid"}, + {"step", "step"}, +#define FOR_CORE(i) \ + {"cycl_" #i, "cycl_" #i}, \ + {"mall_" #i, "mall_" #i}, \ + {"pnum_" #i, "pnum_" #i}, \ + {"pfst_" #i, "pfst_" #i}, \ + {"plst_" #i, "plst_" #i}, \ + {"amb0_" #i, "amb0_" #i}, \ + {"amb1_" #i, "amb1_" #i}, \ + {"emb0_" #i, "emb0_" #i}, \ + {"emb1_" #i, "emb1_" #i}, \ + {"eliv_" #i, "eliv_" #i}, \ + {"edea_" #i, "edea_" #i}, + FOR_CORES +#undef FOR_CORE +}); + +std::array g_core_traces_heatmaps = std::to_array<TraceHeatmap<ImS64>>({ +#define FOR_CORE(i) \ + {"aev_" #i, "aev_" #i}, \ + {"eev_" #i, "eev_" #i}, \ + {"bev_" #i, "bev_" #i}, + FOR_CORES +#undef FOR_CORE +}); + +std::array g_core_plots = std::to_array<PlotLines>({ + {"cycl", "general", { +#define FOR_CORE(i) "cycl_" #i, + FOR_CORES +#undef FOR_CORE + }}, + {"mall", "general", { +#define FOR_CORE(i) "mall_" #i, + FOR_CORES +#undef FOR_CORE + }}, + {"pnum", "general", { +#define FOR_CORE(i) "pnum_" #i, + FOR_CORES +#undef FOR_CORE + }}, + {"ppop", "general", { +#define FOR_CORE(i) "pfst_" #i, "plst_" #i, + FOR_CORES +#undef FOR_CORE + }}, + {"ambs", "general", { +#define FOR_CORE(i) "amb0_" #i, "amb1_" #i, + FOR_CORES +#undef FOR_CORE + }}, + {"eevs", "general", { +#define FOR_CORE(i) "emb0_" #i, "emb1_" #i, "eliv_" #i, "edea_" #i, + FOR_CORES +#undef FOR_CORE + }}, +}); + +std::array g_core_plots_heatmaps = std::to_array<PlotHeatmap>({ +#define FOR_CORE(i) \ + {"aev_" #i, "heatmaps", "aev_" #i}, + FOR_CORES +#undef FOR_CORE +#define FOR_CORE(i) \ + {"eev_" #i, "heatmaps", "eev_" #i}, + FOR_CORES +#undef FOR_CORE +#define FOR_CORE(i) \ + {"bev_" #i, "heatmaps", "bev_" #i}, + FOR_CORES +#undef FOR_CORE +}); + +// ---------------------------------------------------------------------------- +// [section] global variables +// ---------------------------------------------------------------------------- +GLFWwindow *g_window; +ImGuiIO *g_imgui_io; +ImGuiStyle *g_imgui_style; +ImPlotStyle *g_implot_style; + +std::array g_x_axes = std::to_array<const char *>({ + "rowid", + "step", +#define FOR_CORE(i) "cycl_" #i, + FOR_CORES +#undef FOR_CORE +}); + +int g_status; +int g_x_axis = DEFVAL_X_AXIS; +int64_t g_entries = DEFVAL_ENTRIES; +int64_t g_nth = DEFVAL_NTH; +int64_t g_x_low = DEFVAL_X_LOW; +int64_t g_x_high = DEFVAL_X_HIGH; +int64_t g_hm_left = DEFVAL_HM_LEFT; +int64_t g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT; +int64_t g_hm_pixel_pow; // calculate on init +int64_t g_x_current = -1l; +int64_t g_trace_len; +int64_t g_trace_offset; + +thrd_t g_fetching_thread; +mtx_t g_fetching_mutex; + +bool g_data_col_visible = true; +bool g_plot_maximized; +bool g_plot_scroll; +float g_plot_scroll_current; +float g_plot_scroll_to; +float g_data_col_width; +std::vector<Plot *> g_plot_cells; +std::vector<float> g_plot_cells_top; +std::vector<float> g_plot_cells_bottom; +std::vector<bool> g_plots_covered; +Plot *g_plot_selected; +Plot *g_plot_hovered; +int g_plot_stride; +int g_downsampled_trace_len; +int g_plot_cols = 2; +size_t g_plot_col_selected; +size_t g_plot_row_selected; +float g_plot_height = 300.f; +ImPlotColormap g_hm_colormap_id; + +std::map<const char *, TraceNamed<ImS64> *, StringComparator> g_trace_map; +std::vector<TraceNamed<ImS64> *> g_traces; +std::vector<Plot *> g_plots; +Trace<double> g_x_axis_double; +Trace<double> g_zero_trace; + +std::array g_hm_colormap = std::to_array<ImVec4>({ + {0.000f, 0.000f, 0.016f, 1.f}, + {0.106f, 0.047f, 0.255f, 1.f}, + {0.290f, 0.047f, 0.420f, 1.f}, + {0.471f, 0.110f, 0.427f, 1.f}, + {0.647f, 0.173f, 0.376f, 1.f}, + {0.812f, 0.267f, 0.275f, 1.f}, + {0.929f, 0.412f, 0.145f, 1.f}, + {0.984f, 0.608f, 0.024f, 1.f}, + {0.969f, 0.820f, 0.239f, 1.f}, + {0.988f, 1.000f, 0.643f, 1.f}, +}); + +// ---------------------------------------------------------------------------- +// [section] string comparator definition +// ---------------------------------------------------------------------------- +bool StringComparator::operator()(const char *a, const char *b) const { + return strcmp(a, b) < 0; +} + +// ---------------------------------------------------------------------------- +// [section] Trace (base) definition +// ---------------------------------------------------------------------------- +template <class T> +Trace<T>::Trace() : std::vector<T>() {} + +template <class T> +Trace<T>::~Trace() {} + +template <class T> +size_t Trace<T>::start_offset() const { + return (size_t)g_trace_offset; +} + +template <class T> +void Trace<T>::trim() { + trim_spec(); +} + +#if !defined(NDEBUG) +template <class T> +void Trace<T>::validate() const { + assert(this->size() == g_traces[0]->size()); +} +#endif + +template <class T> +void Trace<T>::clear() { + std::vector<T>::clear(); +} + +template <class T> +void Trace<T>::push_back(T value) { + std::vector<T>::push_back(value); +} + +template <class T> +T *Trace<T>::start() { + return this->size() ? &operator[](start_offset()) : nullptr; +} + +template <class T> +T &Trace<T>::operator[](size_t n) { +#if !defined(NDEBUG) + return this->at(n); +#else + return std::vector<T>::operator[](n); +#endif +} + +template <class T> +T Trace<T>::operator[](size_t n) const { +#if !defined(NDEBUG) + return this->at(n); +#else + return std::vector<T>::operator[](n); +#endif +} + +template <class T> +void Trace<T>::trim_spec(int64_t multiplier) { + assert((int64_t)this->size() >= g_entries * multiplier * 2); + this->erase(this->begin(), this->end() - (g_entries * multiplier)); +} + +// ---------------------------------------------------------------------------- +// [section] TraceNamed definition +// ---------------------------------------------------------------------------- +template <class T> +TraceNamed<T>::TraceNamed(const char *name, const char *label) : m_name(name), m_label(label) {} + +template <class T> +const char *TraceNamed<T>::get_name() const { + return m_name; +} + +template <class T> +const char *TraceNamed<T>::get_label() const { + return m_label; +} + +// ---------------------------------------------------------------------------- +// [section] TraceHeatmap definition +// ---------------------------------------------------------------------------- +template <class T> +TraceHeatmap<T>::TraceHeatmap(const char *name, const char *name_fmt) : TraceNamed<T>(name, name_fmt) {} + +template <class T> +size_t TraceHeatmap<T>::start_offset() const { + return (size_t)(g_trace_offset * g_hm_pixel_count); +} + +template <class T> +void TraceHeatmap<T>::trim() { + Trace<T>::trim_spec(g_hm_pixel_count); +} + +#if !defined(NDEBUG) +template <class T> +void TraceHeatmap<T>::validate() const { + assert(this->size() == g_traces[0]->size() * g_hm_pixel_count); +} +#endif + +// ---------------------------------------------------------------------------- +// [section] Plot (base) definition +// ---------------------------------------------------------------------------- +Plot::Plot(const char *name, const char *section) : m_visible(true), m_name(name), m_section(section) {} + +Plot::~Plot() {} + +void Plot::handle_input(int key, int mods) { + (void)key; + (void)mods; +} + +void Plot::flag_for_reset() {} + +const char *Plot::get_name() const { + return m_name; +} + +const char *Plot::get_section() const { + return m_section; +} + +void Plot::render(const ImVec2 &frame_size) { + if (ImPlot::BeginPlot(m_name, ImVec2(frame_size.x - right_margin(), frame_size.y), flags())) { + int axis_flags = ImPlotAxisFlags_Foreground | (g_status != STATUS_STOPPED ? ImPlotAxisFlags_AutoFit : 0); + ImPlot::SetupAxes(nullptr, nullptr, axis_flags, axis_flags); + ImPlot::SetupAxisFormat(ImAxis_X1, x_formatter()); + ImPlot::SetupAxisFormat(ImAxis_Y1, y_formatter()); + if (ImPlot::IsPlotHovered()) g_plot_hovered = this; + render_internal(frame_size); + ImPlot::EndPlot(); + } + + render_post(frame_size); +} + +int Plot::hex_formatter(double value, char *buff, int size, void *data) { + (void)data; + snprintf(buff, size, "%s%#lx", value < 0. ? "-" : "", abs((int64_t)value)); + return 0; +} + +AxisFormatter Plot::x_formatter() const { + return Plot::hex_formatter; +} + +AxisFormatter Plot::y_formatter() const { + return Plot::hex_formatter; +} + +float Plot::right_margin() const { + return 0.f; +} + +int Plot::flags() const { + return 0; +} + +void Plot::render_post(const ImVec2 &frame_size) { + (void)frame_size; +} + +// ---------------------------------------------------------------------------- +// [section] PlotLines definition +// ---------------------------------------------------------------------------- +PlotLines::PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys) {} + +void PlotLines::render_internal(const ImVec2 &frame_size) { + (void)frame_size; + + ImS64 *x = g_trace_map[g_x_axes[g_x_axis]]->start(); + ImPlotSpec spec = ImPlotSpec(ImPlotProp_Stride, sizeof(ImS64) * g_plot_stride); + + for (auto &trace : m_trace_keys) { + TraceNamed<ImS64> *trace_obj = g_trace_map[trace]; + ImS64 *y = trace_obj->start(); + ImPlot::PlotLine(trace_obj->get_label(), x, y, g_downsampled_trace_len, spec); + } +} + +// ---------------------------------------------------------------------------- +// [section] PlotStacked definition +// ---------------------------------------------------------------------------- +PlotStacked::PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys), m_trace_states(trace_keys.size(), true), m_trace_totals(), m_trace_normals(trace_keys.size()), m_needs_reset(true) {} + +void PlotStacked::flag_for_reset() { + m_needs_reset = true; +} + +int PlotStacked::percent_formatter(double value, char *buff, int size, void *data) { + (void)data; + snprintf(buff, size, "%3.0f%%", value * 100.); + return 0; +} + +AxisFormatter PlotStacked::y_formatter() const { + return PlotStacked::percent_formatter; +} + +void PlotStacked::render_internal(const ImVec2 &frame_size) { + (void)frame_size; + + for (size_t i = 0; i < m_trace_keys.size(); i++) { + ImPlot::PlotDummy(g_trace_map[m_trace_keys[i]]->get_label()); + } + + for (size_t i = 0; i < m_trace_keys.size(); i++) { + bool trace_visible = GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show; + + if (m_trace_states[i] != trace_visible) { + m_trace_states[i] = trace_visible; + m_needs_reset = true; + } + } + + if (m_needs_reset) { + m_trace_totals.clear(); + for (auto &trace_normal : m_trace_normals) trace_normal.clear(); + m_needs_reset = false; + } + + size_t trace_size = m_trace_totals.size(); + + for (size_t i = trace_size; i < g_traces[0]->size(); i++) { + m_trace_totals.push_back(0l); + + for (size_t j = 0; j < m_trace_keys.size(); j++) { + if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) { + m_trace_totals.back() += g_trace_map[m_trace_keys[j]]->operator[](i); + } + } + } + + for (size_t i = trace_size; i < g_traces[0]->size(); i++) { + if (m_trace_totals[i]) { + double normal = 0.; + + for (size_t j = 0; j < m_trace_keys.size(); j++) { + if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) { + normal += (double)g_trace_map[m_trace_keys[j]]->operator[](i) / (double)m_trace_totals[i]; + m_trace_normals[j].push_back(normal); + } + } + } else { + for (size_t j = 0; j < m_trace_keys.size(); j++) { + if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) { + m_trace_normals[j].push_back(0.); + } + } + } + } + +#if !defined(NDEBUG) + m_trace_totals.validate(); + + for (size_t i = 0; i < m_trace_keys.size(); i++) { + if (GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show) { + m_trace_normals[i].validate(); + } else { + assert(m_trace_normals[i].empty()); + } + } +#endif + + Trace<double> *prev_trace = &g_zero_trace; + + for (size_t i = 0; i < m_trace_keys.size(); i++) { + assert(m_trace_states[i] == GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show); + + if (m_trace_states[i]) { + bool hovered = GImPlot->CurrentPlot->Items.GetLegendItem(i)->LegendHovered; + ImPlotSpec spec = ImPlotSpec(ImPlotProp_FillAlpha, hovered ? 1.f : 0.9f, ImPlotProp_Stride, sizeof(double) * g_plot_stride); + const char *trace_name = g_trace_map[m_trace_keys[i]]->get_label(); + Trace<double> *current_trace = &m_trace_normals[i]; + ImPlot::PlotShaded(trace_name, g_x_axis_double.start(), prev_trace->start(), current_trace->start(), g_downsampled_trace_len, spec); + ImPlot::PlotLine(trace_name, g_x_axis_double.start(), prev_trace->start(), g_downsampled_trace_len, spec); + if (hovered) ImPlot::PlotLine(trace_name, g_x_axis_double.start(), current_trace->start(), g_downsampled_trace_len, spec); + prev_trace = current_trace; + } + } +} + +// ---------------------------------------------------------------------------- +// [section] PlotHeatmap definition +// ---------------------------------------------------------------------------- +PlotHeatmap::PlotHeatmap(const char *name, const char *section, const char *trace_key) : Plot(name, section), m_trace_key(trace_key), m_tex_scale_pow(-1.f), m_tex_scale_high(0.f), m_tex_rendered_last(0), m_rows_rendered(0), m_tex_id(0), m_tex_render(), m_needs_reset(true) {} + +void PlotHeatmap::handle_input(int key, int mods) { + switch (mods) { + case GLFW_MOD_CONTROL: + switch (key) { + case GLFW_KEY_K: + m_tex_scale_pow = std::clamp(m_tex_scale_pow - 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX); + m_needs_reset = true; + break; + case GLFW_KEY_L: + m_tex_scale_pow = std::clamp(m_tex_scale_pow + 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX); + m_needs_reset = true; + break; + } + } +} + +void PlotHeatmap::flag_for_reset() { + m_needs_reset = true; +} + +float PlotHeatmap::right_margin() const { + return HM_COLORSCALE_WIDTH; +} + +int PlotHeatmap::flags() const { + return ImPlotFlags_NoLegend; +} + +void PlotHeatmap::render_internal(const ImVec2 &frame_size) { + Trace<ImS64> *trace = g_trace_map[m_trace_key]; + + if (m_needs_reset) { + m_tex_scale_high = 0.f; + m_tex_rendered_last = trace->start_offset(); + m_rows_rendered = 0; + glDeleteTextures(1, &m_tex_id); + m_tex_id = 0; + m_needs_reset = false; + } + + if (m_tex_rendered_last == trace->size()) { + render_end(frame_size); + return; + } + + assert(m_tex_render.empty()); + assert((trace->size() - m_tex_rendered_last) % g_hm_pixel_count == 0); + size_t rows_to_append = (trace->size() - m_tex_rendered_last) / (size_t)g_hm_pixel_count; + size_t rows_to_chop_off = (m_rows_rendered + rows_to_append) > (size_t)g_trace_len ? (m_rows_rendered + rows_to_append) - (size_t)g_trace_len : 0; + + if (rows_to_chop_off > m_rows_rendered) { + m_needs_reset = true; + render_internal(frame_size); + return; + } + + if (m_tex_scale_pow == HM_SCALE_POW_MIN) { + ImS64 max = 0; + + for (size_t i = trace->start_offset(); i < trace->size(); i++) { + max = std::max(max, trace->operator[](i)); + } + + m_tex_scale_high = (float)max; + } else { + m_tex_scale_high = pow(2.f, m_tex_scale_pow); + } + + m_tex_render.reserve((trace->size() - m_tex_rendered_last) * 3); + + if (m_tex_scale_high != 0.f) { + for (size_t i = m_tex_rendered_last; i < trace->size(); i++) { + ImS64 value = trace->operator[](i); + float normal = std::min((float)value / m_tex_scale_high, 1.f); + assert(normal >= 0.f && normal <= 1.f); + ImVec4 color = ImPlot::SampleColormap(normal, g_hm_colormap_id); + m_tex_render.push_back((uint8_t)(color.x * 255.f)); // red + m_tex_render.push_back((uint8_t)(color.y * 255.f)); // green + m_tex_render.push_back((uint8_t)(color.z * 255.f)); // blue + } + } else { + for (size_t i = m_tex_rendered_last; i < trace->size(); i++) { + m_tex_render.push_back(0); + m_tex_render.push_back(0); + m_tex_render.push_back(0); + } + } + + GLuint new_tex_id; + glGenTextures(1, &new_tex_id); + glBindTexture(GL_TEXTURE_2D, new_tex_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, g_hm_pixel_count, g_trace_len, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); + assert(glGetError() == GL_NO_ERROR); + assert(new_tex_id); + + if (m_tex_id) { + ((PFNGLCOPYIMAGESUBDATAPROC)glfwGetProcAddress("glCopyImageSubData"))( + m_tex_id, GL_TEXTURE_2D, 0, 0, rows_to_chop_off, 0, + new_tex_id, GL_TEXTURE_2D, 0, 0, 0, 0, + g_hm_pixel_count, m_rows_rendered - rows_to_chop_off, 1 + ); + assert(glGetError() == GL_NO_ERROR); + glDeleteTextures(1, &m_tex_id); + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, m_rows_rendered - rows_to_chop_off, g_hm_pixel_count, rows_to_append, GL_RGB, GL_UNSIGNED_BYTE, m_tex_render.data()); + assert(glGetError() == GL_NO_ERROR); + + render_end(frame_size); + + m_tex_rendered_last = trace->size(); + m_rows_rendered += rows_to_append - rows_to_chop_off; + assert(m_rows_rendered <= (size_t)g_trace_len); + m_tex_id = new_tex_id; + m_tex_render.clear(); +} + +void PlotHeatmap::render_end(const ImVec2 &frame_size) { + if (g_x_axis_double.empty()) return; + + ImVec2 bmin(g_hm_left, (float)*g_x_axis_double.start()); + ImVec2 bmax(g_hm_left + g_hm_pixel_count * pow(2.f, g_hm_pixel_pow), (float)g_x_axis_double.back()); + ImVec2 uv0(0.f, 1.f); + ImVec2 uv1(1.f, 0.f); + double scale_max = m_tex_scale_pow == HM_SCALE_POW_MIN ? (double)m_tex_scale_high : pow(2., (double)m_tex_scale_pow); + + ImPlot::PlotImage(get_name(), (ImTextureID)m_tex_id, bmin, bmax, uv0, uv1); + ImGui::SameLine(); + + ImPlot::PushColormap(g_hm_colormap_id); + ImPlot::ColormapScale("##hm-scale", 0., scale_max, ImVec2(HM_COLORSCALE_WIDTH, frame_size.y), "%.1e"); + ImPlot::PopColormap(); +} + +void PlotHeatmap::render_post(const ImVec2 &frame_size) { + (void)frame_size; +} + +// ---------------------------------------------------------------------------- +// [section] data functions +// ---------------------------------------------------------------------------- +int64_t data_max_hm_pixel_pow(void) { + return (int64_t)floor(log2((double)(MVEC_SIZE - g_hm_left) / (double)g_hm_pixel_count)); +} + +void data_update_stride(void) { + if (g_plot_maximized) { + g_plot_stride = 1; + g_downsampled_trace_len = g_trace_len; + return; + } + + int win_height = 0; + glfwGetWindowSize(g_window, nullptr, &win_height); + + int max_stride = g_plot_cols * ((win_height / (int)g_plot_height) + 1); + int max_points = DEFVAL_ENTRIES / max_stride; + g_plot_stride = 1; + + while (g_trace_len / g_plot_stride > max_points) { + g_plot_stride++; + } + + g_downsampled_trace_len = g_trace_len / g_plot_stride; + + log_info("Updated plot stride: %d", g_plot_stride); + log_info("Updated downsampled trace-len: %d", g_downsampled_trace_len); +} + +void data_on_field_change(void) { + g_entries = std::clamp(g_entries, 1l, DEFVAL_ENTRIES); + g_nth = std::clamp(g_nth, DEFVAL_NTH, INT64_MAX); + g_x_low = std::clamp(g_x_low, DEFVAL_X_LOW, INT64_MAX); + g_x_high = std::clamp(g_x_high, g_x_low + 1l, DEFVAL_X_HIGH); +#if !defined(MVEC_LOOP) + g_hm_left = std::clamp(g_hm_left, DEFVAL_HM_LEFT, (int64_t)MVEC_SIZE); +#endif + g_hm_pixel_count = std::clamp(g_hm_pixel_count, 1l, DEFVAL_HM_PIXEL_COUNT); + g_hm_pixel_pow = std::clamp(g_hm_pixel_pow, 0l, data_max_hm_pixel_pow()); + g_x_current = -1l; + + g_trace_len = 0l; + g_trace_offset = 0;; + g_plot_stride = 0; + g_downsampled_trace_len = 0; + + for (auto &trace : g_traces) trace->clear(); + for (auto &plot: g_plots) plot->flag_for_reset(); + g_x_axis_double.clear(); + g_zero_trace.clear(); +} + +void data_reset_fields(void) { + g_entries = DEFVAL_ENTRIES; + g_nth = DEFVAL_NTH; + g_x_axis = DEFVAL_X_AXIS; + g_x_low = DEFVAL_X_LOW; + g_x_high = DEFVAL_X_HIGH; + g_hm_left = DEFVAL_HM_LEFT; + g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT; + g_hm_pixel_pow = data_max_hm_pixel_pow(); + + data_on_field_change(); +} + +void data_reset_plot_cells(void) { + std::fill(g_plot_cells.begin(), g_plot_cells.end(), nullptr); + std::fill(g_plot_cells_top.begin(), g_plot_cells_top.end(), 0.f); + std::fill(g_plot_cells_bottom.begin(), g_plot_cells_bottom.end(), 0.f); +} + +void data_fetch(void) { + assert(g_status == STATUS_RUNNING); + g_status = STATUS_FETCHING; + + json_object *request = json_object_new_object(); + json_object_object_add(request, "request", json_object_new_string("data")); + json_object_object_add(request, "entries", json_object_new_int64(g_entries)); + json_object_object_add(request, "nth", json_object_new_int64(g_nth)); + json_object_object_add(request, "x-axis", json_object_new_string(g_x_axes[g_x_axis])); + json_object_object_add(request, "x-low", json_object_new_int64(g_x_low)); + json_object_object_add(request, "x-high", json_object_new_int64(g_x_high)); + json_object_object_add(request, "hm-left", json_object_new_int64(g_hm_left)); + json_object_object_add(request, "hm-pixel-count", json_object_new_int64(g_hm_pixel_count)); + json_object_object_add(request, "hm-pixel-pow", json_object_new_int64(g_hm_pixel_pow)); + json_object_object_add(request, "x-current", json_object_new_int64(g_x_current)); + const char *request_str = json_object_to_json_string(request); + + log_info("Sending request to server: %s", request_str); + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + sockaddr_in socket_addr; + memset(&socket_addr, 0, sizeof(sockaddr_in)); + socket_addr.sin_family = AF_INET; + socket_addr.sin_port = htons(PORT); + inet_pton(AF_INET, IP, &socket_addr.sin_addr); + if (connect(socket_fd, (sockaddr *)&socket_addr, sizeof(sockaddr_in))) assert(false); + json_object_to_fd(socket_fd, request, 0); + shutdown(socket_fd, SHUT_WR); + + json_object *response = json_object_from_fd(socket_fd); + + mtx_lock(&g_fetching_mutex); + + json_object_object_foreach(response, key, value) { + size_t new_rows = json_object_array_length(value); + + if (!strcmp(key, g_x_axes[g_x_axis])) { + log_info("Received %lu rows of data from server", new_rows); + + for (size_t i = 0; i < new_rows; i++) { + ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i)); + g_x_axis_double.push_back((double)point); + g_zero_trace.push_back(0.); + } + } + + if (g_trace_map.contains(key)) { + for (size_t i = 0; i < new_rows; i++) { + ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i)); + g_trace_map[key]->push_back(point); + } + } + } + + g_x_current = g_trace_map[g_x_axes[g_x_axis]]->back(); + json_object_put(request); + json_object_put(response); + +#if !defined(NDEBUG) + for (auto &trace : g_traces) trace->validate(); + g_x_axis_double.validate(); + g_zero_trace.validate(); +#endif + + if ((int64_t)g_traces[0]->size() >= g_entries * 2) { + log_info("Trimming traces & flagging plots for reset"); + for (auto &trace : g_traces) trace->trim(); + for (auto &plot : g_plots) plot->flag_for_reset(); + g_x_axis_double.trim(); + g_zero_trace.trim(); + } + +#if !defined(NDEBUG) + for (auto &trace : g_traces) trace->validate(); + g_x_axis_double.validate(); + g_zero_trace.validate(); +#endif + + int64_t current_size = g_traces[0]->size(); + g_trace_len = std::min(current_size, g_entries); + g_trace_offset = current_size > g_entries ? current_size - g_entries : 0l; + data_update_stride(); + + mtx_unlock(&g_fetching_mutex); + g_status = STATUS_RUNNING; +} + +int data_fetching_thread(void *data) { + (void)data; + + assert(!data); + assert(g_status == STATUS_RUNNING); + + while (g_status == STATUS_RUNNING) { + data_fetch(); + + for (int i = 0; i < DATA_FETCH_INTERVAL_SUBDIV && g_status == STATUS_RUNNING; i++) { + usleep((DATA_FETCH_INTERVAL * 1000000) / DATA_FETCH_INTERVAL_SUBDIV); + } + } + + assert(g_status == STATUS_STOPPING); + g_status = STATUS_STOPPED; + return 0; +} + +void data_start_fetching(void) { + log_info("Starting data fetching thread"); + g_status = STATUS_RUNNING; + thrd_create(&g_fetching_thread, (thrd_start_t)data_fetching_thread, nullptr); +} + +void data_stop_fetching(void) { + assert(g_status == STATUS_RUNNING || g_status == STATUS_FETCHING); + log_info("Stopping data fetching thread"); + g_status = STATUS_STOPPING; +} + +// ---------------------------------------------------------------------------- +// [section] gui functions +// ---------------------------------------------------------------------------- +void gui_render_data_input(const char *label, int64_t *target) { + assert(target); + + if (ImGui::InputScalar(label, ImGuiDataType_U64, target, nullptr, nullptr, "%#lx")) { + data_on_field_change(); + } +} + +void gui_render_data_col(void) { + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + const ImVec2 win_pos = viewport->Pos; + const ImVec2 win_size = ImVec2(-1.f, viewport->Size.y); + + ImGui::SetNextWindowPos(win_pos); + ImGui::SetNextWindowSize(win_size); + ImGui::Begin("data-col", nullptr, IMGUI_WINDOW_FLAGS); + g_data_col_width = ImGui::GetWindowWidth(); + + ImGui::SeparatorText("SALIS data client"); + ImGui::LabelText("name", NAME); + ImGui::LabelText("seed", "%#lx", SEED); + ImGui::LabelText("server", IP ":" PORT_STR); + ImGui::LabelText("arch", ARCH); + ImGui::LabelText("cores", "%d", CORES); + ImGui::LabelText("mvec-size", "%#lx", MVEC_SIZE); + #if defined(MVEC_LOOP) + ImGui::LabelText("mvec-loop", "true"); + #else + ImGui::LabelText("mvec-loop", "false"); + #endif + ImGui::LabelText("data-push", "%#lx", DATA_PUSH_INTERVAL); + ImGui::LabelText("fps", "%.1f", g_imgui_io->Framerate); + + ImGui::SeparatorText("Data fields"); + + switch (g_status) { + case STATUS_STOPPED: + gui_render_data_input("entries", &g_entries); + gui_render_data_input("nth", &g_nth); + + if (ImGui::BeginCombo("x-axis", g_x_axes[g_x_axis])) { + for (int i = 0; i < CORES + 2; i++) { + if (ImGui::Selectable(g_x_axes[i], g_x_axis == i)) { + data_reset_fields(); + g_x_axis = i; + } + } + + ImGui::EndCombo(); + } + + gui_render_data_input("x-low", &g_x_low); + gui_render_data_input("x-high", &g_x_high); + gui_render_data_input("hm-left", &g_hm_left); + gui_render_data_input("hm-pxl-count", &g_hm_pixel_count); + gui_render_data_input("hm-pxl-pow", &g_hm_pixel_pow); + break; + case STATUS_RUNNING: + case STATUS_FETCHING: + case STATUS_STOPPING: + ImGui::LabelText("entries", "%#lx", g_entries); + ImGui::LabelText("nth", "%#lx", g_nth); + ImGui::LabelText("x-axis", "%s", g_x_axes[g_x_axis]); + ImGui::LabelText("x-low", "%#lx", g_x_low); + ImGui::LabelText("x-high", "%#lx", g_x_high); + ImGui::LabelText("hm-left", "%#lx", g_hm_left); + ImGui::LabelText("hm-pxl-count", "%#lx", g_hm_pixel_count); + ImGui::LabelText("hm-pxl-pow", "%#lx", g_hm_pixel_pow); + } + + switch (g_status) { + case STATUS_STOPPED: + if (ImGui::Button("Run", ImVec2(-1.f, 0.f))) data_start_fetching(); + if (ImGui::Button("Reset", ImVec2(-1.f, 0.f))) data_reset_fields(); + break; + case STATUS_RUNNING: + if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching(); + ImGui::LabelText("##", "Running"); + break; + case STATUS_FETCHING: + if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching(); + ImGui::LabelText("##", "Fetching ..."); + break; + case STATUS_STOPPING: + ImGui::BeginDisabled(); + ImGui::Button("Stop", ImVec2(-1.f, 0.f)); + ImGui::EndDisabled(); + ImGui::LabelText("##", "Stopping"); + break; + } + + ImGui::SeparatorText("Layout"); + ImGui::DragInt("cols", &g_plot_cols, 1, PLOT_MIN_COLS, PLOT_MAX_COLS); + ImGui::DragFloat("plot-height", &g_plot_height, PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT, PLOT_MAX_HEIGHT, "%.0f"); + + ImGui::SeparatorText("Plots"); + if (ImGui::Button("Show all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = true; + if (ImGui::Button("Hide all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = false; + ImGui::BeginTable("plot-visibility-table", 3); + + for (auto &plot : g_plots) { + ImGui::TableNextColumn(); + ImGui::Checkbox(plot->get_name(), &plot->m_visible); + } + + ImGui::EndTable(); + ImGui::End(); +} + +size_t gui_plot_cell_index(size_t row, size_t col) { + size_t index = row * PLOT_MAX_COLS + col; + assert(index < g_plot_cells.size()); + assert(index < g_plot_cells_top.size()); + assert(index < g_plot_cells_bottom.size()); + return index; +} + +size_t gui_plot_cell_row_up() { + for (size_t row = g_plot_row_selected - 1; row < g_plot_row_selected; row--) { + if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) { + return row; + } + } + + return g_plot_row_selected; +} + +size_t gui_plot_cell_row_down() { + for (size_t row = g_plot_row_selected + 1; row < g_plots.size(); row++) { + if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) { + return row; + } + } + + return g_plot_row_selected; +} + +void gui_render_plots(void) { + const char *section_current = g_plots[0]->get_section(); + const char *section_next = nullptr; + + g_plots_covered.clear(); + g_plots_covered.resize(g_plots.size(), false); + + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + const ImVec2 win_pos = g_data_col_visible ? ImVec2(g_data_col_width, viewport->Pos.y) : viewport->Pos; + const ImVec2 win_size = g_data_col_visible ? ImVec2(viewport->Size.x - g_data_col_width, -1.f) : ImVec2(viewport->Size.x, -1.f); + + if (g_plot_scroll) { + ImGui::SetNextWindowScroll(ImVec2(-1.f, g_plot_scroll_to)); + g_plot_scroll = false; + g_plot_scroll_to = 0.f; + } + + ImGui::SetNextWindowPos(win_pos); + ImGui::SetNextWindowSize(win_size); + ImGui::Begin("plots", nullptr, IMGUI_WINDOW_FLAGS); + g_plot_scroll_current = ImGui::GetScrollY(); + g_plot_hovered = nullptr; + + if (!g_plot_selected->m_visible) { + g_plot_selected = g_plots[0]; + g_plot_col_selected = 0; + g_plot_row_selected = 0; + + for (auto &plot : g_plots) { + if (plot->m_visible) { + g_plot_selected = plot; + } + } + } + + size_t row = 0; + size_t col = 0; + + mtx_lock(&g_fetching_mutex); + + while (section_current) { + ImGui::SeparatorText(section_current); + ImGui::BeginTable("plots-table", g_plot_cols); + + for (size_t i = 0; i < g_plots.size(); i++) { + if (strcmp(g_plots[i]->get_section(), section_current)) { + section_next = (!section_next && !g_plots_covered[i]) ? g_plots[i]->get_section() : section_next; + continue; + } + + if (g_plots[i]->m_visible) { + ImGui::TableNextColumn(); + ImVec2 frame_size = ImVec2(ImGui::GetContentRegionAvail().x, g_plot_height); + g_plot_cells[gui_plot_cell_index(row, col)] = g_plots[i]; + g_plot_cells_top[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY(); + + if (g_plots[i] == g_plot_selected) { + g_plot_col_selected = col; + g_plot_row_selected = row; + g_implot_style->Colors[ImPlotCol_FrameBg] = g_imgui_style->Colors[ImGuiCol_FrameBg]; + } + + g_plots[i]->render(frame_size); + + if (g_plots[i] == g_plot_selected) { + g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK; + } + + g_plot_cells_bottom[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY(); + col = (col + 1) % g_plot_cols; + row += col ? 0 : 1; + } + + g_plots_covered[i] = true; + } + + section_current = section_next; + section_next = nullptr; + ImGui::EndTable(); + row += col ? 1 : 0; + col = 0; + } + + mtx_unlock(&g_fetching_mutex); + ImGui::End(); +} + +void gui_render_plot_maximized(void) { + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::Begin("plot-fullscreen", nullptr, IMGUI_WINDOW_FLAGS); + + ImVec2 frame_size = { + viewport->Size.x - g_imgui_style->WindowPadding.x * 2, + viewport->Size.y - g_imgui_style->WindowPadding.y * 2, + }; + + mtx_lock(&g_fetching_mutex); + g_plot_selected->render(frame_size); + mtx_unlock(&g_fetching_mutex); + ImGui::End(); +} + +void gui_plot_queue_scroll_to_position(bool increased_plot_height) { + size_t row = 0; + float selected_plot_top = 0.f; + + for (row = 0; row < g_plots.size(); row++) { + for (size_t col = 0; col < PLOT_MAX_COLS; col++) { + if (g_plot_selected == g_plot_cells[gui_plot_cell_index(row, col)]) { + selected_plot_top = g_plot_cells_top[gui_plot_cell_index(row, col)]; + goto loop_exit; + } + } + } + + loop_exit: + g_plot_scroll_to = selected_plot_top + (PLOT_HEIGHT_INTERVAL * (float)row * (increased_plot_height ? 1.f : -1.f)) - PLOT_SCROLL_MARGIN; + g_plot_scroll = true; +} + +void gui_plot_queue_scroll_to_selected() { + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + float plot_top = g_plot_cells_top[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + float plot_bottom = g_plot_cells_bottom[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + float win_bottom = g_plot_scroll_current + viewport->Size.y; + + if (plot_bottom > win_bottom) { + g_plot_scroll_to = g_plot_scroll_current + (plot_bottom - win_bottom); + g_plot_scroll = true; + } + + if (plot_top < g_plot_scroll_current) { + g_plot_scroll_to = plot_top - PLOT_SCROLL_MARGIN; + g_plot_scroll = true; + } +} + +void gui_render(void) { + if (g_plot_maximized) { + gui_render_plot_maximized(); + return; + } + + if (g_data_col_visible) gui_render_data_col(); + gui_render_plots(); +} + +// ---------------------------------------------------------------------------- +// [section] main functions +// ---------------------------------------------------------------------------- +void app_sig_handler(int signo) { + (void)signo; + + log_warn("Signal received, will stop SALIS data client..."); + + if (g_status == STATUS_RUNNING || g_status == STATUS_FETCHING) { + data_stop_fetching(); + } + + glfwSetWindowShouldClose(g_window, GLFW_TRUE); +} + +void app_error_callback(int error, const char* description) { + log_warn("GLFW error %d: %s", error, description); +} + +void app_toggle_state() { + switch (g_status) { + case STATUS_STOPPED: + data_start_fetching(); + break; + case STATUS_RUNNING: + case STATUS_FETCHING: + data_stop_fetching(); + break; + } +} + +void app_key_callback_plot_maximized(int key, int mods) { + switch (mods) { + case GLFW_MOD_CONTROL: + switch (key) { + case GLFW_KEY_C: + glfwSetWindowShouldClose(g_window, GLFW_TRUE); + break; + } + + break; + + case 0: + switch (key) { + case GLFW_KEY_F: + g_plot_maximized = false; + data_update_stride(); + break; + case GLFW_KEY_SPACE: + app_toggle_state(); + break; + } + + break; + } + + if (g_plot_selected->m_visible) { + g_plot_selected->handle_input(key, mods); + } +} + +void app_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { + (void)window; + (void)scancode; + + if (ImGui::IsAnyItemActive()) { + return; + } + + if (action != GLFW_PRESS && action != GLFW_REPEAT) { + return; + } + + if (g_plot_maximized) { + app_key_callback_plot_maximized(key, mods); + return; + } + + switch (mods) { + case GLFW_MOD_CONTROL: + switch (key) { + case GLFW_KEY_C: + glfwSetWindowShouldClose(g_window, GLFW_TRUE); + break; + case GLFW_KEY_N: + g_data_col_visible = !g_data_col_visible; + break; + case GLFW_KEY_LEFT: + g_plot_cols = std::max(g_plot_cols - 1, PLOT_MIN_COLS); + data_reset_plot_cells(); + data_update_stride(); + break; + case GLFW_KEY_RIGHT: + g_plot_cols = std::min(g_plot_cols + 1, PLOT_MAX_COLS); + data_reset_plot_cells(); + data_update_stride(); + break; + case GLFW_KEY_UP: + g_plot_height = std::min(g_plot_height + PLOT_HEIGHT_INTERVAL, PLOT_MAX_HEIGHT); + gui_plot_queue_scroll_to_position(true); + data_update_stride(); + break; + case GLFW_KEY_DOWN: + g_plot_height = std::max(g_plot_height - PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT); + gui_plot_queue_scroll_to_position(false); + data_update_stride(); + break; + } + + break; + + case 0: + switch (key) { + case GLFW_KEY_LEFT: + g_plot_col_selected -= g_plot_col_selected ? 1 : 0; + g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + break; + case GLFW_KEY_RIGHT: + g_plot_col_selected += (g_plot_col_selected < PLOT_MAX_COLS - 1 && g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected + 1)]) ? 1 : 0; + g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + break; + case GLFW_KEY_UP: + g_plot_row_selected = gui_plot_cell_row_up(); + g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + gui_plot_queue_scroll_to_selected(); + break; + case GLFW_KEY_DOWN: + g_plot_row_selected = gui_plot_cell_row_down(); + g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)]; + gui_plot_queue_scroll_to_selected(); + break; + case GLFW_KEY_F: + if (g_plot_selected->m_visible) { + g_plot_maximized = !g_plot_maximized; + data_update_stride(); + } + + break; + case GLFW_KEY_SPACE: + app_toggle_state(); + break; + } + + break; + } + + if (g_plot_selected->m_visible) { + g_plot_selected->handle_input(key, mods); + } +} + +void app_window_size_callback(GLFWwindow* window, int width, int height) { + (void)window; + (void)width; + (void)height; + + data_update_stride(); +} + +void app_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { + (void)window; + (void)mods; + + if (button != GLFW_MOUSE_BUTTON_LEFT) return; + + switch (action) { + case GLFW_PRESS: + g_plot_selected = g_plot_hovered ? g_plot_hovered : g_plot_selected; + break; + } +} + +void init() { + signal(SIGINT, app_sig_handler); + signal(SIGTERM, app_sig_handler); + + log_info("Starting SALIS data client"); + + log_info("Initializing GLFW"); + glfwSetErrorCallback(app_error_callback); + glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR); + if (!glfwInit()) assert(false); + + float scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor()); + g_window = glfwCreateWindow((int)(800 * scale), (int)(600 * scale), "SALIS data client", nullptr, nullptr); + assert(g_window); + glfwSetKeyCallback(g_window, app_key_callback); + glfwSetMouseButtonCallback(g_window, app_mouse_button_callback); + glfwSetWindowSizeCallback(g_window, app_window_size_callback); + glfwMakeContextCurrent(g_window); +#if defined(VSYNC) + glfwSwapInterval(1); +#else + glfwSwapInterval(0); +#endif + + log_info("Initializing ImGui"); + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + + g_imgui_io = &ImGui::GetIO(); + g_imgui_io->Fonts->AddFontFromFileTTF(FONT_SOURCE, FONT_SIZE); + g_imgui_io->IniFilename = nullptr; + + g_imgui_style = &ImGui::GetStyle(); + g_imgui_style->Colors[ImGuiCol_WindowBg] = COLOR_BLACK; + g_imgui_style->FontScaleDpi = scale; + g_imgui_style->FontSizeBase = FONT_SIZE; + g_imgui_style->ItemSpacing = ImVec2(g_imgui_style->ItemSpacing.x, 2.f); + g_imgui_style->ScaleAllSizes(scale); + + g_implot_style = &ImPlot::GetStyle(); + g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK; + g_hm_colormap_id = ImPlot::AddColormap("heatmap", g_hm_colormap.data(), g_hm_colormap.size(), false); + + ImGui_ImplGlfw_InitForOpenGL(g_window, true); + ImGui_ImplOpenGL3_Init(GLSL_VERSION); + + for (auto &trace : g_core_traces) g_traces.push_back(&trace); + for (auto &trace : g_arch_traces) g_traces.push_back(&trace); + for (auto &trace : g_core_traces_heatmaps) g_traces.push_back(&trace); + for (auto &trace : g_arch_traces_heatmaps) g_traces.push_back(&trace); + for (auto &plot : g_core_plots) g_plots.push_back(&plot); + for (auto &plot : g_arch_plots) g_plots.push_back(&plot); + for (auto &plot : g_arch_plots_stacked) g_plots.push_back(&plot); + for (auto &plot : g_core_plots_heatmaps) g_plots.push_back(&plot); + for (auto &plot : g_arch_plots_heatmaps) g_plots.push_back(&plot); + for (auto &i : g_traces) g_trace_map[i->get_name()] = i; + + g_plot_cells = std::vector<Plot *>(g_plots.size() * PLOT_MAX_COLS); + g_plot_cells_top = std::vector<float>(g_plots.size() * PLOT_MAX_COLS); + g_plot_cells_bottom = std::vector<float>(g_plots.size() * PLOT_MAX_COLS); + g_plot_selected = g_plots[0]; + + g_hm_pixel_pow = data_max_hm_pixel_pow(); + data_update_stride(); + + mtx_init(&g_fetching_mutex, mtx_plain); +} + +void exec() { + while (!glfwWindowShouldClose(g_window)) { + glfwPollEvents(); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + gui_render(); + + ImGui::Render(); + int display_w; + int display_h; + glfwGetFramebufferSize(g_window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + glfwSwapBuffers(g_window); + } +} + +void quit() { + mtx_destroy(&g_fetching_mutex); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + + log_info("Stopping SALIS data client"); + glfwDestroyWindow(g_window); + glfwTerminate(); +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + init(); + exec(); + quit(); + + return 0; +} diff --git a/core/common.c b/core/common.c new file mode 100644 index 0000000..786ab38 --- /dev/null +++ b/core/common.c @@ -0,0 +1,3 @@ +#define DEFVAL_HM_PIXEL_COUNT 0x400l +#define EVENT_ARRAYS_SIZE (sizeof(uint64_t) * MVEC_SIZE) +#define EVENT_ARRAYS_SIZE_COL_MARKER "_evasize_" diff --git a/core/compress.c b/core/compress.c new file mode 100644 index 0000000..e457de5 --- /dev/null +++ b/core/compress.c @@ -0,0 +1,79 @@ +struct DeflateParams { + z_stream strm; + size_t size; + Bytef *in; + Bytef *out; +#if defined(THREAD_GAP) + uint8_t tgap[THREAD_GAP]; +#endif +}; + +struct InflateParams { + z_stream strm; + size_t avail_in; + size_t size; + Bytef *in; + Bytef *out; +#if defined(THREAD_GAP) + uint8_t tgap[THREAD_GAP]; +#endif +}; + +int comp_deflate(struct DeflateParams *params) { + assert(params); + assert(params->size); + assert(params->in); + assert(params->out); + + params->strm.zalloc = NULL; + params->strm.zfree = NULL; + params->strm.opaque = NULL; + + deflateInit(¶ms->strm, Z_DEFAULT_COMPRESSION); + + params->strm.avail_in = params->size; + params->strm.avail_out = params->size; + params->strm.next_in = params->in; + params->strm.next_out = params->out; + + deflate(¶ms->strm, Z_FINISH); + + return 0; +} + +void comp_deflate_end(struct DeflateParams *params) { + assert(params); + deflateEnd(¶ms->strm); +} + +int comp_inflate(struct InflateParams *params) { + assert(params); + assert(params->avail_in); + assert(params->size); + assert(params->in); + assert(params->out); + + params->strm.next_in = params->in; + params->strm.avail_in = params->avail_in; + params->strm.zalloc = NULL; + params->strm.zfree = NULL; + params->strm.opaque = NULL; + + inflateInit(¶ms->strm); + + params->strm.avail_out = params->size; + params->strm.next_out = params->out; + +#if defined(NDEBUG) + inflate(¶ms->strm, Z_FINISH); +#else + assert(inflate(¶ms->strm, Z_FINISH)); +#endif + + return 0; +} + +void comp_inflate_end(struct InflateParams *params) { + assert(params); + inflateEnd(¶ms->strm); +} diff --git a/core/logger.c b/core/logger.c new file mode 100644 index 0000000..af41dcc --- /dev/null +++ b/core/logger.c @@ -0,0 +1,105 @@ +#include <assert.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> + +#define LOG_LINE_SIZE 1024 + +enum LogLevel { + INFO, + WARN, +}; + +void log_msg_to_buff(char *out, int size, enum LogLevel level, bool colored, const char *format, va_list args) { + assert(out); + assert(size); + assert(level == INFO || level == WARN); + assert(format); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long msec = ts.tv_nsec / 1000000; + struct tm tm = *localtime(&ts.tv_sec); + pid_t pid = getpid(); + const char *level_str = NULL; + + switch (level) { + case INFO: + level_str = "INFO"; + break; + case WARN: + level_str = "WARN"; + break; + default: + assert(false); + } + + const char *color_code = NULL; + if (colored) { + switch (level) { + case INFO: + color_code = "\033[1;32m"; + break; + case WARN: + color_code = "\033[1;33m"; + break; + default: + assert(false); + } + } + + // Imitate formatting style configured in 'salis.py' + int col = snprintf( + out, + size, + "%s%d-%02d-%02d %02d:%02d:%02d,%03ld %07d [%s]%s ", + colored ? color_code : "", + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + msec, + pid, + level_str, + colored ? "\033[0m" : "" + ); + + vsnprintf(out + col, size - col, format, args); +} + +void log_msg(enum LogLevel level, bool colored, const char *format, va_list args) { + assert(level == INFO || level == WARN); + assert(format); + + char buff[LOG_LINE_SIZE]; + log_msg_to_buff(buff, LOG_LINE_SIZE, level, colored, format, args); + printf("\r%s\n", buff); + fflush(stdout); +} + +void log_info_default(const char *format, ...) { + assert(format); + + va_list args; + va_start(args, format); + log_msg(INFO, true, format, args); + va_end(args); +} + +void log_warn_default(const char *format, ...) { + assert(format); + + va_list args; + va_start(args, format); + log_msg(WARN, true, format, args); + va_end(args); +} + +// Client may install their own loggers +void (*log_info)(const char *fmt, ...) = log_info_default; +void (*log_warn)(const char *fmt, ...) = log_warn_default; diff --git a/core/salis.c b/core/salis.c new file mode 100644 index 0000000..11c6efb --- /dev/null +++ b/core/salis.c @@ -0,0 +1,1139 @@ +// index +// [section] includes +// [section] macros & enums +// [section] structs +// [section] globals +// [section] architecture forward declarations +// [section] memory vector functions +// [section] mutator functions +// [section] process functions +// [section] core functions +// [section] salis functions +// [section] architecture & ui includes + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include <assert.h> +#include <sqlite3.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <threads.h> +#include <zlib.h> + +#include "common.c" +#include "compress.c" +#include "logger.c" +#include "sql.c" + +// ---------------------------------------------------------------------------- +// [section] macros & enums +// ---------------------------------------------------------------------------- +#define INST_CAP 0x80 +#define INST_MASK 0x7f +#define IPC_FLAG 0x80 +#define MALL_FLAG 0x80 +#define UINT64_HALF 0x8000000000000000ul + +#define EVENT_ARRAYS(core) \ + EVENT_ARRAY(core, 0, aev) /* allocation events array */ \ + EVENT_ARRAY(core, 1, eev) /* executions events array */ \ + EVENT_ARRAY(core, 2, bev) /* birth events array */ +#define EVENT_ARRAYS_COUNT 3 + +// ---------------------------------------------------------------------------- +// [section] structs +// ---------------------------------------------------------------------------- +struct Proc { +#define PROC_FIELD(type, name) type name; + PROC_FIELDS +#undef PROC_FIELD +}; + +struct Core { + uint64_t cycl; + uint64_t mall; + uint64_t muta[4]; + + uint64_t pnum; + uint64_t pcap; + uint64_t pfst; + uint64_t plst; + uint64_t pcur; + uint64_t psli; + + thrd_t thrd; + uint64_t thrd_steps; + + uint64_t ivpt; + uint64_t *ivav; + uint8_t *iviv; + + uint64_t emb0; // executions within mb0 counter + uint64_t emb1; // executions within mb1 counter + uint64_t eliv; // executions within not-owned live code counter (parasites) + uint64_t edea; // executions within dead code counter + +#define EVENT_ARRAY(core, index, ev) uint64_t ev##a[MVEC_SIZE]; + EVENT_ARRAYS(core) +#undef EVENT_ARRAY + +#define CORE_DATA_FIELD(type, name) type name; + CORE_DATA_FIELDS +#undef CORE_DATA_FIELD + +#define CORE_FIELD(type, name) type name; + CORE_FIELDS +#undef CORE_FIELD + + struct Proc *pvec; + uint8_t mvec[MVEC_SIZE]; + uint8_t tgap[THREAD_GAP]; +}; + +// ---------------------------------------------------------------------------- +// [section] globals +// ---------------------------------------------------------------------------- +struct Core g_cores[CORES]; +uint64_t g_steps; +uint64_t g_syncs; +const struct Proc g_dead_proc; + +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) +char g_asav_pbuf[AUTOSAVE_NAME_LEN]; +char g_eva_pbuf[EVA_SAVE_NAME_LEN]; +#endif + +thrd_t g_eva_thrds[CORES][EVENT_ARRAYS_COUNT]; +struct DeflateParams g_eva_deflate_params[CORES][EVENT_ARRAYS_COUNT]; + +// ---------------------------------------------------------------------------- +// [section] architecture forward declarations +// ---------------------------------------------------------------------------- +#if defined(COMMAND_NEW) +void arch_core_init(struct Core *core); +#endif + +void arch_core_free(struct Core *core); + +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) +void arch_core_save(FILE *f, const struct Core *core); +#endif + +#if defined(COMMAND_LOAD) +void arch_core_load(FILE *f, struct Core *core); +#endif + +uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix); +uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix); +uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_slice(const struct Core *core, uint64_t pix); +void arch_on_proc_kill(struct Core *core); +void arch_proc_step(struct Core *core, uint64_t pix); + +#if !defined(NDEBUG) +void arch_validate_proc(const struct Core *core, uint64_t pix); +#endif + +wchar_t arch_symbol(uint8_t inst); +const char *arch_mnemonic(uint8_t inst); + +#if defined(COMMAND_NEW) +void arch_push_data_header(void); +#endif +void arch_push_data_line(FILE *eva_file); + +// ---------------------------------------------------------------------------- +// [section] memory vector functions +// ---------------------------------------------------------------------------- +#if defined(MVEC_LOOP) +uint64_t mvec_loop(uint64_t addr) { + return addr % MVEC_SIZE; +} +#endif + +bool mvec_is_alloc(const struct Core *core, uint64_t addr) { + assert(core); + +#if defined(MVEC_LOOP) + return core->mvec[mvec_loop(addr)] & MALL_FLAG ? true : false; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr] & MALL_FLAG ? true : false; + } else { + return true; + } +#endif +} + +void mvec_alloc(struct Core *core, uint64_t addr) { + assert(core); + assert(!mvec_is_alloc(core, addr)); + +#if defined(MVEC_LOOP) + core->mvec[mvec_loop(addr)] |= MALL_FLAG; + + // Record deallocation event + ++core->aeva[mvec_loop(addr)]; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] |= MALL_FLAG; + + // Record deallocation event + ++core->aeva[addr]; +#endif + core->mall++; +} + +void mvec_free(struct Core *core, uint64_t addr) { + assert(core); + assert(mvec_is_alloc(core, addr)); + +#if defined(MVEC_LOOP) + core->mvec[mvec_loop(addr)] ^= MALL_FLAG; + + // Record deallocation event + ++core->aeva[mvec_loop(addr)]; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] ^= MALL_FLAG; + + // Record deallocation event + ++core->aeva[addr]; +#endif + core->mall--; +} + +uint8_t mvec_get_byte(const struct Core *core, uint64_t addr) { + assert(core); + +#if defined(MVEC_LOOP) + return core->mvec[mvec_loop(addr)]; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr]; + } else { + return 0; + } +#endif +} + +uint8_t mvec_get_inst(const struct Core *core, uint64_t addr) { + assert(core); + +#if defined(MVEC_LOOP) + return core->mvec[mvec_loop(addr)] & INST_MASK; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr] & INST_MASK; + } else { + return 0; + } +#endif +} + +void mvec_set_inst(struct Core *core, uint64_t addr, uint8_t inst) { + assert(core); + assert(inst < INST_CAP); + +#if defined(MVEC_LOOP) + core->mvec[mvec_loop(addr)] &= MALL_FLAG; + core->mvec[mvec_loop(addr)] |= inst; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] &= MALL_FLAG; + core->mvec[addr] |= inst; +#endif +} + +#if defined(MUTA_FLIP) +void mvec_flip_bit(struct Core *core, uint64_t addr, int bit) { + assert(core); + assert(bit < 8); + core->mvec[addr] ^= (1 << bit) & INST_MASK; +} +#endif + +bool mvec_proc_is_live(const struct Core *core, uint64_t pix) { + assert(core); + + return pix >= core->pfst && pix <= core->plst; +} + +bool mvec_is_in_mb0_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + uint64_t mb0a = arch_proc_mb0_addr(core, pix); + uint64_t mb0s = arch_proc_mb0_size(core, pix); + + return ((addr - mb0a) % MVEC_SIZE) < mb0s; +} + +bool mvec_is_in_mb1_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + uint64_t mb1a = arch_proc_mb1_addr(core, pix); + uint64_t mb1s = arch_proc_mb1_size(core, pix); + + return ((addr - mb1a) % MVEC_SIZE) < mb1s; +} + +bool mvec_is_proc_owner(const struct Core *core, uint64_t addr, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return mvec_is_in_mb0_of_proc(core, addr, pix) || mvec_is_in_mb1_of_proc(core, addr, pix); +} + +uint64_t mvec_get_owner(const struct Core *core, uint64_t addr) { + assert(core); +#if !defined(MVEC_LOOP) + assert(addr < MVEC_SIZE); +#endif + assert(mvec_is_alloc(core, addr)); + + for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) { + if (mvec_is_proc_owner(core, addr, pix)) { + return pix; + } + } + + assert(false); + return -1; +} + +// ---------------------------------------------------------------------------- +// [section] mutator functions +// ---------------------------------------------------------------------------- +#if SEED != 0 +#if defined(COMMAND_NEW) +uint64_t muta_smix(uint64_t *seed) { + assert(seed); + + uint64_t next = (*seed += 0x9e3779b97f4a7c15); + next = (next ^ (next >> 30)) * 0xbf58476d1ce4e5b9; + next = (next ^ (next >> 27)) * 0x94d049bb133111eb; + + return next ^ (next >> 31); +} +#endif + +uint64_t muta_ro64(uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t muta_next(struct Core *core) { + assert(core); + + uint64_t r = muta_ro64(core->muta[1] * 5, 7) * 9; + uint64_t t = core->muta[1] << 17; + + core->muta[2] ^= core->muta[0]; + core->muta[3] ^= core->muta[1]; + core->muta[1] ^= core->muta[2]; + core->muta[0] ^= core->muta[3]; + + core->muta[2] ^= t; + core->muta[3] = muta_ro64(core->muta[3], 45); + + return r; +} + +void muta_cosmic_ray(struct Core *core) { + assert(core); + + uint64_t a = muta_next(core) % MUTA_RANGE; + uint64_t b = muta_next(core); + + if (a < MVEC_SIZE) { +#if defined(MUTA_FLIP) + mvec_flip_bit(core, a, (int)(b % 8)); +#else + mvec_set_inst(core, a, b & INST_MASK); +#endif + } +} +#endif + +// ---------------------------------------------------------------------------- +// [section] process functions +// ---------------------------------------------------------------------------- +void proc_new(struct Core *core, const struct Proc *proc) { + assert(core); + assert(proc); + + if (core->pnum == core->pcap) { + // Reallocate dynamic array + uint64_t new_pcap = core->pcap * 2; + struct Proc *new_pvec = calloc(new_pcap, sizeof(struct Proc)); + + for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) { + uint64_t iold = pix % core->pcap; + uint64_t inew = pix % new_pcap; + memcpy(&new_pvec[inew], &core->pvec[iold], sizeof(struct Proc)); + } + + free(core->pvec); + core->pcap = new_pcap; + core->pvec = new_pvec; + } + + core->pnum++; + core->plst++; + memcpy(&core->pvec[core->plst % core->pcap], proc, sizeof(struct Proc)); + + // Store birth event in database + uint64_t child_addr = arch_proc_mb0_addr(core, core->plst); + uint64_t child_size = arch_proc_mb0_size(core, core->plst); + + for (uint64_t i = 0; i < child_size; i++) { + uint64_t addr = child_addr + i; +#if defined(MVEC_LOOP) + ++core->beva[mvec_loop(addr)]; +#else + ++core->beva[addr]; +#endif + } +} + +void proc_kill(struct Core *core) { + assert(core); + assert(core->pnum > 1); + + arch_on_proc_kill(core); + + core->pcur++; + core->pfst++; + core->pnum--; +} + +const struct Proc *proc_get(const struct Core *core, uint64_t pix) { + assert(core); + + if (mvec_proc_is_live(core, pix)) { + return &core->pvec[pix % core->pcap]; + } else { + return &g_dead_proc; + } +} + +struct Proc *proc_fetch(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + return &core->pvec[pix % core->pcap]; +} + +// ---------------------------------------------------------------------------- +// [section] core functions +// ---------------------------------------------------------------------------- +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) +void core_save(FILE *f, const struct Core *core) { + assert(f); + assert(core); + + fwrite(&core->cycl, sizeof(uint64_t), 1, f); + fwrite(&core->mall, sizeof(uint64_t), 1, f); + fwrite(core->muta, sizeof(uint64_t), 4, f); + fwrite(&core->pnum, sizeof(uint64_t), 1, f); + fwrite(&core->pcap, sizeof(uint64_t), 1, f); + fwrite(&core->pfst, sizeof(uint64_t), 1, f); + fwrite(&core->plst, sizeof(uint64_t), 1, f); + fwrite(&core->pcur, sizeof(uint64_t), 1, f); + fwrite(&core->psli, sizeof(uint64_t), 1, f); + fwrite(&core->ivpt, sizeof(uint64_t), 1, f); + fwrite(&core->emb0, sizeof(uint64_t), 1, f); + fwrite(&core->emb1, sizeof(uint64_t), 1, f); + fwrite(&core->eliv, sizeof(uint64_t), 1, f); + fwrite(&core->edea, sizeof(uint64_t), 1, f); + + fwrite(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f); + fwrite(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f); + fwrite(core->pvec, sizeof(struct Proc), core->pcap, f); + fwrite(core->mvec, sizeof(uint8_t), MVEC_SIZE, f); +#define EVENT_ARRAY(core, index, ev) \ + fwrite(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f); + EVENT_ARRAYS(core) +#undef EVENT_ARRAY + + arch_core_save(f, core); +} +#endif + +#if defined(COMMAND_NEW) +#if defined(ANC_BYTES) +void core_assemble_ancestor(struct Core *core) { + assert(core); + +#if defined(MVEC_LOOP) + uint64_t addr = UINT64_HALF; +#else + uint64_t addr = 0; +#endif + + uint8_t anc_bytes[] = ANC_BYTES; + + for (uint64_t i = 0; i < sizeof(anc_bytes); ++i, ++addr) { + for (uint64_t j = 0; j < CLONES; ++j) { + uint64_t addr_clone = addr + (MVEC_SIZE / CLONES) * j; + + mvec_alloc(core, addr_clone); + mvec_set_inst(core, addr_clone, anc_bytes[i]); + } + } +} +#endif + +void core_init(struct Core *core, uint64_t *seed) { + assert(core); + assert(seed); + +#if SEED != 0 + assert(*seed); + core->muta[0] = muta_smix(seed); + core->muta[1] = muta_smix(seed); + core->muta[2] = muta_smix(seed); + core->muta[3] = muta_smix(seed); +#else + (void)seed; +#endif + + core->pnum = CLONES; + core->pcap = CLONES; + core->plst = CLONES - 1; + core->pcur = CLONES - 1; + core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t)); + core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t)); + core->pvec = calloc(core->pcap, sizeof(struct Proc)); + + assert(core->iviv); + assert(core->ivav); + assert(core->pvec); + +#if defined(ANC_BYTES) + core_assemble_ancestor(core); + arch_core_init(core); +#endif +} +#endif + +#if defined(COMMAND_LOAD) +void core_load(FILE *f, struct Core *core) { + assert(f); + assert(core); + + fread(&core->cycl, sizeof(uint64_t), 1, f); + fread(&core->mall, sizeof(uint64_t), 1, f); + fread(core->muta, sizeof(uint64_t), 4, f); + fread(&core->pnum, sizeof(uint64_t), 1, f); + fread(&core->pcap, sizeof(uint64_t), 1, f); + fread(&core->pfst, sizeof(uint64_t), 1, f); + fread(&core->plst, sizeof(uint64_t), 1, f); + fread(&core->pcur, sizeof(uint64_t), 1, f); + fread(&core->psli, sizeof(uint64_t), 1, f); + fread(&core->ivpt, sizeof(uint64_t), 1, f); + fread(&core->emb0, sizeof(uint64_t), 1, f); + fread(&core->emb1, sizeof(uint64_t), 1, f); + fread(&core->eliv, sizeof(uint64_t), 1, f); + fread(&core->edea, sizeof(uint64_t), 1, f); + + core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t)); + core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t)); + core->pvec = calloc(core->pcap, sizeof(struct Proc)); + + assert(core->iviv); + assert(core->ivav); + assert(core->pvec); + + fread(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f); + fread(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f); + fread(core->pvec, sizeof(struct Proc), core->pcap, f); + fread(core->mvec, sizeof(uint8_t), MVEC_SIZE, f); +#define EVENT_ARRAY(core, index, ev) \ + fread(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f); + EVENT_ARRAYS(core) +#undef EVENT_ARRAY + + arch_core_load(f, core); +} +#endif + +void core_pull_ipcm(struct Core *core) { + assert(core); + assert(core->ivpt < SYNC_INTERVAL); + + uint8_t *iinst = &core->iviv[core->ivpt]; + uint64_t *iaddr = &core->ivav[core->ivpt]; + + if ((*iinst & IPC_FLAG) != 0) { + mvec_set_inst(core, *iaddr, *iinst & INST_MASK); + + *iinst = 0; + *iaddr = 0; + } + + assert(*iinst == 0); + assert(*iaddr == 0); +} + +void core_push_ipcm(struct Core *core, uint8_t inst, uint64_t addr) { + assert(core); + assert(core->ivpt < SYNC_INTERVAL); + assert((inst & IPC_FLAG) == 0); + + uint8_t *iinst = &core->iviv[core->ivpt]; + uint64_t *iaddr = &core->ivav[core->ivpt]; + + assert(*iinst == 0); + assert(*iaddr == 0); + + *iinst = inst | IPC_FLAG; + *iaddr = addr; +} + +void core_step(struct Core *core) { + assert(core); + + if (core->psli != 0) { + core_pull_ipcm(core); + + // Save execution event locations in database + assert(mvec_proc_is_live(core, core->pcur)); + + uint64_t pcur_ip = arch_proc_ip_addr(core, core->pcur); + + if (mvec_is_in_mb0_of_proc(core, pcur_ip, core->pcur)) { + ++core->emb0; + } else if (mvec_is_in_mb1_of_proc(core, pcur_ip, core->pcur)) { + ++core->emb1; + } else if (mvec_is_alloc(core, pcur_ip)) { + ++core->eliv; + } else { + ++core->edea; + } + +#if defined(MVEC_LOOP) + core->eeva[mvec_loop(pcur_ip)]++; +#else + if (pcur_ip < MVEC_SIZE) { + core->eeva[pcur_ip]++; + } +#endif + + arch_proc_step(core, core->pcur); + + core->psli--; + core->ivpt++; + + return; + } + + if (core->pcur != core->plst) { + core->psli = arch_proc_slice(core, ++core->pcur); + core_step(core); + return; + } + + core->pcur = core->pfst; + core->psli = arch_proc_slice(core, core->pcur); + core->cycl++; + + // TODO: Implement a day-night cycle + while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) { + proc_kill(core); + } + +#if SEED != 0 + muta_cosmic_ray(core); +#endif + core_step(core); +} + +// ---------------------------------------------------------------------------- +// [section] salis functions +// ---------------------------------------------------------------------------- +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) +void salis_save(const char *path) { + size_t size = 0; + char *in = NULL; + FILE *f = open_memstream(&in, &size); + + assert(f); + + for (int i = 0; i < CORES; ++i) { + core_save(f, &g_cores[i]); + } + + fwrite(&g_steps, sizeof(uint64_t), 1, f); + fwrite(&g_syncs, sizeof(uint64_t), 1, f); + fclose(f); + + assert(size); + char *out = malloc(size); + assert(out); + + struct DeflateParams params = { + .size = size, + .in = (Bytef *)in, + .out = (Bytef *)out, + }; + + comp_deflate(¶ms); + + FILE *fx = fopen(path, "wb"); + assert(fx); + + fwrite(&size, sizeof(size_t), 1, fx); + fwrite(out, sizeof(char), params.strm.total_out, fx); + fclose(fx); + + comp_deflate_end(¶ms); + + free(in); + free(out); +} + +void salis_auto_save(void) { +#if defined(NDEBUG) + snprintf( +#else + int rem = snprintf( +#endif + g_asav_pbuf, + AUTOSAVE_NAME_LEN, + "%s-%#018lx", + SIM_PATH, + g_steps + ); + + assert(rem >= 0); + assert(rem < AUTOSAVE_NAME_LEN); + + log_info("Saving simulation state on step %#lx", g_steps); + salis_save(g_asav_pbuf); +} +#endif + +#if defined(COMMAND_NEW) +void salis_push_data_header(void) { + assert(g_sim_db); + + log_info("Creating core table in SQLite database"); + sql_exec( + NULL, NULL, + "create table core (" +#define EVENT_ARRAY(core, index, ev) \ + #ev EVENT_ARRAYS_SIZE_COL_MARKER #core " int not null, " \ + #ev "_" #core " blob, " +#define FOR_CORE(i) \ + "cycl_" #i " int not null, " \ + "mall_" #i " int not null, " \ + "pnum_" #i " int not null, " \ + "pfst_" #i " int not null, " \ + "plst_" #i " int not null, " \ + "amb0_" #i " real not null, " \ + "amb1_" #i " real not null, " \ + "emb0_" #i " int not null, " \ + "emb1_" #i " int not null, " \ + "eliv_" #i " int not null, " \ + "edea_" #i " int not null, " \ + EVENT_ARRAYS(i) + FOR_CORES +#undef FOR_CORE +#undef EVENT_ARRAY + "step int not null" + ");" + ); + + arch_push_data_header(); +} +#endif + +void salis_push_data_line(void) { + assert(g_sim_db); + + // Measure average membory block sizes + double amb0[CORES] = { 0 }; + double amb1[CORES] = { 0 }; + + for (int i = 0; i < CORES; ++i) { + struct Core *core = &g_cores[i]; + + for (uint64_t j = core->pfst; j <= core->plst; ++j) { + amb0[i] += (double)arch_proc_mb0_size(core, j); + amb1[i] += (double)arch_proc_mb1_size(core, j); + } + + amb0[i] /= core->pnum; + amb1[i] /= core->pnum; + } + + // Compress event arrays + // Compression (deflation) is CPU intensive so it's done in parallel + memset(&g_eva_deflate_params, 0, sizeof(struct DeflateParams) * CORES * EVENT_ARRAYS_COUNT); + uint64_t blob_sizes[CORES][EVENT_ARRAYS_COUNT]; + + for (int i = 0; i < CORES; ++i) { + for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) { + uint64_t *in = NULL; + + switch (j) { +#define EVENT_ARRAY(core, index, ev) \ + case index: in = g_cores[i].ev##a; break; + EVENT_ARRAYS(core) +#undef EVENT_ARRAY + default: assert(false); + } + + // Compress event data + struct DeflateParams *params = &g_eva_deflate_params[i][j]; + params->size = EVENT_ARRAYS_SIZE, + params->in = (Bytef *)in, + params->out = (Bytef *)malloc(EVENT_ARRAYS_SIZE), + thrd_create(&g_eva_thrds[i][j], (thrd_start_t)comp_deflate, params); + } + } + + int rem = snprintf( + g_eva_pbuf, + EVA_SAVE_NAME_LEN, + "%s/evas-%#018lx", + SIM_EDIR, + g_steps + ); + + assert(rem >= 0); + assert(rem < EVA_SAVE_NAME_LEN); + (void)rem; + + log_info("Saving event-array data to file: %s", g_eva_pbuf); + FILE *eva_file = fopen(g_eva_pbuf, "wb"); + + for (int i = 0; i < CORES; ++i) { + for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) { + thrd_join(g_eva_thrds[i][j], NULL); + struct DeflateParams *params = &g_eva_deflate_params[i][j]; + blob_sizes[i][j] = params->strm.total_out; + fwrite(params->out, sizeof(char), blob_sizes[i][j], eva_file); + comp_deflate_end(params); + free(params->out); + } + } + + log_info("Pushing row to core table in SQLite database"); + sql_exec( + NULL, NULL, + "insert into core (" +#define EVENT_ARRAY(core, index, ev) \ + #ev EVENT_ARRAYS_SIZE_COL_MARKER #core ", " +#define FOR_CORE(i) \ + "cycl_" #i ", " \ + "mall_" #i ", " \ + "pnum_" #i ", " \ + "pfst_" #i ", " \ + "plst_" #i ", " \ + "amb0_" #i ", " \ + "amb1_" #i ", " \ + "emb0_" #i ", " \ + "emb1_" #i ", " \ + "eliv_" #i ", " \ + "edea_" #i ", " \ + EVENT_ARRAYS(i) + FOR_CORES +#undef FOR_CORE +#undef EVENT_ARRAY + "step" + ") values (" +#define EVENT_ARRAY(core, index, ev) \ + "%ld, " +#define FOR_CORE(i) \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%f, " \ + "%f, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + EVENT_ARRAYS(i) + FOR_CORES +#undef FOR_CORE +#undef EVENT_ARRAY + "%ld" + ");", +#define EVENT_ARRAY(core, index, ev) \ + blob_sizes[core][index], +#define FOR_CORE(i) \ + g_cores[i].cycl, \ + g_cores[i].mall, \ + g_cores[i].pnum, \ + g_cores[i].pfst, \ + g_cores[i].plst, \ + amb0[i], \ + amb1[i], \ + g_cores[i].emb0, \ + g_cores[i].emb1, \ + g_cores[i].eliv, \ + g_cores[i].edea, \ + EVENT_ARRAYS(i) + FOR_CORES +#undef FOR_CORE +#undef EVENT_ARRAY + g_steps + ); + + // Reset data aggregation fields + for (int i = 0; i < CORES; ++i) { + struct Core *core = &g_cores[i]; + + core->emb0 = 0; + core->emb1 = 0; + core->eliv = 0; + core->edea = 0; + +#define EVENT_ARRAY(core, index, ev) \ + memset(core->ev##a, 0, EVENT_ARRAYS_SIZE); + EVENT_ARRAYS(core) +#undef EVENT_ARRAY + } + + // Push arch-specific data + arch_push_data_line(eva_file); + fclose(eva_file); +} + +#if defined(COMMAND_NEW) +void salis_init(void) { + uint64_t seed = SEED; + + for (int i = 0; i < CORES; ++i) { + core_init(&g_cores[i], &seed); + } + +#if defined(COMMAND_NEW) + salis_auto_save(); +#endif + + // Initialize database + sql_open(); + salis_push_data_header(); + salis_push_data_line(); +} +#endif + +#if defined(COMMAND_LOAD) +void salis_load(void) { + FILE *fx = fopen(SIM_PATH, "rb"); + assert(fx); + + fseek(fx, 0, SEEK_END); + size_t x_size = ftell(fx) - sizeof(size_t); + char *in = malloc(x_size); + rewind(fx); + assert(x_size); + assert(in); + + size_t size = 0; + fread(&size, sizeof(size_t), 1, fx); + fread(in, 1, x_size, fx); + fclose(fx); + assert(size); + + char *out = malloc(size); + assert(out); + + struct InflateParams params = { + .avail_in = x_size, + .size = size, + .in = (Bytef *)in, + .out = (Bytef *)out, + }; + + comp_inflate(¶ms); + comp_inflate_end(¶ms); + + FILE *f = fmemopen(out, size, "rb"); + + assert(f); + + for (int i = 0; i < CORES; ++i) { + core_load(f, &g_cores[i]); + } + + fread(&g_steps, sizeof(uint64_t), 1, f); + fread(&g_syncs, sizeof(uint64_t), 1, f); + fclose(f); + free(in); + free(out); + + sql_open(); +} +#endif + +int salis_thread(struct Core *core) { + assert(core); + + for (uint64_t i = 0; i < core->thrd_steps; ++i) { + core_step(core); + } + + return 0; +} + +void salis_run_thread(uint64_t ns) { + for (int i = 0; i < CORES; ++i) { + g_cores[i].thrd_steps = ns; + + thrd_create( + &g_cores[i].thrd, + (thrd_start_t)salis_thread, + &g_cores[i] + ); + } + + for (int i = 0; i < CORES; ++i) { + thrd_join(g_cores[i].thrd, NULL); + } + + g_steps += ns; +} + +void salis_sync(void) { +#if !defined(NDEBUG) + for (int i = 0; i < CORES; ++i) { + assert(g_cores[i].ivpt == SYNC_INTERVAL); + } +#endif + + uint8_t *iviv0 = g_cores[0].iviv; + uint64_t *ivav0 = g_cores[0].ivav; + + for (int i = 1; i < CORES; ++i) { + g_cores[i - 1].iviv = g_cores[i].iviv; + g_cores[i - 1].ivav = g_cores[i].ivav; + } + + g_cores[CORES - 1].iviv = iviv0; + g_cores[CORES - 1].ivav = ivav0; + + for (int i = 0; i < CORES; ++i) { + g_cores[i].ivpt = 0; + } + + g_syncs++; +} + +void salis_loop(uint64_t ns, uint64_t dt) { + assert(dt); + + if (ns < dt) { + salis_run_thread(ns); + return; + } + + salis_run_thread(dt); + salis_sync(); + +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) + if (g_steps % AUTOSAVE_INTERVAL == 0) { + salis_auto_save(); + } +#endif + + if (g_steps % DATA_PUSH_INTERVAL == 0) { + salis_push_data_line(); + } + + salis_loop(ns - dt, SYNC_INTERVAL); +} + +#if !defined(NDEBUG) +void salis_validate_core(const struct Core *core) { + assert(core->cycl <= g_steps); + assert(core->plst >= core->pfst); + assert(core->pnum == core->plst + 1 - core->pfst); + assert(core->pnum <= core->pcap); + assert(core->pcur >= core->pfst && core->pcur <= core->plst); + + uint64_t mall = 0; + + for (uint64_t i = 0; i < MVEC_SIZE; ++i) { + mall += mvec_is_alloc(core, i) ? 1 : 0; + } + + assert(core->mall == mall); + + for (uint64_t i = core->pfst; i <= core->plst; ++i) { + arch_validate_proc(core, i); + } + + for (uint64_t i = 0; i < SYNC_INTERVAL; ++i) { + uint8_t iinst = core->iviv[i]; + + if ((iinst & IPC_FLAG) == 0) { + uint64_t iaddr = core->ivav[i]; + + assert(iinst == 0); + assert(iaddr == 0); + } + } + + assert(core->ivpt == g_steps % SYNC_INTERVAL); +} + +void salis_validate(void) { + assert(g_steps / SYNC_INTERVAL == g_syncs); + + for (int i = 0; i < CORES; ++i) { + salis_validate_core(&g_cores[i]); + } +} +#endif + +void salis_step(uint64_t ns) { + assert(ns); + salis_loop(ns, SYNC_INTERVAL - (g_steps % SYNC_INTERVAL)); + +#if !defined(NDEBUG) + salis_validate(); +#endif +} + +void salis_free(void) { + sql_close(); + + for (int i = 0; i < CORES; ++i) { + arch_core_free(&g_cores[i]); + + assert(g_cores[i].pvec); + assert(g_cores[i].iviv); + assert(g_cores[i].ivav); + + free(g_cores[i].pvec); + free(g_cores[i].iviv); + free(g_cores[i].ivav); + + g_cores[i].pvec = NULL; + g_cores[i].iviv = NULL; + g_cores[i].ivav = NULL; + } +} + +// ---------------------------------------------------------------------------- +// [section] architecture & ui includes +// ---------------------------------------------------------------------------- +#include "arch.c" + +#if defined(COMMAND_NEW) || defined(COMMAND_LOAD) +#include "ui.c" +#endif diff --git a/core/server.c b/core/server.c new file mode 100644 index 0000000..6848cf6 --- /dev/null +++ b/core/server.c @@ -0,0 +1,418 @@ +// index +// [section] includes +// [section] macros +// [section] structs +// [section] globals +// [section] event array render function +// [section] sql callbacks +// [section] main functions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include <arpa/inet.h> +#include <assert.h> +#include <json-c/json.h> +#include <signal.h> +#include <sqlite3.h> +#include <string.h> +#include <threads.h> +#include <zlib.h> + +#include "common.c" +#include "compress.c" +#include "logger.c" +#include "sql.c" + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define BACKLOG 10 + +// ---------------------------------------------------------------------------- +// [section] structs +// ---------------------------------------------------------------------------- +struct Socket { + int fd; + struct sockaddr_in addr; +}; + +struct CallbackContext { + struct json_object *response; + int64_t response_rows; + int64_t hm_left; + int64_t hm_pixel_count; + int64_t hm_pixel_pow; +}; + +struct RenderContext { + const struct CallbackContext *callback_context; + void *blob; + size_t blob_size; + uint64_t eva[EVENT_ARRAYS_SIZE]; + int64_t out[DEFVAL_HM_PIXEL_COUNT]; +}; + +// ---------------------------------------------------------------------------- +// [section] globals +// ---------------------------------------------------------------------------- +struct json_object *g_response_header; +size_t g_eva_count; +char g_eva_pbuf[EVA_SAVE_NAME_LEN]; + +// ---------------------------------------------------------------------------- +// [section] event array render function +// ---------------------------------------------------------------------------- +int eva_render(void *data) { + assert(data); + + struct RenderContext *render_context = (struct RenderContext *)data; + int64_t hm_left = render_context->callback_context->hm_left; + int64_t hm_pixel_count = render_context->callback_context->hm_pixel_count; + int64_t hm_pixel_pow = render_context->callback_context->hm_pixel_pow; + int64_t hm_pixel_res = 1 << hm_pixel_pow; + const void *blob = render_context->blob; + size_t blob_size = render_context->blob_size; + + assert(blob); + +#if defined(MVEC_LOOP) + hm_left %= MVEC_SIZE; +#endif + +#if !defined(MVEC_LOOP) +#if !defined(NDEBUG) + int64_t hm_right = hm_left + hm_pixel_res * hm_pixel_count; +#endif + assert(hm_left < (int64_t)MVEC_SIZE); + assert(hm_right <= (int64_t)MVEC_SIZE); +#endif + + // Inflate blob + struct InflateParams params = { + .avail_in = blob_size, + .size = EVENT_ARRAYS_SIZE, + .in = (Bytef *)blob, + .out = (Bytef *)render_context->eva, + }; + + comp_inflate(¶ms); + comp_inflate_end(¶ms); + + for (int64_t i = 0; i < hm_pixel_count; i++) { + render_context->out[i] = 0l; + + for (int64_t j = 0; j < hm_pixel_res; j++) { + int64_t coord = hm_left + (i * hm_pixel_res) + j; +#if defined(MVEC_LOOP) + coord %= MVEC_SIZE; +#endif + render_context->out[i] += render_context->eva[coord]; + } + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// [section] sql callbacks +// ---------------------------------------------------------------------------- +void sql_callback_add_column_name(sqlite3_stmt *sql_stmt, void *data) { + assert(sql_stmt); + assert(data); + assert(sqlite3_column_type(sql_stmt, 0) == SQLITE_TEXT); + assert(sqlite3_column_type(sql_stmt, 1) == SQLITE_TEXT); + assert(!strcmp(sqlite3_column_name(sql_stmt, 0), "name")); + assert(!strcmp(sqlite3_column_name(sql_stmt, 1), "type")); + + const char *col_name = (const char *)sqlite3_column_text(sql_stmt, 0); + struct json_object *response_header = (struct json_object *)data; + + if (!json_object_object_get_ex(response_header, col_name, NULL)) { + json_object_object_add(response_header, col_name, json_object_new_array()); + } + + if (strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER)) { + g_eva_count++; + } +} + +void sql_callback_add_data(sqlite3_stmt *sql_stmt, void *data) { + assert(sql_stmt); + assert(data); + + FILE *eva_file = NULL; + struct CallbackContext *callback_context = (struct CallbackContext *)data; + struct RenderContext *render_contexts = calloc(g_eva_count, sizeof(struct RenderContext)); + thrd_t *threads = calloc(g_eva_count, sizeof(thrd_t)); + size_t tid = 0; + + for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) { + assert(i < sqlite3_column_count(sql_stmt)); + assert(sqlite3_column_type(sql_stmt, i) == SQLITE_INTEGER || sqlite3_column_type(sql_stmt, i) == SQLITE_FLOAT); + + const char *col_name = sqlite3_column_name(sql_stmt, i); + struct json_object *col_data = json_object_object_get(callback_context->response, col_name); + assert(col_name); + + if (!col_data) continue; + + int64_t col_value = sqlite3_column_int64(sql_stmt, i); + json_object_array_add(col_data, json_object_new_int64(col_value)); + + if (i == 1) { + assert(!strcmp(col_name, "step")); + + int rem = snprintf( + g_eva_pbuf, + EVA_SAVE_NAME_LEN, + "%s/evas-%#018lx", + SIM_EDIR, + col_value + ); + + assert(rem >= 0); + assert(rem < EVA_SAVE_NAME_LEN); + (void)rem; + + eva_file = fopen(g_eva_pbuf, "rb"); + assert(eva_file); + } + + if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) { + assert(eva_file); + assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER)); + + render_contexts[tid].callback_context = callback_context; + render_contexts[tid].blob = malloc(col_value); + render_contexts[tid].blob_size = col_value; + assert(render_contexts[tid].blob); + + size_t red = fread(render_contexts[tid].blob, 1, col_value, eva_file); + assert(red == (size_t)col_value); + (void)red; + + thrd_create(&threads[tid], (thrd_start_t)eva_render, &render_contexts[tid]); + + tid++; + i++; + } + } + + assert(tid == g_eva_count); + tid = 0; + + for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) { + const char *col_name = sqlite3_column_name(sql_stmt, i); + struct json_object *col_data = json_object_object_get(callback_context->response, col_name); + assert(col_name); + + if (!col_data) continue; + + if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) { + assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER)); + assert(render_contexts[tid].blob); + + const char *eva_col_name = sqlite3_column_name(sql_stmt, i + 1); + struct json_object *eva_col_data = json_object_object_get(callback_context->response, eva_col_name); + assert(eva_col_name); + assert(eva_col_data); + + thrd_join(threads[tid], NULL); + + for (int64_t j = 0; j < callback_context->hm_pixel_count; j++) { + json_object_array_add(eva_col_data, json_object_new_int64(render_contexts[tid].out[j])); + } + + free(render_contexts[tid].blob); + + tid++; + i++; + } + } + + assert(tid == g_eva_count); + + callback_context->response_rows++; + log_info("Processed row #%ld", callback_context->response_rows); + + assert(eva_file); + fclose(eva_file); + free(render_contexts); + free(threads); +} + +// ---------------------------------------------------------------------------- +// [section] main functions +// ---------------------------------------------------------------------------- +void sig_handler(int signo) { + (void)signo; + + log_warn("Signal received, will stop SALIS data server"); + json_object_put(g_response_header); + sql_close(); + exit(0); +} + +void respond_name(int socket_fd) { + log_info("Client requested simulation name"); + + struct json_object *sim_name = json_object_new_object(); + json_object_object_add(sim_name, "name", json_object_new_string(NAME)); + json_object_to_fd(socket_fd, sim_name, 0); + json_object_put(sim_name); +} + +void respond_opts(int socket_fd) { + log_info("Client requested simulation options"); + + struct json_object *sim_opts = json_object_from_file(SIM_OPTS); + json_object_to_fd(socket_fd, sim_opts, 0); + json_object_put(sim_opts); +} + +void respond_hash(int socket_fd) { + log_info("Client requested git hash"); + + char buff[41] = { 0 }; + FILE *pipe = popen("git rev-parse HEAD", "r"); + fread(buff, sizeof(char), 40, pipe); + pclose(pipe); + + struct json_object *git_hash = json_object_new_object(); + json_object_object_add(git_hash, "hash", json_object_new_string(buff)); + json_object_to_fd(socket_fd, git_hash, 0); + json_object_put(git_hash); +} + +void respond_data(int socket_fd, struct json_object *request) { + assert(request); + + const char *request_str = json_object_to_json_string(request); + log_info("Client requested simulation data with the following parameters: %s", request_str); + const char *x_axis = json_object_get_string(json_object_object_get(request, "x-axis")); + int64_t x_current = json_object_get_int64(json_object_object_get(request, "x-current")); + int64_t x_high = json_object_get_int64(json_object_object_get(request, "x-high")); + int64_t nth = json_object_get_int64(json_object_object_get(request, "nth")); + int64_t entries = json_object_get_int64(json_object_object_get(request, "entries")); + + struct CallbackContext callback_context = { + .response = NULL, + .response_rows = 0l, + .hm_left = json_object_get_int64(json_object_object_get(request, "hm-left")), + .hm_pixel_count = json_object_get_int64(json_object_object_get(request, "hm-pixel-count")), + .hm_pixel_pow = json_object_get_int64(json_object_object_get(request, "hm-pixel-pow")), + }; + + json_object_deep_copy(g_response_header, &callback_context.response, NULL); + + const char *x_axis_pref = (!strcmp(x_axis, "rowid") || !strcmp(x_axis, "step")) ? "core." : ""; + + sql_exec( + sql_callback_add_data, + &callback_context, + "select * from (" + "select core.rowid, core.step, * from core inner join arch " + "where core.rowid = arch.rowid and %s%s > %ld and %s%s <= %ld and core.rowid %% %ld == 0 " + "order by %s%s desc limit %ld" + ") order by %s asc;", + x_axis_pref, + x_axis, + x_current, + x_axis_pref, + x_axis, + x_high, + nth, + x_axis_pref, + x_axis, + entries, + x_axis + ); + + log_info("Sending client %ld rows of data", callback_context.response_rows); + json_object_to_fd(socket_fd, callback_context.response, 0); + json_object_put(callback_context.response); + + shutdown(socket_fd, SHUT_WR); +} + +int handle_client(struct Socket *socket) { + assert(socket); + + char socket_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &socket->addr.sin_addr, socket_ip, INET_ADDRSTRLEN); + log_info("Client connected: %s:%d", socket_ip, ntohs(socket->addr.sin_port)); + + struct json_object *request_json = json_object_from_fd(socket->fd); + struct json_object *request_str = NULL; + + if (!json_object_object_get_ex(request_json, "request", &request_str)) assert(false); + + const char *request = json_object_get_string(request_str); + assert(request); + + if (!strcmp(request, "name")) { + respond_name(socket->fd); + } else if (!strcmp(request, "opts")) { + respond_opts(socket->fd); + } else if (!strcmp(request, "hash")) { + respond_hash(socket->fd); + } else if (!strcmp(request, "data")) { + respond_data(socket->fd, request_json); + } else { + assert(false); + } + + json_object_put(request_json); + + log_info("Client disconnected: %s:%d", socket_ip, ntohs(socket->addr.sin_port)); + close(socket->fd); + + free(socket); + return 0; +} + +int main(void) { + log_info("Initializing salis data server"); + log_info("Connecting to database in: %s", DATA_PUSH_PATH); + sql_open(); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + signal(SIGPIPE, SIG_IGN); // ignore broken pipes + + log_info("Creating response header"); + g_response_header = json_object_new_object(); + json_object_object_add(g_response_header, "rowid", json_object_new_array()); + sql_exec( + sql_callback_add_column_name, + g_response_header, + "select name, type from pragma_table_info('core') union select name, type from pragma_table_info('arch');" + ); + log_info("Found %lu eva-size columns in database", g_eva_count); + + log_info("Binding to port: %d", PORT); + int opt = 1; + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + struct sockaddr_in socket_addr = { 0 }; + socket_addr.sin_family = AF_INET; + socket_addr.sin_addr.s_addr = INADDR_ANY; + socket_addr.sin_port = htons(PORT); + bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(struct sockaddr_in)); + + listen(socket_fd, BACKLOG); + log_info("Listening..."); + + while (true) { + struct Socket *socket = calloc(1, sizeof(struct Socket)); + socklen_t socket_len = sizeof(struct sockaddr_in); + socket->fd = accept(socket_fd, (struct sockaddr *)&socket->addr, &socket_len); + + thrd_t thread; + thrd_create(&thread, (thrd_start_t)handle_client, socket); + thrd_detach(thread); + } + + return 0; +} diff --git a/core/sql.c b/core/sql.c new file mode 100644 index 0000000..760adc4 --- /dev/null +++ b/core/sql.c @@ -0,0 +1,73 @@ +#define DATA_PUSH_BUSY_TIMEOUT 600000 + +sqlite3 *g_sim_db; + +void sql_exec(void (*callback)(sqlite3_stmt *sql_stmt, void *data), void *data, const char *sql_format, ...) { + assert(sql_format); + + va_list args; + va_start(args, sql_format); + int sql_len = vsnprintf(NULL, 0, sql_format, args) + 1; + char *sql_str = malloc(sql_len); + assert(sql_str); + va_end(args); + + va_start(args, sql_format); + vsprintf(sql_str, sql_format, args); + va_end(args); + + int sql_res; + sqlite3_stmt *sql_stmt; + + sql_res = sqlite3_prepare_v2(g_sim_db, sql_str, -1, &sql_stmt, NULL); + assert(sql_res == SQLITE_OK); + free(sql_str); + + while (true) { + sql_res = sqlite3_step(sql_stmt); + + if (sql_res == SQLITE_ROW) { + if (callback) { + callback(sql_stmt, data); + } + + continue; + } + + if (sql_res == SQLITE_DONE) { + break; + } + + log_warn("SQLite database returned error %d with message:", sql_res); + log_warn(sqlite3_errmsg(g_sim_db)); + + // Only handle SQLITE_BUSY error, in which case we retry the query. + // Setting 'journal_mode=wal;' should help prevent busy database errors. + if (sql_res == SQLITE_BUSY) { + log_info("Will retry query..."); + continue; + } + + assert(false); + } + + sqlite3_finalize(sql_stmt); +} + +void sql_open(void) { + sqlite3_open(DATA_PUSH_PATH, &g_sim_db); + assert(g_sim_db); + + // Install busy handler to retry transactions if DB is locked + sqlite3_busy_timeout(g_sim_db, DATA_PUSH_BUSY_TIMEOUT); + + // Enable Write-Ahead Logging (WAL) + // This seems to help prevent DB locks when displaying live data. + // See: https://sqlite.org/wal.html + sql_exec(NULL, NULL, "pragma journal_mode=wal;"); +} + +void sql_close(void) { + assert(g_sim_db); + sqlite3_close(g_sim_db); +} |
