summaryrefslogtreecommitdiff
path: root/vere/pkg/noun/trace.c
diff options
context:
space:
mode:
Diffstat (limited to 'vere/pkg/noun/trace.c')
-rw-r--r--vere/pkg/noun/trace.c1242
1 files changed, 1242 insertions, 0 deletions
diff --git a/vere/pkg/noun/trace.c b/vere/pkg/noun/trace.c
new file mode 100644
index 0000000..8ddbf2e
--- /dev/null
+++ b/vere/pkg/noun/trace.c
@@ -0,0 +1,1242 @@
+/// @file
+
+#include "trace.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <signal.h>
+
+#include "allocate.h"
+#include "imprison.h"
+#include "jets/k.h"
+#include "log.h"
+#include "manage.h"
+#include "options.h"
+#include "retrieve.h"
+#include "vortex.h"
+
+/** Global variables.
+**/
+u3t_spin *stk_u;
+u3t_trace u3t_Trace;
+
+static c3_o _ct_lop_o;
+
+/// Nock PID.
+static c3_ws _nock_pid_i = 0;
+
+/// JSON trace file.
+static FILE* _file_u = NULL;
+
+/// Trace counter. Tracks the number of entries written to the JSON trace file.
+static c3_w _trace_cnt_w = 0;
+
+/// File counter. Tracks the number of times u3t_trace_close() has been called.
+static c3_w _file_cnt_w = 0;
+
+/* u3t_push(): push on trace stack.
+*/
+void
+u3t_push(u3_noun mon)
+{
+ u3R->bug.tax = u3nc(mon, u3R->bug.tax);
+}
+
+/* u3t_mean(): push `[%mean roc]` on trace stack.
+*/
+void
+u3t_mean(u3_noun roc)
+{
+ u3R->bug.tax = u3nc(u3nc(c3__mean, roc), u3R->bug.tax);
+}
+
+/* u3t_drop(): drop from meaning stack.
+*/
+void
+u3t_drop(void)
+{
+ u3_assert(_(u3du(u3R->bug.tax)));
+ {
+ u3_noun tax = u3R->bug.tax;
+
+ u3R->bug.tax = u3k(u3t(tax));
+ u3z(tax);
+ }
+}
+
+/* u3t_slog(): print directly.
+*/
+void
+u3t_slog(u3_noun hod)
+{
+ if ( 0 != u3C.slog_f ) {
+ u3C.slog_f(hod);
+ }
+ else {
+ u3z(hod);
+ }
+}
+
+/* u3t_heck(): profile point.
+*/
+void
+u3t_heck(u3_atom cog)
+{
+#if 0
+ u3R->pro.cel_d++;
+#else
+ c3_w len_w = u3r_met(3, cog);
+ c3_c* str_c = alloca(1 + len_w);
+
+ u3r_bytes(0, len_w, (c3_y *)str_c, cog);
+ str_c[len_w] = 0;
+
+ // Profile sampling, because it allocates on the home road,
+ // only works on when we're not at home.
+ //
+ if ( &(u3H->rod_u) != u3R ) {
+ u3a_road* rod_u;
+
+ rod_u = u3R;
+ u3R = &(u3H->rod_u);
+ {
+ if ( 0 == u3R->pro.day ) {
+ u3R->pro.day = u3do("doss", 0);
+ }
+ u3R->pro.day = u3dc("pi-heck", u3i_string(str_c), u3R->pro.day);
+ }
+ u3R = rod_u;
+ }
+#endif
+}
+
+#if 0
+static void
+_ct_sane(u3_noun lab)
+{
+ if ( u3_nul != lab ) {
+ u3_assert(c3y == u3du(lab));
+ u3_assert(c3y == u3ud(u3h(lab)));
+ _ct_sane(u3t(lab));
+ }
+}
+#endif
+
+#if 1
+/* _t_samp_process(): process raw sample data from live road.
+*/
+static u3_noun
+_t_samp_process(u3_road* rod_u)
+{
+ u3_noun pef = u3_nul; // (list (pair path (map path ,@ud)))
+ u3_noun muf = u3_nul; // (map path ,@ud)
+ c3_w len_w = 0;
+
+ // Accumulate a label/map stack which collapses recursive segments.
+ //
+ while ( rod_u ) {
+ u3_noun don = rod_u->pro.don;
+
+ while ( u3_nul != don ) {
+ // Get surface allocated label
+ //
+ // u3_noun lab = u3nc(u3i_string("foobar"), 0);
+ u3_noun laj = u3h(don),
+ lab = u3a_take(laj);
+ u3a_wash(laj);
+
+ // Add the label to the traced label stack, trimming recursion.
+ //
+ {
+ u3_noun old;
+
+ if ( u3_none == (old = u3kdb_get(u3k(muf), u3k(lab))) ) {
+ muf = u3kdb_put(muf, u3k(lab), len_w);
+ pef = u3nc(u3nc(lab, u3k(muf)), pef);
+ len_w += 1;
+ }
+ else {
+ if ( !_(u3a_is_cat(old)) ) {
+ u3m_bail(c3__fail);
+ }
+
+ u3z(muf);
+ while ( len_w > (old + 1) ) {
+ u3_noun t_pef = u3k(u3t(pef));
+
+ len_w -= 1;
+ u3z(pef);
+ pef = t_pef;
+ }
+ muf = u3k(u3t(u3h(pef)));
+ u3z(lab);
+ }
+ }
+ don = u3t(don);
+ }
+ rod_u = u3tn(u3_road, rod_u->par_p);
+ }
+ u3z(muf);
+
+ // Lose the maps and save a pure label stack in original order.
+ //
+ {
+ u3_noun pal = u3_nul;
+
+ while ( u3_nul != pef ) {
+ u3_noun h_pef = u3h(pef);
+ u3_noun t_pef = u3k(u3t(pef));
+
+ pal = u3nc(u3k(u3h(h_pef)), pal);
+
+ u3z(pef);
+ pef = t_pef;
+ }
+
+ // u3l_log("sample: stack length %d", u3kb_lent(u3k(pal)));
+ return pal;
+ }
+}
+#endif
+
+/* u3t_samp(): sample.
+*/
+void
+u3t_samp(void)
+{
+ if ( c3y == _ct_lop_o ) {
+ // _ct_lop_o here is a mutex for modifying pro.don. we
+ // do not want to sample in the middle of doing that, as
+ // it can cause memory errors.
+ return;
+ }
+
+ c3_w old_wag = u3C.wag_w;
+ u3C.wag_w &= ~u3o_debug_cpu;
+ u3C.wag_w &= ~u3o_trace;
+
+ // Profile sampling, because it allocates on the home road,
+ // only works on when we're not at home.
+ //
+ if ( &(u3H->rod_u) != u3R ) {
+ c3_l mot_l;
+ u3a_road* rod_u;
+
+ if ( _(u3T.mal_o) ) {
+ mot_l = c3_s3('m','a','l');
+ }
+ else if ( _(u3T.coy_o) ) {
+ mot_l = c3_s3('c','o','y');
+ }
+ else if ( _(u3T.euq_o) ) {
+ mot_l = c3_s3('e','u','q');
+ }
+ else if ( _(u3T.far_o) ) {
+ mot_l = c3_s3('f','a','r');
+ }
+ else if ( _(u3T.noc_o) ) {
+ u3_assert(!_(u3T.glu_o));
+ mot_l = c3_s3('n','o','c');
+ }
+ else if ( _(u3T.glu_o) ) {
+ mot_l = c3_s3('g','l','u');
+ }
+ else {
+ mot_l = c3_s3('f','u','n');
+ }
+
+ rod_u = u3R;
+ u3R = &(u3H->rod_u);
+ {
+ u3_noun lab = _t_samp_process(rod_u);
+
+ u3_assert(u3R == &u3H->rod_u);
+ if ( 0 == u3R->pro.day ) {
+ /* bunt a +doss
+ */
+ u3R->pro.day = u3nt(u3nq(0, 0, 0, u3nq(0, 0, 0, 0)), 0, 0);
+ }
+ u3R->pro.day = u3dt("pi-noon", mot_l, lab, u3R->pro.day);
+ }
+ u3R = rod_u;
+ }
+ u3C.wag_w = old_wag;
+}
+
+/* u3t_come(): push on profile stack; return yes if active push. RETAIN.
+*/
+c3_o
+u3t_come(u3_noun lab)
+{
+ if ( (u3_nul == u3R->pro.don) || !_(u3r_sing(lab, u3h(u3R->pro.don))) ) {
+ u3a_gain(lab);
+ _ct_lop_o = c3y;
+ u3R->pro.don = u3nc(lab, u3R->pro.don);
+ _ct_lop_o = c3n;
+ return c3y;
+ }
+ else return c3n;
+}
+
+/* u3t_flee(): pop off profile stack.
+*/
+void
+u3t_flee(void)
+{
+ _ct_lop_o = c3y;
+ u3_noun don = u3R->pro.don;
+ u3R->pro.don = u3k(u3t(don));
+ _ct_lop_o = c3n;
+ u3z(don);
+}
+
+/* u3t_trace_open(): opens a trace file and writes the preamble.
+*/
+void
+u3t_trace_open(const c3_c* dir_c)
+{
+ c3_c fil_c[2048];
+
+ if ( !dir_c ) {
+ return;
+ }
+
+ snprintf(fil_c, 2048, "%s/.urb/put/trace", dir_c);
+
+ struct stat st;
+ if ( (-1 == stat(fil_c, &st))
+ && (-1 == c3_mkdir(fil_c, 0700)) )
+ {
+ fprintf(stderr, "mkdir: %s failed: %s\r\n", fil_c, strerror(errno));
+ return;
+ }
+
+ c3_c lif_c[2056];
+ snprintf(lif_c, 2056, "%s/%d.json", fil_c, _file_cnt_w);
+
+ _file_u = c3_fopen(lif_c, "w");
+ _nock_pid_i = (int)getpid();
+
+ if ( !_file_u ) {
+ fprintf(stderr, "trace open: %s\r\n", strerror(errno));
+ return;
+ }
+
+ fprintf(_file_u, "[ ");
+
+ // We have two "threads", the event processing and the nock stuff.
+ // tid 1 = event processing
+ // tid 2 = nock processing
+ fprintf(_file_u,
+ "{\"name\": \"process_name\", \"ph\": \"M\", \"pid\": %d, \"args\": "
+ "{\"name\": \"urbit\"}},\n",
+ _nock_pid_i);
+ fprintf(_file_u,
+ "{\"name\": \"thread_name\", \"ph\": \"M\", \"pid\": %d, \"tid\": 1, "
+ "\"args\": {\"name\": \"Event Processing\"}},\n",
+ _nock_pid_i);
+ fprintf(_file_u,
+ "{\"name\": \"thread_sort_index\", \"ph\": \"M\", \"pid\": %d, "
+ "\"tid\": 1, \"args\": {\"sort_index\": 1}},\n",
+ _nock_pid_i);
+ fprintf(_file_u,
+ "{\"name\": \"thread_name\", \"ph\": \"M\", \"pid\": %d, \"tid\": 2, "
+ "\"args\": {\"name\": \"Nock\"}},\n",
+ _nock_pid_i);
+ fprintf(_file_u,
+ "{\"name\": \"thread_sort_index\", \"ph\": \"M\", \"pid\": %d, "
+ "\"tid\": 2, \"args\": {\"sort_index\": 2}},\n",
+ _nock_pid_i);
+ _trace_cnt_w = 5;
+}
+
+/* u3t_trace_close(): closes a trace file. optional.
+*/
+void
+u3t_trace_close(void)
+{
+ if ( !_file_u )
+ return;
+
+ // We don't terminate the JSON because of the file format.
+ fclose(_file_u);
+ _trace_cnt_w = 0;
+ _file_cnt_w++;
+}
+
+/* u3t_trace_time(): microsecond clock
+*/
+c3_d u3t_trace_time(void)
+{
+ struct timeval tim_tv;
+ gettimeofday(&tim_tv, 0);
+ return 1000000ULL * tim_tv.tv_sec + tim_tv.tv_usec;
+}
+
+/* u3t_nock_trace_push(): push a trace onto the trace stack; returns yes if pushed.
+ *
+ * The trace stack is a stack of [path time-entered].
+ */
+c3_o
+u3t_nock_trace_push(u3_noun lab)
+{
+ if ( !_file_u )
+ return c3n;
+
+ if ( (u3_nul == u3R->pro.trace) ||
+ !_(u3r_sing(lab, u3h(u3h(u3R->pro.trace)))) ) {
+ u3a_gain(lab);
+ c3_d time = u3t_trace_time();
+ u3R->pro.trace = u3nc(u3nc(lab, u3i_chubs(1, &time)), u3R->pro.trace);
+ return c3y;
+ }
+ else {
+ return c3n;
+ }
+}
+
+/* u3t_nock_trace_pop(): pops a trace from the trace stack.
+ *
+ * When we remove the trace from the stack, we check to see if the sample is
+ * large enough to process, as we'll otherwise keep track of individual +add
+ * calls. If it is, we write it out to the tracefile.
+ */
+void
+u3t_nock_trace_pop(void)
+{
+ if ( !_file_u )
+ return;
+
+ u3_noun trace = u3R->pro.trace;
+ u3R->pro.trace = u3k(u3t(trace));
+
+ u3_noun item = u3h(trace);
+ u3_noun lab = u3h(item);
+ c3_d start_time = u3r_chub(0, u3t(item));
+
+ // 33microseconds (a 30th of a millisecond).
+ c3_d duration = u3t_trace_time() - start_time;
+ if (duration > 33) {
+ c3_c* name = u3m_pretty_path(lab);
+
+ fprintf(_file_u,
+ "{\"cat\": \"nock\", \"name\": \"%s\", \"ph\":\"%c\", \"pid\": %d, "
+ "\"tid\": 2, \"ts\": %" PRIu64 ", \"dur\": %" PRIu64 "}, \n",
+ name,
+ 'X',
+ _nock_pid_i,
+ start_time,
+ duration);
+
+ c3_free(name);
+ _trace_cnt_w++;
+ }
+
+ u3z(trace);
+}
+
+/* u3t_event_trace(): dumps a simple event from outside nock.
+*/
+void
+u3t_event_trace(const c3_c* name, c3_c type)
+{
+ if ( !_file_u )
+ return;
+
+ fprintf(_file_u,
+ "{\"cat\": \"event\", \"name\": \"%s\", \"ph\":\"%c\", \"pid\": %d, "
+ "\"tid\": 1, \"ts\": %" PRIu64 ", \"id\": \"0x100\"}, \n",
+ name,
+ type,
+ _nock_pid_i,
+ u3t_trace_time());
+ _trace_cnt_w++;
+}
+
+/* u3t_print_steps: print step counter.
+*/
+void
+u3t_print_steps(FILE* fil_u, c3_c* cap_c, c3_d sep_d)
+{
+ u3_assert( 0 != fil_u );
+
+ c3_w gib_w = (sep_d / 1000000000ULL);
+ c3_w mib_w = (sep_d % 1000000000ULL) / 1000000ULL;
+ c3_w kib_w = (sep_d % 1000000ULL) / 1000ULL;
+ c3_w bib_w = (sep_d % 1000ULL);
+
+ // XX prints to stderr since it's called on shutdown, daemon may be gone
+ //
+ if ( sep_d ) {
+ if ( gib_w ) {
+ fprintf(fil_u, "%s: G/%d.%03d.%03d.%03d\r\n",
+ cap_c, gib_w, mib_w, kib_w, bib_w);
+ }
+ else if ( mib_w ) {
+ fprintf(fil_u, "%s: M/%d.%03d.%03d\r\n", cap_c, mib_w, kib_w, bib_w);
+ }
+ else if ( kib_w ) {
+ fprintf(fil_u, "%s: K/%d.%03d\r\n", cap_c, kib_w, bib_w);
+ }
+ else if ( bib_w ) {
+ fprintf(fil_u, "%s: %d\r\n", cap_c, bib_w);
+ }
+ }
+}
+
+/* u3t_damp(): print and clear profile data.
+*/
+void
+u3t_damp(FILE* fil_u)
+{
+ u3_assert( 0 != fil_u );
+
+ if ( 0 != u3R->pro.day ) {
+ u3_noun wol = u3do("pi-tell", u3R->pro.day);
+
+ // XX prints to stderr since it's called on shutdown, daemon may be gone
+ //
+ {
+ u3_noun low = wol;
+
+ while ( u3_nul != low ) {
+ c3_c* str_c = (c3_c*)u3r_tape(u3h(low));
+ fputs(str_c, fil_u);
+ fputs("\r\n", fil_u);
+
+ c3_free(str_c);
+ low = u3t(low);
+ }
+
+ u3z(wol);
+ }
+
+ /* bunt a +doss
+ */
+ u3R->pro.day = u3nt(u3nq(0, 0, 0, u3nq(0, 0, 0, 0)), 0, 0);
+ }
+
+ u3t_print_steps(fil_u, "nocks", u3R->pro.nox_d);
+ u3t_print_steps(fil_u, "cells", u3R->pro.cel_d);
+
+ u3R->pro.nox_d = 0;
+ u3R->pro.cel_d = 0;
+}
+
+/* _ct_sigaction(): profile sigaction callback.
+*/
+void _ct_sigaction(c3_i x_i)
+{
+ u3t_samp();
+}
+
+/* u3t_init(): initialize tracing layer.
+*/
+void
+u3t_init(void)
+{
+ u3T.noc_o = c3n;
+ u3T.glu_o = c3n;
+ u3T.mal_o = c3n;
+ u3T.far_o = c3n;
+ u3T.coy_o = c3n;
+ u3T.euq_o = c3n;
+}
+
+c3_w
+u3t_trace_cnt(void)
+{
+ return _trace_cnt_w;
+}
+
+c3_w
+u3t_file_cnt(void)
+{
+ return _file_cnt_w;
+}
+
+/* u3t_boot(): turn sampling on.
+*/
+void
+u3t_boot(void)
+{
+#ifndef U3_OS_windows
+ if ( u3C.wag_w & u3o_debug_cpu ) {
+ _ct_lop_o = c3n;
+#if defined(U3_OS_PROF)
+ // skip profiling if we don't yet have an arvo kernel
+ //
+ if ( 0 == u3A->roc ) {
+ return;
+ }
+
+ // Register _ct_sigaction to be called on `SIGPROF`.
+ {
+ struct sigaction sig_s = {{0}};
+ sig_s.sa_handler = _ct_sigaction;
+ sigemptyset(&(sig_s.sa_mask));
+ sigaction(SIGPROF, &sig_s, 0);
+ }
+
+ // Unblock `SIGPROF` for this thread (we will block it again when `u3t_boff` is called).
+ {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGPROF);
+ if ( 0 != pthread_sigmask(SIG_UNBLOCK, &set, NULL) ) {
+ u3l_log("trace: thread mask SIGPROF: %s", strerror(errno));
+ }
+ }
+
+ // Ask for SIGPROF to be sent every 10ms.
+ {
+ struct itimerval itm_v = {{0}};
+ itm_v.it_interval.tv_usec = 10000;
+ itm_v.it_value = itm_v.it_interval;
+ setitimer(ITIMER_PROF, &itm_v, 0);
+ }
+#endif
+ }
+#endif
+}
+
+/* u3t_boff(): turn profile sampling off.
+*/
+void
+u3t_boff(void)
+{
+#ifndef U3_OS_windows
+ if ( u3C.wag_w & u3o_debug_cpu ) {
+#if defined(U3_OS_PROF)
+ // Mask SIGPROF signals in this thread (and this is the only
+ // thread that unblocked them).
+ {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGPROF);
+ if ( 0 != pthread_sigmask(SIG_BLOCK, &set, NULL) ) {
+ u3l_log("trace: thread mask SIGPROF: %s", strerror(errno));
+ }
+ }
+
+ // Disable the SIGPROF timer.
+ {
+ struct itimerval itm_v = {{0}};
+ setitimer(ITIMER_PROF, &itm_v, 0);
+ }
+
+ // Ignore SIGPROF signals.
+ {
+ struct sigaction sig_s = {{0}};
+ sigemptyset(&(sig_s.sa_mask));
+ sig_s.sa_handler = SIG_IGN;
+ sigaction(SIGPROF, &sig_s, 0);
+ }
+#endif
+ }
+#endif
+}
+
+
+/* u3t_slog_cap(): slog a tank with a caption with
+** a given priority c3_l (assumed 0-3).
+*/
+void
+u3t_slog_cap(c3_l pri_l, u3_noun cap, u3_noun tan)
+{
+ u3t_slog(
+ u3nc(
+ pri_l,
+ u3nt(
+ c3__rose,
+ u3nt(u3nt(':', ' ', u3_nul), u3_nul, u3_nul),
+ u3nt(cap, tan, u3_nul)
+ )
+ )
+ );
+}
+
+
+/* u3t_slog_trace(): given a c3_l priority pri and a raw stack tax
+** flop the order into start-to-end, render, and slog each item
+** until done.
+*/
+void
+u3t_slog_trace(c3_l pri_l, u3_noun tax)
+{
+ // render the stack
+ // Note: ton is a reference to a data struct
+ // we have just allocated
+ // lit is used as a moving cursor pointer through
+ // that allocated struct
+ // once we finish lit will be null, but ton will still
+ // point to the whole valid allocated data structure
+ // and thus we can free it safely at the end of the func
+ // to clean up after ourselves.
+ // Note: flop reverses the stack trace list 'tax'
+ u3_noun ton = u3dc("mook", 2, u3kb_flop(tax));
+ u3_noun lit = u3t(ton);
+
+ // print the stack one stack item at a time
+ while ( u3_nul != lit ) {
+ u3t_slog(u3nc(pri_l, u3k(u3h(lit)) ));
+ lit = u3t(lit);
+ }
+
+ u3z(ton);
+}
+
+
+/* u3t_slog_nara(): slog only the deepest road's trace with
+** c3_l priority pri
+*/
+void
+u3t_slog_nara(c3_l pri_l)
+{
+ u3_noun tax = u3k(u3R->bug.tax);
+ u3t_slog_trace(pri_l, tax);
+}
+
+
+/* u3t_slog_hela(): join all roads' traces together into one tax
+** and pass it to slog_trace along with the given c3_l priority pri_l
+*/
+void
+u3t_slog_hela(c3_l pri_l)
+{
+ // rod_u protects us from mutating the global state
+ u3_road* rod_u = u3R;
+
+ // inits to the the current road's trace
+ u3_noun tax = u3k(rod_u->bug.tax);
+
+ // while there is a parent road ref ...
+ while ( &(u3H->rod_u) != rod_u ) {
+ // ... point at the next road and append its stack to tax
+ rod_u = u3tn(u3_road, rod_u->par_p);
+ tax = u3kb_weld(tax, u3k(rod_u->bug.tax));
+ }
+
+ u3t_slog_trace(pri_l, tax);
+}
+
+/* _ct_roundf(): truncate a float to precision equivalent to %.2f */
+static float
+_ct_roundf(float per_f)
+{
+ // scale the percentage so that all siginificant digits
+ // would be retained when truncted to an int, then add 0.5
+ // to account for rounding without using round or roundf
+ float big_f = (per_f*10000)+0.5;
+ // truncate to int
+ c3_w big_w = (c3_w) big_f;
+ // convert to float and scale down such that
+ // our last two digits are right of the decimal
+ float tuc_f = (float) big_w/100.0;
+ return tuc_f;
+}
+
+/* _ct_meme_percent(): convert two ints into a percentage */
+static float
+_ct_meme_percent(c3_w lit_w, c3_w big_w)
+{
+ // get the percentage of our inputs as a float
+ float raw_f = (float) lit_w/big_w;
+ return _ct_roundf(raw_f);
+}
+
+/* _ct_all_heap_size(): return the size in bytes of ALL space on the Loom
+** over all roads, currently in use as heap.
+*/
+static c3_w
+_ct_all_heap_size(u3_road* r) {
+ if (r == &(u3H->rod_u)) {
+ return u3a_heap(r)*4;
+ } else {
+ // recurse
+ return (u3a_heap(r)*4) + _ct_all_heap_size(u3tn(u3_road, r->par_p));
+ }
+}
+
+/* These two structs, bar_item and bar_info, store the mutable data
+** to normalize measured Loom usage values into ints that will fit
+** into a fixed width ascii bar chart.
+*/
+struct
+bar_item {
+ // index
+ c3_w dex_w;
+ // lower bound
+ c3_w low_w;
+ // original value
+ float ori_f;
+ // difference
+ float dif_f;
+};
+
+struct
+bar_info {
+ struct bar_item s[6];
+};
+
+/* _ct_boost_small(): we want zero to be zero,
+** anything between zero and one to be one,
+** and all else to be whatever it is.
+*/
+static float
+_ct_boost_small(float num_f)
+{
+ return
+ 0.0 >= num_f ? 0.0:
+ 1.0 > num_f ? 1.0:
+ num_f;
+}
+
+/* _ct_global_difference(): each low_w represents the normalized integer value
+ * of its loom item, and ideally the sum of all loom low_w
+ * values should be 100. This function reports how far from
+ * the ideal bar_u is.
+*/
+static c3_ws
+_ct_global_difference(struct bar_info bar_u)
+{
+ c3_w low_w = 0;
+ for (c3_w i=0; i < 6; i++) {
+ low_w += bar_u.s[i].low_w;
+ }
+ return 100 - low_w;
+}
+
+/* _ct_compute_roundoff_error(): for each loom item in bar_u
+** compute the current difference between the int
+** size and the original float size.
+*/
+static struct bar_info
+_ct_compute_roundoff_error(struct bar_info bar_u)
+{
+ for (c3_w i=0; i < 6; i++) {
+ bar_u.s[i].dif_f = bar_u.s[i].ori_f - bar_u.s[i].low_w;
+ }
+ return bar_u;
+}
+
+/* _ct_sort_by_roundoff_error(): sort loom items from most mis-sized to least */
+static struct bar_info
+_ct_sort_by_roundoff_error(struct bar_info bar_u)
+{
+ struct bar_item tem_u;
+ for (c3_w i=1; i < 6; i++) {
+ for (c3_w j=0; j < 6-i; j++) {
+ if (bar_u.s[j+1].dif_f > bar_u.s[j].dif_f) {
+ tem_u = bar_u.s[j];
+ bar_u.s[j] = bar_u.s[j+1];
+ bar_u.s[j+1] = tem_u;
+ }
+ }
+ }
+ return bar_u;
+}
+
+/* _ct_sort_by_index(): sort loom items into loom order */
+static struct bar_info
+_ct_sort_by_index(struct bar_info bar_u)
+{
+ struct bar_item tem_u;
+ for (c3_w i=1; i < 6; i++) {
+ for (c3_w j=0; j < 6-i; j++) {
+ if (bar_u.s[j+1].dex_w < bar_u.s[j].dex_w) {
+ tem_u = bar_u.s[j];
+ bar_u.s[j] = bar_u.s[j+1];
+ bar_u.s[j+1] = tem_u;
+ }
+ }
+ }
+ return bar_u;
+}
+
+/* _ct_reduce_error(): reduce error by one int step
+ * making oversized things a bit smaller
+ * and undersized things a bit bigger
+*/
+static struct bar_info
+_ct_reduce_error(struct bar_info bar_u, c3_ws dif_s)
+{
+ for (c3_w i=0; i < 6; i++) {
+ if (bar_u.s[i].low_w == 0) continue;
+ if (bar_u.s[i].low_w == 1) continue;
+ if (dif_s > 0) {
+ bar_u.s[i].low_w++;
+ dif_s--;
+ }
+ if (dif_s < 0) {
+ bar_u.s[i].low_w--;
+ dif_s++;
+ }
+ }
+ return bar_u;
+}
+
+/* _ct_report_bargraph(): render all six raw loom elements into a fixed-size ascii bargraph */
+static void
+_ct_report_bargraph(
+ c3_c bar_c[105], float hip_f, float hep_f, float fre_f, float pen_f, float tak_f, float tik_f
+)
+{
+ float in[6];
+ in[0] = _ct_boost_small(hip_f);
+ in[1] = _ct_boost_small(hep_f);
+ in[2] = _ct_boost_small(fre_f);
+ in[3] = _ct_boost_small(pen_f);
+ in[4] = _ct_boost_small(tak_f);
+ in[5] = _ct_boost_small(tik_f);
+
+ // init the list of structs
+ struct bar_info bar_u;
+ for (c3_w i=0; i < 6; i++) {
+ bar_u.s[i].dex_w = i;
+ bar_u.s[i].ori_f = in[i];
+ bar_u.s[i].low_w = (c3_w) bar_u.s[i].ori_f;
+ }
+
+ // repeatedly adjust for roundoff error
+ // until it is elemenated or we go 100 cycles
+ c3_ws dif_s = 0;
+ for (c3_w x=0; x<100; x++) {
+ bar_u = _ct_compute_roundoff_error(bar_u);
+ dif_s = _ct_global_difference(bar_u);
+ if (dif_s == 0) break;
+ bar_u = _ct_sort_by_roundoff_error(bar_u);
+ bar_u = _ct_reduce_error(bar_u, dif_s);
+ }
+ bar_u = _ct_sort_by_index(bar_u);
+
+ for (c3_w x=1; x<104; x++) {
+ bar_c[x] = ' ';
+ }
+ bar_c[0] = '[';
+
+ // create our bar chart
+ const c3_c sym_c[6] = "=-%#+~";
+ c3_w x = 0, y = 0;
+ for (c3_w i=0; i < 6; i++) {
+ x++;
+ for (c3_w j=0; j < bar_u.s[i].low_w; j++) {
+ bar_c[x+j] = sym_c[i];
+ y = x+j;
+ }
+ if (y > 0) x = y;
+ }
+ bar_c[101] = ']';
+ bar_c[102] = 0;
+}
+
+/* _ct_size_prefix(): return the correct metric scalar prifix for a given int */
+static c3_c
+_ct_size_prefix(c3_d num_d)
+{
+ return
+ (num_d / 1000000000) ? 'G':
+ (num_d % 1000000000) / 1000000 ? 'M':
+ (num_d % 1000000) / 1000 ? 'K':
+ (num_d % 1000) ? ' ':
+ 'X';
+}
+
+/* _ct_report_string(): convert a int into a string, adding a metric scale prefix letter*/
+static void
+_ct_report_string(c3_c rep_c[32], c3_d num_d)
+{
+ memset(rep_c, ' ', 31);
+
+ // add the G/M/K prefix
+ rep_c[24] = _ct_size_prefix(num_d);
+ // consume wor_w into a string one base-10 digit at a time
+ // including dot formatting
+ c3_w i = 0, j = 0;
+ while (num_d > 0) {
+ if (j == 3) {
+ rep_c[22-i] = '.';
+ i++;
+ j = 0;
+ } else {
+ rep_c[22-i] = (num_d%10)+'0';
+ num_d /= 10;
+ i++;
+ j++;
+ }
+ }
+}
+
+/* _ct_etch_road_depth(): return a the current road depth as a fixed size string */
+static void
+ _ct_etch_road_depth(c3_c rep_c[32], u3_road* r, c3_w num_w) {
+ if (r == &(u3H->rod_u)) {
+ _ct_report_string(rep_c, num_w);
+ // this will be incorrectly indented, so we fix that here
+ c3_w i = 14;
+ while (i > 0) {
+ rep_c[i] = rep_c[i+16];
+ rep_c[i+16] = ' ';
+ i--;
+ }
+ } else {
+ _ct_etch_road_depth(rep_c, u3tn(u3_road, r->par_p), ++num_w);
+ }
+}
+
+/* _ct_etch_memory(): create a single line report of a given captioned item
+ * with a percentage of space used and the bytes used
+ * scaled by a metric scaling postfix (ie MB, GB, etc)
+*/
+static void
+_ct_etch_memory(c3_c rep_c[32], float per_f, c3_w num_w)
+{
+ // create the basic report string
+ _ct_report_string(rep_c, num_w);
+ // add the Bytes postfix to the size report
+ rep_c[25] = 'B';
+
+ // add the space-percentage into the report
+ rep_c[2] = '0', rep_c[3] = '.', rep_c[4] = '0', rep_c[5] = '0';
+ c3_w per_i = (c3_w) (per_f*100);
+ c3_w i = 0;
+ while (per_i > 0 && i < 6) {
+ if (i != 2) {
+ rep_c[5-i] = (per_i%10)+'0';
+ per_i /= 10;
+ }
+ i++;
+ }
+ // add the percent sign
+ rep_c[6] = '%';
+}
+
+/* _ct_etch_steps(): create a single line report of a given captioned item
+** scaled by a metric scaling postfix, but unitless.
+*/
+static void
+_ct_etch_steps(c3_c rep_c[32], c3_d sep_d)
+{
+ _ct_report_string(rep_c, sep_d);
+}
+
+/* u3t_etch_meme(): report memory stats at call time */
+u3_noun
+u3t_etch_meme(c3_l mod_l)
+{
+ u3a_road* lum_r;
+ lum_r = &(u3H->rod_u);
+ // this will need to switch to c3_d when we go to a 64 loom
+ c3_w top_w = u3a_full(lum_r)*4,
+ ful_w = u3a_full(u3R)*4,
+ fre_w = u3a_idle(u3R)*4,
+ tak_w = u3a_temp(u3R)*4,
+ hap_w = u3a_heap(u3R)*4,
+ pen_w = u3a_open(u3R)*4;
+
+ c3_w imu_w = top_w-ful_w;
+ c3_w hep_w = hap_w-fre_w;
+
+
+ float hep_f = _ct_meme_percent(hep_w, top_w),
+ fre_f = _ct_meme_percent(fre_w, top_w),
+ pen_f = _ct_meme_percent(pen_w, top_w),
+ tak_f = _ct_meme_percent(tak_w, top_w);
+ float ful_f = hep_f + fre_f + pen_f + tak_f;
+
+ c3_w hip_w = _ct_all_heap_size(u3R) - hap_w;
+ c3_w tik_w = imu_w - hip_w;
+ float hip_f = _ct_meme_percent(hip_w, top_w),
+ tik_f = _ct_meme_percent(tik_w, top_w);
+
+#ifdef U3_CPU_DEBUG
+ /* iff we are using CPU_DEBUG env var
+ ** we can report more facts:
+ ** max_w: max allocated on the current road (not global, not including child roads)
+ ** cel_d: max cells allocated in current road (inc closed kids, but not parents)
+ ** nox_d: nock steps performed in current road
+ */
+ c3_w max_w = (u3R->all.max_w*4)+imu_w;
+ float max_f = _ct_meme_percent(max_w, top_w);
+ c3_d cel_d = u3R->pro.cel_d;
+ c3_d nox_d = u3R->pro.nox_d;
+ // iff we have a max_f we will render it into the bar graph
+ // in other words iff we have max_f it will always replace something
+ c3_w inc_w = (max_f > hip_f+1.0) ? (c3_w) max_f+0.5 : (c3_w) hip_f+1.5;
+#endif
+
+ // warn if any sanity checks have failed
+ if (100.01 < (hip_f + hep_f + fre_f + pen_f + tak_f + tik_f))
+ u3t_slog_cap(2, u3i_string("warning"), u3i_string("loom sums over 100.01%"));
+ if ( 99.99 > (hip_f + hep_f + fre_f + pen_f + tak_f + tik_f))
+ u3t_slog_cap(2, u3i_string("warning"), u3i_string("loom sums under 99.99%"));
+
+ c3_c bar_c[105];
+ bar_c[0] = 0;
+ _ct_report_bargraph(bar_c, hip_f, hep_f, fre_f, pen_f, tak_f, tik_f);
+
+ c3_w dol = (c3_w) _ct_roundf(hip_f/100);
+ bar_c[dol] = '$';
+#ifdef U3_CPU_DEBUG
+ if (max_f > 0.0) {
+ bar_c[inc_w] = '|';
+ }
+#endif
+
+ c3_c dir_n[8];
+ dir_n[0] = 0;
+ if ( u3a_is_north(u3R) == c3y ) {
+ strcat(dir_n, " North");
+ } else {
+ strcat(dir_n, " South");
+ }
+
+ if (mod_l == 0) {
+ return u3i_string(bar_c);
+ }
+ else {
+ c3_c rep_c[32];
+ rep_c[31] = 0;
+ c3_c str_c[1024];
+ str_c[0] = 0;
+ // each report line is at most 54 chars long
+ strcat(str_c, "Legend | Report:");
+
+ strcat(str_c, "\n loom: "); _ct_etch_memory(rep_c, 100.0, top_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n road: "); _ct_etch_memory(rep_c, ful_f, ful_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n");
+ strcat(str_c, "\n = immutable heap: "); _ct_etch_memory(rep_c, hip_f, hip_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n - solid heap: "); _ct_etch_memory(rep_c, hep_f, hep_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n % freed heap: "); _ct_etch_memory(rep_c, fre_f, fre_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n # open space: "); _ct_etch_memory(rep_c, pen_f, pen_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n + stack: "); _ct_etch_memory(rep_c, tak_f, tak_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n ~ immutable stack: "); _ct_etch_memory(rep_c, tik_f, tik_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n");
+ strcat(str_c, "\n $ allocation frame: "); _ct_etch_memory(rep_c, hip_f, hip_w); strcat(str_c, rep_c);
+#ifdef U3_CPU_DEBUG
+ strcat(str_c, "\n | road max memory: "); _ct_etch_memory(rep_c, max_f, max_w); strcat(str_c, rep_c);
+ strcat(str_c, "\n");
+ strcat(str_c, "\n road cells made: "); _ct_etch_steps(rep_c, cel_d); strcat(str_c, rep_c);
+ strcat(str_c, "\n road nocks made: "); _ct_etch_steps(rep_c, nox_d); strcat(str_c, rep_c);
+#endif
+ strcat(str_c, "\n road direction: "); strcat(str_c, dir_n);
+ strcat(str_c, "\n road depth: "); _ct_etch_road_depth(rep_c, u3R, 1); strcat(str_c, rep_c);
+ strcat(str_c, "\n\nLoom: "); strcat(str_c, bar_c);
+ return u3i_string(str_c);
+ }
+}
+
+/* u3t_sstack_init: initalize a root node on the spin stack
+*/
+void
+u3t_sstack_init()
+{
+#ifndef U3_OS_windows
+ c3_c shm_name[256];
+ snprintf(shm_name, sizeof(shm_name), SLOW_STACK_NAME, getppid());
+ c3_w shm_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);
+ if ( -1 == shm_fd) {
+ perror("shm_open failed");
+ return;
+ }
+
+ if ( -1 == ftruncate(shm_fd, TRACE_PSIZE)) {
+ perror("truncate failed");
+ return;
+ }
+
+ stk_u = mmap(NULL, TRACE_PSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
+
+ if ( MAP_FAILED == stk_u ) {
+ perror("mmap failed");
+ return;
+ }
+
+ stk_u->off_w = 0;
+ stk_u->fow_w = 0;
+ u3t_sstack_push(c3__root);
+#endif
+}
+
+#ifndef U3_OS_windows
+/* u3t_sstack_open: initalize a root node on the spin stack
+ */
+u3t_spin*
+u3t_sstack_open()
+{
+ //Setup spin stack
+ c3_c shm_name[256];
+ snprintf(shm_name, sizeof(shm_name), SLOW_STACK_NAME, getpid());
+ c3_w shm_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0);
+ if ( -1 == shm_fd) {
+ perror("shm_open failed");
+ return NULL;
+ }
+
+ u3t_spin* stk_u = mmap(NULL, TRACE_PSIZE,
+ PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
+
+ if ( MAP_FAILED == stk_u ) {
+ perror("mmap failed");
+ return NULL;
+ }
+
+ return stk_u;
+}
+#endif
+/* u3t_sstack_exit: shutdown the shared memory for thespin stack
+*/
+void
+u3t_sstack_exit()
+{
+ munmap(stk_u, u3a_page);
+}
+
+/* u3t_sstack_push: push a noun on the spin stack.
+*/
+void
+u3t_sstack_push(u3_noun nam)
+{
+ if ( !stk_u ) {
+ u3z(nam);
+ return;
+ }
+
+ if ( c3n == u3ud(nam) ) {
+ u3z(nam);
+ nam = c3__cell;
+ }
+
+ c3_w met_w = u3r_met(3, nam);
+
+ // Exit if full
+ if ( 0 < stk_u->fow_w ||
+ sizeof(stk_u->dat_y) < stk_u->off_w + met_w + sizeof(c3_w) ) {
+ stk_u->fow_w++;
+ return;
+ }
+
+ u3r_bytes(0, met_w, (c3_y*)(stk_u->dat_y+stk_u->off_w), nam);
+ stk_u->off_w += met_w;
+
+ memcpy(&stk_u->dat_y[stk_u->off_w], &met_w, sizeof(c3_w));
+ stk_u->off_w += sizeof(c3_w);
+ u3z(nam);
+}
+
+/* u3t_sstack_pop: pop a noun from the spin stack.
+*/
+void
+u3t_sstack_pop()
+{
+ if ( !stk_u ) return;
+ if ( 0 < stk_u->fow_w ) {
+ stk_u->fow_w--;
+ } else {
+ c3_w len_w = (c3_w) stk_u->dat_y[stk_u->off_w - sizeof(c3_w)];
+ stk_u->off_w -= (len_w+sizeof(c3_w));
+ }
+}
+