swish/commit

Synchronise examples more with normal SWISH

authorJan Wielemaker
Mon May 2 16:50:55 2016 +0200
committerJan Wielemaker
Mon May 2 16:50:55 2016 +0200
commitc20318dbed09ffc205ccf6da195d1b702771cb1f
treebaa2cdb6d1fdf0c9e82723710335c114aaadb081
parent241a52e5a9831efbab6ef10fcbfc674243857b31
Diff style: patch stat
diff --git a/Makefile b/Makefile
index 2178c4b..f5cc010 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,9 @@ FONTFILES=glyphicons-halflings-regular.ttf \
 FONTS=$(addprefix $(FONTDIR)/, $(FONTFILES))
 CLIENTFILES=swish-ask.sh README.md sin-table.html
 CLIENTS=$(addprefix client/, $(CLIENTFILES))
-EXAMPLESFILES=render_c3.swinb render_graphviz.swinb
+EXAMPLESFILES=render_c3.swinb render_graphviz.swinb htmlcell.swinb \
+	      rendering.swinb jquery.swinb stats.swinb \
+	      swish_tutorials.swinb
 EXAMPLES=$(addprefix examples/, $(EXAMPLESFILES))
 CMFILES=mode/htmlmixed/htmlmixed.js \
 	mode/javascript/javascript.js \
