HTRACE-186. gui: support finding the parents and children of spans, add owl (cmccabe)
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) {
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;
+ }
+});
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
});
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 @@
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 @@
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 @@
this.ctx = this.canvas.get(0).getContext("2d");
this.scaleCanvas();
this.setupCoordinates();
- this.setupTimeCursor();
this.setupWidgets();
this.draw();
this.attachEvents();
@@ -160,66 +147,59 @@
//
// 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 @@
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 @@
this.ctx.restore();
// Draw all the widgets.
- this.widgetManager.draw();
- this.timeCursor.draw();
+ this.widgetManager.handle({type: "draw"});
},
checkCanvasTooSmall: function() {
@@ -268,10 +247,6 @@
$("#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 @@
},
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);
+ }
+ });
+ }
}
});
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 @@
// 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 @@
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 @@
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;
+ },
});
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;
+};
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 @@
// 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 @@
// 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 @@
(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 @@
};
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 @@
$("#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;
};
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 @@
// 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 @@
}
};
- 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;
};
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);
+ }
+};
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 @@
} 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;
+ 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;
}
- return false;
- }
-
- this.handleMouseUp = function(x, y) {
- if (this.selected) {
- console.log("executing callback");
- }
- 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;
};
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,44 +19,40 @@
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.listeners = {
+ "mouseDown": [],
+ "mouseUp": [],
+ "mouseMove": [],
+ "mouseOut": [],
+ "draw": [],
+ };
- 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];
+ this.register = function(type, widget) {
+ this.listeners[type].push(widget);
+ }
+
+ this.unregister = function(type, widget) {
+ this.listeners[type] = _.without(this.listeners[type], widget);
+ }
+
+ 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;
}
}
- return (this.focusedWidget != null);
- };
-
- this.handleMouseUp = function(x, y) {
- if (this.focusedWidget != null) {
- this.focusedWidget.handleMouseUp(x, y);
- this.focusedWidget = null;
- }
- };
-
- this.handleMouseMove = function(x, y) {
- return this.focusedWidget != null ?
- this.focusedWidget.handleMouseMove(x, y) : false;
- };
-
- this.draw = function() {
- var numWidgets = this.widgets.length;
- for (var i = 0; i < numWidgets; i++) {
- this.widgets[i].draw();
- }
};
for (var k in params) {
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
--- /dev/null
+++ b/htrace-htraced/src/web/image/owl.png
Binary files differ
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>