swish/commit

Copied from upstream

authorJan Wielemaker
Fri Apr 8 10:46:29 2016 +0200
committerJan Wielemaker
Fri Apr 8 10:46:29 2016 +0200
commit5ed0d4100a65634fec402460be34477e5180172b
tree845c60e3bd64691d71f942aca2bdabd384d0bcb1
parentfa07022a2bf1f4d3260f81f37f4f7ac33e84ce47
Diff style: patch stat
diff --git a/examples/render_c3.swinb b/examples/render_c3.swinb
index a8fc501..3eb8d2d 100644
--- a/examples/render_c3.swinb
+++ b/examples/render_c3.swinb
@@ -14,7 +14,7 @@ Creating a C3 chart requires including the directive  `:- use_rendering(c3).` an
     space changes.
   - The renderer performs some basic sanity checks on the data and may
     report an error.
-  
+
 ## Our first chart
 
 As a first example, we will create a chart for the _sine_ function in the range `0..360` degrees.  The predicate sin/2 defines the sine relation for 37 datapoints on 10 degrees intervals.  We use findall/3 to create the required data rows.
@@ -64,7 +64,7 @@ rows: [ x - sine,
       ]
 ```
 
-Finally, we can use _dicts_.  If we do so, we can omit the first row that 
+We can use _dicts_.  If we do so, we can omit the first row that
 defines the column names as the dict _keys_ are used for that.  So, the data can be represented as:
 
 ```
@@ -101,6 +101,44 @@ chart(Chart) :-
 chart(Chart).
 </div>
 
+<div class="nb-cell markdown">
+You can also represent data with colums. In this case the data should be a list
+of lists where each sublist represent a series of values and the first element
+is the name of the series.
+
+```
+columns: [ [x,0,10,...],
+           [sine,0.0, 0.173,..],
+	   [cosine, 1.0, ...]
+	 ]
+```
+
+</div>
+<div class="nb-cell program">
+:- use_rendering(c3).
+
+sin(X,Y) :-
+    between(0,36,I),
+    X is I*10,
+    Y is sin(X*pi/180).
+
+cos(X,Y) :-
+    between(0,36,I),
+    X is I*10,
+    Y is cos(X*pi/180).
+
+chart(Chart) :-
+    findall(X, sin(X,Y), XData),	% create x values
+    findall(Y, sin(X,Y), SinData),	% create sin-series
+    findall(Y, cos(X,Y), CosData),	% create cos-series
+    Chart = c3{data:_{x:x, columns:[
+     [x|XData],[sine|SinData],[cosine|CosData]]}}.
+</div>
+
+<div class="nb-cell query">
+chart(Chart).
+</div>
+
 <div class="nb-cell markdown">
 ## Creating pie and bar charts
 
@@ -160,7 +198,7 @@ Chart = c3{data:_{columns:_Pairs, type:pie}}.
 <div class="nb-cell query">
 popular_elements(_Pairs),
 Chart = c3{data:_{x:elem, rows:[elem-count|_Pairs], type:bar},
-   	       axis:_{x:_{type:category}}}.
+	       axis:_{x:_{type:category}}}.
 </div>
 
 <div class="nb-cell markdown">
diff --git a/examples/render_graphviz.swinb b/examples/render_graphviz.swinb
index 0e54205..084d315 100644
--- a/examples/render_graphviz.swinb
+++ b/examples/render_graphviz.swinb
@@ -14,7 +14,7 @@ The `graphviz` renderer needs to obtain a dot specification and it needs to know
   - Using Layout(Graph), where `Graph` is a Prolog term representing the
     dot AST (Abstract Syntax Tree).  The AST is translated into concrete
     dot syntax using a DCG grammar.
-    
+
 Before we go into details, we do the _Hello World_ demo.  First, load the `graphviz` renderer:
 </div>
 
@@ -32,7 +32,7 @@ X = dot("digraph G { Hello-&gt;World }").
 
 <div class="nb-cell markdown">
 Using a dot string as specification is of course not desirable because it makes