diff --git a/examples/htmlcell.swinb b/examples/htmlcell.swinb
new file mode 100644
index 0000000..3db8e5d
--- /dev/null
+++ b/examples/htmlcell.swinb
@@ -0,0 +1,170 @@
+<div class="notebook">
+
+<div class="nb-cell html">
+<h2>Using HTML cells in SWISH notebooks</h2>
+
+<p>
+  This notebook shows how HTML cells in a notebook can be used to create arbitrary web applications
+  inside a notebook.  The HTML is placed in a <code>div</code> element and is subject to
+  <a href="http://getbootstrap.com/">Bootstrap</a> styling.
+</p>
+<p>
+  The cell can contain <code>script</code> elements.  The script elements are executed after
+  the whole notebook is loaded and after editing the HTML cell and clicking outside the cell.
+  First, the text of all script element without a <code>lang</code> attribute or <code>lang="text/javascript"</code>
+  is collected.  This is wrapped into an <em>anonymous</em> function with the argument
+  <code>notebook</code>.  The <code>notebook</code> argument is an object with the following
+  properties:
+</p>
+
+<div class="list-group">
+  <dl class="dl-horizontal">
+    <dt>.cell()</dt><dd>Returns a jQuery object pointing to the HTML cell
+    </dd><dt>.notebook()</dt><dd>Returns a jQuery object of the entire notebook
+    </dd><dt>.$(selector)</dt><dd>Returns a jQuery object holding all DOM elements
+    matching <var>selector</var> in the current HTML cell.
+    </dd><dt>.run(query, parameters)</dt><dd>Run the named query cell.  <var>Parameters</var> is an object
+    binding Prolog variables in the query to specified values.
+    </dd><dt>.swish(options)</dt><dd>Wrapper around <code>new Pengine()</code> that fetches the sources
+    using the same algorithm as a query cell and sets the <code>application</code> to <code>swish</code>.
+  </dd></dl>
+</div>
+
+<p>
+  Double click anywhere in this cell to <b>see the source</b>.  Then click anywhere
+  inside the notebook, but <em>outside</em> this cell to see the result.
+</p>
+
+<h4>Example</h4>
+
+<p>In the example below we provide an English grammer, some example sentences
+  and simple Bootstrap form to interact with the query.  The examples are loaded
+  dynamically from the example sentences defined in the Prolog program at the
+  end of the page.
+</p>
+
+<div class="panel panel-default">
+  <div class="panel-body">
+    <div class="form-group">
+      <label>Sentence</label>
+      <div class="input-group">
+        <div class="input-group-btn">
+          <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Example
+            <span class="caret"></span></button>
+          <ul class="dropdown-menu">
+          </ul>
+        </div>
+        <input class="form-control">
+        <div class="input-group-btn">
+          <button type="button" class="btn btn-primary">Parse</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script>
+  // Load examples from the predicate examples/1.  notebook.swish() is a wrapper
+  // around new Pengine() that fetches the sources using the same algorithm as
+  // a query cell and set the `application` to `swish`.
+  // notebook.$() is a shorthand for notebook.cell().find(), evaluating to a
+  // jQuery object that matches the objects from the current cell.
+  function loadExamples() {
+    var seen = 0;
+    notebook.$(".dropdown-menu").html("");
+    notebook.swish({ ask: "example(Ex)",
+                     ondata: function(data) {
+                     notebook.$(".dropdown-menu").append('<li><a>'+data.Ex+'</li>');
+                     if ( seen++ == 0 )
+                       notebook.$("input").val(data.Ex);
+                    }
+                   });
+  }
+  // Load the examples on page load as well as if the user clicks the
+  // dropdown menu, so changes are reflected.
+  loadExamples();
+  notebook.$(".dropdown-toggle").on("click", loadExamples);
+
+  // Pass selected examples to the input field.
+  notebook.$(".dropdown-menu").on("click", "li", function(ev) {
+    notebook.$("input").val($(this).text());
+  });
+
+  // If the "Parse" button is clicked, run the query named "parse"
+  // binding Sentence to the input string.  The function
+  // notebook.run() takes the name of a query and an object
+  // holding bindings.  This is translated to run the query
+  // Sentence = (String), (parse(Sentence, Tree)).
+  notebook.$(".btn-primary").on("click", function() {
+    notebook.run("parse", {Sentence: notebook.$("input").val()});
+  });
+</script>
+</div>
+
+<div class="nb-cell query" name="parse">
+parse(Sentence, Tree).
+</div>
+
+<div class="nb-cell markdown">
+### The programs
+
+Below are three program fragments.  All three are declared as _background_ programs, so they are available to all queries posted from this notebook.  They specify
+
+  - The grammar itself
+  - Examples that are loaded into the above interface.
+  - Calling the grammar and translating it to a graphical tree
+
+You can change the grammar as well as the example sentences and see the immediate effect.
+</div>
+
+<div class="nb-cell program" data-background="true">
+% A simple English DCG grammar
+% ============================
+
+s(s(NP,VP)) --&gt; np(NP, Num), vp(VP, Num).
+
+np(NP, Num) --&gt; pn(NP, Num).
+np(np(Det,N), Num) --&gt; det(Det, Num), n(N, Num).
+np(np(Det,N,PP), Num) --&gt; det(Det, Num), n(N, Num), pp(PP).
+
+vp(vp(V,NP), Num) --&gt; v(V, Num), np(NP, _).
+vp(vp(V,NP,PP), Num) --&gt; v(V, Num), np(NP, _), pp(PP).
+
+pp(pp(P,NP)) --&gt; p(P), np(NP, _).
+
+det(det(a), sg) --&gt; [a].
+det(det(the), _) --&gt; [the].
+
+pn(pn(john), sg) --&gt; [john].
+
+n(n(man), sg) --&gt; [man].
+n(n(men), pl) --&gt; [men].
+n(n(telescope), sg) --&gt; [telescope].
+
+v(v(sees), sg) --&gt; [sees].
+v(v(see), pl) --&gt; [see].
+v(v(saw), _) --&gt; [saw].
+
+p(p(with)) --&gt; [with].
+</div>
+
+<div class="nb-cell program" data-background="true" data-singleline="true">
+example("john sees a man").
+example("a man sees john").
+example("john sees a man with a telescope").
+</div>
+
+<div class="nb-cell program" data-background="true" data-singleline="true">
+:- use_rendering(svgtree, [list(false)]).
+
+parse(Sentence, Tree) :-
+    nonvar(Sentence), !,
+    split_string(Sentence, " ", " ", Strings),
+    maplist(atom_string, Words, Strings),
+    phrase(s(Tree), Words).
+parse(Sentence, Tree) :-
+    phrase(s(Tree), Words),
+    atomics_to_string(Words, " ", Sentence).
+</div>
+
+</div>
diff --git a/examples/index.json b/examples/index.json
index f84c2cf..74efa16 100644
--- a/examples/index.json
+++ b/examples/index.json
@@ -1,7 +1,8 @@
 // list of examples, represented as a JSON list
 
 [
-{ "file":"rdf_examples.swinb",		  "title":"RDF queries" },
-{ "file":"render_c3.swinb",		  "title":"C3 chart rendering" },
-{ "file":"render_graphviz.swinb",	  "title":"Graphviz graph rendering" }
+{ "file":"rdf_examples.swinb",		"title":"RDF queries" },
+{ "file":"swish_tutorials.swinb",	"title":"SWISH tutorials" },
+  "--",
+{ "file":"stats.swinb",			"title":"Usage statistics" }
 ]
