diff --git a/api/plan-from-log.xqy b/api/plan-from-log.xqy index 46d8a27..cf16875 100644 --- a/api/plan-from-log.xqy +++ b/api/plan-from-log.xqy @@ -11,13 +11,15 @@ let $id := "sessionKey=" || $id || " " let $regex := switch ($type) case "estimate" return ("(Optic Plan|SPARQL AST)" || ".*" || $id || "plan=") case "execution" return ("Optic Execution Diagnostics" || ".*" || $id ) - case "optimization" return ("(Optic Optimization|SPARQL Cost Analysis)"|| ".*" || $id ) + case "optimization" return ("(Optic Optimization Trace|SPARQL Cost Analysis)"|| ".*" || $id ) default return fn:error("QV-ARG", "Invalid type") let $res := xdmp:logfile-scan($file, $regex) let $res := $res ! fn:substring-after(., " ") let $res := if ($type = ("optimization")) - then qputils:parseOptimization($res) + then qputils:parseOptimization($res, + xdmp:logfile-scan($file, ("Optic Optimization Path" || ".*" || $id )) ! + fn:substring-after(. , "costPath=")) else qputils:makeGraph(xdmp:unquote($res[1])/*,"N") return json:to-array($res) \ No newline at end of file diff --git a/lib/qputils.xqy b/lib/qputils.xqy index f096dad..2e1abc9 100644 --- a/lib/qputils.xqy +++ b/lib/qputils.xqy @@ -981,14 +981,12 @@ declare function qputils:parseCost ($line, $obj) { return () }; -declare function qputils:parseOptimization ($lines) { - for $line at $j in $lines +declare function qputils:parseOptimizationLine ($line, $linenum) { let $tokens := fn:tokenize($line, " ") - let $name := if (fn:contains($line, "initialCost")) then "start" - else if (fn:contains($line, "bestCost")) then "end" - else "iteration " || ( $j - 1) - let $obj := map:new() => map:with("_id", "node_" || $j) => map:with("_name", $name) - let $_ := if ($j gt 1) then map:put($obj,"_parent", "node_" || $j - 1) else () + let $name := if (fn:contains($line, "initialCost")) then "initial" + else "thread " || ( $linenum - 1) + let $obj := map:new() => map:with("_id", "node_" || $linenum) => map:with("_name", $name) + let $_ := if ($linenum gt 1) then map:put($obj,"_parent", "node_" || 1) else () let $res := for $token in $tokens @@ -1004,3 +1002,28 @@ declare function qputils:parseOptimization ($lines) { return if (fn:starts-with($t[2], "(")) then qputils:parseCost($t[2], $obj) else map:put($obj,$tagname,$t[2]) return ($obj) }; + +declare function qputils:parseCostFunctionValues ($file) { + let $options := + + text + + for $line in fn:tokenize(xdmp:document-get($file,$options), " ") + let $tokens := fn:tokenize($line, " ") + let $time := $tokens[1] + let $cost := $tokens[2] + where $time castable as xs:integer and $time != "0" + return xs:float($cost) + +}; +declare function qputils:parseOptimization ($lines, $paths) { + + let $best := fn:subsequence($lines, fn:count($lines)) + let $best_cost := qputils:parseOptimizationLine ($best, fn:count($lines)) + for $line at $j in fn:subsequence($lines, 1, fn:count($lines) -1) + let $obj := qputils:parseOptimizationLine ($line, $j) + let $sa := $paths [ fn:ends-with(. , "_" || $j - 2)] + let $sa := if ($sa) then map:put($obj, "costFunctionValues", json:to-array(qputils:parseCostFunctionValues ($sa))) else () + let $isbest := if (map:get($best_cost, "cost") = map:get($obj, "cost")) then map:put($obj, "best", "true") else () + return $obj +}; diff --git a/ui/qv.js b/ui/qv.js index 042ecf6..a1dca53 100644 --- a/ui/qv.js +++ b/ui/qv.js @@ -213,7 +213,6 @@ function qv_scanLogForPlans (containerid, fileid, startid, traceid,viewerid) { fetch(url).then(response => { if (!response.ok) { response.json().then(data => { - console.log(data) qv_displayError(response.statusText, data.errorResponse); }) @@ -235,7 +234,7 @@ function qv_loadFromLog (viewerid,file, id, type) { fetch(url).then(response => { if (!response.ok) { response.json().then(data => { - console.log(data) + qv_displayError(response.statusText, data.errorResponse); }) @@ -302,8 +301,10 @@ function qv_tooltipContents(parent, data, doFilter) { var table = parent.append("table"); var display = (key) => { + if (key != "costFunctionValues") { qv_tooltipTableRow(table,key,data[key]); empty = false; + } } qv_tooltipPriority.filter(seenFilter).forEach(display); @@ -489,6 +490,50 @@ function qv_displayCost(metrics, cost, maxcost) { return parent.node() } +//////////////////////////////////////////////////////////////////////////////// +// Line graph + +function qv_lineGraph (node) { + console.log ("-----") + + var data = node.data.data + var paddingTop = qv_nodeTextHeight(data) + qv_nodeCostHeight(data) + var paddingSide = 10; + var height = 100; + + var values = data.costFunctionValues + + var root = d3.create("svg:g") + if (values) { + + var x = d3.scaleLinear() + .domain([1, values.length]) + .range([ paddingSide, qv_box.width - paddingSide]); + + // root.append("g") + // .attr("transform", "translate(0," + height + ")") + // .call(d3.axisBottom(x)); + + var y = d3.scaleLinear() + .domain([0, d3.max(values, function(d) { return +d; })]) + .range([ height , paddingTop ]); + // root.append("g") + // .call(d3.axisLeft(y)); + + // Add the line + root.append("path") + .datum(values) + .attr("fill", "none") + .attr("stroke", "steelblue") + .attr("stroke-width", 1) + .attr("d", d3.line() + .x(function(d,i) { return x(i) }) + .y(function(d) { return y(d) }) + ) + + } + return root.node() +} //////////////////////////////////////////////////////////////////////////////// // Node display @@ -647,12 +692,15 @@ function qv_node(node) { qv_boxInfo .filter((key) => data.hasOwnProperty(key)) .forEach((key) => qv_nodeRow(table,key,data[key],data[key],false)); + + return div.node() } function qv_nodeTextHeight(data) { var linesize = qv_box.lineHeight; var size = linesize * 3; + qv_cardInfo .filter((key) => data.hasOwnProperty(key)) .forEach((key) => { size += qv_nodeLines(data[key]) * linesize }); @@ -671,8 +719,13 @@ function qv_nodeCostHeight(data) { return size } +function qv_nodeGraphHeight(data) { + size=0 + if (data.hasOwnProperty("costFunctionValues")) size=100 + return size +} function qv_nodeHeight(data) { - return qv_nodeTextHeight(data) + qv_nodeCostHeight(data); + return qv_nodeTextHeight(data) + qv_nodeCostHeight(data) + qv_nodeGraphHeight(data) } //////////////////////////////////////////////////////////////////////////////// @@ -824,7 +877,7 @@ function qv_showPlan(containerid, json) { .attr("visibility", "visible") .attr("class", d => "node" + (d.children ? " node--internal" : " node--leaf") + - (d.data.data.dnode==="true" ? " dnode" : " enode") + ((d.data.data.dnode==="true" || d.data.data.best==="true" ) ? " dnode" : " enode") ) .attr("transform", function (d) { return "translate(" + (d.x) + "," + d.y + ")"; @@ -836,7 +889,8 @@ function qv_showPlan(containerid, json) { .attr("rx",5) .attr("width", qv_box.width) .attr("height", d => qv_nodeHeight(d.data.data)); - + + // add cost banner var maxcost = null; nodes.descendants().forEach(element => { @@ -853,11 +907,15 @@ function qv_showPlan(containerid, json) { qv_debug(cost); return qv_displayCost(["ltime","rtime","lmem","rmem","count"],cost,maxcost); }); + + // add text box node.append("foreignObject") .attr("y", d => qv_nodeCostHeight(d.data.data)) .attr("height", d => qv_nodeTextHeight(d.data.data)) .attr("width", qv_box.width) .append(qv_node); + + node.append(qv_lineGraph); }