-it hard to generate a graph from computed data.  We introduce the *Prolog representation* for dot with the same example.  The only noticible difference is that the _render selection corner_ becomes visible because the graph is given 
+it hard to generate a graph from computed data.  We introduce the *Prolog representation* for dot with the same example.  The only noticible difference is that the _render selection corner_ becomes visible because the graph is given
 the attribute `bgcolor=transparent` if no background is given, while the graphviz
 default is `white`.
 </div>
@@ -151,10 +151,20 @@ grammar below:
 <div class="nb-cell markdown">
 ## Generating Graphs from data
 
-Generating a graph from static data is of course not very interesting.  This section provides some examples for generating graphs from Prolog data.  This is where the Prolog representation of a _dot_ program comes in: it takes away the burden of generating the concrete dot syntax, avoiding issues such as proper escaping of labels.  In the example below we render an arbitrary (acyclic) Prolog term as a tree.  We do this by walking down the term, generating a node-id and a label for each node representing a compound term as well as each leaf.  Note that we cannot use the label as node id beause the same label may appear in multiple locations.  Thus, we generate dot statements like this:
+Generating a graph from static data is of course not very interesting.  This section provides an example for generating graphs from Prolog data.  This is where the Prolog representation of a _dot_ program comes in: it takes away the burden of generating the concrete dot syntax, avoiding issues such as proper escaping of labels.  In the example below we render an arbitrary (acyclic) Prolog term as a tree.  We do this by walking down the term, generating a node-id and a label for each node representing a compound term as well as each leaf.  Note that we cannot use the label as node id beause the same label may appear in multiple locations.  Thus, we generate dot statements like this:
 
   - node(Id, [label=Label])
   - `ChildID -&gt; ParentId`
+
+If `Label` is atomic, the plain value (without quotes, see write/1) will be rendered.
+Otherwise the label is handed to print/1 by the graphviz rendering.  Note that the
+graphviz rendering code is called _after_ the toplevel preparation of the answer.
+The toplevel preparation removes cycles, combines multiple variables bound to the
+same variable, binds named variables to a term =|'$VAR'(Name)|= and extracts
+_redidual goals_.  In particular, this implies you can *render a variable using
+its name* by using the plain variable as the label: The toplevel will bind the label to
+=|'$VAR'(Name)|= and print/1 will print this as the variable name.
+This is exploited by any_label/2 below.
 </div>
 
 <div class="nb-cell program">
@@ -171,11 +181,16 @@ tree(Compound, Root, Options0, Options) --&gt;
     children(Arguments, Root, Options0.put(id, ID1), Options).
 tree(Any, Leaf, Options0, Options) --&gt;
     { atom_concat(n, Options0.id, Leaf),
-      format(string(Label), '~p', [Any]),
       ID1 is Options0.id+1,
+      any_label(Any, Label, Color),
       Options = Options0.put(id, ID1)
     },
-    [ node(Leaf, [label=Label]) ].
+    [ node(Leaf, [label=Label, shape=none, fontcolor=Color]) ].
+
+any_label(Any, Label, red4) :-
+    var(Any), !, Label = Any.
+any_label(Any, Label, blue) :-
+    format(string(Label), '~p', [Any]).
 
 children([], _, Options, Options) --&gt; [].
 children([H|T], Parent, Options0, Options) --&gt;
diff --git a/lib/swish/render.pl b/lib/swish/render.pl
index b677d34..2fa89d0 100644
--- a/lib/swish/render.pl
+++ b/lib/swish/render.pl
@@ -117,11 +117,15 @@ user:term_expansion((:- use_rendering(Renderer, Options)), Expanded) :-
 	expand_rendering(Renderer, Options, Expanded).
 
 expand_rendering(Module:Renderer, Options,
-		 Module:'swish renderer'(Renderer, Options)) :- !,
+		 [ (:- discontiguous(Module:'swish renderer'/2)),
+		   Module:'swish renderer'(Renderer, Options)
+		 ]) :- !,
 	must_be(atom, Module),
 	must_be(atom, Renderer).
 expand_rendering(Renderer, Options,
-		 'swish renderer'(Renderer, Options)) :-
+		 [ (:- discontiguous('swish renderer'/2)),
+		   'swish renderer'(Renderer, Options)
+		 ]) :-
 	must_be(atom, Renderer).
 
 %%	pengines_io:binding_term(+Term, +Vars, +Options) is semidet.
