aboutsummaryrefslogtreecommitdiff
path: root/core/client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/client.cpp')
-rw-r--r--core/client.cpp1553
1 files changed, 1553 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;
+}