Repository: incubator-htrace Updated Branches: refs/heads/master 22bcd7a61 -> a560a555f
HTRACE-186. gui: support finding the parents and children of spans, add owl (cmccabe) Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/a560a555 Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/a560a555 Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/a560a555 Branch: refs/heads/master Commit: a560a555f2f682f06443f3177bf3da0272a52cad Parents: 22bcd7a Author: Colin P. Mccabe <[email protected]> Authored: Sun Jun 14 00:39:50 2015 -0700 Committer: Colin P. Mccabe <[email protected]> Committed: Tue Jun 16 17:12:22 2015 -0700 ---------------------------------------------------------------------- htrace-htraced/src/web/app/query_results.js | 2 +- htrace-htraced/src/web/app/search_result.js | 55 ++++++ htrace-htraced/src/web/app/search_results.js | 4 +- .../src/web/app/search_results_view.js | 184 +++++++++--------- htrace-htraced/src/web/app/span.js | 185 ++++++++++++++++++- htrace-htraced/src/web/app/span_group_widget.js | 110 +++++++++++ htrace-htraced/src/web/app/span_widget.js | 144 ++++++++------- htrace-htraced/src/web/app/time_cursor.js | 39 ++-- htrace-htraced/src/web/app/tree.js | 74 ++++++++ htrace-htraced/src/web/app/triangle_button.js | 65 ++++--- htrace-htraced/src/web/app/widget_manager.js | 56 +++--- htrace-htraced/src/web/image/owl.png | Bin 0 -> 435334 bytes htrace-htraced/src/web/index.html | 80 ++++---- 13 files changed, 717 insertions(+), 281 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/query_results.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/query_results.js b/htrace-htraced/src/web/app/query_results.js index 6fdde9f..dc37e1e 100644 --- a/htrace-htraced/src/web/app/query_results.js +++ b/htrace-htraced/src/web/app/query_results.js @@ -20,7 +20,7 @@ var htrace = htrace || {}; htrace.QueryResults = Backbone.Collection.extend({ - // The query results are spans. + // The query results are spans. model: htrace.Span, initialize: function(options) { http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/search_result.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_result.js b/htrace-htraced/src/web/app/search_result.js new file mode 100644 index 0000000..9798ad7 --- /dev/null +++ b/htrace-htraced/src/web/app/search_result.js @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// A pair of span trees: one going up, and the other going down. +// This represents a single search result. +htrace.SearchResult = Backbone.Model.extend({ + initialize: function(options) { + this.set("childrenRoot", { + root: options.span, + contents: null, + }); + this.set("childrenRoot", { + root: options.span, + contents: null, + }); + + this.set("parentsRoot", options.span); + }, + + getBegin: function() { + var begin = this.get("span").get("begin"); + var children = this.get("children"); + for (var childIdx = 0; childIdx < children.length; childIdx++) { + begin = Math.min(begin, children[childIdx].getBegin()); + } + return begin; + }, + + getEnd: function() { + var end = this.get("span").get("end"); + var children = this.get("children"); + for (var childIdx = 0; childIdx < children.length; childIdx++) { + end = Math.max(end, children[childIdx].getEnd()); + } + return end; + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/search_results.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_results.js b/htrace-htraced/src/web/app/search_results.js index d214918..25b18ae 100644 --- a/htrace-htraced/src/web/app/search_results.js +++ b/htrace-htraced/src/web/app/search_results.js @@ -20,6 +20,6 @@ var htrace = htrace || {}; htrace.SearchResults = Backbone.Collection.extend({ - // The search results are spans. - model: htrace.Span + // The search results are span trees. + model: htrace.SpanTreeNode }); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/search_results_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_results_view.js b/htrace-htraced/src/web/app/search_results_view.js index b3473c4..111f530 100644 --- a/htrace-htraced/src/web/app/search_results_view.js +++ b/htrace-htraced/src/web/app/search_results_view.js @@ -27,12 +27,10 @@ htrace.SearchResultsView = Backbone.View.extend({ end: this.MINIMUM_TIME_SPAN, - focused: false, - initialize: function(options) { - this.model = options.searchResults; + this.searchResults = options.searchResults; this.el = options.el; - this.listenTo(this.model, 'add remove change reset', this.render); + this.listenTo(this.searchResults, 'add remove change reset', this.render); // Re-render the canvas when the window size changes. // Add a debouncer delay to avoid spamming render requests. @@ -56,50 +54,40 @@ htrace.SearchResultsView = Backbone.View.extend({ handleMouseDown: function(e) { e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - var focused = this.widgetManager.handleMouseDown(x, y); - if (focused != this.focused) { - this.draw(); - this.focused = focused; - } + this.widgetManager.handle({ + type: "mouseDown", + x: this.getCanvasX(e), + y: this.getCanvasY(e) + }); + this.draw(); }, handleMouseUp: function(e) { e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - this.widgetManager.handleMouseUp(x, y); - this.focused = false; + this.widgetManager.handle({ + type: "mouseUp", + x: this.getCanvasX(e), + y: this.getCanvasY(e) + }); this.draw(); }, - // When the mouse leaves the canvas, treat it like a mouse up event at -1, -1 - // if something is focused. handleMouseOut: function(e) { - if (this.focused) { - this.widgetManager.handleMouseUp(-1, -1); - this.focused = false; - this.draw(); - } + e.preventDefault(); + this.widgetManager.handle({ + type: "mouseOut" + }); + this.draw(); }, handleMouseMove: function(e) { e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - if (this.focused) { - var mustDraw = false; - if (this.widgetManager.handleMouseMove(x, y)) { - mustDraw = true; - } - } - if (this.timeCursor.handleMouseMove(x, y)) { - mustDraw = true; - } - if (mustDraw) { - this.draw(); - } + this.widgetManager.handle({ + type: "mouseMove", + x: this.getCanvasX(e), + y: this.getCanvasY(e) + }); + this.draw(); }, render: function() { @@ -110,7 +98,6 @@ htrace.SearchResultsView = Backbone.View.extend({ this.ctx = this.canvas.get(0).getContext("2d"); this.scaleCanvas(); this.setupCoordinates(); - this.setupTimeCursor(); this.setupWidgets(); this.draw(); this.attachEvents(); @@ -160,66 +147,59 @@ htrace.SearchResultsView = Backbone.View.extend({ // // Set up the screen coordinates. // - // 0 buttonX descX scrollX maxX + // 0 xB xD xS maxX // +--------------+----------+--------------------+-----------+ // |ProcessId | Buttons | Span Description | Scrollbar | // +--------------+----------+--------------------+-----------+ // setupCoordinates: function() { - this.buttonX = Math.min(300, Math.floor(this.maxX / 5)); - this.descX = this.buttonX + Math.min(75, Math.floor(this.maxX / 20)); + this.xB = Math.min(300, Math.floor(this.maxX / 5)); + this.xD = this.xB + Math.min(75, Math.floor(this.maxX / 20)); var scrollBarWidth = Math.min(50, Math.floor(this.maxX / 10)); - this.scrollX = this.maxX - scrollBarWidth; - }, - - setupTimeCursor: function() { - var selectedTime; - if (this.timeCursor != null) { - selectedTime = this.timeCursor.selectedTime; - console.log("setupTimeCursor: selectedTime = (prev) " + selectedTime); - } else { - selectedTime = this.begin; - console.log("setupTimeCursor: selectedTime = (begin) " + selectedTime); - } - this.timeCursor = new htrace.TimeCursor({ - ctx: this.ctx, - x0: this.descX, - xF: this.scrollX, - el: "#selectedTime", - y0: 0, - yF: this.maxY, - begin: this.begin, - end: this.end, - selectedTime: selectedTime - }); + this.xS = this.maxX - scrollBarWidth; }, setupWidgets: function() { - var widgets = []; - var spanWidgetHeight = Math.min(25, Math.floor(this.maxY / 32)); + this.widgetManager = new htrace.WidgetManager({searchResultsView: this}); // Create a SpanWidget for each span we know about - var numSpans = this.model.size(); - for (var i = 0; i < numSpans; i++) { - var spanWidget = new htrace.SpanWidget({ + var spanWidgetHeight = Math.min(25, Math.floor(this.maxY / 32)); + var numResults = this.searchResults.size(); + var groupY = 0; + for (var i = 0; i < numResults; i++) { + var widget = new htrace.SpanGroupWidget({ + manager: this.widgetManager, ctx: this.ctx, - span: this.model.at(i), + span: this.searchResults.at(i), x0: 0, - xB: this.buttonX, - xD: this.descX, - xF: this.scrollX, - y0: i * spanWidgetHeight, - yF: (i * spanWidgetHeight) + (spanWidgetHeight - 1), + xB: this.xB, + xD: this.xD, + xF: this.xS, + y0: groupY, begin: this.begin, - end: this.end + end: this.end, + spanWidgetHeight: spanWidgetHeight }); - widgets.push(spanWidget); + groupY = widget.yF; } - // Create a new root-leve WidgetManager - this.widgetManager = new htrace.WidgetManager({ - widgets: widgets + // Create the time cursor widget. + var selectedTime = this.begin; + if (this.timeCursor != null) { + selectedTime = this.timeCursor.selectedTime; + } + this.timeCursor = new htrace.TimeCursor({ + manager: this.widgetManager, + selectedTime: selectedTime, + el: "#selectedTime" }); + this.timeCursor.ctx = this.ctx; + this.timeCursor.x0 = this.xD; + this.timeCursor.xF = this.xS; + this.timeCursor.y0 = 0; + this.timeCursor.yF = this.maxY; + this.timeCursor.begin = this.begin; + this.timeCursor.end = this.end; }, draw: function() { @@ -227,7 +207,7 @@ htrace.SearchResultsView = Backbone.View.extend({ return; } - // Set the background to white. + // Set the background to white. this.ctx.save(); this.ctx.fillStyle="#ffffff"; this.ctx.strokeStyle="#000000"; @@ -235,8 +215,7 @@ htrace.SearchResultsView = Backbone.View.extend({ this.ctx.restore(); // Draw all the widgets. - this.widgetManager.draw(); - this.timeCursor.draw(); + this.widgetManager.handle({type: "draw"}); }, checkCanvasTooSmall: function() { @@ -268,10 +247,6 @@ htrace.SearchResultsView = Backbone.View.extend({ $("#resultsCanvas").on("mouseout", function(e) { view.handleMouseOut(e); }); - $(window).off("mouseup"); - $(window).on("mouseup"), function(e) { - view.handleGlobalMouseUp(e); - } $("#resultsCanvas").off("mousemove"); $("#resultsCanvas").on("mousemove", function(e) { view.handleMouseMove(e); @@ -342,24 +317,45 @@ htrace.SearchResultsView = Backbone.View.extend({ }, zoomFitAll: function() { - var numSpans = this.model.size(); - if (numSpans == 0) { + var numResults = this.searchResults.size(); + if (numResults == 0) { this.setBegin(0); this.setEnd(this.MINIMUM_TIME_SPAN); return; } var minStart = 4503599627370496; var maxEnd = 0; - for (var i = 0; i < numSpans; i++) { - var span = this.model.at(i); - if (span.get('begin') < minStart) { - minStart = span.get('begin'); + for (var i = 0; i < numResults; i++) { + var span = this.searchResults.at(i); + var begin = span.getEarliestBegin(); + if (begin < minStart) { + minStart = begin; } - if (span.get('end') > maxEnd) { - maxEnd = span.get('end'); + var end = span.getLatestEnd(); + if (end > minStart) { + maxEnd = end; } } this.setBegin(minStart); this.setEnd(maxEnd); + }, + + // Apply a function to all spans + applyToAllSpans: function(cb) { + for (var i = 0; i < this.searchResults.length; i++) { + htrace.treeTraverseDepthFirstPre(this.searchResults.at(i), + htrace.getReifiedChildren, 0, + function(node, depth) { + console.log("node = " + node + ", node.constructor.name = " + node.constructor.name); + cb(node); + }); + htrace.treeTraverseDepthFirstPre(this.searchResults.at(i), + htrace.getReifiedParents, 0, + function(node, depth) { + if (depth > 0) { + cb(node); + } + }); + } } }); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/span.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span.js b/htrace-htraced/src/web/app/span.js index 2c06fa0..a056b4f 100644 --- a/htrace-htraced/src/web/app/span.js +++ b/htrace-htraced/src/web/app/span.js @@ -22,6 +22,46 @@ var htrace = htrace || {}; // The invalid span ID, which is all zeroes. htrace.INVALID_SPAN_ID = "0000000000000000"; +// Convert an array of htrace.Span models into a comma-separated string. +htrace.spanModelsToString = function(spans) { + var ret = ""; + var prefix = ""; + for (var i = 0; i < spans.length; i++) { + ret += prefix + JSON.stringify(spans[i].unparse()); + prefix = ", "; + } + return ret; +}; + +// Convert an array of return results from ajax calls into an array of +// htrace.Span models. +htrace.parseMultiSpanAjaxQueryResults = function(ajaxCalls) { + var parsedSpans = []; + for (var i = 0; i < ajaxCalls.length; i++) { + var text = ajaxCalls[i][0]; + var result = ajaxCalls[i][1]; + if (ajaxCalls[i]["status"] != "200") { + throw "ajax error: " + ajaxCalls[i].statusText; + } + var parsedSpan = new htrace.Span({}); + try { + parsedSpan.parse(ajaxCalls[i].responseJSON, {}); + } catch (e) { + throw "span parse error: " + e; + } + parsedSpans.push(parsedSpan); + } + return parsedSpans; +}; + +htrace.getReifiedParents = function(span) { + return span.get("reifiedParents") || []; +}; + +htrace.getReifiedChildren = function(span) { + return span.get("reifiedChildren") || []; +}; + htrace.Span = Backbone.Model.extend({ // Parse a span sent from htraced. // We use more verbose names for some attributes. @@ -36,6 +76,16 @@ htrace.Span = Backbone.Model.extend({ this.set("description", response.d ? response.d : ""); this.set("begin", response.b ? parseInt(response.b, 10) : 0); this.set("end", response.e ? parseInt(response.e, 10) : 0); + + this.set("selected", false); + + // reifiedChildren starts off as null and will be filled in as needed. + this.set("reifiedChildren", null); + + // If there are parents, reifiedParents starts off as null. Otherwise, we + // know it is the empty array. + this.set("reifiedParents", (this.get("parents").length == 0) ? [] : null); + return span; }, @@ -65,5 +115,138 @@ htrace.Span = Backbone.Model.extend({ obj.e = this.get("end"); } return obj; - } + }, + + // + // Although the parent IDs are always present in the 'parents' field of the + // span, sometimes we need the actual parent span models. In that case we + // must "reify" them (make them real). + // + // This functionReturns a jquery promise which reifies all the parents of this + // span and stores them into reifiedParents. The promise returns the empty + // string on success, or an error string on failure. + // + reifyParents: function() { + var span = this; + var numParents = span.get("parents").length; + var ajaxCalls = []; + // Set up AJAX queries to reify the parents. + for (var i = 0; i < numParents; i++) { + ajaxCalls.push($.ajax({ + url: "span/" + span.get("parents")[i], + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + })); + } + var rootDeferred = jQuery.Deferred(); + $.when.apply($, ajaxCalls).then(function() { + var reifiedParents = []; + try { + reifiedParents = htrace.parseMultiSpanAjaxQueryResults(ajaxCalls); + } catch (e) { + rootDeferred.resolve("Error reifying parents for " + + span.get("spanId") + ": " + e); + return; + } + // The current span is a child of the reified parents. There may be other + // children of those parents, but we are ignoring that here. By making + // this non-null, the "expand children" button will not appear for these + // paren spans. + for (var j = 0; j < reifiedParents.length; j++) { + reifiedParents[j].set("reifiedChildren", [span]); + } + console.log("Setting reified parents for " + span.get("spanId") + + " to " + htrace.spanModelsToString (reifiedParents)); + span.set("reifiedParents", reifiedParents); + rootDeferred.resolve(""); + }); + return rootDeferred.promise(); + }, + + // + // The span itself does not contain its children. However, the server has an + // index which can be used to easily find the children of a particular span. + // + // This function returns a jquery promise which reifies all the children of + // this span and stores them into reifiedChildren. The promise returns the + // empty string on success, or an error string on failure. + // + reifyChildren: function() { + var rootDeferred = jQuery.Deferred(); + var span = this; + $.ajax({ + url: "span/" + span.get("spanId") + "/children?lim=50", + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + }).done(function(childIds) { + var ajaxCalls = []; + for (var i = 0; i < childIds.length; i++) { + ajaxCalls.push($.ajax({ + url: "span/" + childIds[i], + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + })); + }; + $.when.apply($, ajaxCalls).then(function() { + var reifiedChildren; + try { + reifiedChildren = htrace.parseMultiSpanAjaxQueryResults(ajaxCalls); + } catch (e) { + reifiedChildren = rootDeferred.resolve("Error reifying children " + + "for " + span.get("spanId") + ": " + e); + return; + } + // The current span is a parent of the new child. + // There may be other parents, but we are ignoring that here. + // By making this non-null, the "expand parents" button will not + // appear for these child spans. + for (var j = 0; j < reifiedChildren.length; j++) { + reifiedChildren[j].set("reifiedParents", [span]); + } + console.log("Setting reified children for " + span.get("spanId") + + " to " + htrace.spanModelsToString (reifiedChildren)); + span.set("reifiedChildren", reifiedChildren); + rootDeferred.resolve(""); + }); + }).fail(function(statusData) { + // Check if the /children query failed. + rootDeferred.resolve("Error querying children of " + + span.get("spanId") + ": got " + statusData); + return; + }); + return rootDeferred.promise(); + }, + + // Get the earliest begin time of this span or any of its reified parents or + // children. + getEarliestBegin: function() { + var earliestBegin = this.get("begin"); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedParents, 0, + function(span, depth) { + earliestBegin = Math.min(earliestBegin, span.get("begin")); + }); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedChildren, 0, + function(span, depth) { + earliestBegin = Math.min(earliestBegin, span.get("begin")); + }); + return earliestBegin; + }, + + // Get the earliest begin time of this span or any of its reified parents or + // children. + getLatestEnd: function() { + var latestEnd = this.get("end"); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedParents, 0, + function(span, depth) { + latestEnd = Math.max(latestEnd, span.get("end")); + }); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedChildren, 0, + function(span, depth) { + latestEnd = Math.max(latestEnd, span.get("end")); + }); + return latestEnd; + }, }); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/span_group_widget.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span_group_widget.js b/htrace-htraced/src/web/app/span_group_widget.js new file mode 100644 index 0000000..e32c2db --- /dev/null +++ b/htrace-htraced/src/web/app/span_group_widget.js @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Widget containing a group of trace spans displayed on the canvas. +htrace.SpanGroupWidget = function(params) { + this.draw = function() { + this.ctx.save(); + this.ctx.fillStyle="#ffffff"; + this.ctx.fillRect(this.x0, this.y0, this.xF - this.x0, this.yF - this.y0); + this.ctx.strokeStyle="#aaaaaa"; + this.ctx.beginPath(); + this.ctx.moveTo(this.x0, this.y0); + this.ctx.lineTo(this.xF, this.y0); + this.ctx.stroke(); + this.ctx.beginPath(); + this.ctx.moveTo(this.x0, this.yF); + this.ctx.lineTo(this.xF, this.yF); + this.ctx.stroke(); + this.ctx.restore(); + return true; + }; + + this.createSpanWidget = function(node, indentLevel, + allowUpButton, allowDownButton) { + new htrace.SpanWidget({ + manager: this.manager, + ctx: this.ctx, + span: node, + x0: this.x0, + xB: this.xB, + xD: this.xD, + xF: this.xF, + xT: this.childIndent * indentLevel, + y0: this.spanY, + yF: this.spanY + this.spanWidgetHeight, + allowUpButton: allowUpButton, + allowDownButton: allowDownButton, + begin: this.begin, + end: this.end + }); + this.spanY += this.spanWidgetHeight; + } + + this.handle = function(e) { + switch (e.type) { + case "draw": + this.draw(); + return true; + } + } + + for (var k in params) { + this[k]=params[k]; + } + this.manager.register("draw", this); + this.spanY = this.y0 + 4; + + // Figure out how much to indent each child's description text. + this.childIndent = Math.max(10, (this.xF - this.xD) / 50); + + // Get the maximum depth of the parents tree to find out how far to indent. + var parentTreeHeight = + htrace.treeHeight(this.span, htrace.getReifiedParents); + + console.log("parentTreeHeight = " + parentTreeHeight); + // Traverse the parents tree upwards. + var thisWidget = this; + htrace.treeTraverseDepthFirstPost(this.span, htrace.getReifiedParents, 0, + function(node, depth) { + if (depth > 0) { + thisWidget.createSpanWidget(node, + parentTreeHeight - depth, true, false); + } + }); + thisWidget.createSpanWidget(this.span, parentTreeHeight, true, true); + // Traverse the children tree downwards. + htrace.treeTraverseDepthFirstPre(this.span, htrace.getReifiedChildren, 0, + function(node, depth) { + if (depth > 0) { + thisWidget.createSpanWidget(node, + parentTreeHeight + depth, false, true); + } + }); + this.yF = this.spanY + 4; + console.log("SpanGroupWidget(this.span=" + + JSON.stringify(this.span.unparse()) + + ", x0=" + this.x0 + ", xB=" + this.xB + + ", xD=" + this.xD + ", xF=" + this.xF + + ", y0=" + this.y0 + ", yF=" + this.yF + + ")"); + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/span_widget.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span_widget.js b/htrace-htraced/src/web/app/span_widget.js index f9333d6..0d18fef 100644 --- a/htrace-htraced/src/web/app/span_widget.js +++ b/htrace-htraced/src/web/app/span_widget.js @@ -21,50 +21,16 @@ var htrace = htrace || {}; // Widget containing the trace span displayed on the canvas. htrace.SpanWidget = function(params) { - for (var k in params) { - this[k]=params[k]; - } - - this.selected = false; - this.widgetManagerFocused = false; - this.xSize = this.xF - this.x0; - this.ySize = this.yF - this.y0; - this.xDB = this.xD - this.xB; - - var widgets = []; - this.upWidget = new htrace.TriangleButton({ - ctx: this.ctx, - direction: "up", - x0: this.xB + 2, - xF: this.xB + (this.xDB / 2) - 2, - y0: this.y0 + 2, - yF: this.yF - 2, - }); - widgets.push(this.upWidget); - this.downWidget = new htrace.TriangleButton({ - ctx: this.ctx, - direction: "down", - x0: this.xB + (this.xDB / 2) + 2, - xF: this.xD - 2, - y0: this.y0 + 2, - yF: this.yF - 2, - }); - widgets.push(this.downWidget); - this.widgetManager = new htrace.WidgetManager({ - widgets: widgets, - }); - this.draw = function() { this.drawBackground(); this.drawProcessId(); this.drawDescription(); - this.widgetManager.draw(); }; // Draw the background of this span widget. this.drawBackground = function() { this.ctx.save(); - if (this.selected) { + if (this.span.get("selected")) { this.ctx.fillStyle="#ffccff"; } else { this.ctx.fillStyle="#ffffff"; @@ -133,7 +99,9 @@ htrace.SpanWidget = function(params) { // Draw description text this.ctx.fillStyle="#000000"; this.ctx.font = (this.ySize - gapY) + "px sans-serif"; - this.ctx.fillText(this.span.get('description'), this.xD, this.yF - gapY - 2); + this.ctx.fillText(this.span.get('description'), + this.xD + this.xT, + this.yF - gapY - 2); this.ctx.restore(); }; @@ -145,42 +113,11 @@ htrace.SpanWidget = function(params) { (this.end - this.begin)); }; - this.inBoundingBox = function(x, y) { - return ((x >= this.x0) && (x <= this.xF) && (y >= this.y0) && (y <= this.yF)); - }; - - this.handleMouseDown = function(x, y) { - if (!this.inBoundingBox(x, y)) { - return false; - } - if (this.widgetManager.handleMouseDown(x, y)) { - this.widgetManagerFocused = true; - return true; - } - this.selected = !this.selected; - this.fillSpanDetailsView(); - return true; - }; - - this.handleMouseUp = function(x, y) { - if (this.widgetManagerFocused) { - this.widgetManager.handleMouseUp(x, y); - this.widgetManagerFocused = false; - } - }; - - this.handleMouseMove = function(x, y) { - if (!this.widgetManagerFocused) { - return false; - } - return this.widgetManager.handleMouseUp(x, y); - }; - this.fillSpanDetailsView = function() { var info = { spanID: this.span.get("spanID"), begin: htrace.dateToString(parseInt(this.span.get("begin"), 10)), - end: htrace.dateToString(parseInt(this.span.get("end"), 10)) + end: htrace.dateToString(parseInt(this.span.get("end"), 10)), }; var explicitOrder = { spanId: -3, @@ -189,6 +126,12 @@ htrace.SpanWidget = function(params) { }; keys = []; for(k in this.span.attributes) { + if (k == "reifiedChildren") { + continue; + } + if (k == "reifiedParents") { + continue; + } keys.push(k); if (info[k] == null) { info[k] = this.span.get(k); @@ -225,5 +168,70 @@ htrace.SpanWidget = function(params) { $("#spanDetails").html(h); }; + this.handle = function(e) { + switch (e.type) { + case "mouseDown": + if (!htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + return true; + } + this.manager.searchResultsView.applyToAllSpans(function(span) { + if (span.get("selected") == true) { + span.set("selected", false); + } + }); + this.span.set("selected", true); + this.fillSpanDetailsView(); + return true; + case "draw": + this.draw(); + return true; + } + }; + + for (var k in params) { + this[k]=params[k]; + } + this.xSize = this.xF - this.x0; + this.ySize = this.yF - this.y0; + this.xDB = this.xD - this.xB; + this.manager.register("draw", this); + + var widget = this; + if ((this.span.get("reifiedParents") == null) && (this.allowUpButton)) { + new htrace.TriangleButton({ + ctx: this.ctx, + manager: this.manager, + direction: "up", + x0: this.xB + 2, + xF: this.xB + (this.xDB / 2) - 2, + y0: this.y0 + 2, + yF: this.yF - 2, + callback: function() { + $.when(widget.span.reifyParents()).done(function (result) { + console.log("reifyParents: result was '" + result + "'"); + widget.manager.searchResultsView.render(); + }); + }, + }); + } + if ((this.span.get("reifiedChildren") == null) && (this.allowDownButton)) { + new htrace.TriangleButton({ + ctx: this.ctx, + manager: this.manager, + direction: "down", + x0: this.xB + (this.xDB / 2) + 2, + xF: this.xD - 2, + y0: this.y0 + 2, + yF: this.yF - 2, + callback: function() { + $.when(widget.span.reifyChildren()).done(function (result) { + console.log("reifyChildren: result was '" + result + "'"); + widget.manager.searchResultsView.render(); + }); + }, + }); + } + this.manager.register("mouseDown", this); return this; }; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/time_cursor.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/time_cursor.js b/htrace-htraced/src/web/app/time_cursor.js index 0060abb..1caaa9a 100644 --- a/htrace-htraced/src/web/app/time_cursor.js +++ b/htrace-htraced/src/web/app/time_cursor.js @@ -21,11 +21,6 @@ var htrace = htrace || {}; // Draws a vertical bar selecting a time. htrace.TimeCursor = function(params) { - this.selectedTime = -1; - for (var k in params) { - this[k]=params[k]; - } - this.positionToTime = function(x) { if ((x < this.x0) || (x > this.xF)) { return -1; @@ -56,19 +51,31 @@ htrace.TimeCursor = function(params) { } }; - this.handleMouseMove = function(x, y) { - if ((y >= this.y0) && (y <= this.yF) && - (x >= this.x0) && (x <= this.xF)) { - this.selectedTime = this.positionToTime(x); - if (this.selectedTime < 0) { - $(this.el).val(""); - } else { - $(this.el).val(htrace.dateToString(this.selectedTime)); - } - return true; + this.handle = function(e) { + switch (e.type) { + case "mouseMove": + if (htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + this.selectedTime = this.positionToTime(e.x); + if (this.selectedTime < 0) { + $(this.el).val(""); + } else { + $(this.el).val(htrace.dateToString(this.selectedTime)); + } + return true; + } + return true; + case "draw": + this.draw(); + return true; } - return false; }; + this.selectedTime = -1; + for (var k in params) { + this[k]=params[k]; + } + this.manager.register("mouseMove", this); + this.manager.register("draw", this); return this; }; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/tree.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/tree.js b/htrace-htraced/src/web/app/tree.js new file mode 100644 index 0000000..046085c --- /dev/null +++ b/htrace-htraced/src/web/app/tree.js @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// +// Get the height of a tree-- that is, the number of edges on the longest +// downward path between the root and a leaf +// +htrace.treeHeight = function(node, getDescendants) { + var height = 0; + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + height = Math.max(height, + 1 + htrace.treeHeight(descendants[i], getDescendants)); + } + return height; +}; + +// +// Perform a depth-first, post-order traversal on the tree, invoking the +// callback on every node with the node and depth as the arguments. +// +// Example: +// 5 +// / \ +// 3 4 +// / \ +// 1 2 +// +htrace.treeTraverseDepthFirstPost = function(node, getDescendants, depth, cb) { + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + htrace.treeTraverseDepthFirstPost(descendants[i], + getDescendants, depth + 1, cb); + } + cb(node, depth); +}; + +// +// Perform a depth-first, pre-order traversal on the tree, invoking the +// callback on every node with the node and depth as the arguments. +// +// Example: +// 1 +// / \ +// 2 5 +// / \ +// 3 4 +// +htrace.treeTraverseDepthFirstPre = function(node, getDescendants, depth, cb) { + cb(node, depth); + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + htrace.treeTraverseDepthFirstPre(descendants[i], + getDescendants, depth + 1, cb); + } +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/triangle_button.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/triangle_button.js b/htrace-htraced/src/web/app/triangle_button.js index 89f9514..f252476 100644 --- a/htrace-htraced/src/web/app/triangle_button.js +++ b/htrace-htraced/src/web/app/triangle_button.js @@ -60,44 +60,49 @@ htrace.TriangleButton = function(params) { } else { console.log("TriangleButton: unknown direction " + this.direction); } - this.ctx.closePath(); + this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); }; - this.inBoundingBox = function(x, y) { - return ((x >= this.x0) && (x <= this.xF) && (y >= this.y0) && (y <= this.yF)); - } - - this.handleMouseDown = function(x, y) { -// console.log("TriangleButton#handleMouseDown(x=" + x + ", y=" + y + -// ", x0=" + this.x0 + ", y0="+ this.y0 + -// ", xF=" + this.xF + ", yF=" + this.yF); - if (this.inBoundingBox(x,y)) { - this.selected = true; - return true; - } - return false; - } - - this.handleMouseUp = function(x, y) { - if (this.selected) { - console.log("executing callback"); + this.handle = function(e) { + switch (e.type) { + case "mouseDown": + if (!htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + return true; + } + this.manager.register("mouseUp", this); + this.manager.register("mouseMove", this); + this.manager.register("mouseOut", this); + this.selected = true; + return false; + case "mouseUp": + if (this.selected) { + this.callback(); + this.selected = false; + } + this.manager.unregister("mouseUp", this); + this.manager.unregister("mouseMove", this); + this.manager.unregister("mouseOut", this); + return true; + case "mouseMove": + this.selected = htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF); + return true; + case "mouseOut": + this.selected = false; + return true; + case "draw": + this.draw(); + return true; } - this.selected = false; - } - - this.handleMouseMove = function(x, y) { - var selected = this.inBoundingBox(x,y); - if (this.selected != selected) { - this.selected = selected; - return true; - } - return false; - } + }; for (var k in params) { this[k]=params[k]; } + this.manager.register("mouseDown", this); + this.manager.register("draw", this); return this; }; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/app/widget_manager.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/widget_manager.js b/htrace-htraced/src/web/app/widget_manager.js index 49202c5..5f393b0 100644 --- a/htrace-htraced/src/web/app/widget_manager.js +++ b/htrace-htraced/src/web/app/widget_manager.js @@ -19,43 +19,39 @@ var htrace = htrace || {}; +// Check if a point is inside a bounding box. +htrace.inBoundingBox = function(x, y, x0, xF, y0, yF) { + return ((x >= x0) && (x <= xF) && (y >= y0) && (y <= yF)); + } + // Manages a set of widgets on the canvas. // Buttons and sliders are both widgets. htrace.WidgetManager = function(params) { - this.widgets = []; - this.focusedWidget = null; - - this.handleMouseDown = function(x, y) { - if (this.focusedWidget != null) { - this.focusedWidget = null; - } - var numWidgets = this.widgets.length; - console.log("WidgetManager looking through " + numWidgets + " widgets."); - for (var i = 0; i < numWidgets; i++) { - if (this.widgets[i].handleMouseDown(x, y)) { - this.focusedWidget = this.widgets[i]; - break; - } - } - return (this.focusedWidget != null); + this.listeners = { + "mouseDown": [], + "mouseUp": [], + "mouseMove": [], + "mouseOut": [], + "draw": [], }; - this.handleMouseUp = function(x, y) { - if (this.focusedWidget != null) { - this.focusedWidget.handleMouseUp(x, y); - this.focusedWidget = null; - } - }; + this.register = function(type, widget) { + this.listeners[type].push(widget); + } - this.handleMouseMove = function(x, y) { - return this.focusedWidget != null ? - this.focusedWidget.handleMouseMove(x, y) : false; - }; + this.unregister = function(type, widget) { + this.listeners[type] = _.without(this.listeners[type], widget); + } - this.draw = function() { - var numWidgets = this.widgets.length; - for (var i = 0; i < numWidgets; i++) { - this.widgets[i].draw(); + this.handle = function(e) { + // Make a copy of the listeners, in case the handling functions change the + // array. + var listeners = this.listeners[e.type].slice(); + var len = listeners.length; + for (var i = 0; i < len; i++) { + if (!listeners[i].handle(e)) { + break; + } } }; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/image/owl.png ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/image/owl.png b/htrace-htraced/src/web/image/owl.png new file mode 100644 index 0000000..be6fabd Binary files /dev/null and b/htrace-htraced/src/web/image/owl.png differ http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a560a555/htrace-htraced/src/web/index.html ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/index.html b/htrace-htraced/src/web/index.html index 66ef0dc..bd15c9a 100644 --- a/htrace-htraced/src/web/index.html +++ b/htrace-htraced/src/web/index.html @@ -61,6 +61,45 @@ <div class="col-md-3" role="form"> <div class="panel panel-default"> <div class="panel-heading"> + <h1 class="panel-title">Timeline</h1> + </div style="border: 1px solid #000000;"> + <div class="panel-body"> + <div class="form-horizontal"> + <div class="form-group"> + <label class="col-sm-2 control-label">Begin</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="begin" value="1970-01-01T00:00:00,000"/> + </div> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">End</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="end" value="1970-01-01T00:00:00,100"/> + </div> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">Cur</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="selectedTime" value=""/> + </div> + </div> + <div class="form-horizontal"> + <button type="button" class="btn btn btn-warning" + id="zoomButton">Zoom</button> + </div> + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h1 class="panel-title">Span Details</h1> + </div style="border: 1px solid #000000;"> + <div class="panel-body"> + <div id="spanDetails" ></div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> <h1 class="panel-title">Search</h1> </div style="border: 1px solid #000000;"> <div class="panel-body"> @@ -105,45 +144,6 @@ </form> </div> </div> - <div class="panel panel-default"> - <div class="panel-heading"> - <h1 class="panel-title">Timeline</h1> - </div style="border: 1px solid #000000;"> - <div class="panel-body"> - <div class="form-horizontal"> - <div class="form-group"> - <label class="col-sm-2 control-label">Begin</label> - <div class="col-sm-10"> - <input type="text" class="form-control" id="begin" value="1970-01-01T00:00:00,000"/> - </div> - </div> - <div class="form-group"> - <label class="col-sm-2 control-label">End</label> - <div class="col-sm-10"> - <input type="text" class="form-control" id="end" value="1970-01-01T00:00:00,100"/> - </div> - </div> - <div class="form-group"> - <label class="col-sm-2 control-label">Cur</label> - <div class="col-sm-10"> - <input type="text" class="form-control" id="selectedTime" value=""/> - </div> - </div> - <div class="form-horizontal"> - <button type="button" class="btn btn btn-warning" - id="zoomButton">Zoom</button> - </div> - </div> - </div> - </div> - <div class="panel panel-default"> - <div class="panel-heading"> - <h1 class="panel-title">Span Details</h1> - </div style="border: 1px solid #000000;"> - <div class="panel-body"> - <div id="spanDetails" ></div> - </div> - </div> </div> <div class="col-md-9" id="resultsView"> <div id="results"> @@ -200,6 +200,7 @@ <script src="lib/moment-2.10.3.js" type="text/javascript"></script> <script src="app/string.js" type="text/javascript"></script> + <script src="app/tree.js" type="text/javascript"></script> <script src="app/time_cursor.js" type="text/javascript"></script> <script src="app/widget_manager.js" type="text/javascript"></script> @@ -207,6 +208,7 @@ <script src="app/span.js" type="text/javascript"></script> + <script src="app/span_group_widget.js" type="text/javascript"></script> <script src="app/span_widget.js" type="text/javascript"></script> <script src="app/search_results.js" type="text/javascript"></script> <script src="app/about_view.js" type="text/javascript"></script>