diff --git a/lib/swish/render/c3.pl b/lib/swish/render/c3.pl
index b90fd57..dba189e 100644
--- a/lib/swish/render/c3.pl
+++ b/lib/swish/render/c3.pl
@@ -148,15 +148,12 @@ valid_c3_data(Data0, Data) :-
 	->  Data0 = Data
 	;   Data = Data0.put(rows,Rows)
 	).
-valid_c3_data(Data0, Data) :-
-	Columns0 = Data0.get(columns), !,
-	must_be(acyclic, Columns0),
-	rows_to_matrix(Columns0, Columns),
-	must_be(list(ground), Columns),
-	(   same_term(Columns0, Columns)
-	->  Data0 = Data
-	;   Data = Data0.put(columns,Columns)
-	).
+valid_c3_data(Data, Data) :-
+	Columns = Data.get(columns), !,
+	must_be(acyclic, Columns),
+	must_be(list,Columns),
+	maplist(must_be(list),Columns).
+
 valid_c3_data(Data, Data) :-
 	throw(error(c3_no_data(Data), _)).
 
diff --git a/lib/swish/render/graphviz.pl b/lib/swish/render/graphviz.pl
index 36f1264..74d4329 100644
--- a/lib/swish/render/graphviz.pl
+++ b/lib/swish/render/graphviz.pl
@@ -139,6 +139,7 @@ render_dot(DOTString, Program, _Options) -->	% <svg> rendering
 			   read_string(ErrorOut, _, Error)
 		       ),
 		       (   process_wait(PID, _Status),
+			   close(ErrorOut, [force(true)]),
 			   close(XDotOut)
 		       ))
 	},
