aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/dummy/arch_vars.py1
-rw-r--r--arch/v1/arch.c40
-rw-r--r--arch/v1/arch_vars.py10
-rw-r--r--core.c94
-rw-r--r--data/compress.c56
-rw-r--r--data/render.c86
-rw-r--r--data/vue/App.vue212
-rw-r--r--data/vue/Plot.vue104
-rw-r--r--data/vue/Section.vue4
-rwxr-xr-xsalis.py80
10 files changed, 490 insertions, 197 deletions
diff --git a/arch/dummy/arch_vars.py b/arch/dummy/arch_vars.py
index 6e37eca..c8d5e55 100644
--- a/arch/dummy/arch_vars.py
+++ b/arch/dummy/arch_vars.py
@@ -25,3 +25,4 @@ class ArchVars:
]
self.plots = {}
+ self.heatmaps = {}
diff --git a/arch/v1/arch.c b/arch/v1/arch.c
index 152a97d..49d9db5 100644
--- a/arch/v1/arch.c
+++ b/arch/v1/arch.c
@@ -47,7 +47,7 @@ void arch_core_save(FILE *f, const struct Core *core) {
fwrite(&core->wdea, sizeof(uint64_t), 1, f);
#if defined(DATA_PUSH_PATH)
- //fwrite(core->weva, sizeof(uint64_t), MVEC_SIZE, f);
+ fwrite(core->weva, sizeof(uint64_t), MVEC_SIZE, f);
#endif
}
#endif
@@ -64,7 +64,7 @@ void arch_core_load(FILE *f, struct Core *core) {
fread(&core->wdea, sizeof(uint64_t), 1, f);
#if defined(DATA_PUSH_PATH)
- //fread(core->weva, sizeof(uint64_t), MVEC_SIZE, f);
+ fread(core->weva, sizeof(uint64_t), MVEC_SIZE, f);
#endif
}
#endif
@@ -626,19 +626,22 @@ void _write(struct Core *core, uint64_t pix) {
if (_is_writeable_by(core, *regs[0], pix)) {
// Store write event
uint8_t inst = *regs[1] % INST_COUNT;
+ uint8_t inst_rep = *regs[1] % INST_CAP;
+ uint64_t addr = *regs[0];
++core->iwrt[inst];
+ ++core->weva[addr];
- if (mvec_is_in_mb0_of_proc(core, *regs[0], pix)) {
+ if (mvec_is_in_mb0_of_proc(core, addr, pix)) {
++core->wmb0;
- } else if (mvec_is_in_mb1_of_proc(core, *regs[0], pix)) {
+ } else if (mvec_is_in_mb1_of_proc(core, addr, pix)) {
++core->wmb1;
} else {
++core->wdea;
}
// Write instruction
- mvec_set_inst(core, *regs[0], *regs[1] % INST_CAP);
+ mvec_set_inst(core, addr, inst_rep);
}
_increment_ip(core, pix);
@@ -860,7 +863,7 @@ void arch_push_data_header() {
}
// Memory events
- char *eprefs[] = { /* "wev" */ };
+ char *eprefs[] = { "wev" };
int eprefs_cnt = sizeof(eprefs) / sizeof(eprefs[0]);
for (int i = 0; i < CORES; ++i) {
@@ -984,16 +987,16 @@ void arch_push_data_line() {
}
// TODO: insert write memory events
- char *eprefs[] = { /* "wev" */ };
+ char *eprefs[] = { "wev" };
int eprefs_cnt = sizeof(eprefs) / sizeof(eprefs[0]);
for (int i = 0; i < CORES; ++i) {
for (int j = 0; j < eprefs_cnt; ++j) {
uint64_t *in = NULL;
- //if (!strcmp("wev", eprefs[j])) {
- // in = g_cores[i].weva;
- //}
+ if (!strcmp("wev", eprefs[j])) {
+ in = g_cores[i].weva;
+ }
// Compress event data
size_t size = sizeof(uint64_t) * MVEC_SIZE;
@@ -1001,18 +1004,7 @@ void arch_push_data_line() {
assert(out);
z_stream strm = { 0 };
- strm.zalloc = NULL;
- strm.zfree = NULL;
- strm.opaque = NULL;
-
- deflateInit(&strm, Z_DEFAULT_COMPRESSION);
-
- strm.avail_in = size;
- strm.avail_out = size;
- strm.next_in = (Bytef *)in;
- strm.next_out = (Bytef *)out;
-
- deflate(&strm, Z_FINISH);
+ salis_deflate(&strm, size, (Bytef *)in, (Bytef *)out);
// Insert blob
const void *blob = out;
@@ -1039,7 +1031,7 @@ void arch_push_data_line() {
blob_size, g_steps
);
- deflateEnd(&strm);
+ salis_deflate_end(&strm);
free(out);
}
}
@@ -1055,7 +1047,7 @@ void arch_push_data_line() {
core->wmb1 = 0;
core->wdea = 0;
- //memset(core->weva, 0, sizeof(uint64_t) * MVEC_SIZE);
+ memset(core->weva, 0, sizeof(uint64_t) * MVEC_SIZE);
}
}
#endif
diff --git a/arch/v1/arch_vars.py b/arch/v1/arch_vars.py
index 65c5e87..b9d4c4f 100644
--- a/arch/v1/arch_vars.py
+++ b/arch/v1/arch_vars.py
@@ -83,7 +83,7 @@ class ArchVars:
("uint64_t", "wmb1", ""), # writes within mb1 counter
("uint64_t", "wdea", ""), # writes within dead code counter
- #("uint64_t", "weva", f"[{2 ** args.mvec_pow}]"), # write events array
+ ("uint64_t", "weva", f"[{2 ** args.mvec_pow}]"), # write events array
]
self.data_is_compressed = True
@@ -138,3 +138,11 @@ class ArchVars:
} for i in range(args.cores)
},
}
+
+ self.heatmaps = {
+ "Events": {
+ f"wev_{i}": {
+ "table": f"wev_{i}",
+ } for i in range(args.cores)
+ }
+ }
diff --git a/core.c b/core.c
index 79fc6f2..b6b5609 100644
--- a/core.c
+++ b/core.c
@@ -14,6 +14,10 @@
#define MALL_FLAG 0x80
#define UINT64_HALF 0x8000000000000000ul
+#if defined(COMPRESS) || defined(DATA_PUSH_PATH)
+#include "data/compress.c"
+#endif
+
struct Proc {
#define PROC_FIELD(type, name) type name;
PROC_FIELDS
@@ -46,7 +50,7 @@ struct Core {
uint64_t edea; // executions within dead code counter
uint64_t aeva[MVEC_SIZE]; // allocation events array
- //uint64_t eeva[MVEC_SIZE]; // execution events array
+ uint64_t eeva[MVEC_SIZE]; // execution events array
#define CORE_DATA_FIELD(type, name, suff) type name suff;
CORE_DATA_FIELDS
@@ -418,7 +422,7 @@ void core_save(FILE *f, const struct Core *core) {
fwrite(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
#if defined(DATA_PUSH_PATH)
fwrite(core->aeva, sizeof(uint64_t), MVEC_SIZE, f);
- //fwrite(core->eeva, sizeof(uint64_t), MVEC_SIZE, f);
+ fwrite(core->eeva, sizeof(uint64_t), MVEC_SIZE, f);
#endif
arch_core_save(f, core);
@@ -514,7 +518,7 @@ void core_load(FILE *f, struct Core *core) {
fread(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
#if defined(DATA_PUSH_PATH)
fread(core->aeva, sizeof(uint64_t), MVEC_SIZE, f);
- //fread(core->eeva, sizeof(uint64_t), MVEC_SIZE, f);
+ fread(core->eeva, sizeof(uint64_t), MVEC_SIZE, f);
#endif
arch_core_load(f, core);
@@ -564,17 +568,25 @@ void core_step(struct Core *core) {
// Save execution event locations in database
assert(mvec_proc_is_live(core, core->pcur));
- struct Proc *proc = proc_fetch(core, core->pcur);
+ uint64_t pcur_ip = arch_proc_ip_addr(core, core->pcur);
- if (mvec_is_in_mb0_of_proc(core, proc->ip, 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, proc->ip, core->pcur)) {
+ } else if (mvec_is_in_mb1_of_proc(core, pcur_ip, core->pcur)) {
++core->emb1;
- } else if (mvec_is_alloc(core, proc->ip)) {
+ } 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
#endif
arch_proc_step(core, core->pcur);
@@ -634,18 +646,7 @@ void salis_save(const char *path) {
assert(out);
z_stream strm = { 0 };
- strm.zalloc = NULL;
- strm.zfree = NULL;
- strm.opaque = NULL;
-
- deflateInit(&strm, Z_DEFAULT_COMPRESSION);
-
- strm.avail_in = size;
- strm.avail_out = size;
- strm.next_in = (Bytef *)in;
- strm.next_out = (Bytef *)out;
-
- deflate(&strm, Z_FINISH);
+ salis_deflate(&strm, size, (Bytef *)in, (Bytef *)out);
FILE *fx = fopen(path, "wb");
assert(fx);
@@ -654,7 +655,7 @@ void salis_save(const char *path) {
fwrite(out, sizeof(char), strm.total_out, fx);
fclose(fx);
- deflateEnd(&strm);
+ salis_deflate_end(&strm);
free(in);
free(out);
@@ -762,7 +763,7 @@ void salis_push_data_header() {
);
// Memory events
- char *eprefs[] = { "aev" /*, "eev" */ };
+ char *eprefs[] = { "aev", "eev" };
int eprefs_cnt = sizeof(eprefs) / sizeof(eprefs[0]);
for (int i = 0; i < CORES; ++i) {
@@ -798,10 +799,8 @@ void salis_push_data_line() {
struct Core *core = &g_cores[i];
for (uint64_t j = core->pfst; j <= core->plst; ++j) {
- const struct Proc *proc = proc_get(core, j);
-
- amb0[i] += (double)proc->mb0s;
- amb1[i] += (double)proc->mb1s;
+ amb0[i] += (double)arch_proc_mb0_size(core, j);
+ amb1[i] += (double)arch_proc_mb1_size(core, j);
}
amb0[i] /= core->pnum;
@@ -851,7 +850,7 @@ void salis_push_data_line() {
);
// TODO: insert execute memory events
- char *eprefs[] = { "aev" /*, "eev" */ };
+ char *eprefs[] = { "aev", "eev" };
int eprefs_cnt = sizeof(eprefs) / sizeof(eprefs[0]);
for (int i = 0; i < CORES; ++i) {
@@ -860,9 +859,9 @@ void salis_push_data_line() {
if (!strcmp("aev", eprefs[j])) {
in = g_cores[i].aeva;
- } // else if (!strcmp("eev", eprefs[j])) {
- // in = g_cores[i].eeva;
- // }
+ } else if (!strcmp("eev", eprefs[j])) {
+ in = g_cores[i].eeva;
+ }
// Compress event data
size_t size = sizeof(uint64_t) * MVEC_SIZE;
@@ -870,18 +869,7 @@ void salis_push_data_line() {
assert(out);
z_stream strm = { 0 };
- strm.zalloc = NULL;
- strm.zfree = NULL;
- strm.opaque = NULL;
-
- deflateInit(&strm, Z_DEFAULT_COMPRESSION);
-
- strm.avail_in = size;
- strm.avail_out = size;
- strm.next_in = (Bytef *)in;
- strm.next_out = (Bytef *)out;
-
- deflate(&strm, Z_FINISH);
+ salis_deflate(&strm, size, (Bytef *)in, (Bytef *)out);
// Insert blob
const void *blob = out;
@@ -908,7 +896,7 @@ void salis_push_data_line() {
blob_size, g_steps
);
- deflateEnd(&strm);
+ salis_deflate_end(&strm);
free(out);
}
}
@@ -923,7 +911,7 @@ void salis_push_data_line() {
core->edea = 0;
memset(core->aeva, 0, sizeof(uint64_t) * MVEC_SIZE);
- //memset(core->eeva, 0, sizeof(uint64_t) * MVEC_SIZE);
+ memset(core->eeva, 0, sizeof(uint64_t) * MVEC_SIZE);
}
// Push arch-specific data
@@ -988,24 +976,8 @@ void salis_load() {
assert(out);
z_stream strm = { 0 };
- strm.next_in = (Bytef *)in;
- strm.avail_in = x_size;
- strm.zalloc = NULL;
- strm.zfree = NULL;
- strm.opaque = NULL;
-
- inflateInit(&strm);
-
- strm.avail_out = size;
- strm.next_out = (Bytef *)out;
-
-#if defined(NDEBUG)
- inflate(&strm, Z_FINISH);
-#else
- assert(inflate(&strm, Z_FINISH));
-#endif
-
- inflateEnd(&strm);
+ salis_inflate(&strm, x_size, size, (Bytef *)in, (Bytef *)out);
+ salis_inflate_end(&strm);
FILE *f = fmemopen(out, size, "rb");
#else
diff --git a/data/compress.c b/data/compress.c
new file mode 100644
index 0000000..df61123
--- /dev/null
+++ b/data/compress.c
@@ -0,0 +1,56 @@
+void salis_deflate(z_stream *strm, size_t size, Bytef *in, Bytef *out) {
+ assert(strm);
+ assert(size);
+ assert(in);
+ assert(out);
+
+ strm->zalloc = NULL;
+ strm->zfree = NULL;
+ strm->opaque = NULL;
+
+ deflateInit(strm, Z_DEFAULT_COMPRESSION);
+
+ strm->avail_in = size;
+ strm->avail_out = size;
+ strm->next_in = in;
+ strm->next_out = out;
+
+ deflate(strm, Z_FINISH);
+}
+
+void salis_deflate_end(z_stream *strm) {
+ assert(strm);
+
+ deflateEnd(strm);
+}
+
+void salis_inflate(z_stream *strm, size_t avail_in, size_t size, Bytef *in, Bytef *out) {
+ assert(strm);
+ assert(avail_in);
+ assert(size);
+ assert(in);
+ assert(out);
+
+ strm->next_in = in;
+ strm->avail_in = avail_in;
+ strm->zalloc = NULL;
+ strm->zfree = NULL;
+ strm->opaque = NULL;
+
+ inflateInit(strm);
+
+ strm->avail_out = size;
+ strm->next_out = out;
+
+#if defined(NDEBUG)
+ inflate(strm, Z_FINISH);
+#else
+ assert(inflate(strm, Z_FINISH));
+#endif
+}
+
+void salis_inflate_end(z_stream *strm) {
+ assert(strm);
+
+ inflateEnd(strm);
+}
diff --git a/data/render.c b/data/render.c
new file mode 100644
index 0000000..f9da65d
--- /dev/null
+++ b/data/render.c
@@ -0,0 +1,86 @@
+#include <sqlite3ext.h>
+SQLITE_EXTENSION_INIT1
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <zlib.h>
+
+#include "compress.c"
+
+#define EVA_SIZE (sizeof(uint64_t) * MVEC_SIZE)
+
+void eva_render(sqlite3_context *context, int argc, sqlite3_value **argv) {
+ assert(context);
+ assert(argc == 4);
+ assert(argv);
+
+ (void)argc;
+
+ size_t left = (size_t)sqlite3_value_int(argv[0]);
+#if defined(MVEC_LOOP)
+ left %= MVEC_SIZE;
+#endif
+
+ size_t px_count = (size_t)sqlite3_value_int(argv[1]);
+ size_t px_pow = (size_t)sqlite3_value_int(argv[2]);
+ size_t px_res = 1 << px_pow;
+#if !defined(MVEC_LOOP)
+#if !defined(NDEBUG)
+ size_t right = left + px_res * px_count;
+#endif
+ assert(left < MVEC_SIZE);
+ assert(right <= MVEC_SIZE);
+#endif
+
+ const void *blob = sqlite3_value_blob(argv[3]);
+ size_t blob_size = (size_t)sqlite3_value_bytes(argv[3]);
+
+ // Inflate blob
+ size_t out_size = sizeof(uint64_t) * px_count;
+ uint64_t *eva = sqlite3_malloc(EVA_SIZE);
+ uint64_t *out = sqlite3_malloc(out_size);
+ z_stream strm = { 0 };
+ salis_inflate(&strm, blob_size, EVA_SIZE, (Bytef *)blob, (Bytef *)eva);
+ salis_inflate_end(&strm);
+
+ // Render image
+ for (size_t i = 0; i < px_count; i++) {
+ out[i] = 0;
+
+ for (size_t j = 0; j < px_res; j++) {
+ size_t in_coord = left + i * px_res + j;
+#if defined(MVEC_LOOP)
+ in_coord %= MVEC_SIZE;
+#endif
+ out[i] += eva[in_coord];
+ }
+ }
+
+ sqlite3_free(eva);
+
+ // Transform rendered image into textual representation
+ // A comma-separated list of hexadecimal integers
+ char *csv = sqlite3_malloc(px_count * 17 + 1);
+ char *ptr = csv;
+
+ for (size_t i = 0; i < px_count; i++) {
+ ptr += sprintf(ptr, "%lx ", out[i]);
+ }
+
+ *(--ptr) = '\0';
+ sqlite3_free(out);
+
+ sqlite3_result_text(context, csv, -1, sqlite3_free);
+}
+
+int sqlite3_render_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
+ assert(db);
+ assert(pzErrMsg);
+ assert(pApi);
+
+ (void)pzErrMsg;
+
+ SQLITE_EXTENSION_INIT2(pApi);
+ return sqlite3_create_function(db, "eva_render", 4, SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS | SQLITE_UTF8, NULL, eva_render, NULL, NULL);
+}
diff --git a/data/vue/App.vue b/data/vue/App.vue
index e672911..73923dd 100644
--- a/data/vue/App.vue
+++ b/data/vue/App.vue
@@ -9,11 +9,15 @@
</span>
</h1>
<form @change="trigger_reload">
- <span class="nobr">Entries (max): <input class="input_small" v-model="entries" /></span><wbr />
- <span class="nobr">nth: <input class="input_small" v-model="nth" /></span><wbr />
- <span class="nobr">X-axis: <select class="input_small" v-model="x_axis"><option v-for="axis in x_axes">{{ axis }}</option></select></span><wbr />
+ <span class="nobr">Entries: <input v-model="entries" /></span><wbr />
+ <span class="nobr">nth: <input v-model="nth" /></span><wbr />
+ <span class="nobr">X-axis: <select v-model="x_axis"><option v-for="axis in x_axes">{{ axis }}</option></select></span><wbr />
<span class="nobr">X-low: <input v-model="x_low" /></span><wbr />
- <span class="nobr">X-high: <input v-model="x_high" /></span>
+ <span class="nobr">X-high: <input v-model="x_high" /></span><wbr />
+ <span class="nobr">Left: <input v-model="hm_left" /></span><wbr />
+ <span class="nobr">Px-count: <input v-model="hm_px_count" /></span><wbr />
+ <span class="nobr">Px-pow: <input v-model="hm_px_pow" /></span><wbr />
+ <span class="reset_button" @click="reset_inputs">↻</span>
</form>
</div>
<Section name="Options" visible>
@@ -29,6 +33,9 @@
<Section :name="section" grid ref="plot_sections" :visible="section === 'General'" triggers_reload v-for="(section_plots, section) in plots">
<Plot :name="name" :section="section" v-for="(_, name) in section_plots" />
</Section>
+ <Section :name="section" grid ref="heatmap_sections" triggers_reload v-for="(section_heatmaps, section) in heatmaps">
+ <Plot is_heatmap :name="name" :section="section" v-for="(_, name) in section_heatmaps" />
+ </Section>
</div>
</template>
@@ -41,7 +48,7 @@ import Section from './Section.vue'
const root = window.location.href
const id = v => v
const hex = v => v !== undefined ? `0x${v.toString(16)}` : ''
-const hex_pow = v => v !== undefined ? `0x${Math.pow(2, v).toString(16)}` : ''
+const hex_pow = v => v !== undefined ? `0x${(2 ** v).toString(16)}` : ''
const disabled = v => v ? 'disabled' : 'enabled'
const opt_fmts = [
@@ -58,88 +65,120 @@ const opt_fmts = [
['Seed', 'seed', hex],
]
-let visible_tables = []
+let visible_plot_tables = []
+let visible_heatmap_tables = []
let query_timeout = null
-let plot_x_low = 0n
+let plot_x_low = 0
let plot_redraw = false
+let mvec_size = 0
+
+const uint32_max = (2 ** 32) - 1
+const hm_max_pixels = 2 ** 11
+
+const entries_def = 2000
+const nth_def = 1
+const x_axis_def = 'rowid'
+const x_low_def = hex(0)
+const x_high_def = hex(uint32_max)
+const hm_left_def = hex(0)
+const hm_px_count_def = hex(2 ** 10)
+
+const entries = ref(entries_def)
+const nth = ref(nth_def)
+const x_axes = ref(['rowid', 'step'])
+const x_axis = ref(x_axis_def)
+const x_low = ref(x_low_def)
+const x_high = ref(x_high_def)
+const hm_left = ref(hm_left_def)
+const hm_px_count = ref(hm_px_count_def)
+const hm_px_pow = ref(hex(0))
const opts = ref({})
const plots = ref({})
+const heatmaps = ref({})
const loaded = ref(false)
-const entries = ref(2000)
-const nth = ref(BigInt(1))
-const x_axes = ref(['rowid', 'step'])
-const x_axis = ref(x_axes.value[0])
-const x_low = ref(hex(BigInt(0)))
-const x_high = ref(hex(BigInt(Math.pow(2, 64))))
-
const query_in_progress = ref(false)
const data = ref([])
const top_pad = useTemplateRef('top_pad')
const top_bar = useTemplateRef('top_bar')
const plot_sections = useTemplateRef('plot_sections')
+const heatmap_sections = useTemplateRef('heatmap_sections')
const update_visible_tables = () => {
- const section_visibility = plot_sections.value.map(section => section.visible)
- visible_tables = Object.entries(plots.value).filter((_, i) => section_visibility[i]).map((section, _) => [...new Set(Object.entries(section[1]).map(plot => plot[1].table))]).flat()
+ const plot_section_visibility = plot_sections.value.map(section => section.visible)
+ const heatmap_section_visibility = heatmap_sections.value.map(section => section.visible)
+ visible_plot_tables = Object.entries(plots.value).filter((_, i) => plot_section_visibility[i]).map((section, _) => [...new Set(Object.entries(section[1]).map(plot => plot[1].table))]).flat()
+ visible_heatmap_tables = Object.entries(heatmaps.value).filter((_, i) => heatmap_section_visibility[i]).map((section, _) => [...new Set(Object.entries(section[1]).map(plot => plot[1].table))]).flat()
}
const sanitize = (input, min, max, def, fmt) => {
- if (isNaN(Number(input.value)) || input.value === '' || input.value < min || input.value > max) {
+ if (isNaN(Number(input.value)) || input.value === '' || input.value < min || input.value >= max) {
input.value = fmt(def)
}
}
+const max_hm_px_pow = () => Math.floor(Math.log2((mvec_size - Number(hm_left.value)) / Number(hm_px_count.value)))
+
const trigger_reload = () => {
update_visible_tables()
- sanitize(entries, 1n, BigInt(Math.pow(2, 64)), 2000n, id)
- sanitize(nth, 1n, BigInt(Math.pow(2, 64)), 1n, id)
- sanitize(x_low, 0n, BigInt(Math.pow(2, 64)), 0n, hex)
- sanitize(x_high, 1n, BigInt(Math.pow(2, 64)), BigInt(Math.pow(2, 64)), hex)
+ sanitize(entries, 1, uint32_max, 2000, id)
+ sanitize(nth, 1, uint32_max, 1, id)
+ sanitize(x_low, 0, uint32_max, 0, hex)
+ sanitize(x_high, 1, uint32_max, uint32_max, hex)
+
+ if (opts.value.mvec_loop) {
+ sanitize(hm_left, 0, uint32_max, 0, hex)
+ sanitize(hm_px_count, 1, hm_max_pixels, hm_max_pixels, hex)
+ sanitize(hm_px_pow, 0, uint32_max, uint32_max, hex)
+ } else {
+ sanitize(hm_left, 0, mvec_size, 0, hex)
+ sanitize(hm_px_count, 1, hm_max_pixels, hm_max_pixels, hex)
+ sanitize(hm_px_pow, 0, max_hm_px_pow(), max_hm_px_pow(), hex)
+ }
- plot_x_low = x_low.value
+ plot_x_low = Number(x_low.value)
plot_redraw = true
query()
}
-const pad_top_bar = () => {
- top_pad.value.style.height = `${Math.round(top_bar.value.getBoundingClientRect().height)}px`
-}
-
-const reviver = (_, val, { source }) => {
- if (Number.isInteger(val) && !Number.isSafeInteger(val)) {
- try { return BigInt(source) } catch {}
- }
-
- return val
+const reset_inputs = () => {
+ entries.value = entries_def
+ nth.value = nth_def
+ x_axis.value = x_axis_def
+ x_low.value = x_low_def
+ x_high.value = x_high_def
+ hm_left.value = hm_left_def
+ hm_px_count.value = hm_px_count_def
+ hm_px_pow.value = max_hm_px_pow()
+
+ trigger_reload()
}
-const query_table = async table => {
+const query_table = async (table, is_heatmap, x_high_now) => {
const params = {
table: table,
entries: entries.value,
nth: nth.value,
x_axis: x_axis.value,
- x_low: Number(plot_x_low),
- x_high: Number(x_high.value),
+ x_low: plot_x_low,
+ x_high: x_high_now,
+ is_eva: is_heatmap,
+ ...is_heatmap ? {
+ hm_left: Number(hm_left.value),
+ hm_px_count: Number(hm_px_count.value),
+ hm_px_pow: Number(hm_px_pow.value),
+ } : {},
}
const search_params = new URLSearchParams(params)
const resp_table = await fetch(root + `data?${search_params}`, { method: 'GET' })
- const resp_json = JSON.parse(await resp_table.text(), reviver)
+ const text_table = await resp_table.text()
- // Keep track of the highest x-axis value fetched so far.
- // Future queries will set this as the minimum, which prevents re-fetching already stored data.
- if (resp_json.length) {
- const x_last = BigInt(resp_json.slice(-1)[0][x_axis.value] + 1)
- plot_x_low = plot_x_low > x_last ? plot_x_low : x_last
- }
-
- return resp_json
+ return JSON.parse(text_table)
}
const query = async () => {
@@ -148,27 +187,51 @@ const query = async () => {
clearTimeout(query_timeout)
query_in_progress.value = true
- const query_results = await Promise.all(visible_tables.map(query_table))
- const query_values = Object.fromEntries(visible_tables.map((key, i) => [key, query_results[i]]))
+ const high_params = new URLSearchParams({ x_axis: x_axis.value })
+ const resp_x_high = await fetch(root + `x_high?${high_params}`, { method: 'GET' })
+ const text_x_high = await resp_x_high.text()
+ const json_x_high = JSON.parse(text_x_high)
+ const x_high_max = json_x_high.x_high + 1
+ const x_high_val = Number(x_high.value)
+ const x_high_now = x_high_max < x_high_val ? x_high_max : x_high_val;
+
+ const plot_query_results = await Promise.all(visible_plot_tables.map(table => query_table(table, false, x_high_now)))
+ const heatmap_query_results = await Promise.all(visible_heatmap_tables.map(table => query_table(table, true, x_high_now)))
+ const plot_query_values = Object.fromEntries(visible_plot_tables.map((table, i) => [table, plot_query_results[i]]))
+ const heatmap_query_values = Object.fromEntries(visible_heatmap_tables.map((table, i) => [table, heatmap_query_results[i]]))
- data.value = { redraw: plot_redraw, values: query_values }
+ // Keep track of the highest x-axis value fetched so far.
+ // Future queries will set this as the minimum, which prevents re-fetching already stored data.
+ plot_x_low = x_high_now
+
+ data.value = { redraw: plot_redraw, plot_values: plot_query_values, heatmap_values: heatmap_query_values }
plot_redraw = false
query_in_progress.value = false
query_timeout = setTimeout(query, 10000)
}
-onMounted(async () => {
- window.onresize = _ => pad_top_bar()
- pad_top_bar()
+const with_big_ints = (_, val, { source }) => {
+ if (Number.isInteger(val) && !Number.isSafeInteger(val)) {
+ try { return BigInt(source) } catch {}
+ }
+ return val
+}
+
+onMounted(async () => {
const resp_opts = await fetch(root + 'opts', { method: 'GET' })
const resp_plots = await fetch(root + 'plots', { method: 'GET' })
+ const resp_heatmaps = await fetch(root + 'heatmaps', { method: 'GET' })
- opts.value = JSON.parse(await resp_opts.text(), reviver)
- plots.value = JSON.parse(await resp_plots.text(), reviver)
+ opts.value = JSON.parse(await resp_opts.text(), with_big_ints)
+ plots.value = JSON.parse(await resp_plots.text())
+ heatmaps.value = JSON.parse(await resp_heatmaps.text())
loaded.value = true
+ mvec_size = 2 ** opts.value.mvec_pow
+ hm_px_pow.value = hex(max_hm_px_pow())
+
// All tables should include one cycle column for each core.
// This allows normalizing the plots against each core's cycle count
// (i.e. making `cycl_#` the plots' x-axis).
@@ -176,37 +239,45 @@ onMounted(async () => {
})
watch(loaded, _ => {
+ top_pad.value.style.height = `${Math.round(top_bar.value.getBoundingClientRect().height)}px`
+
update_visible_tables()
query()
}, { flush: 'post' })
provide('plots', plots)
+provide('heatmaps', heatmaps)
provide('entries', entries)
provide('x_axis', x_axis)
+provide('hm_left', hm_left)
+provide('hm_px_count', hm_px_count)
+provide('hm_px_pow', hm_px_pow)
provide('data', data)
provide('trigger_reload', trigger_reload)
</script>
<style>
html {
- background-color: #002b36;
- color: #586e75;
+ background-color: black;
+ color: gray;
font-family: sans-serif;
}
h1 {
- font-size: 20px;
+ font-size: 18px;
font-weight: 600;
+ margin: 8px 0;
}
input, select {
- background-color: #586e75;
+ background-color: gray;
border: none;
- color: #002b36;
+ color: black;
font-family: monospace;
- font-size: 14px;
+ font-size: 12px;
margin: 0 4px;
padding: 2px;
+ width: 80px;
}
table {
@@ -217,7 +288,7 @@ table {
}
tr:nth-child(odd) {
- background-color: #073642;
+ background-color: #111;
}
td {
@@ -228,7 +299,7 @@ td {
}
.top_bar {
- background-color: #073642;
+ background-color: #111;
left: 0;
padding: 8px;
position: fixed;
@@ -243,12 +314,25 @@ td {
}
.nobr {
- line-height: 32px;
- margin-right: 16px;
+ font-size: 12px;
+ line-height: 28px;
+ margin-right: 6px;
white-space: nowrap;
}
-.input_small {
- width: 80px;
+.reset_button {
+ background-color: black;
+ border: 1.5px solid gray;
+ color: gray;
+ cursor: pointer;
+ display: inline-block;
+ font-family: monospace;
+ font-size: 14px;
+ height: 16px;
+ line-height: 16px;
+ margin: 0 4px;
+ padding: 2px;
+ text-align: center;
+ width: 16px;
}
</style>
diff --git a/data/vue/Plot.vue b/data/vue/Plot.vue
index 3c08d73..5bbc89a 100644
--- a/data/vue/Plot.vue
+++ b/data/vue/Plot.vue
@@ -11,7 +11,7 @@
<script setup>
import { defineProps, inject, onMounted, ref, useTemplateRef, watch } from 'vue'
-const props = defineProps({ section: String, name: String })
+const props = defineProps({ is_heatmap: Boolean, section: String, name: String })
const maximized = ref(false)
@@ -19,8 +19,12 @@ const plot_ref = useTemplateRef('plot_ref')
const plot_container = useTemplateRef('plot_container')
const plots = inject('plots')
+const heatmaps = inject('heatmaps')
const entries = inject('entries')
const x_axis = inject('x_axis')
+const hm_left = inject('hm_left')
+const hm_px_count = inject('hm_px_count')
+const hm_px_pow = inject('hm_px_pow')
const data = inject('data')
const plot_toggle_maximize = () => {
@@ -34,32 +38,32 @@ const prevent_plotly_buttons_tab_focus = () => {
focusableElements.forEach(elem => elem.setAttribute('tabindex', '-1'))
}
-onMounted(() => {
- const plot_config = plots.value[props.section][props.name]
+const heatmap_init = [{ colorscale: 'Electric', type: 'heatmap', x: [], y: [], z: [] }]
- switch (plot_config.type) {
- case 'lines':
- var data_defs = { mode: 'lines', line: { width: 1 }}
- break
- case 'stack':
- var data_defs = { mode: 'lines', line: { width: 1 }, stackgroup: 'stackgroup' }
- break
- case 'stack_percent':
- var data_defs = { mode: 'lines', line: { width: 1 }, stackgroup: 'stackgroup', groupnorm: 'percent' }
- break
- }
+const plot_init = () => {
+ const plot_config = plots.value[props.section][props.name]
+ const plot_defs = { mode: 'lines', line: { width: 1 }, x: [], y: [] }
+
+ switch (plot_config.type) {
+ case 'lines':
+ return Array.from(plot_config.cols, col => ({ ...plot_defs, name: col }))
+ case 'stack':
+ return Array.from(plot_config.cols, col => ({ ...plot_defs, stackgroup: 'sg', name: col }))
+ case 'stack_percent':
+ return Array.from(plot_config.cols, col => ({ ...plot_defs, stackgroup: 'sg', groupnorm: 'percent', name: col }))
+ }
+}
- const columns = plot_config.cols
- const data = Array.from(columns, column => ({ ...data_defs, x: [], y: [], name: column }))
-
- Plotly.newPlot(plot_ref.value, data, {
- legend: { font: { color: '#586e75', family: 'monospace' }, maxheight: 100, orientation: 'h' },
- margin: { b: 32, l: 32, r: 32, t: 32 },
- paper_bgcolor: '#002b36',
- plot_bgcolor: '#002b36',
- title: { font: { color: '#586e75' }, text: props.name, x: 0, xref: 'paper' },
- xaxis: { gridcolor: '#073642', tickfont: { color: '#586e75' }, zerolinecolor: '#586e75' },
- yaxis: { gridcolor: '#073642', tickfont: { color: '#586e75' }, zerolinecolor: '#586e75' },
+onMounted(() => {
+ Plotly.newPlot(plot_ref.value, props.is_heatmap ? heatmap_init : plot_init(), {
+ font: { color: 'gray', family: 'monospace' },
+ legend: { maxheight: 100, orientation: 'h' },
+ margin: { b: 48, l: 48, r: 48, t: 48 },
+ paper_bgcolor: 'black',
+ plot_bgcolor: 'black',
+ title: { font: { size: 16 }, text: props.name, x: 0, xref: 'paper' },
+ xaxis: { gridcolor: '#111', tickfont: { color: 'gray' }, zerolinecolor: 'gray' },
+ yaxis: { gridcolor: '#111', tickfont: { color: 'gray' }, zerolinecolor: 'gray' },
}, {
displayModeBar: true,
responsive: true,
@@ -68,32 +72,54 @@ onMounted(() => {
prevent_plotly_buttons_tab_focus()
})
-watch(data, new_data => {
+const update_plot = new_data => {
const plot_config = plots.value[props.section][props.name]
- const columns = plot_config.cols
- const column_count = columns.length
- const table_data = new_data.values[plot_config.table]
- const traces = [...Array(column_count).keys()]
- const xs = Array(column_count).fill(table_data.map(elem => elem[x_axis.value]))
- const ys = columns.map(column => table_data.map(elem => elem[column]))
+ const cols = plot_config.cols
+ const cols_count = cols.length
+ const table_data = new_data.plot_values[plot_config.table]
+ const traces = [...Array(cols_count).keys()]
+ const xs = Array(cols_count).fill(table_data.map(elem => elem[x_axis.value]))
+ const ys = cols.map(column => table_data.map(elem => elem[column]))
// Clear traces
if (new_data.redraw) {
const restyle = {
- x: Array.from(columns, () => []),
- y: Array.from(columns, () => []),
+ x: Array.from(cols, () => []),
+ y: Array.from(cols, () => []),
}
Plotly.restyle(plot_ref.value, restyle)
}
Plotly.extendTraces(plot_ref.value, { x: xs, y: ys }, traces, entries.value)
-})
+}
+
+const update_heatmap = new_data => {
+ const heatmap_config = heatmaps.value[props.section][props.name]
+ const table_data = new_data.heatmap_values[heatmap_config.table]
+ const ys = [table_data.map(elem => elem[x_axis.value])]
+ const zs = [table_data.map(elem => elem.eva_render.split(' ').map(str => Number('0x' + str)))]
+
+ if (new_data.redraw) {
+ const px_size = Math.pow(2, Number(hm_px_pow.value))
+ const restyle = {
+ x: [Array.from(Array(Number(hm_px_count.value)).keys()).map(i => Number(hm_left.value) + i * px_size)],
+ y: [[]],
+ z: [[]],
+ }
+
+ Plotly.restyle(plot_ref.value, restyle)
+ }
+
+ Plotly.extendTraces(plot_ref.value, { y: ys, z: zs }, [0], entries.value)
+}
+
+watch(data, props.is_heatmap ? update_heatmap : update_plot)
</script>
<style>
.plot_container {
- background-color: #002b36;
+ background-color: black;
display: inline-block;
width: 100%;
}
@@ -113,9 +139,9 @@ watch(data, new_data => {
}
.plot_button {
- background-color: #002b36;
- border: 1.5px solid #586e75;
- color: #586e75;
+ background-color: black;
+ border: 1.5px solid gray;
+ color: gray;
cursor: pointer;
font-family: monospace;
font-size: 18px;
diff --git a/data/vue/Section.vue b/data/vue/Section.vue
index f0202c0..7f4d633 100644
--- a/data/vue/Section.vue
+++ b/data/vue/Section.vue
@@ -24,9 +24,9 @@ defineExpose({ visible })
<style>
.section_header {
- border-bottom: 1px solid #586e75;
+ border-bottom: 1px solid gray;
cursor: pointer;
- font-size: 18px;
+ font-size: 16px;
font-weight: normal;
}
diff --git a/salis.py b/salis.py
index dc0eb93..7167b03 100755
--- a/salis.py
+++ b/salis.py
@@ -70,16 +70,16 @@ option_list = [
["d", "data-push-pow", "POW", "data aggregation interval exponent (interval == 2^{POW} >= {sync-pow}); a value of 0 disables data aggregation (requires 'sqlite')", 28, False, ipos, [new]],
["f", "force", None, "overwrite existing simulation of given name", False, False, bool, [new]],
["F", "muta-flip", None, "cosmic rays flip bits instead of randomizing whole bytes", False, False, bool, [bench, new]],
- ["g", "compiler", "CC", "C compiler to use", "gcc", False, str, [bench, load, new]],
- ["G", "compiler-flags", "FLAGS", "base set of flags to pass to C compiler", "-Wall -Wextra -Werror", False, str, [bench, load, new]],
+ ["g", "compiler", "CC", "C compiler to use", "gcc", False, str, [bench, load, new, serve]],
+ ["G", "compiler-flags", "FLAGS", "base set of flags to pass to C compiler", "-Wall -Wextra -Werror", False, str, [bench, load, new, serve]],
["M", "muta-pow", "POW", "mutator range exponent (range == 2^{POW})", 32, False, ipos, [bench, new]],
["m", "mvec-pow", "POW", "memory vector size exponent (size == 2^{POW})", 20, False, ipos, [bench, new]],
["n", "name", "NAME", "name of new or loaded simulation", "def.sim", False, str, [load, new, serve]],
- ["o", "optimized", None, "builds salis binary with optimizations", False, False, bool, [bench, load, new]],
+ ["o", "optimized", None, "build with optimizations", False, False, bool, [bench, load, new, serve]],
["P", "port", "PORT", "port number for data server", 8080, False, iport, [serve]],
["p", "pre-cmd", "CMD", "shell command to wrap call to executable (e.g. gdb, time, valgrind, etc.)", None, False, str, [bench, load, new]],
["s", "seed", "SEED", "seed value for new simulation; a value of 0 disables cosmic rays; a value of -1 creates a random seed", 0, False, seed, [bench, new]],
- ["T", "keep-temp-dir", None, "delete temporary directory on exit", False, False, bool, [bench, load, new]],
+ ["T", "keep-temp-dir", None, "delete temporary directory on exit", False, False, bool, [bench, load, new, serve]],
["t", "thread-gap", "N", "memory gap between cores in bytes (may help reduce cache misses)", 0x100, False, inat, [bench, load, new]],
["u", "ui", uis, "user interface", "curses", False, str, [load, new]],
["U", "update", None, "update vendors (call 'git commit' afterwards to track updated files, if any)", False, False, bool, [serve]],
@@ -216,8 +216,42 @@ if args.command in ["serve"]:
info("Connecting to SQLite database:", sim_db)
db_con = sqlite3.connect(sim_db)
db_con.row_factory = sqlite3.Row
+ db_con.enable_load_extension(True)
db_cur = db_con.cursor()
+ # Build SQLite event-array render extension
+ sqlx_flags = set()
+ sqlx_defines = set()
+ sqlx_links = set()
+
+ sqlx_flags.update({*args.compiler_flags.split(), "-shared", "-fPIC", "-Idata"})
+ sqlx_defines.add(f"-DMVEC_SIZE={2 ** args.mvec_pow}ul")
+
+ if arch_vars.mvec_loop: sqlx_defines.add("-DMVEC_LOOP")
+
+ if args.optimized:
+ sqlx_flags.add("-O3")
+ sqlx_defines.add("-DNDEBUG")
+ else:
+ sqlx_flags.add("-ggdb")
+
+ sqlx_links.add("-lz")
+
+ sqlx_tempdir = TemporaryDirectory(prefix="salis_sqlx_", delete=not args.keep_temp_dir)
+ info("Created a temporary salis SQLite extension directory at:", sqlx_tempdir.name)
+
+ sqlx_so = os.path.join(sqlx_tempdir.name, "render.so")
+ info("Building salis SQLite extension at:", sqlx_so)
+
+ sqlx_build_cmd = [args.compiler, "data/render.c", "-o", sqlx_so]
+ sqlx_build_cmd.extend(sqlx_flags)
+ sqlx_build_cmd.extend(sqlx_defines)
+ sqlx_build_cmd.extend(sqlx_links)
+
+ info("Using build command:", sqlx_build_cmd)
+ subprocess.run(sqlx_build_cmd, check=True)
+ db_cur.execute(f"SELECT load_extension('{sqlx_so}')")
+
# Generate configuration so front-end knows how to render the plots.
# Each architecture may also provide its own set of plots, which will be merged with the
# default dictionary below.
@@ -256,10 +290,26 @@ if args.command in ["serve"]:
},
}
+ heatmaps = {
+ "Events": {
+ f"aev_{i}": {
+ "table": f"aev_{i}",
+ } for i in range(args.cores)
+ } | {
+ f"eev_{i}": {
+ "table": f"eev_{i}",
+ } for i in range(args.cores)
+ },
+ }
+
for key in arch_vars.plots:
plots[key] = (plots[key] if key in plots else {}) | arch_vars.plots[key]
+ for key in arch_vars.heatmaps:
+ heatmaps[key] = (heatmaps[key] if key in heatmaps else {}) | arch_vars.heatmaps[key]
+
info("Generated plot configuration:", plots)
+ info("Generated heatmap configuration:", heatmaps)
# NOTE: this server implementation is very minimal and has no built-in security.
# Please do not put this on the internet! Only run the data server within secure
@@ -292,8 +342,9 @@ if args.command in ["serve"]:
if bits.path == "/": return self.send_file_as("data/index.html", "text/html")
if bits.path.split("/")[1] in ["js", "vendor", "vue"]: return self.send_file_as("data" + bits.path, "text/javascript")
- if bits.path == "/opts": return self.send_as_json(opt_vars | {"name": args.name})
+ if bits.path == "/opts": return self.send_as_json(opt_vars | {"mvec_loop": arch_vars.mvec_loop, "name": args.name})
if bits.path == "/plots": return self.send_as_json(plots)
+ if bits.path == "/heatmaps": return self.send_as_json(heatmaps)
if bits.path == "/data":
http_query = urllib.parse.parse_qs(bits.query)
@@ -303,11 +354,28 @@ if args.command in ["serve"]:
x_axis = http_query["x_axis"][0]
x_low = http_query["x_low"][0]
x_high = http_query["x_high"][0]
- sql_query = f"SELECT * FROM (SELECT rowid, * FROM {table} WHERE {x_axis} >= {x_low} AND {x_axis} <= {x_high} AND rowid % {nth} == 0 ORDER BY {x_axis} DESC LIMIT {entries}) ORDER BY {x_axis} ASC;"
+ is_eva = http_query["is_eva"][0]
+
+ if is_eva == "true":
+ hm_left = http_query["hm_left"][0]
+ hm_px_count = http_query["hm_px_count"][0]
+ hm_px_pow = http_query["hm_px_pow"][0]
+ selects = ", ".join([f"cycl_{i}" for i in range(args.cores)]) + f", eva_render({hm_left}, {hm_px_count}, {hm_px_pow}, evts) as eva_render, step"
+ else:
+ selects = "*"
+
+ sql_query = f"SELECT * FROM (SELECT rowid, {selects} FROM {table} WHERE {x_axis} >= {x_low} AND {x_axis} <= {x_high} AND rowid % {nth} == 0 ORDER BY {x_axis} DESC LIMIT {entries}) ORDER BY {x_axis} ASC;"
sql_res = db_cur.execute(sql_query)
sql_list = [dict(row) for row in sql_res.fetchall()]
return self.send_as_json(sql_list)
+ if bits.path == "/x_high":
+ http_query = urllib.parse.parse_qs(bits.query)
+ x_axis = http_query["x_axis"][0]
+ sql_query = f"SELECT {x_axis} as x_high FROM general ORDER BY {x_axis} DESC LIMIT 1;"
+ sql_dict = dict(db_cur.execute(sql_query).fetchone())
+ return self.send_as_json(sql_dict)
+
self.log_error(f"Unsupported endpoint: {bits.path}")
self.send_response(400)
self.end_headers()