Skip to content

Commit

Permalink
[SPARK-6939] [STREAMING] [WEBUI] Add timeline and histogram graphs fo…
Browse files Browse the repository at this point in the history
…r streaming statistics

This is the initial work of SPARK-6939. Not yet ready for code review. Here are the screenshots:

![graph1](https://cloud.githubusercontent.com/assets/1000778/7165766/465942e0-e3dc-11e4-9b05-c184b09d75dc.png)

![graph2](https://cloud.githubusercontent.com/assets/1000778/7165779/53f13f34-e3dc-11e4-8714-a4a75b7e09ff.png)

TODOs:
- [x] Display more information on mouse hover
- [x] Align the timeline and distribution graphs
- [x] Clean up the codes

Author: zsxwing <zsxwing@gmail.com>

Closes #5533 from zsxwing/SPARK-6939 and squashes the following commits:

9f7cd19 [zsxwing] Merge branch 'master' into SPARK-6939
deacc3f [zsxwing] Remove unused import
cd03424 [zsxwing] Fix .rat-excludes
70cc87d [zsxwing] Streaming Scheduling Delay => Scheduling Delay
d457277 [zsxwing] Fix UIUtils in BatchPage
b3f303e [zsxwing] Add comments for unclear classes and methods
ff0bff8 [zsxwing] Make InputDStream.name private[streaming]
cc392c5 [zsxwing] Merge branch 'master' into SPARK-6939
e275e23 [zsxwing] Move time related methods to Streaming's UIUtils
d5d86f6 [zsxwing] Fix incorrect lastErrorTime
3be4b7a [zsxwing] Use InputInfo
b50fa32 [zsxwing] Jump to the batch page when clicking a point in the timeline graphs
203605d [zsxwing] Merge branch 'master' into SPARK-6939
74307cf [zsxwing] Reuse the data for histogram graphs to reduce the page size
2586916 [zsxwing] Merge branch 'master' into SPARK-6939
70d8533 [zsxwing] Remove BatchInfo.numRecords and a few renames
7bbdc0a [zsxwing] Hide the receiver sub table if no receiver
a2972e9 [zsxwing] Add some ui tests for StreamingPage
fd03ad0 [zsxwing] Add a test to verify no memory leak
4a8f886 [zsxwing] Merge branch 'master' into SPARK-6939
18607a1 [zsxwing] Merge branch 'master' into SPARK-6939
d0b0aec [zsxwing] Clean up the codes
a459f49 [zsxwing] Add a dash line to processing time graphs
8e4363c [zsxwing] Prepare for the demo
c81a1ee [zsxwing] Change time unit in the graphs automatically
4c0b43f [zsxwing] Update Streaming UI
04c7500 [zsxwing] Make the server and client use the same timezone
fed8219 [zsxwing] Move the x axis at the top and show a better tooltip
c23ce10 [zsxwing] Make two graphs close
d78672a [zsxwing] Make the X axis use the same range
881c907 [zsxwing] Use histogram for distribution
5688702 [zsxwing] Fix the unit test
ddf741a [zsxwing] Fix the unit test
ad93295 [zsxwing] Remove unnecessary codes
a0458f9 [zsxwing] Clean the codes
b82ed1e [zsxwing] Update the graphs as per comments
dd653a1 [zsxwing] Add timeline and histogram graphs for streaming statistics

(cherry picked from commit 489700c)
Signed-off-by: Tathagata Das <tathagata.das1565@gmail.com>
  • Loading branch information
zsxwing authored and tdas committed May 5, 2015
1 parent 94ac9eb commit 8109c9e
Show file tree
Hide file tree
Showing 17 changed files with 1,228 additions and 244 deletions.
30 changes: 30 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,36 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

========================================================================
For d3 (core/src/main/resources/org/apache/spark/ui/static/d3.min.js):
========================================================================

Copyright (c) 2010-2015, Michael Bostock
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

========================================================================
For Scala Interpreter classes (all .scala files in repl/src/main/scala
Expand Down
135 changes: 104 additions & 31 deletions core/src/main/resources/org/apache/spark/ui/static/bootstrap-tooltip.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* ===========================================================
* bootstrap-tooltip.js v2.2.2
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* bootstrap-tooltip.js v2.3.2
* http://getbootstrap.com/2.3.2/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
* Copyright 2013 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,19 +38,27 @@
, init: function (type, element, options) {
var eventIn
, eventOut
, triggers
, trigger
, i

this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true

if (this.options.trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (this.options.trigger != 'manual') {
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
triggers = this.options.trigger.split(' ')

for (i = triggers.length; i--;) {
trigger = triggers[i]
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (trigger != 'manual') {
eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
}

this.options.selector ?
Expand All @@ -59,7 +67,7 @@
}

, getOptions: function (options) {
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)

if (options.delay && typeof options.delay == 'number') {
options.delay = {
Expand All @@ -72,7 +80,15 @@
}

, enter: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
var defaults = $.fn[this.type].defaults
, options = {}
, self

this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value
}, this)

self = $(e.currentTarget)[this.type](options).data(this.type)

if (!self.options.delay || !self.options.delay.show) return self.show()

Expand All @@ -97,14 +113,16 @@

, show: function () {
var $tip
, inside
, pos
, actualWidth
, actualHeight
, placement
, tp
, e = $.Event('show')

if (this.hasContent() && this.enabled) {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip = this.tip()
this.setContent()

Expand All @@ -116,19 +134,18 @@
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement

inside = /in/.test(placement)

$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.insertAfter(this.$element)

pos = this.getPosition(inside)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

pos = this.getPosition()

actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight

switch (inside ? placement.split(' ')[1] : placement) {
switch (placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
Expand All @@ -143,11 +160,56 @@
break
}

$tip
.offset(tp)
.addClass(placement)
.addClass('in')
this.applyPlacement(tp, placement)
this.$element.trigger('shown')
}
}

, applyPlacement: function(offset, placement){
var $tip = this.tip()
, width = $tip[0].offsetWidth
, height = $tip[0].offsetHeight
, actualWidth
, actualHeight
, delta
, replace

$tip
.offset(offset)
.addClass(placement)
.addClass('in')

actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight

if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
replace = true
}

if (placement == 'bottom' || placement == 'top') {
delta = 0

if (offset.left < 0){
delta = offset.left * -2
offset.left = 0
$tip.offset(offset)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
}

this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
} else {
this.replaceArrow(actualHeight - height, actualHeight, 'top')
}

if (replace) $tip.offset(offset)
}

, replaceArrow: function(delta, dimension, position){
this
.arrow()
.css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
}

, setContent: function () {
Expand All @@ -161,6 +223,10 @@
, hide: function () {
var that = this
, $tip = this.tip()
, e = $.Event('hide')

this.$element.trigger(e)
if (e.isDefaultPrevented()) return

$tip.removeClass('in')

Expand All @@ -179,6 +245,8 @@
removeWithAnimation() :
$tip.detach()

this.$element.trigger('hidden')

return this
}

Expand All @@ -193,11 +261,12 @@
return this.getTitle()
}

, getPosition: function (inside) {
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
, getPosition: function () {
var el = this.$element[0]
return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
width: el.offsetWidth
, height: el.offsetHeight
}, this.$element.offset())
}

, getTitle: function () {
Expand All @@ -215,6 +284,10 @@
return this.$tip = this.$tip || $(this.options.template)
}

, arrow: function(){
return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
}

, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
Expand All @@ -236,8 +309,8 @@
}

, toggle: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
self[self.tip().hasClass('in') ? 'hide' : 'show']()
var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
self.tip().hasClass('in') ? self.hide() : self.show()
}

, destroy: function () {
Expand Down Expand Up @@ -269,10 +342,11 @@
, placement: 'top'
, selector: false
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
, trigger: 'hover'
, trigger: 'hover focus'
, title: ''
, delay: 0
, html: false
, container: false
}


Expand All @@ -285,4 +359,3 @@
}

}(window.jQuery);

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.
*/


.graph {
font: 10px sans-serif;
}

.axis path, .axis line {
fill: none;
stroke: gray;
shape-rendering: crispEdges;
}

.axis text {
fill: gray;
}

.tooltip-inner {
max-width: 500px !important; // Make sure we only have one line tooltip
}

.line {
fill: none;
stroke: #0088cc;
stroke-width: 1.5px;
}

.bar rect {
fill: #0088cc;
shape-rendering: crispEdges;
}

.bar rect:hover {
fill: #00c2ff;
}

.timeline {
width: 500px;
}

.histogram {
width: auto;
}
Loading

0 comments on commit 8109c9e

Please sign in to comment.