[LNT] r264524 - [profile] Redesign the top level counter UI
James Molloy via llvm-commits
llvm-commits at lists.llvm.org
Sat Mar 26 16:01:20 PDT 2016
Author: jamesm
Date: Sat Mar 26 18:01:20 2016
New Revision: 264524
URL: http://llvm.org/viewvc/llvm-project?rev=264524&view=rev
Log:
[profile] Redesign the top level counter UI
The previous "statsbar" was difficult to read and understand. The new one is a horizontal view which makes it\
easier to see exactly what counter values belong to which runs. It also includes a chart because who doesn't\
like charts?
As part of this, implement a "toolbar" that holds:
- the current counter dropdown,
- whether to display relative values (percentages) or absolute values
- next/prev buttons to cycle through the hottest instructions
Modified:
lnt/trunk/lnt/server/ui/profile_views.py
lnt/trunk/lnt/server/ui/static/lnt_profile.css
lnt/trunk/lnt/server/ui/static/lnt_profile.js
lnt/trunk/lnt/server/ui/templates/v4_profile.html
Modified: lnt/trunk/lnt/server/ui/profile_views.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/profile_views.py?rev=264524&r1=264523&r2=264524&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/profile_views.py (original)
+++ lnt/trunk/lnt/server/ui/profile_views.py Sat Mar 26 18:01:20 2016
@@ -94,7 +94,7 @@ def v4_profile_ajax_getTopLevelCounters(
idx += 1
# If the 1'th counter is None for all keys, truncate the list.
- if all(k[1] is None for k in tlc.values()):
+ if all(len(k) > 1 and k[1] is None for k in tlc.values()):
tlc = {k: [v[0]] for k,v in tlc.items()}
return json.dumps(tlc)
Modified: lnt/trunk/lnt/server/ui/static/lnt_profile.css
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_profile.css?rev=264524&r1=264523&r2=264524&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_profile.css (original)
+++ lnt/trunk/lnt/server/ui/static/lnt_profile.css Sat Mar 26 18:01:20 2016
@@ -67,7 +67,6 @@
}
.statsrow {
- background-color: #fafafa;
border: 1px solid #ddd;
border-left: none;
border-right: none;
@@ -75,8 +74,32 @@
padding-bottom: 5px;
}
-.fnrow {
+.toolbarrow {
+ border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
+ padding-top: 5px;
+ padding-bottom: 0px;
+ background-color: #eee;
+
+ font-size: small;
+}
+
+.toolbarrow select {
+ font-size: small;
+ height: 25px;
+}
+
+.toolbarrow table {
+ margin-left: 20px;
+}
+
+.toolbarrow td {
+ vertical-align: center;
+ padding-right: 20px;
+}
+
+.fnrow {
+ background-color: #fafafa;
padding-top: 10px;
padding-bottom: 5px;
}
Modified: lnt/trunk/lnt/server/ui/static/lnt_profile.js
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_profile.js?rev=264524&r1=264523&r2=264524&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_profile.js (original)
+++ lnt/trunk/lnt/server/ui/static/lnt_profile.js Sat Mar 26 18:01:20 2016
@@ -19,8 +19,10 @@ Profile.prototype = {
$(this.element).html('<center><i>Select a run and function above<br> ' +
'to view a performance profile</i></center>');
},
- go: function(function_name, counter_name) {
+ go: function(function_name, counter_name, absolute, total_ctr_for_fn) {
this.counter_name = counter_name
+ this.absolute = absolute;
+ this.total_ctr = total_ctr_for_fn;
if (this.function_name != function_name)
this._fetch_and_go(function_name);
else
@@ -68,6 +70,7 @@ Profile.prototype = {
},
_labelTd: function(value) {
+ var this_ = this;
var labelPct = function(value) {
// Colour scheme: Black up until 1%, then yellow fading to red at 10%
var bg = '#fff';
@@ -82,10 +85,22 @@ Profile.prototype = {
}
return $('<td style="background-color:' + bg + '; border-right: 1px solid ' + hl + ';"></td>')
.text(value.toFixed(2) + '%');
- }
+ };
+
+ var labelAbs = function(value) {
+ var hue = lerp(50.0, 0.0, value / 100.0);
+ var bg = 'hsl(' + hue.toFixed(0) + ', 100%, 50%)';
+ var hl = 'hsl(' + hue.toFixed(0) + ', 100%, 30%)';
- // FIXME: Implement absolute numbering.
- return labelPct(value);
+ var absVal = (value / 100.0) * this_.total_ctr;
+ return $('<td style="background-color:' + bg + '; border-right: 1px solid ' + hl + ';"></td>')
+ .text(currencyify(absVal))
+ .append($('<span></span>')
+ .text(value.toFixed(2) + '%')
+ .hide());
+ };
+
+ return this.absolute ? labelAbs(value) : labelPct(value);
},
};
@@ -110,21 +125,69 @@ StatsBar.prototype = {
dataType: "json",
data: {'runids': this.runids.join(), 'testid': this.testid},
success: function(data) {
- var t = $('<table align="center" valign="middle"></table>');
- var r = $('<tr></tr>');
+ this_.data = data;
+ var t = $('<table></table>').addClass('table table-striped table-condensed table-hover');
+ this_.element.html(t);
+
+ var gdata = [];
+ var ticks = [];
var i = 0;
+ var n = 0;
+ for (counter in data)
+ ++n;
for (counter in data) {
- if (i > 0 && i % 4 == 0) {
- t.append(r);
- r = $('<tr></tr>');
- }
+ var barvalue = data[counter][0] - data[counter][1];
+ var percent = (barvalue / data[counter][0]) * 100;
+
+ var r = $('<tr></tr>');
+
+ r.append($('<th>' + counter + '</th>').addClass('span2'));
+ r.append($('<td></td>').append(this_._formatValue(data[counter][0]))
+ .addClass('span4')
+ .css({'text-align': 'right'}));
+ r.append($('<td></td>').append(this_._formatValue(data[counter][1]))
+ .addClass('span2')
+ .css({'text-align': 'left'}));
+ r.append($('<td></td>').append(this_._formatPercentage(percent))
+ .addClass('span1')
+ .css({'text-align': 'right'}));
+ t.append(r);
- var cell = this_._formatCell(counter, data[counter]);
- r.append(cell);
+ var color = 'red';
+ if (barvalue < 0)
+ color = 'green';
+
+ gdata.push({data: [[percent, n - i]], color: color});
+ ticks.push([i, counter]);
+ ++i;
}
- if (r.children().length > 0)
- t.append(r);
- this_.element.html(t);
+
+ $('#stats-graph').height(this_.element.height());
+ $.plot('#stats-graph', gdata, {
+ series: {
+ bars: {
+ show: true,
+ barWidth: 0.6,
+ align: "center",
+ horizontal: true,
+ },
+ },
+ xaxis: {
+ tickFormatter: function(f) {
+ return this_._percentageify(f);
+ },
+ autoscaleMargin: 0.05,
+ },
+ yaxis: {
+ show: false
+ },
+ grid: {
+ borderWidth: 0,
+ }
+
+ });
+
+ $('#toolbar').toolBar().triggerResize();
},
error: function(xhr, textStatus, errorThrown) {
pf_flash_error('accessing URL ' + g_urls.getTopLevelCounters +
@@ -134,30 +197,155 @@ StatsBar.prototype = {
},
- _formatCell: function(counter, values) {
- if (values.length > 1) {
- // We have both counters, so we can compare them.
- var data1 = values[0];
- var data2 = values[1];
- percent = (data2 * 100.0 / data1) - 100.0;
- if (percent > 0.0) {
- // Make sure 2% is formatted as +2%.
- percent = '+' + percent.toFixed(2);
- } else if (percent < 0.0) {
- percent = percent.toFixed(2);
+ getCounterValue: function(counter) {
+ return this.data[counter];
+ },
+
+ _percentageify: function(value) {
+ return value.toFixed(0) + '%';
+ },
+
+ _formatPercentage: function(value) {
+ if (!value)
+ return "";
+
+ var color;
+ if (value > 0)
+ color = 'red';
+ else
+ color = 'green';
+
+ var f = value.toFixed(2);
+ if (f > 0)
+ f = '+' + f;
+
+ var s = $('<span></span>').text(f + '%');
+ s.css({color: color});
+ return s;
+ },
+
+ _formatValue: function(value) {
+ if (!value)
+ return "";
+ var s = '<span data-toggle="tooltip" title="' + add_commas(value) + '">';
+ s += currencyify(value);
+ s += '</span>';
+
+ return $(s).tooltip();
+ },
+};
+
+function ToolBar(element) {
+ this.element = $(element);
+ var this_ = this;
+
+ // We want the toolbar to "stick" just below the main nav, which is fixed.
+ // However, on narrow screens the main nav is not fixed. So detect that here.
+ if ($('#header').css('position') == 'fixed') {
+ // There is some weird padding issue where $(#header).height() is not the
+ // same as $(#header ul).top + $(#header ul).height. The header height
+ // somehow has about 4px more. Use the ul here.
+ var obj = $('#header .breadcrumb');
+ this.marginTop = obj.position().top + obj.innerHeight();
+ } else {
+ this.marginTop = 0;
+ }
+
+ var marginLeft;
+ var marginRight;
+ // We use the jQuery plugin "scrollToFixed" which can do everything we want.
+ $(element).scrollToFixed({
+ marginTop: this.marginTop,
+ // But, our toolbar is a row inside a container-fluid, and depending on the
+ // screen width this can contain negative margins. These margins differ too
+ // on the screen size. Once the element is fixed, those negative margins no longer
+ // just cancel out the padding in #container-fluid, and the bar appears off
+ // the screen.
+ //
+ // So here, first save the current margins...
+ preFixed: function() {
+ marginLeft = parseInt(element.css('marginLeft'));
+ marginRight = parseInt(element.css('marginRight'));
+ },
+ // ... Then set the left margin back to zero, and the width to 100% AFTER
+ // position: fixed has been set! (because width is overridden by scrollToFixed).
+ fixed: function() {
+ element.css({marginLeft: 0, width: '100%'});
+ },
+ // Then when we revert to relative positioning, restore the correct margins.
+ postFixed: function() {
+ element.css({marginLeft: marginLeft, marginRight: marginRight});
+ },
+ });
+
+ var this_ = this;
+ element.find('.next-btn-l').click(function() {this_._findNextInstruction(this_, false);});
+ element.find('.prev-btn-l').click(function() {this_._findNextInstruction(this_, true);});
+}
+
+ToolBar.prototype = {
+ _findNearestInstructionInProfile: function(this_, profile_elem_name) {
+ var windowTop = $(window).scrollTop();
+ var offset = this_.marginTop + this.element.innerHeight();
+
+ var y = windowTop + offset;
+ var ret = null;
+ $('#' + profile_elem_name + ' a').each(function(idx, obj) {
+ var objY = $(obj).position().top;
+ if (objY > y) {
+ ret = obj;
+ return false;
}
- // FIXME: Add colors here.
- var element = '<th>'+counter+':</th>';
- element += '<td>' + add_commas(data1) + '<br>' + add_commas(data2);
- if (percent != 0.0)
- element += ' (' + percent + '%)';
- element += '</td>';
- return $(element);
- } else {
- var element = '<th>'+counter+':</th>';
- return $(element + '<td>' + add_commas(values[0]) + '</td>');
- }
+ });
+ return ret;
+ },
+
+ _findNextInstructionInProfile: function(this_, profile_elem_name, isPrev) {
+ var inst_a = this_._findNearestInstructionInProfile(this_, profile_elem_name);
+ var inst_tr = $(inst_a).closest('tr');
+
+ var ret = null;
+ var selector = isPrev ? inst_tr.prevAll('tr') : inst_tr.nextAll('tr');
+ selector.each(function(idx, obj) {
+ var counter = $(obj).children('td').first().text();
+ var s = $(obj).children('td').first().children('span');
+ if (s.length)
+ counter = s.first().text();
+
+ if (counter.length == 0)
+ return;
+ var c = parseFloat(counter);
+ if (!c || c < 5.0)
+ return;
+
+ ret = obj;
+ return false;
+ });
+ return ret;
+ },
+
+ _findNextInstruction: function(this_, isPrev) {
+ var offset = this_.marginTop + this.element.innerHeight();
+
+ var ret1 = this_._findNextInstructionInProfile(this_, 'profile1', isPrev);
+ var ret2 = this_._findNextInstructionInProfile(this_, 'profile2', isPrev);
+
+ var obj = ret1;
+ if (ret1 && ret2 && $(ret2).position().top < $(ret1).position().top)
+ obj = ret2;
+ else if (!ret1)
+ obj = ret2;
+
+ $('html, body').animate({
+ scrollTop: $(obj).position().top - offset
+ }, 500);
+
+ $(obj).effect("highlight", {}, 1500);
},
+
+ triggerResize: function() {
+ $(window).trigger('resize.ScrollToFixed');
+ }
};
function RunTypeahead(element, options) {
@@ -355,6 +543,15 @@ FunctionTypeahead.prototype = {
}
});
},
+ getFunctionPercentage: function(fname) {
+ var this_ = this;
+ var ret = null;
+ $.each(this.data, function(idx, obj) {
+ if (obj[0] == fname)
+ ret = obj[1].counters[this_.options.getCounter()];
+ });
+ return ret;
+ },
_source: function () {
return this.$element.data('functionTypeahead').data;
},
@@ -397,9 +594,9 @@ FunctionTypeahead.prototype = {
$(document).ready(function () {
jQuery.fn.extend({
- profile: function(arg1, arg2, arg3) {
+ profile: function(arg1, arg2, arg3, arg4, arg5) {
if (arg1 == 'go')
- this.data('profile').go(arg2, arg3);
+ this.data('profile').go(arg2, arg3, arg4, arg5);
else if (arg1 && !arg2)
this.data('profile',
new Profile(this,
@@ -419,6 +616,13 @@ $(document).ready(function () {
return this.data('statsBar');
},
+ toolBar: function() {
+ if (!this.data('toolBar'))
+ this.data('toolBar',
+ new ToolBar(this));
+
+ return this.data('toolBar');
+ },
runTypeahead: function(options) {
if (options)
this.data('runTypeahead',
@@ -463,7 +667,11 @@ function pf_init(run1, run2, testid, url
return pf_get_counter();
},
updated: function(fname) {
- $('#profile1').profile('go', fname, pf_get_counter());
+ var fn_percentage = $('#fn1_box').functionTypeahead().getFunctionPercentage(fname);
+ var ctr_value = $('#stats').statsBar().getCounterValue(pf_get_counter());
+ $('#profile1').profile('go', fname,
+ pf_get_counter(), pf_get_absolute(),
+ fn_percentage * ctr_value);
},
sourceRunUpdated: function(data) {
pf_set_default_counter(data);
@@ -493,7 +701,11 @@ function pf_init(run1, run2, testid, url
return pf_get_counter();
},
updated: function(fname) {
- $('#profile2').profile('go', fname, pf_get_counter());
+ var fn_percentage = $('#fn2_box').functionTypeahead().getFunctionPercentage(fname);
+ var ctr_value = $('#stats').statsBar().getCounterValue(pf_get_counter());
+ $('#profile2').profile('go', fname,
+ pf_get_counter(), pf_get_absolute(),
+ fn_percentage * ctr_value);
},
sourceRunUpdated: function(data) {
pf_set_default_counter(data);
@@ -553,14 +765,26 @@ function pf_init(run1, run2, testid, url
if (!$.isEmptyObject(run2))
r2.update(pf_make_stub(run2.machine, run2.order), run2.id);
+ $('#toolbar')
+ .toolBar();
+
+
// Bind change events for the counter dropdown so that profiles are
// updated when it is modified.
- $('#counters').change(function () {
+ $('#counters, #absolute').change(function () {
g_counter = $('#counters').val();
- if ($('#fn1_box').val())
- $('#profile1').profile('go', $('#fn1_box').val(), g_counter);
- if ($('#fn2_box').val())
- $('#profile2').profile('go', $('#fn2_box').val(), g_counter);
+ if ($('#fn1_box').val()) {
+ var fn_percentage = $('#fn1_box').functionTypeahead().getFunctionPercentage(fname);
+ var ctr_value = $('#stats').statsBar().getCounterValue(pf_get_counter());
+ $('#profile1').profile('go', $('#fn1_box').val(), g_counter, pf_get_absolute(),
+ fn_percentage * ctr_value);
+ }
+ if ($('#fn2_box').val()) {
+ var fn_percentage = $('#fn2_box').functionTypeahead().getFunctionPercentage(fname);
+ var ctr_value = $('#stats').statsBar().getCounterValue(pf_get_counter());
+ $('#profile2').profile('go', $('#fn2_box').val(), g_counter, pf_get_absolute(),
+ fn_percentage * ctr_value);
+ }
});
// FIXME: Implement navigating to an address properly.
@@ -667,6 +891,11 @@ function pf_get_counter() {
return g_counter;
}
+// pf_get_absolute - Whether we should display absolute values or percentages.
+function pf_get_absolute() {
+ return $('#absolute').val() == "absolute";
+}
+
// pf_update_history - Push a new history entry, as we've just navigated
// to what could be a new bookmarkable page.
function pf_update_history() {
@@ -710,6 +939,22 @@ function add_commas(nStr) {
return x1 + x2;
}
+function currencyify(value, significant_figures) {
+ if (!significant_figures)
+ significant_figures = 3;
+ value = value.toPrecision(significant_figures);
+
+ var SI = ["K", "M", "Bn", "Tn"];
+ SI.reverse();
+
+ for (i in SI) {
+ var multiplier = Math.pow(10, 3 * (SI.length - i));
+ if (Math.abs(value) > multiplier)
+ return (value / multiplier) + " " + SI[i];
+ }
+ return "" + value;
+}
+
function lerp(s, e, x) {
return s + (e - s) * x;
}
Modified: lnt/trunk/lnt/server/ui/templates/v4_profile.html
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/templates/v4_profile.html?rev=264524&r1=264523&r2=264524&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/templates/v4_profile.html (original)
+++ lnt/trunk/lnt/server/ui/templates/v4_profile.html Sat Mar 26 18:01:20 2016
@@ -7,7 +7,16 @@
{% block head %}
<script language="javascript" type="text/javascript"
src="{{ url_for('.static',
- filename='lnt_profile.js') }}"></script>
+ filename='lnt_profile.js') }}"></script>
+ <script language="javascript" type="text/javascript"
+ src="{{ url_for('.static',
+ filename='flot/jquery.flot.min.js') }}"> </script>
+ <script language="javascript" type="text/javascript"
+ src="{{ url_for('.static',
+ filename='jquery/1.7.2/jquery-ui-1.8.22.custom.min.js') }}"> </script>
+ <script language="javascript" type="text/javascript"
+ src="{{ url_for('.static',
+ filename='jquery/jquery.scrolltofixed/jquery-scrolltofixed.js') }}"> </script>
<link rel="stylesheet" href="{{ url_for('.static',
filename='lnt_profile.css') }}">
<script>
@@ -20,6 +29,7 @@
{% block onload %}
pf_init(g_run1, g_run2, g_test, g_urls);
+ $('.tooltip').tooltip();
{% endblock %}
{% block title %}Profile{% endblock %}
@@ -50,9 +60,7 @@
<div class="row statsrow">
<div class="span9" id="stats">
</div>
- <div class="span3">
- <select id="counters" class="input-medium">
- </select>
+ <div class="span3" id="stats-graph">
</div>
</div>
<div class="row fnrow">
@@ -70,6 +78,24 @@
</div>
</div>
+ <div class="anchor"></div>
+ <div class="row toolbarrow" id="toolbar">
+
+ <div class="span4">
+ <b>Hottest instructions:</b>
+ <button class="btn btn-small prev-btn-l"><i class="icon-backward"></i> Prev</button>
+ <button class="btn btn-small next-btn-l">Next <i class="icon-forward"></i></button>
+ </div>
+ <div class="span6">
+ <b>Counter:</b>
+ <select id="counters" class="input-medium"></select>
+ <select class="input-medium" id="absolute">
+ <option value="relative">Relative (%)</option>
+ <option value="absolute">Absolute numbers</option>
+ </select>
+ </div>
+ </div>
+
<div class="row">
<div class="span6" id="profile1"></div>
<div class="span6" id="profile2"></div>
More information about the llvm-commits
mailing list