diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81154dd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +samples diff --git a/api/plan-from-log.xqy b/api/plan-from-log.xqy new file mode 100644 index 0000000..81d584e --- /dev/null +++ b/api/plan-from-log.xqy @@ -0,0 +1,24 @@ +import module namespace qputils = "http://marklogic.com/optic/qputils" at "../lib/qputils.xqy"; + +xdmp:set-response-content-type("application/json"), +let $file := xdmp:get-request-field("file") +let $id := xdmp:get-request-field("id") +let $type := xdmp:get-request-field("type", "estimate") + + +let $id := "sessionKey=" || $id || " " + +let $regex := switch ($type) + case "estimate" return ("Event:id=Optic Plan Trace" || ".*" || $id || "plan=") + case "execution" return ("Event:id=Optic Execution Diagnostics Trace" || ".*" || $id ) + case "optimization" return ("Optic Optimization Trace"|| ".*" || $id ) + default return fn:error("QV-ARG", "Invalid type") + + +let $res := xdmp:logfile-scan($file, $regex) +let $res := if ($type = ("optimization")) + then qputils:parseOptimization ($res ! fn:substring-after(., $id)) + else qputils:makeGraph(xdmp:unquote(fn:substring-after($res, " "))/*,"N") + + +return json:to-array($res) \ No newline at end of file diff --git a/api/scan-for-plans.xqy b/api/scan-for-plans.xqy new file mode 100644 index 0000000..d2e7a12 --- /dev/null +++ b/api/scan-for-plans.xqy @@ -0,0 +1,35 @@ + +xdmp:set-response-content-type("application/json"), + +array-node { + let $file := xdmp:get-request-field("file") + let $trace := xdmp:get-request-field("trace") + let $regex := xdmp:get-request-field("regex", "( plan=| diagnostic_plan=)") + let $regex := + if ($trace) + then ("trace=" || $trace || ".*" || $regex ) + else $regex + let $start := xdmp:get-request-field("start") + let $start := + if ($start) + then xs:dateTime (fn:substring(xdmp:logfile-scan($file, (), (), (), (), 1),1,10) || "T" || $start) + else () + let $end := () + let $limit := 20 + for $e in xdmp:logfile-scan($file, $regex, "s", $start, $end, $limit) + let $type := if (fn:contains($e, "diagnostic_plan")) then "execution" else "estimate" + let $trace := + if (fn:contains($e, "trace=")) + then fn:substring-before(fn:substring-after($e, " trace="), " sessionKey=") + else "" + let $key := + if ($type eq "estimate") + then fn:substring-before(fn:substring-after($e, " sessionKey="), " plan=") + else fn:substring-before(fn:substring-after($e, " sessionKey="), " diagnostic_plan=") + let $time := fn:substring($e, 1, 23) + return + object-node { "time" : $time, "key" : $key, "trace": $trace } + +} + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..a962e04 Binary files /dev/null and b/favicon.ico differ diff --git a/lib/qputils.xqy b/lib/qputils.xqy index 4adc6a5..d1a8b9d 100644 --- a/lib/qputils.xqy +++ b/lib/qputils.xqy @@ -866,3 +866,58 @@ declare function makeScripts($in as element()) ) }; + + +declare function qputils:normalize ($string) { + let $res := fn:normalize-space($string) + return + if (fn:starts-with($res, "(")) + then fn:substring($res, 2, fn:string-length($string) -2) + else $res +}; + +declare function qputils:parseCost ($line, $obj) { + let $line := qputils:normalize ($line) + let $cost := fn:substring-before($line,"crd:") + let $card := fn:substring-before(fn:substring-after($line,"crd:["),"]") + let $_ := map:put($obj, "cardinalities", $card) + let $_ := + for $token in fn:tokenize($cost,",") + let $t := fn:tokenize($token, ":") + let $name := $t[1] + let $name := + if ($name = ("mem","dmem","nw","io")) + then ($name|| "-cost") + else if (fn:starts-with($name,"dcpu")) then "dcpu-cost" + else if (fn:starts-with($name,"cpu")) then "cpu-cost" + else if ($name = "m") then "cost" + else if ($name = "c") then "estimated-count" + else if ($name = "r") then "rule-count" + else $name + return map:put($obj,$name,qputils:normalize($t[2])) + return () +}; + +declare function qputils:parseOptimization ($lines) { + for $line at $j in $lines + 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 $res := + for $token in $tokens + let $t := fn:tokenize($token, "=") + let $tagname := + switch ($t[1]) + case "t" return "temperature" + case "r" return "repeats" + case "c" return "cool" + default return $t[1] + + where fn:matches($name, "^\w") + return if (fn:starts-with($t[2], "(")) then qputils:parseCost($t[2], $obj) else map:put($obj,$tagname,$t[2]) + return ($obj) +}; diff --git a/log.html b/log.html new file mode 100644 index 0000000..d94c86a --- /dev/null +++ b/log.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + +
+ +
+
+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ + + diff --git a/samples.xqy b/samples.xqy new file mode 100644 index 0000000..f3f527c --- /dev/null +++ b/samples.xqy @@ -0,0 +1,29 @@ +xdmp:set-response-content-type("text/html"), + +let $dir := xdmp:modules-root() || "samples/" +return +element html { + element body { + element h2 { "Samples" }, + for $group in xdmp:filesystem-directory ($dir)/dir:entry + let $entries := xdmp:filesystem-directory($group/dir:pathname)/dir:entry[fn:ends-with(./dir:filename, ".xml")] + return + ( + element h3 { $group/dir:filename}, + element table { + attribute id {"box-table-a"}, + for $entry in $entries + order by $entry/dir:filename + return + element tr { + element td { + element a { attribute href {"show.xqy?filename=" || $entry/dir:pathname} , + $entry/dir:filename/fn:string(.) + } + } + } + } + ) + } + } + diff --git a/show.xqy b/show.xqy index 5ef4dd9..55ab97f 100644 --- a/show.xqy +++ b/show.xqy @@ -30,17 +30,29 @@ declare function local:makeHTML($out) input = { xdmp:quote(json:to-array($out)), xdmp:log(json:to-array($out)) } - -
-
 
+ + + +
+ +
+
+
+ }; -let $in := xdmp:unquote (xdmp:get-request-field("plan") )/* + +let $file := xdmp:get-request-field("filename") +let $plan := xdmp:get-request-field("plan") + +let $in := if ($file) then xdmp:document-get($file)/* + else if ($plan) then xdmp:unquote (xdmp:get-request-field("plan"))/* + else fn:error(xs:QName("XDMP-ARG"), "Missing argument: filename or plan required") return ( xdmp:set-response-content-type("text/html"), diff --git a/ui/css/style.css b/ui/css/style.css index bcb25d7..f36f1da 100644 --- a/ui/css/style.css +++ b/ui/css/style.css @@ -46,15 +46,15 @@ margin-left: 2px; text-align: left; color: #505050; + line-height: 1.0; } -#viewer { - overflow: "auto"; -} + body { - background: #F8F8F8; - font-family: Arial, Helvetica, sans-serif; + padding: 0; + margin: 0 + height: 100%; } .tooltip { @@ -73,7 +73,87 @@ body { z-index: 5 } +.tooltip table { + border-collapse: collapse; + white-space: pre-wrap; + font-size: 1em; +} +.tooltip table > tr > td { + border: 1px solid goldenrod; +} +.tooltip table > tr:first-child > td { + border-top: 0; +} +.tooltip table > tr > td:first-child { + border-left: 0; +} +.tooltip table > tr:last-child > td { + border-bottom: 0; +} +.tooltip table > tr > td:last-child { + border-right: 0; +} + pre { white-space: pre-wrap; font-size: .80em; } + + +table.plantable { + background-color: #FFFFFF; + border-collapse: collapse; + border-width: 1px; + border-color: steelblue; + border-style: solid; + color: #000000; +} + +table.plantable td, table.plantable th { + border-width: 1px; + border-color: steelblue; + border-style: solid; + padding: 5px; +} + +table.plantable thead { + background-color: whitesmoke; +} + + + +table.criteria td, table.criteria th { + padding: 5px; +} + +.full-height { + height: 100%; +} + +.qv-tabs { + height: 34px; +} +.qv-banner { + height: 100%; +} + +.qv-vertical-center { + margin: 0; + position: absolute; + top: 70px; + +} + + +#viewer { + overflow: auto; + max-width: 100%; + height: 100%; +} + +#wrapper { +width: 100%; +margin: 0; +background: #F8F8F8; +} + diff --git a/ui/css/w3-theme.css b/ui/css/w3-theme.css new file mode 100644 index 0000000..cb5b884 --- /dev/null +++ b/ui/css/w3-theme.css @@ -0,0 +1,22 @@ +.w3-theme-l5 {color:#000 !important; background-color:#fbfdfe !important} +.w3-theme-l4 {color:#000 !important; background-color:#f3f8fc !important} +.w3-theme-l3 {color:#000 !important; background-color:#e6f1f9 !important} +.w3-theme-l2 {color:#000 !important; background-color:#dae9f6 !important} +.w3-theme-l1 {color:#000 !important; background-color:#cee2f3 !important} +.w3-theme-d1 {color:#000 !important; background-color:#9fc7e8 !important} +.w3-theme-d2 {color:#000 !important; background-color:#7cb2df !important} +.w3-theme-d3 {color:#fff !important; background-color:#599ed7 !important} +.w3-theme-d4 {color:#fff !important; background-color:#368ace !important} +.w3-theme-d5 {color:#fff !important; background-color:#2a73ae !important} + +.w3-theme-light {color:#000 !important; background-color:#fbfdfe !important} +.w3-theme-dark {color:#fff !important; background-color:#2a73ae !important} +.w3-theme-action {color:#fff !important; background-color:#2a73ae !important} + +.w3-theme {color:#000 !important; background-color:#c2dbf0 !important} +.w3-text-theme {color:#c2dbf0 !important} +.w3-border-theme {border-color:#c2dbf0 !important} + +.w3-hover-theme:hover {color:#000 !important; background-color:#c2dbf0 !important} +.w3-hover-text-theme:hover {color:#c2dbf0 !important} +.w3-hover-border-theme:hover {border-color:#c2dbf0 !important} \ No newline at end of file diff --git a/ui/css/w3.css b/ui/css/w3.css new file mode 100644 index 0000000..822fa75 --- /dev/null +++ b/ui/css/w3.css @@ -0,0 +1,235 @@ +/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */ +html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} +/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ +html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} +article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} +audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} +audio:not([controls]){display:none;height:0}[hidden],template{display:none} +a{background-color:transparent}a:active,a:hover{outline-width:0} +abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} +b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} +small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} +code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} +button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} +button,input{overflow:visible}button,select{text-transform:none} +button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} +button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} +button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} +fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} +legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} +[type=checkbox],[type=radio]{padding:0} +[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} +[type=search]{-webkit-appearance:textfield;outline-offset:-2px} +[type=search]::-webkit-search-decoration{-webkit-appearance:none} +::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} +/* End extract */ +html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} +h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px} +.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace} +h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} +hr{border:0;border-top:1px solid #eee;margin:20px 0} +.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} +.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} +.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} +.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} +.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} +.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} +.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} +.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} +.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} +.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} +.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} +.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} +.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} +.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} +.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} +.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} +.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} +.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} +.w3-dropdown-hover:hover .w3-dropdown-content{display:block} +.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} +.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} +.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} +.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} +.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} +.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} +.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} +.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} +.w3-main,#main{transition:margin-left .4s} +.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} +.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} +.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} +.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} +.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} +.w3-bar .w3-button{white-space:normal} +.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} +.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} +.w3-responsive{display:block;overflow-x:auto} +.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, +.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} +.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} +.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} +.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} +.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} +@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} +.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} +.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} +@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} +.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} +.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} +.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} +.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} +.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} +.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} +.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} +@media (max-width:1205px){.w3-auto{max-width:95%}} +@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} +.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} +.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} +.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} +@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} +@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} +@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} +@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} +.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} +.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} +.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} +.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} +.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} +.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} +.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} +.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} +.w3-display-position{position:absolute} +.w3-circle{border-radius:50%} +.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} +.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} +.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} +.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} +.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} +.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} +.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} +.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} +.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} +.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} +.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} +.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} +.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} +.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} +.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} +.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} +.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} +.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} +.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} +.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} +.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} +.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} +.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} +.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} +.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} +.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} +.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} +.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} +.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} +.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} +.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} +.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} +.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} +.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important} +.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important} +.w3-left{float:left!important}.w3-right{float:right!important} +.w3-button:hover{color:#000!important;background-color:#ccc!important} +.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} +.w3-hover-none:hover{box-shadow:none!important} +/* Colors */ +.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} +.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} +.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} +.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} +.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} +.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} +.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} +.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} +.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} +.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} +.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} +.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} +.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} +.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} +.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} +.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} +.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} +.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} +.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} +.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} +.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} +.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} +.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} +.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} +.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} +.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} +.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} +.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} +.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} +.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} +.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} +.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} +.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} +.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} +.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} +.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} +.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} +.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} +.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} +.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} +.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} +.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} +.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} +.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} +.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} +.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} +.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} +.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} +.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} +.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} +.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} +.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} +.w3-text-black,.w3-hover-text-black:hover{color:#000!important} +.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} +.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} +.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} +.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} +.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} +.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} +.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} +.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} +.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} +.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} +.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} +.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} +.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} +.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} +.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} +.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} +.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} +.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} +.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} +.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} +.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} +.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} +.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} +.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} +.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} +.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} +.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} +.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} +.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} +.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} +.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} \ No newline at end of file diff --git a/ui/qv.js b/ui/qv.js index 6ebe52a..628fbaf 100644 --- a/ui/qv.js +++ b/ui/qv.js @@ -17,11 +17,50 @@ var qv_palettes = { magnitude: d3.scaleSequential([0, 10], d3.interpolateGreens) } -// var qv_boxInfo = ["condition","column","row","expr","iri","nullable","order-spec","aggregate","left-graph-node","join-filter","left"]; - -var qv_cardInfo = [ "subject","predicate","object","graph","value","fragment","row" ] ; - -var qv_boxInfo = [ "column","condition","expr","order-spec","aggregate","join-filter", "content","cross-product", "default-graph","named-graph","varIn","varOut"]; +var qv_colors = { + selected: "orange", + default: "steelblue" +} +// Displayed in the node box with cardinality info +var qv_cardInfo = [ + "subject","predicate","object","graph","value","fragment","row" +]; + +// Displayed in the node box +var qv_boxInfo = [ + "column","condition","expr","order-spec","aggregate","join-filter","content", + "cross-product", "default-graph","named-graph","varIn","varOut","num-sorted", + "limit","offset", "percent", "temperature" +]; + +var qv_titleInfo = [ "permutation", "type"]; + +// Displayed in the cost banner +var qv_costInfo = [ + "cost","mem-cost","dmem-cost","io-cost","cpu-cost","dcpu-cost","nw-cost","estimated-count" +]; + +// Displayed in the execution banner +var qv_executionInfo = [ + "count","local-time","remote-time","local-max-memory","remote-max-memory" +]; + +// Tooltip row order +var qv_tooltipPriority = [ + "id","type","name","schema","view","column-index", + "op","permutation","descending","dedup", + "left","right", + "aggregate", + "order","num-sorted", + "ltime","rtime","lmem","rmem", + "cost","mem","dmem","cpu","dcpu","nw","io","count" +]; + +var qv_logTabs = { + "optimization" : "Optimization", + "estimate" : "Final Plan", + "execution": "Execution" +}; function qv_debug(msg) { if (qv_box.debug) { @@ -29,6 +68,115 @@ function qv_debug(msg) { } } + +function qv_displayTabs (containerid, viewerid, file, data) { + d3.select(containerid).select("div").remove() + var bar = d3.select(containerid).append("div").attr("class","w3-bar w3-theme-l1") + const types = Object.keys(qv_logTabs); + + types.forEach ( + type => { + var a = bar.append("a") + if (type === "execution") { + a.attr("class", "w3-bar-item w3-button w3-small w3-theme-action") + } else { + a.attr("class", "w3-bar-item w3-button w3-small w3-theme-d1") + } + a.attr("href","#") + .text(qv_logTabs[type]) + .on("click",(event) => { + bar.selectAll('a[class = "w3-bar-item w3-button w3-small w3-theme-action"]').attr("class", "w3-bar-item w3-button w3-small w3-theme-d1") + d3.select(event.target).attr("class", "w3-bar-item w3-button w3-small w3-theme-action") + qv_loadFromLog(viewerid, file, data.key, type) + }) + }) + var message = "Displaying Plan: " + if (data.trace) {message += data.trace} else {message += data.key} + bar.append("span").style("float","right").attr("class","w3-bar-item w3-small w3-right-align w3-orange").text(message) +} + + +function qv_visualizeTicks(scale, tickArguments, box) { + const width = box.width; + const height = box.height, + m = 10; + if (tickArguments === undefined) tickArguments = []; + scale.range([m, height - m]); + const svg = d3.create("svg") + .attr("width", width) + .attr("height", height); + svg.append("g") + .attr("transform", "translate(" + (width - 60) + ",0)") + .call(d3.axisRight(scale).ticks(...tickArguments)); + return svg + } + +function qv_jitter (range, unit) { + return Math.floor((Math.random() * (range / unit)) * unit) +} + +function qv_displayPlansAsTimeline (containerid, viewerid, file, data) { + d3.select(containerid).select("svg").remove() + times = d3.group(data, d => Date.parse(d.time)) + var extent = d3.extent(times.keys()) + extent[0]=extent[0]-(1000 * 30) + extent[1]=extent[1]+(1000 * 30) + dst = d3 + .scaleTime() + .domain(extent) + + const container = d3.select(containerid) + const box = container.node().getBoundingClientRect() + + box.height = d3.select("#plans").node().parentNode.getBoundingClientRect().height - d3.select("#form").node().getBoundingClientRect().height - 10 + const jitter = (box.height / 10) + const svg = qv_visualizeTicks(dst, [10, d3.timeFormat("%H:%M:%S")],box); + + svg + .selectAll("circle") + .data(data) + .enter() + .append("circle") + .attr("r", function (d) { return 3}) + .attr("fill", qv_colors.default) + .attr("cy", function (d) { return dst(Date.parse(d.time ) + qv_jitter(3000, 500))}) + .attr("cx", function (d) { return 10 + qv_jitter (box.width - 100 , 5 )}) + .on("click",function (event, d) { + + svg.selectAll('circle[fill = "' + qv_colors.selected+ '"]').attr("fill", qv_colors.default) + d3.select(event.target).attr("fill", qv_colors.selected) + + qv_displayTabs("#tabs", viewerid, file, d) + qv_loadFromLog(viewerid, file, d.key, "execution") + } + ).each( function (d) {qv_tooltipEvents (d3.select(this),d)}) + container.append((d) => { return svg.node()}) + +} + +function qv_scanLogForPlans (containerid, fileid, startid, traceid,viewerid) { + + var file=d3.select(fileid).node().value + var start= "" + var trace="" + if (d3.select(startid).node().value) { start = "&start=" + d3.select(startid).node().value.replace(" ","T") } + if (d3.select(traceid).node().value) { trace = "&trace=" + d3.select(traceid).node().value } + d3.select(containerid).select("table").remove() + qv_debug(file + start +trace) + + d3.json('api/scan-for-plans.xqy?regex=+plan%3d&file=' +file + start +trace).then(function(data){ + qv_displayPlansAsTimeline(containerid, viewerid, file, data) + + }) +} + +function qv_loadFromLog (viewerid,file, id, type) { + d3.select(viewerid).select("svg").remove() + d3.json('api/plan-from-log.xqy?file=' +file+ "&id=" +id + "&type=" +type).then(function(data){ + qv_showPlan(viewerid, data); + }) +} + function qv_hierarchy(json) { var data = d3.stratify() .id(function (d) { return d._id; }) @@ -43,10 +191,10 @@ function qv_buildTree(nodes, width, height) { return treemap(nnodes) } -function qv_tooltipEvents(element, tooltip) { +function qv_tooltipEvents(element, tooltip, doFilter) { if(tooltip) element .on("mouseover", function(event, d) { - qv_tooltipShow(event,tooltip); + qv_tooltipShow(event,tooltip,doFilter); }) .on("mouseout", function(event, d) { qv_tooltipHide(event); @@ -54,7 +202,7 @@ function qv_tooltipEvents(element, tooltip) { } function qv_title(div, title, tooltip) { - var span = div.append("xhtml:p") + var para = div.append("xhtml:p") .attr("class", "tree-node") .style("color", "#202020") .style("padding", "4px") @@ -62,8 +210,8 @@ function qv_title(div, title, tooltip) { .style("font-size",qv_box.titleFont) .style("font-weight", "bold") .text(title); - qv_tooltipEvents(span,tooltip); - return span; + qv_tooltipEvents(para,tooltip,true); + return para; } function qv_row(table, title, text, tooltip, color) { @@ -78,7 +226,7 @@ function qv_row(table, title, text, tooltip, color) { } td1.append("xhtml:span").style("font-weight", "bold").text(title + (text ? ":" : "")); if (text) td2.append("xhtml:span").text(text) - qv_tooltipEvents(row,tooltip); + qv_tooltipEvents(row,tooltip,false); return row; } @@ -98,29 +246,32 @@ function qv_cost(div, percent) { // splits parallel and serial cost, and returns values as proportion of a given whole. -function qv_parallelVsSerial (value, whole) { - if (value) { - var components= value.split("/").map(x=>parseFloat(x)) - var parallel = (components[0] + components[1] + components[2]) /whole.parallel - var serial = (components[3] + components[4]) / whole.serial - return {parallel:parallel,serial:serial} - } else { - return {parallel:0,serial:0} - } -} -function qv_maxParallelVsSerial (value, max) { - value = qv_parallelVsSerial(value, {parallel:1,serial:1}) - maxp = Math.max (value.parallel, max.parallel) - maxs = Math.max (value.serial, max.serial) - return {parallel:maxp,serial:maxs} -} - -function qv_proportion (value, max) { +function qv_proportion(value, max) { + if(typeof max === "object") { + var result = {}; + Object.keys(max).forEach((k) => { + result[k] = qv_proportion(value[k],max[k]); + }); + return result; + } if (max == 0) return 0 return value/max } +function qv_max(a,b) { + if(!a) return b; + if(!b) return a; + if(typeof a === "object") { + var result = {}; + Object.keys(a).forEach((k) => { + result[k] = qv_max(a[k],b[k]); + }); + return result; + } + return Math.max(a,b); +} + function qv_parseMemory(str) { var r = parseFloat(str) if (str.endsWith("Gb")) return r * (1024^3) @@ -135,74 +286,39 @@ function qv_parseTime(str) { } // compute maximum of all the metrics across the plan +function qv_fetchCost(data) { + var fetchParallelSerial = (value) => { + if(!value) return {parallel:0,serial:0}; -function qv_maxCost (nodes) { - var maxcost = { - label : "maxcost", - cost: 0, - mem: 0, - dmem: 0, - io: {parallel:0,serial:0}, - cpu: {parallel:0,serial:0}, - dcpu: {parallel:0,serial:0}, - nw: {parallel:0,serial:0}, - count: 0, - ltime: 0, - rtime: 0, - lmem:0, - rmem:0 - } - - nodes.descendants().forEach(element => { - var data = element.data.data - if (data.cost) { - maxcost.cost = Math.max(parseFloat(data.cost),maxcost.cost) - maxcost.mem = Math.max(parseFloat(data["mem-cost"]), maxcost.mem) - maxcost.dmem = Math.max(parseFloat(data["dmem-cost"]), maxcost.dmem) - maxcost.count = Math.max(parseFloat(data["estimated-count"]), maxcost.count) - maxcost.io = qv_maxParallelVsSerial(data["io-cost"], maxcost.io) - maxcost.cpu = qv_maxParallelVsSerial(data["cpu-cost"], maxcost.cpu) - maxcost.dcpu = qv_maxParallelVsSerial(data["dcpu-cost"], maxcost.dcpu) - maxcost.nw = qv_maxParallelVsSerial(data["nw-cost"], maxcost.nw) - } else if (data["local-time"]) { - maxcost.count = Math.max(parseFloat(data.count),maxcost.count) - maxcost.lmem = Math.max(qv_parseMemory(data["local-max-memory"]), maxcost.lmem) - maxcost.rmem = Math.max(qv_parseMemory(data["remote-max-memory"]), maxcost.rmem) - maxcost.ltime = Math.max(qv_parseTime(data["local-time"]), maxcost.ltime) - maxcost.rtime = Math.max(qv_parseTime(data["remote-time"]), maxcost.rtime) - } - }) - return maxcost -} - - - -function qv_relativeCost( data, maxcost) { - if (data.cost) { - return { - id: data._id, - cost: qv_proportion(parseFloat(data.cost) , maxcost.cost), - mem: qv_proportion(parseFloat(data["mem-cost"]) , maxcost.mem), - dmem: qv_proportion(parseFloat(data["dmem-cost"]) , maxcost.dmem), - io: qv_parallelVsSerial(data["io-cost"],maxcost.io), - cpu: qv_parallelVsSerial(data["cpu-cost"],maxcost.cpu), - dcpu: qv_parallelVsSerial(data["dcpu-cost"],maxcost.dcpu), - nw: qv_parallelVsSerial(data["nw-cost"],maxcost.nw), - count: qv_proportion(parseFloat(data["estimated-count"]) , maxcost.count) - } - } else if (data["local-time"]) { - return { - id: data._id, - count: qv_proportion(parseFloat(data.count) , maxcost.count), - ltime: qv_proportion(qv_parseTime(data["local-time"]) , maxcost.ltime), - rtime: qv_proportion(qv_parseTime(data["remote-time"]) , maxcost.rtime), - lmem: qv_proportion(qv_parseMemory(data["local-max-memory"]) , maxcost.lmem), - rmem: qv_proportion(qv_parseMemory(data["remote-max-memory"]) , maxcost.rmem) - } - } - -} + var components= value.split("/").map(x=>parseFloat(x)); + var parallel = components[0] + components[1] + components[2]; + var serial = components[3] + components[4]; + return {parallel:parallel,serial:serial}; + }; + var result = null; + + if (data.cost) result = { + // id: data._id, + cost: parseFloat(data.cost), + mem: parseFloat(data["mem-cost"]), + dmem: parseFloat(data["dmem-cost"]), + io: fetchParallelSerial(data["io-cost"]), + cpu: fetchParallelSerial(data["cpu-cost"]), + dcpu: fetchParallelSerial(data["dcpu-cost"]), + nw: fetchParallelSerial(data["nw-cost"]), + count: parseFloat(data["estimated-count"]) + }; + else if (data["local-time"]) result = { + // id: data._id, + count: parseFloat(data.count), + ltime: qv_parseTime(data["local-time"]), + rtime: qv_parseTime(data["remote-time"]), + lmem: qv_parseMemory(data["local-max-memory"]), + rmem: qv_parseMemory(data["remote-max-memory"]) + }; + return result; +} function qv_displayCostBox (parent , rect, palette, value, prefix="") { var r = 2; @@ -217,14 +333,14 @@ function qv_displayCostBox (parent , rect, palette, value, prefix="") { .attr("height", qv_box.lineHeight).append("title").text(prefix + Math.round(value*100) +"%") } -function qv_displayCost(metrics, cost) { +function qv_displayCost(metrics, cost, maxcost) { var parent = d3.create("svg:g").attr("width", qv_box.width ) var i = 0; var padding = 4 var width = (qv_box.width -(padding*2)) / metrics.length; metrics.forEach ( v => { - value = cost[v] + value = qv_proportion(cost[v],maxcost[v]) x= i * width + padding tx= i * width + padding + width/2 rx= i * width + padding @@ -249,10 +365,11 @@ function qv_displayCost(metrics, cost) { } } ) + qv_tooltipEvents(parent,cost,false); return parent.node() } -function qv_parse_cardinalities(cardinalities) { +function qv_parseCardinalities(cardinalities) { if (cardinalities) { if (cardinalities.startsWith("(")) { var l=cardinalities.length; @@ -261,17 +378,17 @@ function qv_parse_cardinalities(cardinalities) { } else return [] } -function qv_triple_info(table, type, value, cardinalities) { +function qv_tripleInfo(table, type, value, cardinalities) { value = qv_decode(value) var v= parseInt(value.split(' ')[0]) if (v >= 0) { var colors = null; - if (cardinalities[v]) colors = cardinalities[v].map( (x) => qv_palettes.magnitude(Math.round(Math.log10(parseFloat(x))))) - return qv_row(table, type, value , { - type : type, - value : value, - cardinality: cardinalities[v]} - , colors) + var tooltip = value; + if (cardinalities[v]) { + colors = cardinalities[v].map( (x) => qv_palettes.magnitude(Math.round(Math.log10(parseFloat(x))))); + tooltip = { value : value, cardinality: cardinalities[v].join(",") }; + } + return qv_row(table,type,value,tooltip,colors); } else return qv_row(table, type, value, value) } @@ -298,10 +415,12 @@ function qv_node(node) { var data = node.data.data var div = d3.create("div") var name = data._name - if ( data.permutation) name += " (" + data.permutation + ")" - if ( data.type) name += " (" + data.type + ")" - if ( data.limit) name += " (" + data.limit + ")" - if ( data["num-sorted"]) name += " ( num-sorted=" + data["num-sorted"] + ")" + qv_titleInfo.filter((key) => data.hasOwnProperty(key)) + .forEach((key) => {name += " (" + data[key] + ")"}); + //if ( data.permutation) name += " (" + data.permutation + ")" + //if ( data.type) name += " (" + data.type + ")" + // if ( data.limit) name += " (" + data.limit + ")" + // if ( data["num-sorted"]) name += " ( num-sorted=" + data["num-sorted"] + ")" qv_title(div, name, data) .on("click", (event) => { if (node.children) { @@ -319,11 +438,11 @@ function qv_node(node) { .style("border","0px") .style("font-size", qv_box.font); - var cardinalities = qv_parse_cardinalities(data.cardinalities) - //console.log(cardinalities) + var cardinalities = qv_parseCardinalities(data.cardinalities) + qv_cardInfo .filter((key) => data.hasOwnProperty(key)) - .forEach((key) => qv_triple_info(table,key,data[key],cardinalities)); + .forEach((key) => qv_tripleInfo(table,key,data[key],cardinalities)); qv_boxInfo .filter((key) => data.hasOwnProperty(key)) .forEach((key) => qv_row(table,key,data[key],data[key])); @@ -339,30 +458,77 @@ function qv_nodeHeight(data) { qv_boxInfo .filter((key) => data.hasOwnProperty(key)) .forEach((key) => { size += linesize }); - if(data.cost || data.count) size = size + 2 * linesize + if(qv_costInfo.some((v) => data.hasOwnProperty(v))) + size = size + 2 * linesize + if(qv_executionInfo.some((v) => data.hasOwnProperty(v))) + size = size + 2 * linesize return size } -function qv_tooltipShow(event, data) { - var tooltip = d3.select("#tooltip") - tooltip.style("left", (event.pageX + 28) + "px") - .style("top", (event.pageY - 28) + "px"); - tooltip.select("pre").text(JSON.stringify(data, null,2)) - tooltip.transition() - .duration(200) - .style("opacity", .9); - -} +function qv_tooltipTableRow(table, key, value) { + if(Array.isArray(value)) { + value.forEach((v) => qv_tooltipTableRow(table,key,v)); + } + else if(typeof(value)==="object") { + var tr = table.append("tr"); + if(key!==null) tr.append("td").text(key); + var table2 = tr.append("td").append("table"); + Object.keys(value).forEach((key) => qv_tooltipTableRow(table2,key,value[key])); + } + else { + var tr = table.append("tr"); + if(key!==null) tr.append("td").text(key); + if(typeof(value)==="string") value = qv_decode(value); + tr.append("td").text(value); + } +} +function qv_tooltipContents(parent, data, doFilter) { + if(!Array.isArray(data) && typeof(data)==="object") { + var seen = {}; + if(doFilter) { + Object.keys(data).filter((key) => key.charAt(0)=='_') + .forEach((key) => { seen[key] = true }); + qv_cardInfo.forEach((key) => { seen[key] = true }); + qv_boxInfo.forEach((key) => { seen[key] = true }); + qv_costInfo.forEach((key) => { seen[key] = true }); + qv_executionInfo.forEach((key) => { seen[key] = true }); + } -function qv_tooltipHide(event) { + var seenFilter = (key) => { + if(seen[key]) return false; + seen[key] = true; + return data.hasOwnProperty(key); + }; + + var table = parent.append("table"); + var display = (key) => qv_tooltipTableRow(table,key,data[key]); + + qv_tooltipPriority.filter(seenFilter).forEach(display); + Object.keys(data).filter(seenFilter).forEach(display); + } + else { + qv_tooltipTableRow(parent.append("table"),null,data); + } +} + +function qv_tooltipShow(event, data, doFilter) { var tooltip = d3.select("#tooltip") - tooltip.transition() - .duration(500) - .style("opacity", 0) - + .style("left", (event.pageX + 28) + "px") + .style("top", (event.pageY - 28) + "px"); + tooltip.html(""); + qv_tooltipContents(tooltip,data,doFilter); + tooltip.transition() + .duration(200) + .style("opacity", .9); } +function qv_tooltipHide(event) { + var tooltip = d3.select("#tooltip"); + tooltip.transition() + .duration(500) + .style("opacity", 0); +} function qv_isVisible(node) { var id=node.data.id @@ -380,35 +546,36 @@ function qv_toggle(nodes, visibility) { }) } -function qv_init(containerid, json) { +function qv_showPlan(containerid, json) { qv_debug(json) var container = d3.select(containerid); - var margin = { top: 40, right: 90, bottom: 200, left: 90 } + var margin = { top: 20, right: 0, bottom: 0, left: 0 } var nodes = qv_hierarchy(json) - var maxcost = qv_maxCost(nodes) - - qv_debug(maxcost); - - var width = (nodes.leaves().length + 2) * (qv_box.width + qv_box.hpadding); - var height = nodes.height * (qv_box.height + qv_box.vpadding) * 2; + //var width = (nodes.leaves().length + 2) * (qv_box.width + qv_box.hpadding); + //var height = nodes.height * (qv_box.height + qv_box.vpadding) * 2; + const box = container.node().getBoundingClientRect() + var width = box.width + var height = box.height - 40 nodes = qv_buildTree(nodes,width,height) qv_debug(nodes); var svg = container.append("svg") - .attr("width", Math.max(2048, width + margin.left + margin.right)) - .attr("height", height + margin.top + margin.bottom) + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) // set up transform and zoom g = svg.append("g") - .attr("transform", - "translate(" + ( margin.left) + "," + margin.top + ")"); + .attr("transform", d3.zoomIdentity.translate(width/2, 10)); + var zoom = d3.zoom() .scaleExtent([0.1, 10]) .on('zoom', function (event) { + g.attr('transform', event.transform); }); svg.call(zoom); + //links qv_debug(nodes.links()) @@ -451,7 +618,7 @@ function qv_init(containerid, json) { .append("animateMotion") .attr("calcMode","linear") .attr("rotate","auto") - .attr("keyPoints","0.3;0.3") + .attr("keyPoints","0.6;0.6") .attr("keyTimes","0.0;1.0") .append("mpath") .attr("href", function(d) { return "#link_" +d.target.data.id; }); @@ -481,23 +648,35 @@ function qv_init(containerid, json) { return h}) // add cost banner - node.filter(function(d){ return d.data.data.cost }).append((d) => { - var cost = qv_relativeCost(d.data.data, maxcost) - return qv_displayCost(["cost","mem","dmem","io","cpu", "dcpu","nw", "count"],cost) - }) - node.filter(function(d){ return d.data.data["local-time"] }).append((d) => { - var cost = qv_relativeCost(d.data.data, maxcost) - qv_debug(cost) - return qv_displayCost(["ltime","rtime", "lmem","rmem", "count"],cost) - }) + var maxcost = null; + nodes.descendants().forEach(element => { + maxcost = qv_max(qv_fetchCost(element.data.data),maxcost); + }); + qv_debug(maxcost); + + node.filter((d) => qv_costInfo.some((v) => d.data.data[v])).append((d) => { + var cost = qv_fetchCost(d.data.data); + return qv_displayCost(["cost","mem","dmem","cpu","dcpu","nw","io","count"],cost,maxcost); + }); + node.filter((d) => qv_executionInfo.some((v) => d.data.data[v])).append((d) => { + var cost = qv_fetchCost(d.data.data); + qv_debug(cost); + return qv_displayCost(["ltime","rtime","lmem","rmem","count"],cost,maxcost); + }); // add text box var fo = node.append("foreignObject") - .attr("y", function (d) { if (d.data.data.cost || d.data.data["local-time"] ) {return qv_box.lineHeight * 2} else {return 0}}) + .attr("y", (d) => { + var result = 0; + if(qv_costInfo.some((v) => d.data.data.hasOwnProperty(v))) + result += qv_box.lineHeight * 2; + if(qv_executionInfo.some((v) => d.data.data.hasOwnProperty(v))) + result += qv_box.lineHeight * 2; + return result; + }) .attr("height", function (d) { return qv_nodeHeight(d.data.data) }) .attr("width", qv_box.width) fo.append((d) => { return qv_node(d, maxcost); - } - ) + }) }