HTRACE-174. Refactor GUI (cmccabe)
Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/baaa3a52 Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/baaa3a52 Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/baaa3a52 Branch: refs/heads/master Commit: baaa3a5287ac7f096fbbdfc5cbb135050f22aca4 Parents: 4051054 Author: Colin P. Mccabe <[email protected]> Authored: Tue Jun 9 14:57:12 2015 -0700 Committer: Colin P. Mccabe <[email protected]> Committed: Tue Jun 9 14:57:12 2015 -0700 ---------------------------------------------------------------------- LICENSE.txt | 30 +- README.md | 2 +- htrace-htraced/BUILDING.txt | 2 +- .../src/go/src/org/apache/htrace/common/rest.go | 2 +- htrace-htraced/src/web/app/about_view.js | 33 + htrace-htraced/src/web/app/app.js | 20 - htrace-htraced/src/web/app/modal.js | 34 + htrace-htraced/src/web/app/models/span.js | 144 - htrace-htraced/src/web/app/predicate.js | 117 + htrace-htraced/src/web/app/predicate_view.js | 68 + htrace-htraced/src/web/app/query_results.js | 45 + htrace-htraced/src/web/app/router.js | 74 + htrace-htraced/src/web/app/search_results.js | 25 + .../src/web/app/search_results_view.js | 365 + htrace-htraced/src/web/app/search_view.js | 197 + htrace-htraced/src/web/app/server_info.js | 31 + htrace-htraced/src/web/app/setup.js | 192 - htrace-htraced/src/web/app/span.js | 69 + htrace-htraced/src/web/app/span_details_view.js | 39 + htrace-htraced/src/web/app/span_widget.js | 229 + htrace-htraced/src/web/app/string.js | 66 + htrace-htraced/src/web/app/time_cursor.js | 74 + htrace-htraced/src/web/app/triangle_button.js | 103 + .../src/web/app/views/details/details.js | 47 - htrace-htraced/src/web/app/views/graph/graph.js | 262 - .../src/web/app/views/search/field.js | 124 - .../src/web/app/views/search/search.js | 105 - .../src/web/app/views/swimlane/swimlane.js | 178 - htrace-htraced/src/web/app/widget_manager.js | 66 + htrace-htraced/src/web/custom.css | 101 + htrace-htraced/src/web/index.html | 306 +- htrace-htraced/src/web/lib/backbone-1.1.2.js | 1608 +++ .../src/web/lib/css/backgrid-0.3.5.min.css | 1 - .../lib/css/backgrid-paginator-0.3.5.min.css | 1 - htrace-htraced/src/web/lib/css/main.css | 45 - htrace-htraced/src/web/lib/jquery-2.1.4.js | 9210 +++++++++++++++++ htrace-htraced/src/web/lib/js/backbone-1.1.2.js | 1608 --- .../web/lib/js/backbone.marionette-2.4.1.min.js | 23 - .../src/web/lib/js/backbone.paginator-2.0.2.js | 1325 --- htrace-htraced/src/web/lib/js/backgrid-0.3.5.js | 2883 ------ .../src/web/lib/js/backgrid-paginator-0.3.5.js | 433 - htrace-htraced/src/web/lib/js/d3-3.5.5.js | 9504 ------------------ .../src/web/lib/js/jquery-2.1.3.min.js | 4 - .../src/web/lib/js/moment-2.9.0.min.js | 7 - .../src/web/lib/js/underscore-1.7.0.js | 1416 --- htrace-htraced/src/web/lib/moment-2.10.3.js | 3111 ++++++ htrace-htraced/src/web/lib/rome-2.1.0/rome.css | 94 - htrace-htraced/src/web/lib/rome-2.1.0/rome.js | 4796 --------- .../src/web/lib/rome-2.1.0/rome.min.css | 2 - .../src/web/lib/rome-2.1.0/rome.min.js | 3 - .../src/web/lib/rome-2.1.0/rome.standalone.js | 1860 ---- .../web/lib/rome-2.1.0/rome.standalone.min.js | 2 - htrace-htraced/src/web/lib/underscore-1.7.0.js | 1416 +++ pom.xml | 9 +- 54 files changed, 17253 insertions(+), 25258 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/LICENSE.txt ---------------------------------------------------------------------- diff --git a/LICENSE.txt b/LICENSE.txt index 19dd8f7..3404aff 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -244,43 +244,15 @@ backbone, is a javascript library, that is Copyright (c) 2010-2014 Jeremy Ashkenas, DocumentCloud. It is MIT licensed: https://github.com/jashkenas/backbone/blob/master/LICENSE -backbone-paginator, is a javascript library, that is Copyright (c) 2012-2014 -Jimmy Yuen Ho Wong and contributors. It is MIT licensed: -https://github.com/backbone-paginator/backbone.paginator/blob/master/LICENSE-MIT - -backgrid, is a javascript library, that is Copyright (c) 2013 -Jimmy Yuen Ho Wong. It is MIT licensed: -https://github.com/wyuenho/backgrid/blob/master/LICENSE-MIT - -backgrid-paginator, is a javascript library, that is Copyright (c) 2013 -Jimmy Yuen Ho Wong. It is MIT licensed: -https://github.com/wyuenho/backgrid-paginator/blob/master/LICENSE-MIT - moment.js is a front end time conversion project. It is (c) 2011-2014 Tim Wood, Iskren Chernev, Moment.js contributors and shared under the MIT license: https://github.com/moment/moment/blob/develop/LICENSE -rome.js is a customizable date (and time) picker. -It is Copyright © 2014 Nicolas Bevacqua -https://github.com/bevacqua/rome - -Backbone.Wreqr is a message passing system for Backbone.js. -It is (c) 2012 Derick Bailey, Muted Solutions, LLC and MIT licensed: -https://github.com/marionettejs/backbone.wreqr/blob/v1.3.1/LICENSE.md - -Backbone.Babysitter manages child views for Backbone.js. -It is (c) 2013 Derick Bailey, Muted Solutions, LLC and MIT licensed: -https://github.com/marionettejs/backbone.babysitter/blob/v0.1.6/LICENSE.md - -Backbone.Marionette is a composite application library for Backbone.js. -It is MIT licensed: -https://github.com/marionettejs/backbone.marionette/blob/v2.4.1/license.txt - CMP is an implementation of the MessagePack serialization format in C. It is licensed under the MIT license: https://github.com/camgunz/cmp/blob/master/LICENSE go-codec is an implementation of several serialization and deserialization codecs in Go. It is licensed under the MIT license: -https://github.com/ugorji/go/blob/master/LICENSE \ No newline at end of file +https://github.com/ugorji/go/blob/master/LICENSE http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index b3b486e..d469e22 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,6 @@ HTrace ====== -HTrace is a tracing framework for use with distributed systems written in java. +HTrace is a tracing framework for use with distributed systems. See documentation at src/main/site/markdown/index.md or at http://htrace.incubator.apache.org. http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/BUILDING.txt ---------------------------------------------------------------------- diff --git a/htrace-htraced/BUILDING.txt b/htrace-htraced/BUILDING.txt index d54d410..abc0113 100644 --- a/htrace-htraced/BUILDING.txt +++ b/htrace-htraced/BUILDING.txt @@ -7,7 +7,7 @@ The htrace go code consists of 4 main parts: * The "htrace" command-line program which can query the server This is a simple command-line program which can query the htrace server. -* The htraced Javascript Web UI (not yet implemented) +* The htraced Javascript Web UI * The htrace go client library (not yet implemented) This is the equivalent of the Java HTrace client library, but written in Go. http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/go/src/org/apache/htrace/common/rest.go ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/go/src/org/apache/htrace/common/rest.go b/htrace-htraced/src/go/src/org/apache/htrace/common/rest.go index eeb9568..b898ca4 100644 --- a/htrace-htraced/src/go/src/org/apache/htrace/common/rest.go +++ b/htrace-htraced/src/go/src/org/apache/htrace/common/rest.go @@ -19,7 +19,7 @@ package common -// Info returned by /serverInfo +// Info returned by /server/info type ServerInfo struct { // The server release version. ReleaseVersion string http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/about_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/about_view.js b/htrace-htraced/src/web/app/about_view.js new file mode 100644 index 0000000..7dfe868 --- /dev/null +++ b/htrace-htraced/src/web/app/about_view.js @@ -0,0 +1,33 @@ +/* + * 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.AboutView = Backbone.View.extend({ + 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/baaa3a52/htrace-htraced/src/web/app/app.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/app.js b/htrace-htraced/src/web/app/app.js deleted file mode 100644 index 0bc7100..0000000 --- a/htrace-htraced/src/web/app/app.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -window.app = new Marionette.Application(); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/modal.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/modal.js b/htrace-htraced/src/web/app/modal.js new file mode 100644 index 0000000..91d55fe --- /dev/null +++ b/htrace-htraced/src/web/app/modal.js @@ -0,0 +1,34 @@ +/* + * 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 || {}; + +// Show a modal dialog box with a warning message. +htrace.showModalWarning = function(title, body) { + var html = _.template($("#modal-warning-template").html()) + ({ title: title, body: body }); + htrace.showModal(html); +} + +// Show a modal dialog box. +htrace.showModal = function(html) { + var el = $("#modal"); + el.html(html); + el.modal(); +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/models/span.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/models/span.js b/htrace-htraced/src/web/app/models/span.js deleted file mode 100644 index b8dc114..0000000 --- a/htrace-htraced/src/web/app/models/span.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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. - */ - -// Span model -app.Span = Backbone.Model.extend({ - "defaults": { - "spanId": null, - "traceId": null, - "processId": null, - "parents": null, - "description": null, - "beginTime": 0, - "stopTime": 0 - }, - - shorthand: { - "s": "spanId", - "b": "beginTime", - "e": "stopTime", - "d": "description", - "r": "processId", - "p": "parents", - "i": "traceId" - }, - - parse: function(response, options) { - var attrs = {}; - var $this = this; - $.each(response, function(key, value) { - attrs[(key in $this.shorthand) ? $this.shorthand[key] : key] = value; - }); - return attrs; - }, - - duration: function() { - return this.get('stopTime') - this.get('beginTime'); - } -}); - -app.Spans = Backbone.PageableCollection.extend({ - model: app.Span, - mode: "infinite", - url: "/query", - state: { - pageSize: 10, - lastSpanId: null, - finished: false, - predicates: [] - }, - queryParams: { - totalPages: null, - totalRecords: null, - firstPage: null, - lastPage: null, - currentPage: null, - pageSize: null, - sortKey: null, - order: null, - directions: null, - - /** - * Query parameter for htraced. - */ - query: function() { - var predicates = this.state.predicates.slice(0); - var lastSpanId = this.state.lastSpanId; - - /** - * Use last pulled span ID to paginate. - * The htraced API works such that order is defined by the first predicate. - * Adding a predicate to the end of the predicates list won't change the order. - * Providing the predicate on spanid will filter all previous spanids. - */ - if (lastSpanId) { - predicates.push({ - "op": "gt", - "field": "spanid", - "val": lastSpanId - }); - } - - return JSON.stringify({ - lim: this.state.pageSize + 1, - pred: predicates - }); - } - }, - - initialize: function() { - this.on("reset", function(collection, response, options) { - if (response.length == 0) { - delete this.links[this.state.currentPage]; - this.getPreviousPage(); - } - }, this); - }, - - parseLinks: function(resp, xhr) { - this.state.finished = resp.length <= this.state.pageSize; - - if (this.state.finished) { - this.state.lastSpanId = null; - } else { - this.state.lastSpanId = resp[this.state.pageSize - 1].s; - } - - if (this.state.finished) { - return {}; - } - - return { - "next": "/query?query=" + this.queryParams.query.call(this) - }; - }, - - parseRecords: function(resp) { - return resp.slice(0, 10); - }, - - setPredicates: function(predicates) { - if (!$.isArray(predicates)) { - console.error("predicates should be an array"); - return; - } - - this.state.predicates = predicates; - } -}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/predicate.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/predicate.js b/htrace-htraced/src/web/app/predicate.js new file mode 100644 index 0000000..87a5602 --- /dev/null +++ b/htrace-htraced/src/web/app/predicate.js @@ -0,0 +1,117 @@ +/* + * 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.checkStringIsPositiveWholeNumber = function(val) { + if (!val.match(/^[0-9]([0-9]*)$/)) { + if (!val.match(/[^\s]/)) { + throw "You entered an empty string into a numeric field."; + } + throw "Non-numeric characters found."; + } +}; + +htrace.checkStringIsNotEmpty = function(val) { + if (!val.match(/[^\s]/)) { + throw "You entered an empty string into a text field."; + } +}; + +// Predicate type +htrace.PType = Backbone.Model.extend({ + initialize: function(options) { + this.name = options.name; + this.field = options.field; + this.op = options.op; + }, + + // Try to normalize a value of this type into something htraced can accept. + // Returns a string containing the normalized value on success. Throws a + // string explaining the parse error otherwise. + // Dates are represented by milliseconds since the epoch; span ids don't start + // with 0x. + normalize: function(val) { + switch (this.field) { + case "begin": + return htrace.parseDate(val).valueOf().toString(); + case "end": + return htrace.parseDate(val).valueOf().toString(); + case "description": + htrace.checkStringIsNotEmpty(val); + return val; + case "duration": + htrace.checkStringIsPositiveWholeNumber(val); + return val; + case "spanid": + return htrace.normalizeSpanId(val); + default: + return "Normalization not implemented for field '" + this.field + "'"; + } + }, + + getDefaultValue: function() { + switch (this.field) { + case "begin": + return htrace.dateToString(moment()); + case "end": + return htrace.dateToString(moment()); + case "description": + return ""; + case "duration": + return "0"; + case "spanid": + return ""; + default: + return "(unknown)"; + } + } +}); + +htrace.parsePType = function(name) { + switch (name) { + case "Began after": + return new htrace.PType({name: name, field:"begin", op:"gt"}); + case "Began at or before": + return new htrace.PType({name: name, field:"begin", op:"le"}); + case "Ended after": + return new htrace.PType({name: name, field:"end", op:"gt"}); + case "Ended at or before": + return new htrace.PType({name: name, field:"end", op:"le"}); + case "Description contains": + return new htrace.PType({name: name, field:"description", op:"cn"}); + case "Description is exactly": + return new htrace.PType({name: name, field:"description", op:"eq"}); + case "Duration is longer than": + return new htrace.PType({name: name, field:"duration", op:"gt"}); + case "Duration is at most": + return new htrace.PType({name: name, field:"duration", op:"le"}); + case "Span ID is": + return new htrace.PType({name: name, field:"spanid", op:"eq"}); + default: + return null + } +}; + +htrace.Predicate = function(options) { + this.op = options.ptype.op; + this.field = options.ptype.field; + this.val = options.val; + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/predicate_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/predicate_view.js b/htrace-htraced/src/web/app/predicate_view.js new file mode 100644 index 0000000..aefe896 --- /dev/null +++ b/htrace-htraced/src/web/app/predicate_view.js @@ -0,0 +1,68 @@ +/* + * 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.PredicateView = Backbone.View.extend({ + initialize: function(options) { + this.el = options.el; + this.index = options.index; + this.ptype = options.ptype; + this.searchView = options.searchView; + }, + + events: { + "click .closeButton": "remove", + }, + + render: function() { + this.$el.html(_.template($("#predicate-template").html()) + ({ desc: this.ptype.name, id: this.index })) + if (this.getText() === "") { + $(this.$el).find(".form-control").val(this.ptype.getDefaultValue()); + } + console.log(this.toString() + "#render"); + return this; + }, + + // Handle the user removing this predicate. + remove: function() { + this.searchView.removePredicateView(this); + Backbone.View.prototype.remove.apply(this, arguments); + }, + + // Get the text which the user has entered in. + getText: function() { + return $(this.$el).find(".form-control").val().trim(); + }, + + // Get the predicate expressed by this view. + // Throw an exception if the predicate can't be parsed. + getPredicate: function() { + return new htrace.Predicate({ + ptype: this.ptype, + val: this.ptype.normalize(this.getText()) + }); + }, + + toString: function() { + return "PredicateView(this.el=" + this.el + ", this.index=" + + this.index + ", this.ptype='" + this.ptype.name + "')"; + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/query_results.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/query_results.js b/htrace-htraced/src/web/app/query_results.js new file mode 100644 index 0000000..6fdde9f --- /dev/null +++ b/htrace-htraced/src/web/app/query_results.js @@ -0,0 +1,45 @@ +/* + * 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.QueryResults = Backbone.Collection.extend({ + // The query results are spans. + model: htrace.Span, + + initialize: function(options) { + this.queryJson = options.queryJson; + }, + + url: function() { + return "query?query=" + this.queryString(); + }, + + parse: function(response, xhr) { + return response; + }, + + prettyQueryString: function() { + return JSON.stringify(this.queryJson, null, 2); + }, + + queryString: function() { + return JSON.stringify(this.queryJson); + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/router.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/router.js b/htrace-htraced/src/web/app/router.js new file mode 100644 index 0000000..607da44 --- /dev/null +++ b/htrace-htraced/src/web/app/router.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 || {}; + +htrace.HTraceRouter = Backbone.Router.extend({ + "routes": { + "": "empty", + "about": "about", + "search": "search", + "*unknown": "unknown" + }, + + empty: function() { + console.log("Redirecting to #about."); + Backbone.history.navigate("about", {"trigger": true, "replace": true}); + }, + + about: function() { + console.log("Visiting #about."); + serverInfo = new htrace.ServerInfo(); + var router = this; + serverInfo.fetch({ + "success": function(model, response, options) { + router.switchView(new htrace.AboutView({model: serverInfo, el: "#app"})); + router.activateNavBarEntry("about") + }, + "error": function(model, response, options) { + window.alert("Failed to fetch htraced server info via GET " + + "/server/info: " + JSON.stringify(response)); + } + }); + }, + + search: function() { + console.log("Visiting #search."); + this.switchView(new htrace.SearchView({el : "#app"})); + htrace.router.activateNavBarEntry("search"); + }, + + unknown: function() { + console.log("Unknown route " + Backbone.history.getFragment() + ".") + }, + + "switchView": function(view) { + this.view && this.view.close(); + this.view = view; + this.view.render(); + }, + + "activateNavBarEntry": function(id) { + $(".nav").find(".active").removeClass("active"); + $(".nav").find("#" + id).addClass("active"); + } +}); + +htrace.router = new htrace.HTraceRouter(); +Backbone.history.start(); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/search_results.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_results.js b/htrace-htraced/src/web/app/search_results.js new file mode 100644 index 0000000..d214918 --- /dev/null +++ b/htrace-htraced/src/web/app/search_results.js @@ -0,0 +1,25 @@ +/* + * 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.SearchResults = Backbone.Collection.extend({ + // The search results are spans. + model: htrace.Span +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/search_results_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_results_view.js b/htrace-htraced/src/web/app/search_results_view.js new file mode 100644 index 0000000..b3473c4 --- /dev/null +++ b/htrace-htraced/src/web/app/search_results_view.js @@ -0,0 +1,365 @@ +/* + * 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.SearchResultsView = Backbone.View.extend({ + // The minimum time span we will allow between begin and end. + MINIMUM_TIME_SPAN: 100, + + begin: 0, + + end: this.MINIMUM_TIME_SPAN, + + focused: false, + + initialize: function(options) { + this.model = options.searchResults; + this.el = options.el; + this.listenTo(this.model, 'add remove change reset', this.render); + + // Re-render the canvas when the window size changes. + // Add a debouncer delay to avoid spamming render requests. + var view = this; + $(window).on("resize", _.debounce(function() { + view.render(); + }, 250)); + }, + + // Get the canvas X coordinate of a mouse click from the absolute event + // coordinate. + getCanvasX: function(e) { + return e.pageX - $("#resultsCanvas").offset().left; + }, + + // Get the canvas Y coordinate of a mouse click from the absolute event + // coordinate. + getCanvasY: function(e) { + return e.pageY - $("#resultsCanvas").offset().top; + }, + + 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; + } + }, + + handleMouseUp: function(e) { + e.preventDefault(); + var x = this.getCanvasX(e); + var y = this.getCanvasY(e); + this.widgetManager.handleMouseUp(x, y); + this.focused = false; + 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(); + } + }, + + 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(); + } + }, + + render: function() { + console.log("SearchResultsView#render."); + $(this.el).html(_.template($("#search-results-view-template").html())); + $('#selectedTime').attr('readonly', 'readonly'); + this.canvas = $("#resultsCanvas"); + this.ctx = this.canvas.get(0).getContext("2d"); + this.scaleCanvas(); + this.setupCoordinates(); + this.setupTimeCursor(); + this.setupWidgets(); + this.draw(); + this.attachEvents(); + return this; + }, + + /* + * Compute the ratio to use between the size of the canvas (i.e. + * canvas.ctx.width, canvas.ctx.height) and the size in "HTML5 pixels." Note + * that 'HTML5 pixels" don't actually correspond to screen pixels. A line 1 + * "HTML5 pixel" wide actually takes up multiple scren pixels, etc. + * + * TODO: fix this to be sharper + */ + computeScaleFactor: function() { + var backingStoreRatio = this.ctx.backingStorePixelRatio || + this.ctx.mozBackingStorePixelRatio || + this.ctx.msBackingStorePixelRatio || + this.ctx.webkitBackingStorePixelRatio || + this.ctx.oBackingStorePixelRatio || + this.ctx.backingStorePixelRatio || 1; + return (window.devicePixelRatio || 1) / backingStoreRatio; + }, + + // Sets up the canvas size and scaling. + scaleCanvas: function() { + var cssX = this.canvas.parent().innerWidth(); + var cssY = $(window).innerHeight() - $("#header").innerHeight() - 50; + var ratio = this.computeScaleFactor(); + console.log("scaleCanvas: cssX=" + cssX + ", cssY=" + cssY + ", ratio=" + ratio); + this.maxX = cssX; + this.maxY = cssY; + $('#searchView').css('height', cssY + "px"); + $('#results').css('width', cssX + "px"); + $('#results').css('height', cssY + "px"); + $('#resultsView').css('width', cssX + "px"); + $('#resultsView').css('height', cssY + "px"); + $('#resultsDiv').css('width', cssX + "px"); + $('#resultsDiv').css('height', cssY + "px"); + $('#resultsCanvas').css('width', cssX + "px"); + $('#resultsCanvas').css('height', cssY + "px"); + this.ctx.canvas.width = cssX * ratio; + this.ctx.canvas.height = cssY * ratio; + this.ctx.scale(ratio, ratio); + }, + + // + // Set up the screen coordinates. + // + // 0 buttonX descX scrollX 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)); + 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 + }); + }, + + setupWidgets: function() { + var widgets = []; + var spanWidgetHeight = Math.min(25, Math.floor(this.maxY / 32)); + + // 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({ + ctx: this.ctx, + span: this.model.at(i), + x0: 0, + xB: this.buttonX, + xD: this.descX, + xF: this.scrollX, + y0: i * spanWidgetHeight, + yF: (i * spanWidgetHeight) + (spanWidgetHeight - 1), + begin: this.begin, + end: this.end + }); + widgets.push(spanWidget); + } + + // Create a new root-leve WidgetManager + this.widgetManager = new htrace.WidgetManager({ + widgets: widgets + }); + }, + + draw: function() { + if (this.checkCanvasTooSmall()) { + return; + } + + // Set the background to white. + this.ctx.save(); + this.ctx.fillStyle="#ffffff"; + this.ctx.strokeStyle="#000000"; + this.ctx.fillRect(0, 0, this.maxX, this.maxY); + this.ctx.restore(); + + // Draw all the widgets. + this.widgetManager.draw(); + this.timeCursor.draw(); + }, + + checkCanvasTooSmall: function() { + if ((this.maxX < 200) || (this.maxY < 200)) { + this.ctx.fillStyle="#cccccc"; + this.ctx.strokeStyle="#000000"; + this.ctx.fillRect(0, 0, this.maxX, this.maxY); + this.ctx.font = "24px serif"; + this.ctx.fillStyle="#000000"; + this.ctx.fillText("Canvas too small!", 0, 24); + return true; + } + return false; + }, + + attachEvents: function() { + // Use jquery to capture mouse events on the canvas. + // For some reason using backbone doesn't work for getting these events. + var view = this; + $("#resultsCanvas").off("mousedown"); + $("#resultsCanvas").on("mousedown", function(e) { + view.handleMouseDown(e); + }); + $("#resultsCanvas").off("mouseup"); + $("#resultsCanvas").on("mouseup", function(e) { + view.handleMouseUp(e); + }); + $("#resultsCanvas").off("mouseout"); + $("#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); + }); + }, + + remove: function() { + $(window).off("resize"); + $("#resultsCanvas").off("mousedown"); + $("#resultsCanvas").off("mouseup"); + $("#resultsCanvas").off("mousemove"); + Backbone.View.prototype.remove.apply(this, arguments); + }, + + handleBeginOrEndChange: function(e, type) { + e.preventDefault(); + var text = $(e.target).val().trim(); + var d = null; + try { + d = htrace.parseDate(text); + } catch(err) { + $("#begin").val(htrace.dateToString(this.begin)); + $("#end").val(htrace.dateToString(this.end)); + htrace.showModalWarning("Timeline " + type + " Format Error", + "Please enter a valid time in the timeline " + type + " field.<p/>" + + err); + return null; + } + if (type === "begin") { + this.setBegin(d.valueOf()); + } else if (type === "end") { + this.setEnd(d.valueOf()); + } else { + throw "invalid type for handleBeginOrEndChange: expected begin or end."; + } + }, + + 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)); + } + this.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)); + } + this.render(); + }, + + zoomFitAll: function() { + var numSpans = this.model.size(); + if (numSpans == 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'); + } + if (span.get('end') > maxEnd) { + maxEnd = span.get('end'); + } + } + this.setBegin(minStart); + this.setEnd(maxEnd); + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/search_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/search_view.js b/htrace-htraced/src/web/app/search_view.js new file mode 100644 index 0000000..52f9101 --- /dev/null +++ b/htrace-htraced/src/web/app/search_view.js @@ -0,0 +1,197 @@ +/* + * 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: "#results" + }); + }, + + events: { + "click #searchButton": "searchHandler", + "click #clearButton": "clearHandler", + "click .add-field": "dropdownHandler", + "blur #begin": "blurBeginHandler", + "blur #end": "blurEndHandler", + "click #zoomButton": "zoomFitAllHandler" + }, + + searchHandler: function(e){ + e.preventDefault(); + + // Do a new search. + this.doSearch(e.ctrlKey); + }, + + clearHandler: function(e){ + e.preventDefault(); + + // Clear existing search results. + this.searchResults.reset(); + }, + + 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.zoomFitAll(); + } + 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/>"); + } + } + }, + error: function(model, response, options){ + this.searchResults.clear(); + var err = "Error " + JSON.stringify(response) + + " on span query " + query.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"); + }, + + zoomFitAllHandler: function(e) { + e.preventDefault(); + this.resultsView.zoomFitAll(); + }, + + 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/baaa3a52/htrace-htraced/src/web/app/server_info.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/server_info.js b/htrace-htraced/src/web/app/server_info.js new file mode 100644 index 0000000..b03f706 --- /dev/null +++ b/htrace-htraced/src/web/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/baaa3a52/htrace-htraced/src/web/app/setup.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/setup.js b/htrace-htraced/src/web/app/setup.js deleted file mode 100644 index beb06db..0000000 --- a/htrace-htraced/src/web/app/setup.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 BaseView = Backbone.Marionette.LayoutView.extend({ - "el": "body", - "regions": { - "header": "#header", - "app": "#app" - } -}); - -var Router = Backbone.Marionette.AppRouter.extend({ - "routes": { - "": "init", - "!/search(?:query)": "search", - "!/spans/:id": "span", - "!/swimlane/:id": "swimlane", - "!/swimlane/:id:?:lim": "swimlane" - }, - - "initialize": function() { - // Collection - this.spansCollection = new app.Spans(); - }, - - "init": function() { - Backbone.history.navigate("!/search", {"trigger": true}); - }, - - "search": function(query) { - app.root.app.show(new app.SearchView()); - - var predicates; - - this.spansCollection.switchMode("infinite", { - fetch: false, - resetState: true - }); - - if (query) { - predicates = _(query.split(";")) - .map(function(predicate) { - return _(predicate.split('&')) - .reduce(function(mem, op) { - var op = op.split('='); - mem[op[0]] = op[1]; - return mem; - }, {}); - }); - this.spansCollection.fullCollection.reset(); - this.spansCollection.setPredicates(predicates); - } - else { - this.spansCollection.fullCollection.reset(); - this.spansCollection.setPredicates([{"op":"cn","field":"description","val":""}]); - } - this.spansCollection.fetch(); - - app.root.app.currentView.controls.show( - new app.SearchControlsView({ - "collection": this.spansCollection, - "predicates": predicates - })); - app.root.app.currentView.main.show( - new Backgrid.Grid({ - "collection": this.spansCollection, - "columns": [{ - "label": "Begin", - "cell": Backgrid.Cell.extend({ - className: "begin-cell", - formatter: { - fromRaw: function(rawData, model) { - var beginMs = model.get("beginTime") - return moment(beginMs).format('YYYY/MM/DD HH:mm:ss,SSS'); - }, - toRaw: function(formattedData, model) { - return formattedData // data entry not supported for this cell - } - } - }), - "editable": false, - "sortable": false - }, { - "name": "spanId", - "label": "ID", - "cell": "string", - "editable": false, - "sortable": false - }, { - "name": "processId", - "label": "processId", - "cell": "string", - "editable": false, - "sortable": false - }, { - "label": "Duration", - "cell": Backgrid.Cell.extend({ - className: "duration-cell", - formatter: { - fromRaw: function(rawData, model) { - return model.duration() + " ms" - }, - toRaw: function(formattedData, model) { - return formattedData // data entry not supported for this cell - } - } - }), - "editable": false, - "sortable": false - }, { - "name": "description", - "label": "Description", - "cell": "string", - "editable": false, - "sortable": false - }], - "row": Backgrid.Row.extend({ - "events": { - "click": "details" - }, - "details": function() { - Backbone.history.navigate("!/spans/" + this.model.get("spanId"), {"trigger": true}); - } - }) - })); - app.root.app.currentView.pagination.show( - new Backgrid.Extension.Paginator({ - collection: this.spansCollection, - })); - }, - - "span": function(id) { - var span = this.spansCollection.findWhere({ - "spanId": id - }); - - if (!span) { - Backbone.history.navigate("!/search", {"trigger": true}); - return; - } - - var graphView = new app.GraphView({ - "collection": this.spansCollection, - "id": "span-graph" - }); - - graphView.on("update:span", function(d) { - app.root.app.currentView.span.show( - new app.SpanDetailsView({ - "model": d.span - })); - }); - - app.root.app.show(new app.DetailsView()); - app.root.app.currentView.content.show(graphView); - app.root.app.currentView.content.currentView.setSpanId(id); - }, - - "swimlane": function(id, lim) { - var top = new app.SwimlaneView(); - app.root.app.show(top); - top.swimlane.show(new app.SwimlaneGraphView({ - "spanId": id, - "lim": lim - })); - } -}); - -app.on("start", function(options) { - app.root = new BaseView(); - app.routes = new Router(); - - Backbone.history.start(); -}); - -app.start(); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/span.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span.js b/htrace-htraced/src/web/app/span.js new file mode 100644 index 0000000..2c06fa0 --- /dev/null +++ b/htrace-htraced/src/web/app/span.js @@ -0,0 +1,69 @@ +/* + * 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 = "0000000000000000"; + +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.s ? response.s : htrace.INVALID_SPAN_ID); + this.set("traceId", response.i ? response.i : htrace.INVALID_SPAN_ID); + this.set("processId", 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); + 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.s = this.get("spanId"); + } + if (!(this.get("traceId") === htrace.INVALID_SPAN_ID)) { + obj.i = this.get("traceId"); + } + if (!(this.get("processId") === "")) { + obj.r = this.get("processId"); + } + 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"); + } + return obj; + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/span_details_view.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span_details_view.js b/htrace-htraced/src/web/app/span_details_view.js new file mode 100644 index 0000000..9a37055 --- /dev/null +++ b/htrace-htraced/src/web/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/baaa3a52/htrace-htraced/src/web/app/span_widget.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/span_widget.js b/htrace-htraced/src/web/app/span_widget.js new file mode 100644 index 0000000..f9333d6 --- /dev/null +++ b/htrace-htraced/src/web/app/span_widget.js @@ -0,0 +1,229 @@ +/* + * 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 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) { + 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.drawProcessId = 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('processId'), 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 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.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.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)) + }; + var explicitOrder = { + spanId: -3, + begin: -2, + end: -1 + }; + keys = []; + for(k in this.span.attributes) { + 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 = '<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('<tr bgcolor="' + colorString + '">' + + '<td style="width:30%;word-wrap:break-word"><%- key %></td>' + + '<td style="width:70%;word-wrap:break-word"><%- val %></td>' + + "</tr>")({key: keys[i], val: info[keys[i]]}); + } + h += '</table>'; + $("#spanDetails").html(h); + }; + + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/string.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/string.js b/htrace-htraced/src/web/app/string.js new file mode 100644 index 0000000..b0dfb74 --- /dev/null +++ b/htrace-htraced/src/web/app/string.js @@ -0,0 +1,66 @@ +/* + * 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 +// (no leading 0x). +htrace.normalizeSpanId = function(str) { + // Strip off the 0x prefix, if there is one. + if (str.indexOf("0x") == 0) { + str = str.substring(2); + } + if (str.length != 16) { + throw "The length of '" + str + "' was " + str.length + + ", but span IDs must be 16 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/baaa3a52/htrace-htraced/src/web/app/time_cursor.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/time_cursor.js b/htrace-htraced/src/web/app/time_cursor.js new file mode 100644 index 0000000..0060abb --- /dev/null +++ b/htrace-htraced/src/web/app/time_cursor.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 || {}; + +// 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; + } + 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.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; + } + return false; + }; + + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/triangle_button.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/triangle_button.js b/htrace-htraced/src/web/app/triangle_button.js new file mode 100644 index 0000000..89f9514 --- /dev/null +++ b/htrace-htraced/src/web/app/triangle_button.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 || {}; + +// 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.inBoundingBox = function(x, y) { + return ((x >= this.x0) && (x <= this.xF) && (y >= this.y0) && (y <= this.yF)); + } + + this.handleMouseDown = function(x, y) { +// console.log("TriangleButton#handleMouseDown(x=" + x + ", y=" + y + +// ", x0=" + this.x0 + ", y0="+ this.y0 + +// ", xF=" + this.xF + ", yF=" + this.yF); + if (this.inBoundingBox(x,y)) { + this.selected = true; + return true; + } + return false; + } + + this.handleMouseUp = function(x, y) { + if (this.selected) { + console.log("executing callback"); + } + this.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]; + } + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/views/details/details.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/views/details/details.js b/htrace-htraced/src/web/app/views/details/details.js deleted file mode 100644 index 2f79e1b..0000000 --- a/htrace-htraced/src/web/app/views/details/details.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -app.DetailsView = Backbone.Marionette.LayoutView.extend({ - "template": "#details-layout-template", - "regions": { - "span": "div[role='complementary']", - "content": "div[role='main']" - } -}); - -app.SpanDetailsView = Backbone.Marionette.ItemView.extend({ - "className": "span", - "template": "#span-details-template", - - "serializeData": function() { - var context = { - "span": this.model.toJSON() - }; - context["span"]["duration"] = this.model.duration(); - return context; - }, - - "events": { - "click": "swimlane" - }, - "swimlane": function() { - Backbone.history.navigate("!/swimlane/" + this.model.get("spanId"), - {"trigger": true}); - } -}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/baaa3a52/htrace-htraced/src/web/app/views/graph/graph.js ---------------------------------------------------------------------- diff --git a/htrace-htraced/src/web/app/views/graph/graph.js b/htrace-htraced/src/web/app/views/graph/graph.js deleted file mode 100644 index 7b4f89e..0000000 --- a/htrace-htraced/src/web/app/views/graph/graph.js +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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. - */ - -app.GraphView = Backbone.View.extend({ - initialize: function(options) { - options = options || {}; - - if (!options.id) { - console.error("GraphView requires argument 'id' to uniquely identify this graph."); - return; - } - - _.bindAll(this, "render"); - this.collection.bind('change', this.render); - - var links = this.links = []; - var linkTable = this.linkTable = {}; - var nodes = this.nodes = []; - var nodeTable = this.nodeTable = {}; - var force = this.force - = d3.layout.force().size([$(window).width(), $(window).height() * 3/4]) - .linkDistance($(window).height() / 5) - .charge(-120) - .gravity(0) - ; - force.nodes(nodes) - .links(links); - - force.on("tick", function(e) { - var root = d3.select("#" + options.id); - - if (!root.node()) { - return; - } - - var selectedDatum = root.select(".selected").datum(); - - // center selected node - root.select("svg").attr("width", $(root.node()).width()); - selectedDatum.x = root.select("svg").attr("width") / 2; - selectedDatum.y = root.select("svg").attr("height") / 2; - - // Push sources up and targets down to form a weak tree. - var k = 10 * e.alpha; - force.links().forEach(function(d, i) { - d.source.y -= k; - d.target.y += k; - }); - - var nodes = root.selectAll(".node").data(force.nodes()); - nodes.select("circle") - .attr("cx", function(d) { return d.x; }) - .attr("cy", function(d) { return d.y; }); - nodes.select("text") - .attr("x", function(d) { return d.x - this.getComputedTextLength() / 2; }) - .attr("y", function(d) { return d.y; }); - root.selectAll(".link").data(force.links()) - .attr("d", function(d) { - var start = {}, - end = {}, - angle = Math.atan2((d.target.x - d.source.x), (d.target.y - d.source.y)); - start.x = d.source.x + d.source.r * Math.sin(angle); - end.x = d.target.x - d.source.r * Math.sin(angle); - start.y = d.source.y + d.source.r * Math.cos(angle); - end.y = d.target.y - d.source.r * Math.cos(angle); - return "M" + start.x + " " + start.y - + " L" + end.x + " " + end.y; - }); - }); - }, - - updateLinksAndNodes: function() { - if (!this.spanId) { - return; - } - - var $this = this, collection = this.collection; - - var selectedSpan = this.collection.findWhere({ - "spanId": this.spanId - }); - - var findChildren = function(span) { - var spanId = span.get("spanId"); - var spans = collection.filter(function(model) { - return _(model.get("parents")).contains(spanId); - }); - return _(spans).reject(function(span) { - return span == null; - }); - }; - var findParents = function(span) { - var spans = _(span.get("parents")).map(function(parentSpanId) { - return collection.findWhere({ - "spanId": parentSpanId - }); - }); - return _(spans).reject(function(span) { - return span == null; - }); - }; - var spanToNode = function(span, level) { - var table = $this.nodeTable; - if (!(span.get("spanId") in table)) { - table[span.get("spanId")] = { - "name": span.get("spanId"), - "span": span, - "level": level, - "group": 0, - "x": parseInt($this.svg.attr('width')) / 2, - "y": 250 + level * 50 - }; - $this.nodes.push(table[span.get("spanId")]); - } - - return table[span.get("spanId")]; - }; - var createLink = function(source, target) { - var table = $this.linkTable; - var name = source.span.get("spanId") + "-" + target.span.get("spanId"); - if (!(name in table)) { - table[name] = { - "source": source, - "target": target - }; - $this.links.push(table[name]); - } - - return table[name]; - }; - - var parents = [], children = []; - var selectedSpanNode = spanToNode(selectedSpan, 1); - - Array.prototype.push.apply(parents, findParents(selectedSpan)); - _(parents).each(function(span) { - Array.prototype.push.apply(parents, findParents(span)); - createLink(spanToNode(span, 0), selectedSpanNode) - }); - - Array.prototype.push.apply(children, findChildren(selectedSpan)); - _(children).each(function(span) { - Array.prototype.push.apply(children, findChildren(span)); - createLink(selectedSpanNode, spanToNode(span, 2)) - }); - }, - - renderLinks: function(selection) { - var path = selection.enter().append("path") - .classed("link", true) - .style("marker-end", "url(#suit)"); - selection.exit().remove(); - return selection; - }, - - renderNodes: function(selection) { - var $this = this; - var g = selection.enter().append("g").attr("class", "node"); - var circle = g.append("circle") - .attr("r", function(d) { - if (!d.radius) { - d.r = Math.log(d.span.duration()); - - if (d.r > app.GraphView.MAX_NODE_SIZE) { - d.r = app.GraphView.MAX_NODE_SIZE; - } - - if (d.r < app.GraphView.MIN_NODE_SIZE) { - d.r = app.GraphView.MIN_NODE_SIZE; - } - } - - return d.r; - }); - var text = g.append("text").text(function(d) { - return d.span.get("description"); - }); - - selection.exit().remove(); - - circle.on("click", function(d) { - $this.setSpanId(d.name); - }); - - selection.classed("selected", null); - selection.filter(function(d) { - return d.span.get("spanId") == $this.spanId; - }).classed("selected", true); - - return selection; - }, - - setSpanId: function(spanId) { - var $this = this; - this.spanId = spanId; - - this.updateLinksAndNodes(); - - this.renderNodes( - this.svg.selectAll(".node") - .data(this.force.nodes(), function(d) { - return d.name; - })); - - this.renderLinks( - this.svg.selectAll(".link") - .data(this.force.links(), function(d) { - return d.source.name + "-" + d.target.name; - })); - - this.force.start(); - - Backbone.history.navigate("!/spans/" + spanId); - this.trigger("update:span", {"span": this.collection.findWhere({ - "spanId": spanId - })}); - }, - - render: function() { - this.svg = d3.select(this.$el[0]).append("svg"); - this.svg.attr("height", 500) - .attr("width", $(window).width()) - .attr("id", this.id); - - // Arrows - this.svg.append("defs").selectAll("marker") - .data(["suit", "licensing", "resolved"]) - .enter().append("marker") - .attr("id", function(d) { return d; }) - .attr("viewBox", "0 -5 10 10") - .attr("refX", 25) - .attr("refY", 0) - .attr("markerWidth", 6) - .attr("markerHeight", 6) - .attr("orient", "auto") - .append("path") - .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") - .style("stroke", "#4679BD") - .style("opacity", "0.6"); - - return this; - } -}); - -app.GraphView.MAX_NODE_SIZE = 150; -app.GraphView.MIN_NODE_SIZE = 50;