diff --git a/examples/jquery.swinb b/examples/jquery.swinb
new file mode 100644
index 0000000..a545833
--- /dev/null
+++ b/examples/jquery.swinb
@@ -0,0 +1,55 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# Accessing the SWISH interface
+
+The jquery/3 predicate can be used to access arbitrary JavaScript functionality on the SWISH interface from Prolog.  This notebook gives some examples for using this functionality.
+
+## Status
+
+This API is *speculative*.  Please consider the possibilities and make suggestions how to turn this into something generally useful for customizing SWISH and/or define new types of applications using it.
+
+## Get access to the tabs
+
+The jQuery interface was added to provide access to the tabs in the SWISH interface.  Our first example asks for for the available tabs.  It returns an list holding a _dict_ for each
+tab.
+</div>
+
+<div class="nb-cell query">
+jquery(swish(), swish(tabData), Reply).
+</div>
+
+<div class="nb-cell markdown">
+The =|swish#tabData|= jQuery plugin method accepts an `options`
+dict with the following values:
+
+  - active:Boolean
+  Restrict the result to the active tab
+  - type:String
+  Restrict the result to tabs with objects of this type
+  - data:Boolean|String
+  If `true`, include the content of the reported tabs.  If
+  `"if_modified"`, only include the data if the tab was modified.
+  
+The example below fetches the data from the active tab.
+</div>
+
+<div class="nb-cell query">
+jquery(swish(), 
+       swish(tabData, _{active:true, data:true}),
+       Reply).
+</div>
+
+<div class="nb-cell markdown">
+## The potential
+
+Except for querying UI properties, the general jQuery API can be used to add new element to the interface.  The `this` root selector refers to the jQuery plugin `prologRunner`, which runs a single query.  The query below inserts some HTML into the runner DOM.
+
+This level of interaction with the interface is probably not desirable.  Restricted to a documented set of jQuery selectors and methods however, it can be used to generalise the user interaction.
+</div>
+
+<div class="nb-cell query">
+jquery(this(".runner-results"), append("&lt;b&gt;Hello world&lt;/b&gt;"), R).
+</div>
+
+</div>
diff --git a/examples/rendering.swinb b/examples/rendering.swinb
new file mode 100644
index 0000000..0016b67
--- /dev/null
+++ b/examples/rendering.swinb
@@ -0,0 +1,84 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# Result rendering
+
+SWISH allows for _rendering_ a Prolog answer in a domain specific way.  This
+can be compared to the Prolog _hook_ portray/1, although it currently only applies to the answer as a whole and *not* recursively on terms embedded in an answer.  A renderer is selected using use_rendering/1 or use_rendering/2,
+for example:
+</div>
+
+<div class="nb-cell program">
+:- use_rendering(table).
+</div>
+
+<div class="nb-cell markdown">
+The [table renderer](example/render_table.swinb) recognises a table and renders it as an HTML table.  Press the _play_ button to run the simple example below.
+</div>
+
+<div class="nb-cell query">
+X = [ [ 'Amsterdam', 'The Netherlands' ],
+      [ 'London', 'United Kingdom' ],
+      [ 'Paris', 'France' ]
+    ].
+</div>
+
+<div class="nb-cell markdown">
+## Provided renderers
+
+The following rendering plugins are currently supported.  Please visit the linked notebook for more documentation and examples exploiting these renderers.
+
+  - General purpose renderers
+    - [table](example/render_table.swinb) renders tables
+    - [svgtree](example/render_svgtree.swinb) renders Prolog terms as trees
+    - [graphviz](example/render_graphviz.swinb) render graphs using
+      [Graphviz](http://www.graphviz.org/).
+    - [c3](example/render_c3.swinb) renders =c3= dicts as charts (line, bar, pie
+      etc.) using [C3.js](http://www.c3js.org)
+    - [codes](example/render_codes.swinb) renders lists of code-points as text.
+  - Domain specific renderers
+    - [bdd](example/render_bdd.swinb) renders CLP(B) residual goals as Binary Decision Diagrams (BDDs)
+    - [chess](example/render_chess.swinb) renders chess positions (currently only
+      N-queen boards).
+    - [sudoku](example/render_sudoku.swinb) renders sudoku puzzles.
+</div>
+
+<div class="nb-cell markdown">
+## Adding a new render plugin
+
+The rendering infrastructure is specialized using a series of _plugins_ provided as Prolog libraries in the directory =lib/render= of the SWISH sources.  A render plugin is a _module_ that performs these steps:
+
+  1. Registering the renderer using register_renderer/2
+  2. Define a rule for term_rendering//3, which
+     - Identifies the Prolog term as suitable for the plugin.  For
+       example, a _table_ renderer requires a datastructure that can be
+       interpreted as a table.
+     - Define the translation to an HTML fragment using
+       library(http/html_write).  The HTML fragment can include _style_
+       and (JavaScript) scripts.
+
+### Embedded JavaScript
+
+Embedded JavaScript is executed explicitly after the provided HTML DOM node has been added to the document.  The infrastructure sets =|$.ajaxScript|= to a `jQuery` object that refers to the script element.  This can be used to find the inserted DOM node without generating an `id` attribute.  The typical
+JavaScript skeleton that avoids poluting the namespace and ensures executing if the node is properly embedded is given below.
+
+```
+(function() {
+  if ( $.ajaxScript ) {
+  // do the work
+  }
+})();
+```
+
+SWISH uses [RequireJS](http://requirejs.org/) for dependency tracking.  This functionality can be exploited to load JavaScript only once.  For example, the [svgtree](example/render_svgtree.swinb) uses this fragment.
+
+```
+  require(["render/svg-tree-drawer", "jquery"], function(svgtree) {
+  // setup the tree
+  });
+```
+
+The RequireJS paths `c3` and `d3` are preconfigured to load [D3.js](http://www.d3js.org) and [C3.js](http://www.c3js.org)
+</div>
+
+</div>
diff --git a/examples/stats.swinb b/examples/stats.swinb
new file mode 100644
index 0000000..6af0e4e
--- /dev/null
+++ b/examples/stats.swinb
@@ -0,0 +1,143 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# Display SWISH server statistics
+
+This page examines the performance and health of the SWISH server.  Most of the statistics are gathered by `lib/swish_debug`, which is by default loaded into http://swish.swi-prolog.org but must be explicitly loaded into your own SWISH server.  Part of the statistics are based on reading the Linux =|/proc|= file system and thus only function on Linux.
+
+The first step is easy, showing the overall statistics of the server.
+</div>
+
+<div class="nb-cell query">
+statistics.
+</div>
+
+<div class="nb-cell markdown">
+## Historical performance statistics
+
+The charts below render historical performance characteristics of the server.  Please open the
+program below for a description of chart/3.
+</div>
+
+<div class="nb-cell program" data-singleline="true">
+:- use_rendering(c3).
+
+%%	chart(+Period, +Keys, -Chart) is det.
+%
+%	Compute a Chart for the given period combining graphs for the given Keys.
+%	Defined values for Period are:
+%	  - `minute`: the last 60 1 second measurements
+%	  - `hour`: the last 60 1 minute averages
+%	  - `day`: the last 24 1 hour averages
+%	  - `week`: the last 7 1 day averages
+%	  - `year`: the last 52 1 week averages
+%	Defines keys are:
+%	  - `cpu`: Total process CPU time in seconds
+%	  - `d_cpu`: Differential CPU time (% CPU)
+%	  - `pengines`: Total number of living Pengines
+%	  - `d_pengines_created`: Pengines create rate (per second)
+%	  - `rss`: Resident set size in bytes
+%	  - `stack`: Total amount of memory allocated for Prolog stacks in bytes
+%	  - `heap`: `rss - stack`.  This is a rough estimate of the memory used
+%	    for the program, which should stay bounded if the server is free of
+%	    leaks.  Note that it can still grow significantly and can be temporarily
+%	    high if user applications use the dynamic database.
+%	  - `rss_mb`, `stack_mb`, `heap_mb` are the above divided by 1024^2.
+
+chart(Period, Keys, Chart) :-
+    swish_stats(Period, Dicts0),
+    maplist(add_heap_mb, Dicts0, Dicts1),
+    maplist(rss_mb, Dicts1, Dicts2),
+    maplist(free_mb, Dicts2, Dicts3),
+    maplist(stack_mb, Dicts3, Dicts4),
+    maplist(fix_date, Dicts4, Dicts),
+    dicts_slice([time|Keys], Dicts, LastFirstRows),
+    reverse(LastFirstRows, Rows),
+    period_format(Period, DateFormat),
+    Chart = c3{data:_{x:time, xFormat:null, rows:Rows},
+               axis:_{x:_{type: timeseries,
+                          tick: _{format: DateFormat,
+                                  rotate: 90,
+                                  multiline: false}}}}.
+
+period_format(minute, '%M:%S').
+period_format(hour,   '%H:%M').
+period_format(day,    '%m-%d %H:00').
+period_format(week,   '%Y-%m-%d').
+period_format(year,   '%Y-%m-%d').
+
+add_heap_mb(Stat0, Stat) :-
+    Heap is ( Stat0.get(rss) -
+              Stat0.get(stack) -
+              Stat0.get(fordblks)
+            ) / (1024^2), !,
+    put_dict(heap_mb, Stat0, Heap, Stat).
+add_heap_mb(Stat0, Stat) :-
+    Heap is ( Stat0.get(rss) -
+              Stat0.get(stack)
+            ) / (1024^2), !,
+    put_dict(heap_mb, Stat0, Heap, Stat).
+add_heap_mb(Stat, Stat).
+
+rss_mb(Stat0, Stat) :-
+    Gb is Stat0.get(rss)/(1024**2), !,
+    put_dict(rss_mb, Stat0, Gb, Stat).
+rss_mb(Stat, Stat).
+
+free_mb(Stat0, Stat) :-
+    Gb is Stat0.get(fordblks)/(1024**2), !,
+    put_dict(free_mb, Stat0, Gb, Stat).
+free_mb(Stat, Stat).
+
+stack_mb(Stat0, Stat) :-
+    Gb is Stat0.get(stack)/(1024**2), !,
+    put_dict(stack_mb, Stat0, Gb, Stat).
+stack_mb(Stat, Stat).
+
+fix_date(Stat0, Stat) :-
+	Time is Stat0.time * 1000,
+    put_dict(time, Stat0, Time, Stat).
+</div>
+
+<div class="nb-cell markdown">
+### Number of Pengines and CPU load over the past hour
+
+The number of Pegines denotes the number of actively executing queries.  These queries may be sleeping while waiting for input, a debugger command or the user asking for more answers. Note that the number of Pengines is sampled and short-lived Pengines does not appear in this chart.
+</div>
+
+<div class="nb-cell query">
+chart(hour, [pengines,d_cpu], Chart).
+</div>
+
+<div class="nb-cell markdown">
+### Memory usage over the past hour
+
+*rss* is the total (_resident_) memory usage as reported by Linux.  *stack* is the memory occupied by all Prolog stacks.
+*heap* is an approximation of the memory used for the Prolog program space, computed as _rss_ - _stack_ - _free_.  This is incorrect for two reasons.  It ignores the C-stacks and the not-yet-committed memory of the Prolog stacks is not part of *rss*.  *free* is memory that is freed but not yet reused as reported by GNU =|malinfo()|= as `fordblks`.
+</div>
+
+<div class="nb-cell query">
+chart(hour, [rss_mb,heap_mb,stack_mb,free_mb], Chart).
+</div>
+
+<div class="nb-cell markdown">
+## Health statistics
+
+The statistics below assesses the number of *Pengines* (actively executing queries from users) and the *highlight states*, the number of server-side mirrors we have from client's source code used to compute the semantically enriched tokens.   If such states are not explicitly invalidated by the client, they are removed after having not been accessed for one hour.  The *stale modules* count refers to temporary modules that are not associated to a Pengine, nor to a highlight state and probably indicate a leak.
+</div>
+
+<div class="nb-cell program" data-singleline="true">
+:- use_rendering(table).
+
+stats([stale_modules-Stale|Pairs]) :-
+    aggregate_all(count, pengine_stale_module(_), Stale),
+    findall(Key-Value,
+            (swish_statistics(Stat), Stat =.. [Key,Value]),
+            Pairs).
+</div>
+
+<div class="nb-cell query">
+stats(Stats).
+</div>
+
+</div>
diff --git a/examples/swish_tutorials.swinb b/examples/swish_tutorials.swinb
new file mode 100644
index 0000000..e118ec1
--- /dev/null
+++ b/examples/swish_tutorials.swinb
@@ -0,0 +1,13 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# SWISH Tutorials
+
+This notebook provides an overview of tutorials about using SWISH.
+
+  - [Rendering answers graphically](example/rendering.swinb)
+  - [Using HTML cells in notebooks](example/htmlcell.swinb)
+  - [Access the SWISH interface from Prolog](example/jquery.swinb)
+</div>
+
+</div>