@@ -247,6 +248,7 @@ swish_send_graphviz(Request) :-
 		       read_string(ErrorOut, _, Error)
 		     ),
 		     (	 process_wait(PID, _Status),
+			 close(ErrorOut, [force(true)]),
 			 close(XDotOut)
 		     )),
 	(   Error == ""
@@ -454,7 +456,7 @@ attribute(NameValue, _O)  -->
 
 value(Name, Value) -->
 	{ string_attribute(Name), !,
-	  atom_codes(Value, Codes)
+	  value_codes(Value, Codes)
 	},
 	"\"", cstring(Codes), "\"".
 value(_Name, Value, List, Tail) :-
@@ -478,6 +480,11 @@ spaces(N) -->
 	" ",
 	spaces(N2).
 
+value_codes(Value, Codes) :-
+	atomic(Value), !,
+	format(codes(Codes), '~w', [Value]).
+value_codes(Value, Codes) :-
+	format(codes(Codes), '~p', [Value]).
 
 
 		 /*******************************
@@ -494,6 +501,8 @@ string_attribute(href).
 string_attribute(id).
 string_attribute('URL').
 string_attribute(fillcolor).
+string_attribute(fontcolor).
+string_attribute(fontname).
 string_attribute(style).
 string_attribute(size).
 
diff --git a/lib/swish/swish_debug.pl b/lib/swish/swish_debug.pl
index 1f33490..da9d783 100644
--- a/lib/swish/swish_debug.pl
+++ b/lib/swish/swish_debug.pl
@@ -210,9 +210,10 @@ swish_stats(Name, Ring, Stats) :-
 stat_collect(Dims, Interval) :-
 	new_sliding_stats(Dims, SlidingStat),
 	get_time(Now),
-	stat_loop(SlidingStat, _{}, Now, Interval).
+	ITime is floor(Now),
+	stat_loop(SlidingStat, _{}, ITime, Interval, [true]).
 
-stat_loop(SlidingStat, Stat0, StatTime, Interval) :-
+stat_loop(SlidingStat, Stat0, StatTime, Interval, Wrap) :-
 	(   thread_self(Me),
 	    thread_get_message(Me, Request,
 			       [ deadline(StatTime)
@@ -221,12 +222,12 @@ stat_loop(SlidingStat, Stat0, StatTime, Interval) :-
 	    ->	true
 	    ;	debug(swish_stats, 'Failed to process ~p', [Request])
 	    ),
-	    stat_loop(SlidingStat, Stat0, StatTime, Interval)
-	;   swish_stats(Stat1),
+	    stat_loop(SlidingStat, Stat0, StatTime, Interval, Wrap)
+	;   get_stats(Wrap, Stat1),
 	    dif_stat(Stat1, Stat0, Stat),
-	    push_sliding_stats(SlidingStat, Stat),
+	    push_sliding_stats(SlidingStat, Stat, Wrap1),
 	    NextTime is StatTime+Interval,
-	    stat_loop(SlidingStat, Stat1, NextTime, Interval)
+	    stat_loop(SlidingStat, Stat1, NextTime, Interval, Wrap1)
 	).
 
 dif_stat(Stat1, Stat0, Stat) :-
@@ -247,38 +248,43 @@ reply_stats_request(Client-get_stats(Period), SlidingStat) :-
 	ring_values(Ring, Values),
 	thread_send_message(Client, get_stats(Period, Values)).
 
-%%	swish_stats(-Stats:dict) is det.
+%%	get_stats(+Wrap, -Stats:dict) is det.
 %
 %	Request elementary statistics.
 
-swish_stats(stats{ cpu:CPU,
-		   rss:RSS,
-		   fordblks:Fordblks,
-		   stack:Stack,
-		   pengines:Pengines,
-		   pengines_created:PenginesCreated,
-		   time:Time
-		 }) :-
+get_stats(Wrap, Stats) :-
+	Stats0 = stats{ cpu:CPU,
+			rss:RSS,
+			stack:Stack,
+			pengines:Pengines,
+			pengines_created:PenginesCreated,
+			time:Time
+		      },
 	get_time(Now),
 	Time is floor(Now),
 	statistics(process_cputime, PCPU),
 	statistics(cputime, MyCPU),
 	CPU is PCPU-MyCPU,
 	statistics(stack, Stack),
-	fordblks(Fordblks),
 	catch(procps_stat(Stat), _,
 	      Stat = stat{rss:0}),
 	RSS = Stat.rss,
 	swish_statistics(pengines(Pengines)),
-	swish_statistics(pengines_created(PenginesCreated)).
-
-:- if(\+current_predicate(mallinfo/1)).
-mallinfo(_{fordblks:0}).
+	swish_statistics(pengines_created(PenginesCreated)),
+	add_fordblks(Wrap, Stats0, Stats).
+
+:- if(current_predicate(mallinfo/1)).
+add_fordblks(Wrap, Stats0, Stats) :-
+	(   Wrap = [true|_]
+	->  mallinfo(MallInfo),
+	    FordBlks = MallInfo.get(fordblks),
+	    b_setval(fordblks, FordBlks)
+	;   nb_current(fordblks, FordBlks)
+	), !,
+	Stats = Stats0.put(fordblks, FordBlks).
 :- endif.
+add_fordblks(_, Stats, Stats).
 
-fordblks(Fordblks) :-
-	mallinfo(MallInfo),
-	Fordblks = MallInfo.fordblks.
 
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Maintain sliding statistics. The statistics are maintained in a ring. If
@@ -289,20 +295,20 @@ new_sliding_stats(Dims, Stats) :-
 	maplist(new_ring, Dims, Rings),
 	compound_name_arguments(Stats, sliding_stats, Rings).
 
-push_sliding_stats(Stats, Values) :-
-	push_sliding_stats(1, Stats, Values).
+push_sliding_stats(Stats, Values, Wrap) :-
+	push_sliding_stats(1, Stats, Values, Wrap).
 
-push_sliding_stats(I, Stats, Values) :-
+push_sliding_stats(I, Stats, Values, [Wrap|WrapT]) :-
 	arg(I, Stats, Ring),
 	push_ring(Ring, Values, Wrap),
 	(   Wrap == true
 	->  average_ring(Ring, Avg),
 	    I2 is I+1,
-	    (	push_sliding_stats(I2, Stats, Avg)
+	    (	push_sliding_stats(I2, Stats, Avg, WrapT)
 	    ->	true
 	    ;	true
 	    )
-	;   true
+	;   WrapT = []
 	).
 
 new_ring(Dim, ring(0, Ring)) :-