diff --git a/htrace-htraced/src/web/app/search_results_view.js b/htrace-htraced/src/web/app/search_results_view.js
index 111f530..4b76ae8 100644
--- a/htrace-htraced/src/web/app/search_results_view.js
+++ b/htrace-htraced/src/web/app/search_results_view.js
@@ -57,7 +57,8 @@ htrace.SearchResultsView = Backbone.View.extend({
this.widgetManager.handle({
type: "mouseDown",
x: this.getCanvasX(e),
- y: this.getCanvasY(e)
+ y: this.getCanvasY(e),
+ raw: e
});
this.draw();
},
@@ -67,7 +68,8 @@ htrace.SearchResultsView = Backbone.View.extend({
this.widgetManager.handle({
type: "mouseUp",
x: this.getCanvasX(e),
- y: this.getCanvasY(e)
+ y: this.getCanvasY(e),
+ raw: e
});
this.draw();
},
@@ -75,7 +77,8 @@ htrace.SearchResultsView = Backbone.View.extend({
handleMouseOut: function(e) {
e.preventDefault();
this.widgetManager.handle({
- type: "mouseOut"
+ type: "mouseOut",
+ raw: e
});
this.draw();
},
@@ -85,7 +88,8 @@ htrace.SearchResultsView = Backbone.View.extend({
this.widgetManager.handle({
type: "mouseMove",
x: this.getCanvasX(e),
- y: this.getCanvasY(e)
+ y: this.getCanvasY(e),
+ raw: e
});
this.draw();
},
@@ -276,68 +280,116 @@ htrace.SearchResultsView = Backbone.View.extend({
return null;
}
if (type === "begin") {
- this.setBegin(d.valueOf());
+ this.setTimes({begin: d.valueOf()});
} else if (type === "end") {
- this.setEnd(d.valueOf());
+ this.setTimes({end: d.valueOf()});
} else {
throw "invalid type for handleBeginOrEndChange: expected begin or end.";
}
+ this.render();
},
- setBegin: function(val) {
- if (this.end < val + this.MINIMUM_TIME_SPAN) {
- this.begin = val;
- this.end = val + this.MINIMUM_TIME_SPAN;
- console.log("SearchResultsView#setBegin(begin=" + this.begin +
- ", end=" + this.end + ")");
- $("#begin").val(htrace.dateToString(this.begin));
- $("#end").val(htrace.dateToString(this.end));
- } else {
- this.begin = val;
- console.log("SearchResultsView#setBegin(begin=" + this.begin + ")");
- $("#begin").val(htrace.dateToString(this.begin));
+ setTimes: function(params) {
+ if (params["begin"]) {
+ this.begin = params["begin"];
}
- this.render();
+ if (params["end"]) {
+ this.end = params["end"];
+ }
+ if (this.end < this.begin) {
+ var b = this.begin;
+ this.begin = this.end;
+ this.end = b;
+ }
+ var delta = this.end - this.begin;
+ if (delta < this.MINIMUM_TIME_SPAN) {
+ var needed = this.MINIMUM_TIME_SPAN - delta;
+ this.begin -= (needed / 2);
+ this.end += (needed / 2);
+ }
+ $("#begin").val(htrace.dateToString(this.begin));
+ $("#end").val(htrace.dateToString(this.end));
+ // caller should invoke render()
},
- setEnd: function(val) {
- if (this.begin + this.MINIMUM_TIME_SPAN > val) {
- this.begin = val;
- this.end = this.begin + this.MINIMUM_TIME_SPAN;
- console.log("SearchResultsView#setEnd(begin=" + this.begin +
- ", end=" + this.end + ")");
- $("#begin").val(htrace.dateToString(this.begin));
- $("#end").val(htrace.dateToString(this.end));
- } else {
- this.end = val;
- console.log("SearchResultsView#setEnd(end=" + this.end + ")");
- $("#end").val(htrace.dateToString(this.end));
+ clearHandler: function() {
+ console.log("invoking clearHandler.");
+ var toDelete = []
+ var noneSelected = true;
+ for (var i = 0; i < this.searchResults.length; i++) {
+ var resultSelected = false;
+ var model = this.searchResults.at(i);
+ htrace.treeTraverseDepthFirstPre(model,
+ htrace.getReifiedChildren, 0,
+ function(node, depth) {
+ if (noneSelected) {
+ if (node.get("selected")) {
+ resultSelected = true;
+ }
+ }
+ });
+ htrace.treeTraverseDepthFirstPre(model,
+ htrace.getReifiedParents, 0,
+ function(node, depth) {
+ if (node.get("selected")) {
+ resultSelected = true;
+ }
+ });
+ if (resultSelected) {
+ if (noneSelected) {
+ toDelete = [];
+ noneSelected = false;
+ }
+ toDelete.push(model);
+ } else if (noneSelected) {
+ toDelete.push(model);
+ }
}
- this.render();
+ console.log("clearHandler: removing " + JSON.stringify(toDelete));
+ this.searchResults.remove(toDelete);
},
- zoomFitAll: function() {
- var numResults = this.searchResults.size();
+ getSelectedSpansOrAllSpans: function() {
+ // Get the list of selected spans.
+ // If there are no spans selected, we return all spans.
+ var ret = [];
+ var noneSelected = true;
+ this.applyToAllSpans(function(span) {
+ if (span.get("selected")) {
+ if (noneSelected) {
+ ret = [];
+ noneSelected = false;
+ }
+ ret.push(span);
+ } else if (noneSelected) {
+ ret.push(span);
+ }
+ });
+ return ret;
+ },
+
+ zoomHandler: function() {
+ var zoomSpans = this.getSelectedSpansOrAllSpans();
+ var numResults = zoomSpans.length;
if (numResults == 0) {
- this.setBegin(0);
- this.setEnd(this.MINIMUM_TIME_SPAN);
+ this.setTimes({begin:0, end:this.MINIMUM_TIME_SPAN});
+ this.render();
return;
}
var minStart = 4503599627370496;
var maxEnd = 0;
for (var i = 0; i < numResults; i++) {
- var span = this.searchResults.at(i);
- var begin = span.getEarliestBegin();
+ var begin = zoomSpans[i].getEarliestBegin();
if (begin < minStart) {
minStart = begin;
}
- var end = span.getLatestEnd();
+ var end = zoomSpans[i].getLatestEnd();
if (end > minStart) {
maxEnd = end;
}
}
- this.setBegin(minStart);
- this.setEnd(maxEnd);
+ this.setTimes({begin: minStart, end: maxEnd});
+ this.render();
},
// Apply a function to all spans
@@ -346,7 +398,6 @@ htrace.SearchResultsView = Backbone.View.extend({
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),
diff --git a/htrace-htraced/src/web/app/search_view.js b/htrace-htraced/src/web/app/search_view.js
index 52f9101..cd8e207 100644
--- a/htrace-htraced/src/web/app/search_view.js
+++ b/htrace-htraced/src/web/app/search_view.js
@@ -36,21 +36,19 @@ htrace.SearchView = Backbone.View.extend({
"click .add-field": "dropdownHandler",
"blur #begin": "blurBeginHandler",
"blur #end": "blurEndHandler",
- "click #zoomButton": "zoomFitAllHandler"
+ "click #zoomButton": "zoomHandler"
},
searchHandler: function(e){
e.preventDefault();
- // Do a new search.
+ // Do a search.
this.doSearch(e.ctrlKey);
},
clearHandler: function(e){
e.preventDefault();
-
- // Clear existing search results.
- this.searchResults.reset();
+ this.resultsView.clearHandler();
},
doSearch: function(showDebug){
@@ -105,7 +103,7 @@ htrace.SearchView = Backbone.View.extend({
if (firstResults) {
// After the initial search, zoom to fit everything.
// On subsequent searches, we leave the viewport alone.
- searchView.resultsView.zoomFitAll();
+ searchView.resultsView.zoomHandler();
}
searchView.searchInProgress = false;
if (showDebug) {
@@ -126,7 +124,7 @@ htrace.SearchView = Backbone.View.extend({
}
},
error: function(model, response, options){
- this.searchResults.clear();
+ searchView.searchResults.clear();
var err = "Error " + JSON.stringify(response) +
" on span query " + query.url();
console.log(err);
@@ -168,9 +166,9 @@ htrace.SearchView = Backbone.View.extend({
return this.resultsView.handleBeginOrEndChange(e, "end");
},
- zoomFitAllHandler: function(e) {
+ zoomHandler: function(e) {
e.preventDefault();
- this.resultsView.zoomFitAll();
+ this.resultsView.zoomHandler();
},
removePredicateView: function(predicateView) {
diff --git a/htrace-htraced/src/web/app/span.js b/htrace-htraced/src/web/app/span.js
index a056b4f..553239e 100644
--- a/htrace-htraced/src/web/app/span.js
+++ b/htrace-htraced/src/web/app/span.js
@@ -76,7 +76,21 @@ htrace.Span = Backbone.Model.extend({
this.set("description", response.d ? response.d : "");
this.set("begin", response.b ? parseInt(response.b, 10) : 0);
this.set("end", response.e ? parseInt(response.e, 10) : 0);
-
+ 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.
@@ -114,6 +128,12 @@ htrace.Span = Backbone.Model.extend({
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;
},
diff --git a/htrace-htraced/src/web/app/span_widget.js b/htrace-htraced/src/web/app/span_widget.js
index 0d18fef..66e65b7 100644
--- a/htrace-htraced/src/web/app/span_widget.js
+++ b/htrace-htraced/src/web/app/span_widget.js
@@ -19,6 +19,94 @@
var htrace = htrace || {};
+htrace.fillSpanDetailsView = 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 = '
';
+ for (i = 0; i < len; i++) {
+ // Make every other row grey to improve visibility.
+ var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
+ h += _.template('' +
+ '| <%- key %> | ' +
+ '<%- val %> | ' +
+ "
")({key: keys[i], val: info[keys[i]]});
+ }
+ h += '
';
+ $("#spanDetails").html(h);
+};
+
+htrace.clearSpanDetailsView = function() {
+ $("#spanDetails").html("");
+};
+
// Widget containing the trace span displayed on the canvas.
htrace.SpanWidget = function(params) {
this.draw = function() {
@@ -113,61 +201,6 @@ htrace.SpanWidget = function(params) {
(this.end - this.begin));
};
- 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)),
- };
- var explicitOrder = {
- spanId: -3,
- begin: -2,
- end: -1
- };
- 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);
- }
- }
- // 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] || 0;
- var ob = explicitOrder[b] || 0;
- 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 = '';
- for (i = 0; i < len; i++) {
- // Make every other row grey to improve visibility.
- var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
- h += _.template('' +
- '| <%- key %> | ' +
- '<%- val %> | ' +
- "
")({key: keys[i], val: info[keys[i]]});
- }
- h += '
';
- $("#spanDetails").html(h);
- };
-
this.handle = function(e) {
switch (e.type) {
case "mouseDown":
@@ -175,13 +208,42 @@ htrace.SpanWidget = function(params) {
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();
+ if (e.raw.ctrlKey) {
+ // If the control key is pressed, we can unselect the current
+ // selection, or create multiple selections.
+ if (this.span.get("selected")) {
+ this.span.set("selected", false);
+ } else {
+ this.span.set("selected", true);
+ }
+ var selection = null;
+ var multipleSelections = false;
+ this.manager.searchResultsView.applyToAllSpans(function(span) {
+ if (span.get("selected")) {
+ if (selection == null) {
+ selection = span;
+ } else {
+ multipleSelections = true;
+ }
+ }
+ });
+ if (multipleSelections) {
+ selection = null;
+ }
+ if (selection == null) {
+ htrace.clearSpanDetailsView();
+ } else {
+ htrace.fillSpanDetailsView(selection);
+ }
+ } else {
+ this.manager.searchResultsView.applyToAllSpans(function(span) {
+ if (span.get("selected")) {
+ span.set("selected", false);
+ }
+ });
+ this.span.set("selected", true);
+ htrace.fillSpanDetailsView(this.span);
+ }
return true;
case "draw":
this.draw();