You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by iw...@apache.org on 2015/09/16 06:29:57 UTC
[11/22] incubator-htrace git commit: HTRACE-246. HTrace WebApp not
properly defined and therefore not packaged into .war (Lewis John McGibbney
via iwasakims)
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/search_view.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/search_view.js b/htrace-webapp/src/main/webapp/app/search_view.js
new file mode 100644
index 0000000..aeb4273
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/search_view.js
@@ -0,0 +1,196 @@
+/*
+ * 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 || {};
+htrace.SearchView = Backbone.View.extend({
+ initialize : function() {
+ this.predicateViews = [];
+ this.highestPredicateIndex = 0;
+ this.searchInProgress = false;
+ this.searchResults = new htrace.SearchResults();
+ this.resultsView = new htrace.SearchResultsView({
+ searchResults: this.searchResults,
+ el: "#resultsView"
+ });
+ },
+
+ events: {
+ "click #searchButton": "searchHandler",
+ "click #clearButton": "clearHandler",
+ "click .add-field": "dropdownHandler",
+ "blur #begin": "blurBeginHandler",
+ "blur #end": "blurEndHandler",
+ "click #zoomButton": "zoomHandler"
+ },
+
+ searchHandler: function(e){
+ e.preventDefault();
+
+ this.doSearch(e.ctrlKey);
+ },
+
+ clearHandler: function(e){
+ e.preventDefault();
+
+ this.resultsView.clearHandler(true);
+ },
+
+ doSearch: function(showDebug){
+ if (this.searchInProgress) {
+ console.log("Can't start a new search while another one is in " +
+ "progress.");
+ return false;
+ }
+
+ // Check if there are no search criteria.
+ if (this.predicateViews.length == 0) {
+ htrace.showModalWarning("No Search Criteria Specified",
+ "You have not specified any search criteria. " +
+ "Use the 'Add Predicate' button to specify what to search for.");
+ return false;
+ }
+
+ // Build the predicate array.
+ predicates = []
+ var predicateViewsLen = this.predicateViews.length;
+ for (var i = 0; i < predicateViewsLen; i++) {
+ var predicateView = this.predicateViews[i];
+ try {
+ predicates.push(predicateView.getPredicate());
+ } catch(err) {
+ htrace.showModalWarning("Search Field Validation Error",
+ "Invalid search string for the '" + predicateView.ptype.name +
+ "' field.<p/>" + err);
+ return false;
+ }
+ }
+ var queryJson = {
+ pred: predicates,
+ lim: 20
+ };
+ // If there are existing search results, we want results which "come after"
+ // those. So pass the last span we saw as a continuation token.
+ if (this.searchResults.size() > 0) {
+ queryJson.prev =
+ this.searchResults.at(this.searchResults.size() - 1).unparse();
+ }
+ var searchView = this;
+ var queryResults = new htrace.QueryResults({queryJson: queryJson});
+ console.log("Starting span query " + queryResults.url());
+ this.searchInProgress = true;
+ queryResults.fetch({
+ success: function(model, response, options){
+ var firstResults = (searchView.searchResults.size() === 0);
+ console.log("Success on span query " + queryResults.url() + ": got " +
+ queryResults.size() + " result(s). firstResults=" + firstResults);
+ searchView.searchResults.add(queryResults.models);
+ if (firstResults) {
+ // After the initial search, zoom to fit everything.
+ // On subsequent searches, we leave the viewport alone.
+ searchView.resultsView.zoomHandler();
+ }
+ searchView.searchInProgress = false;
+ if (showDebug) {
+ htrace.showModalWarning("Search Debug",
+ "This is the search debug box, accessible by holding down the " +
+ "control key while clicking the search button.<p/>" +
+ "<h3>Query JSON</h3><pre>" + queryResults.prettyQueryString() +
+ "</pre><p/><h3>Response JSON</h3><pre>" +
+ JSON.stringify(queryResults, null, 2) + "</pre><p/>");
+ } else if (queryResults.size() == 0) {
+ if (firstResults) {
+ htrace.showModalWarning("No Results Found",
+ "No results were found for your query.<p/>");
+ } else {
+ htrace.showModalWarning("No Additional Results Found",
+ "No additional results were found for your query.<p/>");
+ }
+ }
+ searchView.resultsView.render();
+ },
+ error: function(model, response, options){
+ searchView.searchResults.reset();
+ var err = "Error " + JSON.stringify(response, null, 2) +
+ " on span query " + queryResults.url();
+ console.log(err);
+ alert(err);
+ searchView.searchInProgress = false;
+ }
+ });
+ return false;
+ },
+
+ dropdownHandler: function(e){
+ e.preventDefault();
+ var text = $(e.target).text();
+ var ptype = htrace.parsePType(text);
+ if (!ptype) {
+ alert("Unable to parse predicate type '" + text + "'");
+ return false;
+ }
+ var index = this.highestPredicateIndex;
+ this.highestPredicateIndex++;
+ var el = "pred" + index;
+ $("#predicates").append('<div id="' + el + '"/></div>');
+ predicateView = new htrace.PredicateView({
+ el: "#" + el,
+ index: index,
+ ptype: ptype,
+ searchView: this
+ });
+ this.predicateViews.push(predicateView);
+ predicateView.render();
+ return true;
+ },
+
+ blurBeginHandler: function(e) {
+ return this.resultsView.handleBeginOrEndChange(e, "begin");
+ },
+
+ blurEndHandler: function(e) {
+ return this.resultsView.handleBeginOrEndChange(e, "end");
+ },
+
+ zoomHandler: function(e) {
+ e.preventDefault();
+ this.resultsView.zoomHandler();
+ },
+
+ removePredicateView: function(predicateView) {
+ this.predicateViews = _.without(this.predicateViews, predicateView);
+ },
+
+ render: function() {
+ this.$el.html(_.template($("#search-view-template").html())
+ ({ model : this.model }))
+ this.resultsView.render();
+ console.log("SearchView#render");
+ return this;
+ },
+
+ close: function() {
+ console.log("SearchView#close")
+ while (this.predicateViews.length > 0) {
+ this.predicateViews[0].remove();
+ }
+ this.resultsView.remove();
+ this.resultsView = null;
+ this.undelegateEvents();
+ }
+});
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/server_info.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/server_info.js b/htrace-webapp/src/main/webapp/app/server_info.js
new file mode 100644
index 0000000..b03f706
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/server_info.js
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+// htraced ServerInfo sent back from /serverInfo.
+// See rest.go.
+htrace.ServerInfo = Backbone.Model.extend({
+ defaults: {
+ "ReleaseVersion": "unknown",
+ "GitVersion": "unknown",
+ },
+
+ url: function() {
+ return "server/info";
+ }
+});
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/span.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/span.js b/htrace-webapp/src/main/webapp/app/span.js
new file mode 100644
index 0000000..cd87543
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/span.js
@@ -0,0 +1,282 @@
+/*
+ * 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 || {};
+
+// The invalid span ID, which is all zeroes.
+htrace.INVALID_SPAN_ID = "00000000000000000000000000000000";
+
+// 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.sortSpansByBeginTime = function(spans) {
+ return spans.sort(function(a, b) {
+ if (a.get("begin") < b.get("begin")) {
+ return -1;
+ } else if (a.get("begin") > b.get("begin")) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+};
+
+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.
+ // Missing attributes are treated as zero or empty. Numerical attributes are
+ // forced to be numbers.
+ parse: function(response, options) {
+ var span = {};
+ this.set("spanId", response.a ? response.a : htrace.INVALID_SPAN_ID);
+ this.set("tracerId", response.r ? response.r : "");
+ this.set("parents", response.p ? response.p : []);
+ 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);
+ if (response.t) {
+ var t = response.t.sort(function(a, b) {
+ if (a.t < b.t) {
+ return -1;
+ } else if (a.t > b.t) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ this.set("timeAnnotations", t);
+ } else {
+ this.set("timeAnnotations", []);
+ }
+ this.set("infoAnnotations", response.n ? response.n : {});
+ 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;
+ },
+
+ // Transform a span model back into a JSON string suitable for sending over
+ // the wire.
+ unparse: function() {
+ var obj = { };
+ if (!(this.get("spanId") === htrace.INVALID_SPAN_ID)) {
+ obj.a = this.get("spanId");
+ }
+ if (!(this.get("tracerId") === "")) {
+ obj.r = this.get("tracerId");
+ }
+ if (this.get("parents").length > 0) {
+ obj.p = this.get("parents");
+ }
+ if (this.get("description").length > 0) {
+ obj.d = this.get("description");
+ }
+ if (this.get("begin") > 0) {
+ obj.b = this.get("begin");
+ }
+ if (this.get("end") > 0) {
+ obj.e = this.get("end");
+ }
+ if (this.get("timeAnnotations").length > 0) {
+ obj.t = this.get("timeAnnotations");
+ }
+ if (_.size(this.get("infoAnnotations")) > 0) {
+ obj.n = this.get("infoAnnotations");
+ }
+ 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;
+ }
+ reifiedParents = htrace.sortSpansByBeginTime(reifiedParents);
+ // 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;
+ }
+ reifiedChildren = htrace.sortSpansByBeginTime(reifiedChildren);
+ // 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/05ce37fb/htrace-webapp/src/main/webapp/app/span_details_view.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/span_details_view.js b/htrace-webapp/src/main/webapp/app/span_details_view.js
new file mode 100644
index 0000000..9a37055
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/span_details_view.js
@@ -0,0 +1,39 @@
+/*
+ * 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 || {};
+
+htrace.SpanDetailsView = Backbone.View.extend({
+ initialize: function(options) {
+ this.el = options.el;
+ this.model = options.model;
+ }
+
+ render: function() {
+ this.$el.html(_.template($("#about-view-template").html())
+ ({ model : this.model }));
+ console.log("AboutView#render");
+ return this;
+ },
+
+ close: function() {
+ console.log("AboutView#close")
+ this.undelegateEvents();
+ }
+});
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/span_group_widget.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/span_group_widget.js b/htrace-webapp/src/main/webapp/app/span_group_widget.js
new file mode 100644
index 0000000..ad0b482
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/span_group_widget.js
@@ -0,0 +1,103 @@
+/*
+ * 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);
+
+ // 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;
+ return this;
+};
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/span_widget.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/span_widget.js b/htrace-webapp/src/main/webapp/app/span_widget.js
new file mode 100644
index 0000000..50bea91
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/span_widget.js
@@ -0,0 +1,309 @@
+/*
+ * 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 || {};
+
+htrace.showSpanDetails = function(span) {
+ var info = {
+ spanID: span.get("spanID"),
+ begin: htrace.dateToString(span.get("begin"), 10),
+ end: htrace.dateToString(span.get("end"), 10),
+ duration: ((span.get("end") - span.get("begin")) + " ms")
+ };
+ var explicitOrder = {
+ spanId: 1,
+ begin: 2,
+ end: 3,
+ duration: 4
+ };
+ keys = ["duration"];
+ for(k in span.attributes) {
+ if (k == "reifiedChildren") {
+ continue;
+ }
+ if (k == "reifiedParents") {
+ continue;
+ }
+ if (k == "selected") {
+ continue;
+ }
+ if (k == "timeAnnotations") {
+ // For timeline annotations, make the times into top-level keys.
+ var timeAnnotations = span.get("timeAnnotations");
+ for (var i = 0; i < timeAnnotations.length; i++) {
+ var key = htrace.dateToString(timeAnnotations[i].t);
+ keys.push(key);
+ info[key] = timeAnnotations[i].m;
+ explicitOrder[key] = 200;
+ }
+ continue;
+ }
+ if (k == "infoAnnotations") {
+ // For info annotations, move the keys to the top level.
+ // Surround them in brackets to make it clear that they are
+ // user-defined.
+ var infoAnnotations = span.get("infoAnnotations");
+ _.each(infoAnnotations, function(value, key) {
+ key = "[" + key + "]";
+ keys.push(key);
+ info[key] = value;
+ explicitOrder[key] = 200;
+ });
+ continue;
+ }
+ keys.push(k);
+ if (info[k] == null) {
+ info[k] = span.get(k);
+ }
+ }
+ // We sort the keys so that the stuff we want at the top appears at the top,
+ // and everything else is in alphabetical order.
+ keys = keys.sort(function(a, b) {
+ var oa = explicitOrder[a] || 100;
+ var ob = explicitOrder[b] || 100;
+ if (oa < ob) {
+ return -1;
+ } else if (oa > ob) {
+ return 1;
+ } else if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ var len = keys.length;
+ var h = '<table style="table-layout:fixed;width:100%;word-wrap:break-word">';
+ for (i = 0; i < len; i++) {
+ // Make every other row grey to improve visibility.
+ var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
+ h += _.template($("#table-row-template").html())(
+ {bgcolor: colorString, key: keys[i], val: info[keys[i]]});
+ }
+ h += '</table>';
+ htrace.showModal(_.template($("#modal-table-template").html())(
+ {title: "Span Details", body: h}));
+};
+
+// Widget containing the trace span displayed on the canvas.
+htrace.SpanWidget = function(params) {
+ this.draw = function() {
+ this.drawBackground();
+ this.drawTracerId();
+ this.drawDescription();
+ };
+
+ // Draw the background of this span widget.
+ this.drawBackground = function() {
+ this.ctx.save();
+ if (this.span.get("selected")) {
+ this.ctx.fillStyle="#ffccff";
+ } else {
+ this.ctx.fillStyle="#ffffff";
+ }
+ this.ctx.fillRect(this.x0, this.y0, this.xSize, this.ySize);
+ this.ctx.restore();
+ }
+
+ // Draw process ID text.
+ this.drawTracerId = function() {
+ this.ctx.save();
+ this.ctx.fillStyle="#000000";
+ this.ctx.font = (this.ySize - 2) + "px sans-serif";
+ this.ctx.beginPath();
+ this.ctx.rect(this.x0, this.y0, this.xB - this.x0, this.ySize);
+ this.ctx.clip();
+ this.ctx.fillText(this.span.get('tracerId'), this.x0, this.yF - 4);
+ this.ctx.restore();
+ };
+
+ // Draw the span description
+ this.drawDescription = function() {
+ // Draw the light blue bar representing time.
+ this.ctx.save();
+ this.ctx.beginPath();
+ this.ctx.rect(this.xD, this.y0, this.xF - this.xD, this.ySize);
+ this.ctx.clip();
+ this.ctx.strokeStyle="#000000";
+ this.ctx.fillStyle="#a7b7ff";
+ var beginX = this.timeToPosition(this.span.get('begin'));
+ var endX = this.timeToPosition(this.span.get('end'));
+
+ // If the span is completely off the screen, draw a diamond at either the
+ // beginning or the end of the bar to indicate whether it's too early or too
+ // late to be seen.
+ if (endX < this.x0) {
+ beginX = this.xD;
+ endX = this.xD;
+ }
+ if (beginX > this.xF) {
+ beginX = this.xF;
+ endX = this.xF;
+ }
+
+ var gapY = 2;
+ var epsilon = Math.max(2, Math.floor(this.xSize / 1000));
+ if (endX - beginX < epsilon) {
+ // The time interval is too narrow to see. Draw a diamond on the point instead.
+ this.ctx.beginPath();
+ this.ctx.moveTo(beginX, this.y0 + gapY);
+ this.ctx.lineTo(beginX + (Math.floor(this.ySize / 2) - gapY),
+ this.y0 + Math.floor(this.ySize / 2));
+ this.ctx.lineTo(beginX, this.yF - gapY);
+ this.ctx.lineTo(beginX - (Math.floor(this.ySize / 2) - gapY),
+ this.y0 + Math.floor(this.ySize / 2));
+ this.ctx.closePath();
+ this.ctx.fill();
+ } else {
+ // Draw a bar from the start time to the end time.
+// console.log("beginX=" + beginX + ", endX=" + endX +
+// ", begin=" + this.span.get('begin') + ", end=" + this.span.get('end'));
+ this.ctx.fillRect(beginX, this.y0 + gapY, endX - beginX,
+ this.ySize - (gapY * 2));
+
+ // Draw a dots showing time points where annotations are.
+ var annotations = this.span.get('timeAnnotations');
+ var annotationY = this.y0 + gapY;
+ var annotationW = 4;
+ var annotationH = (this.ySize - (gapY * 2)) / 2;
+ this.ctx.fillStyle="#419641";
+ for (var i = 0; i < annotations.length; i++) {
+ this.ctx.fillRect(this.timeToPosition(annotations[i].t), annotationY,
+ annotationW, annotationH);
+ }
+ }
+
+ // 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.xT,
+ this.yF - gapY - 2);
+
+ this.ctx.restore();
+ };
+
+ // Convert a time in milliseconds since the epoch to an x position.
+ this.timeToPosition = function(time) {
+ return this.xD +
+ (((time - this.begin) * (this.xF - this.xD)) /
+ (this.end - this.begin));
+ };
+
+ 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;
+ }
+ if (e.raw.ctrlKey) {
+ // If the control key is pressed, we toggle the current selection.
+ // The user can create multiple selections this way.
+ if (this.span.get("selected")) {
+ this.span.set("selected", false);
+ } else {
+ this.span.set("selected", true);
+ }
+ } else {
+ var that = this;
+ this.manager.searchResultsView.applyToAllSpans(function(span) {
+ // Note: we don't want to set the selection state unless we need
+ // to. Setting the state (even to the same thing it already is)
+ // triggers a full re-render, if the span is one in the results
+ // collection. A full re-render slows us down and disrupts events
+ // like double-clicking.
+ if (that.span === span) {
+ if (!span.get("selected")) {
+ span.set("selected", true);
+ }
+ } else if (span.get("selected")) {
+ span.set("selected", false);
+ }
+ });
+ }
+ return true;
+ case "draw":
+ this.draw();
+ return true;
+ case "dblclick":
+ if (htrace.inBoundingBox(e.x, e.y,
+ this.x0, this.xF, this.y0, this.yF)) {
+ htrace.showSpanDetails(this.span);
+ }
+ 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 + "'");
+ if (result != "") {
+ alert(result);
+ } else {
+ 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 + "'");
+ if (result != "") {
+ alert(result);
+ } else {
+ widget.manager.searchResultsView.render();
+ }
+ });
+ },
+ });
+ }
+ this.manager.register("mouseDown", this);
+ this.manager.register("dblclick", this);
+ return this;
+};
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/string.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/string.js b/htrace-webapp/src/main/webapp/app/string.js
new file mode 100644
index 0000000..c9c514b
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/string.js
@@ -0,0 +1,62 @@
+/*
+ * 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 || {};
+
+// Parse an ISO8601 date string into a moment.js object.
+htrace.parseDate = function(val) {
+ if (val.match(/^[0-9]([0-9]*)$/)) {
+ // Treat an all-numeric field as UTC milliseconds since the epoch.
+ return moment.utc(parseInt(val, 10));
+ }
+ // Look for approved date formats.
+ var toTry = [
+ "YYYY-MM-DDTHH:mm:ss,SSS",
+ "YYYY-MM-DDTHH:mm:ss",
+ "YYYY-MM-DDTHH:mm",
+ "YYYY-MM-DD"
+ ];
+ for (var i = 0; i < toTry.length; i++) {
+ var m = moment.utc(val, toTry[i], true);
+ if (m.isValid()) {
+ return m;
+ }
+ }
+ throw "Please enter the date either as YYYY-MM-DDTHH:mm:ss,SSS " +
+ "in UTC, or as the number of milliseconds since the epoch.";
+};
+
+// Convert a moment.js moment into an ISO8601-style date string.
+htrace.dateToString = function(val) {
+ return moment.utc(val).format("YYYY-MM-DDTHH:mm:ss,SSS");
+};
+
+// Normalize a span ID into the format the server expects to see--
+// i.e. something like 00000000000000000000000000000000.
+htrace.normalizeSpanId = function(str) {
+ if (str.length != 36) {
+ throw "The length of '" + str + "' was " + str.length +
+ ", but span IDs must be 36 characters long.";
+ }
+ if (str.search(/[^0-9a-fA-F]/) != -1) {
+ throw "Span IDs must contain only hexadecimal digits, but '" + str +
+ "' contained invalid characters.";
+ }
+ return str;
+};
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/app/time_cursor.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/time_cursor.js b/htrace-webapp/src/main/webapp/app/time_cursor.js
new file mode 100644
index 0000000..1caaa9a
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/time_cursor.js
@@ -0,0 +1,81 @@
+/*
+ * 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 || {};
+
+// Draws a vertical bar selecting a time.
+htrace.TimeCursor = function(params) {
+ this.positionToTime = function(x) {
+ if ((x < this.x0) || (x > this.xF)) {
+ return -1;
+ }
+ return this.begin +
+ (((x - this.x0) * (this.end - this.begin)) / (this.xF - this.x0));
+ };
+
+ this.timeToPosition = function(time) {
+ return this.x0 + (((time - this.begin) *
+ (this.xF - this.x0)) / (this.end - this.begin));
+ };
+
+ this.draw = function() {
+ if (this.selectedTime != -1) {
+ this.ctx.save();
+ this.ctx.beginPath();
+ this.ctx.rect(this.x0, this.y0,
+ this.xF - this.x0, this.yF - this.y0);
+ this.ctx.clip();
+ this.ctx.strokeStyle="#ff0000";
+ var x = this.timeToPosition(this.selectedTime);
+ this.ctx.beginPath();
+ this.ctx.moveTo(x, this.y0);
+ this.ctx.lineTo(x, this.yF);
+ this.ctx.stroke();
+ this.ctx.restore();
+ }
+ };
+
+ 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;
+ }
+ };
+
+ 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/05ce37fb/htrace-webapp/src/main/webapp/app/tree.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/tree.js b/htrace-webapp/src/main/webapp/app/tree.js
new file mode 100644
index 0000000..046085c
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/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/05ce37fb/htrace-webapp/src/main/webapp/app/triangle_button.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/triangle_button.js b/htrace-webapp/src/main/webapp/app/triangle_button.js
new file mode 100644
index 0000000..f252476
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/triangle_button.js
@@ -0,0 +1,108 @@
+/*
+ * 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 || {};
+
+// Triangle button widget.
+htrace.TriangleButton = function(params) {
+ this.fgColor = "#6600ff";
+ this.bgColor = "#ffffff";
+ this.selected = false;
+ this.direction = "down";
+
+ this.draw = function() {
+ this.ctx.save();
+ var fg = this.selected ? this.bgColor : this.fgColor;
+ var bg = this.selected ? this.fgColor : this.bgColor;
+ this.ctx.beginPath();
+ this.ctx.rect(this.x0, this.y0,
+ this.xF - this.x0, this.yF - this.y0);
+ this.ctx.clip();
+ this.ctx.fillStyle = bg;
+ this.ctx.strokeStyle = fg;
+ this.ctx.fillRect(this.x0, this.y0,
+ this.xF - this.x0, this.yF - this.y0);
+ this.ctx.lineWidth = 3;
+ this.ctx.strokeRect(this.x0, this.y0,
+ this.xF - this.x0, this.yF - this.y0);
+ var xPad = (this.xF - this.x0) / 5;
+ var yPad = (this.yF - this.y0) / 5;
+ this.ctx.fillStyle = fg;
+ this.ctx.strokeStyle = fg;
+ this.ctx.beginPath();
+ this.ctx.strokeStyle = fg;
+ if (this.direction === "up") {
+ this.ctx.moveTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)),
+ this.y0 + yPad);
+ this.ctx.lineTo(this.xF - xPad, this.yF - yPad);
+ this.ctx.lineTo(this.x0 + xPad, this.yF - yPad);
+ } else if (this.direction === "down") {
+ this.ctx.moveTo(this.x0 + xPad, this.y0 + yPad);
+ this.ctx.lineTo(this.xF - xPad, this.y0 + yPad);
+ this.ctx.lineTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)),
+ this.yF - yPad);
+ } else {
+ console.log("TriangleButton: unknown direction " + this.direction);
+ }
+ this.ctx.closePath();
+ this.ctx.fill();
+ this.ctx.restore();
+ };
+
+ 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;
+ }
+ };
+
+ 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/05ce37fb/htrace-webapp/src/main/webapp/app/widget_manager.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/app/widget_manager.js b/htrace-webapp/src/main/webapp/app/widget_manager.js
new file mode 100644
index 0000000..e519485
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/widget_manager.js
@@ -0,0 +1,67 @@
+/*
+ * 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 || {};
+
+// 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.listeners = {
+ "mouseDown": [],
+ "mouseUp": [],
+ "mouseMove": [],
+ "mouseOut": [],
+ "dblclick": [],
+ "draw": [],
+ };
+
+ this.register = function(type, widget) {
+ this.listeners[type].push(widget);
+ }
+
+ this.registerHighPriority = function(type, widget) {
+ this.listeners[type].unshift(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;
+ }
+ }
+ };
+
+ for (var k in params) {
+ this[k]=params[k];
+ }
+ return this;
+};
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/custom.css
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/custom.css b/htrace-webapp/src/main/webapp/custom.css
new file mode 100644
index 0000000..17945cb
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/custom.css
@@ -0,0 +1,101 @@
+/*!
+ * 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.
+ */
+
+.navbar-default {
+ background-color: #001da9;
+ border-color: #b2b5db;
+}
+.navbar-default .navbar-brand {
+ color: #ecf0f1;
+}
+.navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus {
+ color: #ffffff;
+}
+.navbar-default .navbar-text {
+ color: #ecf0f1;
+}
+.navbar-default .navbar-nav > li > a {
+ color: #ecf0f1;
+}
+.navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus {
+ color: #ffffff;
+}
+.navbar-default .navbar-nav > li > .dropdown-menu {
+ background-color: #001da9;
+}
+.navbar-default .navbar-nav > li > .dropdown-menu > li > a {
+ color: #ecf0f1;
+}
+.navbar-default .navbar-nav > li > .dropdown-menu > li > a:hover,
+.navbar-default .navbar-nav > li > .dropdown-menu > li > a:focus {
+ color: #ffffff;
+ background-color: #b2b5db;
+}
+.navbar-default .navbar-nav > li > .dropdown-menu > li > .divider {
+ background-color: #001da9;
+}
+.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus {
+ color: #ffffff;
+ background-color: #b2b5db;
+}
+.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus {
+ color: #ffffff;
+ background-color: #b2b5db;
+}
+.navbar-default .navbar-toggle {
+ border-color: #b2b5db;
+}
+.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus {
+ background-color: #b2b5db;
+}
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #ecf0f1;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+ border-color: #ecf0f1;
+}
+.navbar-default .navbar-link {
+ color: #ecf0f1;
+}
+.navbar-default .navbar-link:hover {
+ color: #ffffff;
+}
+.htrace-canvas-container {
+ overflow: hidden;
+ position: relative;
+}
+.htrace-canvas {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+}
+
+@media (max-width: 767px) {
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #ecf0f1;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #ffffff;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #ffffff;
+ background-color: #b2b5db;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/image/owl.png
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/image/owl.png b/htrace-webapp/src/main/webapp/image/owl.png
new file mode 100644
index 0000000..be6fabd
Binary files /dev/null and b/htrace-webapp/src/main/webapp/image/owl.png differ
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/05ce37fb/htrace-webapp/src/main/webapp/index.html
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/webapp/index.html b/htrace-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..ec28fe6
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/index.html
@@ -0,0 +1,246 @@
+<!doctype html>
+<!--
+ 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.
+-->
+<html lang="en-US">
+ <head>
+ <title>HTrace</title>
+ <meta charset="utf-8" name="viewport"
+ content="width=device-width, initial-scale=1.0">
+ <link href="lib/bootstrap-3.3.1/css/bootstrap.css" rel="stylesheet" type="text/css">
+ <link href="custom.css" rel="stylesheet" type="text/css">
+ </head>
+ <body>
+ <header id="header" role="banner">
+ <nav class="navbar navbar-default navbar-static-top" role="navigation">
+ <div class="collapse navbar-collapse">
+ <a class="navbar-brand" href="#">HTrace</a>
+ <ul class="nav navbar-nav">
+ <li id="about"><a href="#about">About</a></li>
+ <li id="search"><a href="#search">Search</a></li>
+ </ul>
+ </div>
+ </nav>
+ </header>
+ <div id="app" class="container-fluid" role="application"></div>
+ <div id="modal" class="modal fade"></div>
+ <footer></footer>
+
+ <script id="about-view-template" type="text/template">
+ <div class="row">
+ <div class="col-md-1">
+ </div>
+ <div class="col-md-10">
+ <h1>Welcome to HTrace</h1>
+ <img src="image/owl.png" width="15%">
+ <h2>Server Version</h2>
+ <%= model.get("ReleaseVersion") %>
+ <h2>Server Git Hash</h2>
+ <%= model.get("GitVersion") %>
+ </div>
+ <div class="col-md-1">
+ </div>
+ </div>
+ </script>
+
+ <script id="search-view-template" type="text/template">
+ <div class="row" id="searchView">
+ <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">Search</h1>
+ </div style="border: 1px solid #000000;">
+ <div class="panel-body">
+ <form>
+ <div id="predicates">
+ </div>
+ <div class="form-group">
+ <div class="btn-group">
+ <button type="button" data-toggle="dropdown"
+ aria-expanded="false"
+ class="btn btn-default dropdown-toggle">
+ Add Predicate<span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" role="menu">
+ <li><a href="javascript:void(0)"
+ class="add-field">Began after</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Began at or before</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Ended after</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Ended at or before</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Description contains</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Description is exactly</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Duration is longer than</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Duration is at most</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">Span ID is</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">TracerId contains</a></li>
+ <li><a href="javascript:void(0)"
+ class="add-field">TracerId is exactly</a></li>
+ </ul>
+ </div>
+ <button type="submit" class="btn btn-primary" id="searchButton">
+ Search</button>
+ </div>
+ <div class="form-group">
+ <button type="button" class="btn btn btn-danger" id="clearButton">
+ Clear</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-9" id="resultsView">
+ </div>
+ </div>
+ </script>
+
+ <script id="predicate-template" type="text/template">
+ <form class="form-horizontal">
+ <div class="form-group">
+ <%= desc %>
+ <button type="button" class="btn pull-right btn-link btn-sm closeButton"
+ >X</button><br/>
+ <input type="text" class="form-control"/>
+ </div>
+ </form>
+ </script>
+
+ <script id="search-results-view-template" type="text/template">
+ <!-- tabindex=1 is needed or else the canvas can never gain mouse focus on Chrome. -->
+ <canvas id="resultsCanvas" class="htrace-canvas" tabindex="1">
+ <h2>Sorry, your browser does not support the HTML5 canvas element. Please
+ upgrade to a newer browser.</h2>
+ </canvas>
+ </script>
+
+ <script id="modal-warning-template" type="text/template">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"
+ aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title"><%= title %></h4>
+ </div>
+ <div class="modal-body">
+ <%= body %><p/>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </script>
+
+ <script id="modal-table-template" type="text/template">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"
+ aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title"><%= title %></h4>
+ </div>
+ <div class="modal-body">
+ <%= body %><p/>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </script>
+
+ <script id="table-row-template" type="text/template">
+ <tr bgcolor="<%= bgcolor %>">
+ <td style="width:30%;word-wrap:break-word"><%- key %></td>
+ <td style="width:70%;word-wrap:break-word"><%- val %></td>
+ </tr>
+ </script>
+
+ <script src="lib/jquery-2.1.4.js" type="text/javascript"></script>
+ <script src="lib/bootstrap-3.3.1/js/bootstrap.min.js" type="text/javascript"></script>
+ <script src="lib/underscore-1.7.0.js" type="text/javascript"></script>
+ <script src="lib/backbone-1.1.2.js" type="text/javascript"></script>
+ <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>
+ <script src="app/triangle_button.js" type="text/javascript"></script>
+ <script src="app/partition_widget.js" type="text/javascript"></script>
+
+ <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>
+ <script src="app/modal.js" type="text/javascript"></script>
+ <script src="app/predicate.js" type="text/javascript"></script>
+ <script src="app/predicate_view.js" type="text/javascript"></script>
+ <script src="app/query_results.js" type="text/javascript"></script>
+ <script src="app/search_results_view.js" type="text/javascript"></script>
+ <script src="app/search_view.js" type="text/javascript"></script>
+ <script src="app/server_info.js" type="text/javascript"></script>
+
+ <script src="app/router.js" type="text/javascript"></script>
+ </body>
+</html>
+