[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