blob: ca867ec6349dcc55f74532af89524262434d9ee0 [file] [log] [blame]
/*
* 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;
},
reifyParentsRecursive: function(depth) {
var rootDeferred = jQuery.Deferred();
var span = this;
span.reifyParents().done(function(err) {
if ((err != "") || (depth <= 0)) {
rootDeferred.resolve(err);
return;
}
var recursivePromises = [];
var reifiedParents = span.get("reifiedParents");
for (var j = 0; j < reifiedParents.length; j++) {
recursivePromises.push(reifiedParents[j].reifyParentsRecursive(depth - 1));
}
$.when.apply($, recursivePromises).then(function() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] != "") {
rootDeferred.resolve(arguments[i]);
return;
}
}
rootDeferred.resolve("");
});
});
return rootDeferred.promise();
},
//
// 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();
},
reifyChildrenRecursive: function(depth) {
var rootDeferred = jQuery.Deferred();
var span = this;
span.reifyChildren().done(function(err) {
if ((err != "") || (depth <= 0)) {
rootDeferred.resolve(err);
return;
}
var recursivePromises = [];
var reifiedChildren = span.get("reifiedChildren");
for (var j = 0; j < reifiedChildren.length; j++) {
recursivePromises.push(reifiedChildren[j].reifyChildrenRecursive(depth - 1));
}
$.when.apply($, recursivePromises).then(function() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] != "") {
rootDeferred.resolve(arguments[i]);
return;
}
}
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;
},
});