[LNT] r249772 - Basic graphing support for regression tracking

Chris Matthews via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 8 16:12:12 PDT 2015


Author: cmatthews
Date: Thu Oct  8 18:12:12 2015
New Revision: 249772

URL: http://llvm.org/viewvc/llvm-project?rev=249772&view=rev
Log:
Basic graphing support for regression tracking

Added:
    lnt/trunk/lnt/server/ui/static/lnt_graph.js
Modified:
    lnt/trunk/lnt/server/ui/api.py
    lnt/trunk/lnt/server/ui/templates/v4_new_regressions.html

Modified: lnt/trunk/lnt/server/ui/api.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/api.py?rev=249772&r1=249771&r2=249772&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/api.py (original)
+++ lnt/trunk/lnt/server/ui/api.py Thu Oct  8 18:12:12 2015
@@ -3,7 +3,7 @@ from flask import request
 from sqlalchemy.orm.exc import NoResultFound
 from flask_restful import Resource, reqparse, fields, marshal_with, abort
 from lnt.testing import PASS
-
+import json
 parser = reqparse.RequestParser()
 parser.add_argument('db', type=str)
 
@@ -155,21 +155,10 @@ class Order(Resource):
         return changes
 
 
-graph_fields = {
-    'val': fields.Float,
-    'rev': fields.String,
-    'time': fields.DateTime(dt_format=DATE_FORMAT),
-    }
-graph_list_fields = {
-    fields.List(fields.Nested(graph_fields)),
-}
-
-
 class Graph(Resource):
     """List all the machines and give summary information."""
     method_decorators = [in_db]
 
-    @marshal_with(graph_fields)
     def get(self, machine_id, test_id, field_index):
         """Get the data for a particular line in a graph."""
         ts = request.get_testsuite()
@@ -190,16 +179,14 @@ class Graph(Resource):
             .join(ts.Order) \
             .filter(ts.Run.machine_id == machine.id) \
             .filter(ts.Sample.test == test) \
-            .filter(field.column != None)
+            .filter(field.column != None) \
+            .order_by(ts.Order.llvm_project_revision)
 
         if field.status_field:
             q = q.filter((field.status_field.column == PASS) |
                          (field.status_field.column == None))
+        samples = [[int(rev), val, {'label': rev, 'date': str(time)}] for val, rev, time in q.all()]
 
-        samples = [{'val': val, 'rev': rev, 'time': time} for val, rev, time in q.all()]
-        import pprint
-        print "Samples"
-        pprint.pprint(samples)
         return samples
 
 

Added: lnt/trunk/lnt/server/ui/static/lnt_graph.js
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_graph.js?rev=249772&view=auto
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_graph.js (added)
+++ lnt/trunk/lnt/server/ui/static/lnt_graph.js Thu Oct  8 18:12:12 2015
@@ -0,0 +1,101 @@
+// Keep the graph data we download.
+// Each element is a list of graph data points.
+var data_cache = [];
+var is_checked = []; // The current list of lines to plot.
+var normalize = false;
+
+
+function try_normal(data_array, end_rev) {
+    $("#graph_range").prop("min", 0);
+    var max = $("#graph_range").prop("max");
+    if (max < data_array.length) {
+        $("#graph_range").prop("max", data_array.length);
+    }
+    var center = -1;
+    for (var i = 0; i < data_array.length; i++) {
+        if (data_array[i][0] == end_rev) {
+            center = i;
+            break;
+        }
+    }
+    console.assert(center != -1, "Center was not found");
+    var smaller = $("#graph_range").val();
+    var total = data_array.length;
+    var to_draw = total - smaller;
+    var upper = data_array.length;
+    var lower = 0;
+    if (center - (to_draw/2) > 0) {
+        lower = center - (to_draw/2);
+    }
+    if (center + (to_draw/2) < total) {
+        upper = center + (to_draw/2);
+    }
+        
+    data_array = data_array.slice(lower, upper);
+
+    if (normalize) {
+        console.log(data_array);
+        return normalize_data(data_array);
+    } else {
+        return data_array;
+    }
+}
+
+function normalize_data(data_array) {
+    var new_data = []
+
+    
+    for (var i = 0; i < data_array.length; i++) {
+        new_data[i] = jQuery.extend({}, data_array[i]);
+        new_data[i][1] = data_array[i][1] / data_array[0][1];
+    }
+    return new_data;
+    
+}
+
+function make_graph_point_entry(data, color) {
+    var entry = {"color": color,
+                 "data": data,
+                 "lines": {"show": false},
+                 "points": {"fill": true,
+                            "radius": 0.25,
+                            "show": true
+                           }
+                };
+    return entry;
+}
+
+var color_codes = ["#4D4D4D",
+                   "#5DA5DA",
+                   "#FAA43A",
+                   "#60BD68",
+                   "#F17CB0",
+                   "#B2912F",
+                   "#B276B2",
+                   "#DECF3F",
+                   "#F15854"]
+
+function new_graph_data_callback(data, index) {
+    data_cache[index] = data;
+    is_checked[index] = true;
+    update_graph();
+}
+
+function update_graph() {
+    var to_draw = [];
+    var starts = [];
+    var ends = [];
+    for ( var i = 0; i < changes.length; i++) {
+            if (is_checked[i]) {
+                    starts.push(changes[i].start);
+                    ends.push(changes[i].end);
+                    var color = color_codes[i % color_codes.length];
+                    var data = try_normal(data_cache[i], changes[i].end);
+                    to_draw.push(make_graph_point_entry(data, color));
+                    to_draw.push({"color": color, "data": data});
+            }
+    }
+    var lowest_rev = Math.min.apply(Math, starts);
+    var highest_rev = Math.max.apply(Math, ends);
+    init(to_draw, lowest_rev, highest_rev);    
+}

Modified: lnt/trunk/lnt/server/ui/templates/v4_new_regressions.html
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/templates/v4_new_regressions.html?rev=249772&r1=249771&r2=249772&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/templates/v4_new_regressions.html (original)
+++ lnt/trunk/lnt/server/ui/templates/v4_new_regressions.html Thu Oct  8 18:12:12 2015
@@ -7,11 +7,200 @@
 {% set components = [(testsuite_name, v4_url_for("v4_recent_activity"))] %}
 {% block title %}Regression Triage{% endblock %}
 
+{% block head %}
+  <script src="{{ url_for('.static', filename='popup.js') }}"></script>
+  <script src="{{ url_for('.static', filename='sorttable.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='flot/jquery.flot.errorbars.min.js') }}"> </script>
+  <script language="javascript" type="text/javascript"
+          src="{{ url_for('.static',
+                          filename='flot/jquery.flot.navigate.min.js') }}"> </script>
+  <script language="javascript" type="text/javascript"
+          src="{{ url_for('.static',
+                          filename='flot/jquery.flot.selection.min.js') }}"> </script>
+  <script language="javascript" type="text/javascript"
+          src="{{ url_for('.static',
+                          filename='flot/jquery.flot.highlight.min.js') }}"></script>
+  <script src="{{ url_for('.static', filename='lnt_graph.js') }}"></script>
+{% endblock %}
+
+{% block javascript %}
+var g = {}
+{% set api_graph = "api/db_default/v4/" + testsuite_name + "/graph"%}
+var changes = [
+{% for form_change in form.field_changes%}
+    {% set fc = changes[loop.index -1] %}
+    {"url": "/{{api_graph}}/{{ fc.ri.machine.id}}/{{fc.ri.test.id}}/{{fc.ri.field.index}}",
+     "start": {{fc.ri.start_order.llvm_project_revision}},
+     "end": {{fc.ri.end_order.llvm_project_revision}}
+ },
+{% endfor %}
+];
+
+/* Bind events to the zoom bar buttons, so that 
+ * the zoom buttons work, then position them
+ * over top of the main graph.
+ */
+function bind_zoom_bar(my_plot) {
+
+	$('#out').click(function (e) {
+        e.preventDefault(); 
+        my_plot.zoomOut(); 
+    }); 
+
+    $('#in').click(function (e) {
+        e.preventDefault(); 
+        my_plot.zoom(); 
+    }); 
+    
+
+	// Now move the bottons onto the graph.
+	$('#zoombar').css('position', 'relative');
+	$('#zoombar').css('left', '40px');
+	$('#zoombar').css('top', '-235px');
+}
+$('#normalize').click(function (e) {
+    normalize = !normalize;
+    alert(normalize);
+    update_graph();
+}); 
+function init(data, start_highlight, end_highlight) {
+  // Set up the primary graph.
+	var graph = $("#graph");
+	var graph_plots = data;
+    var line_width = 1;
+    if (data.length > 0 && data[0]['data'].length < 50) {
+        line_width = 2;
+    }
+	var graph_options = {
+      series : {
+        lines : {
+          lineWidth : line_width },
+        shadowSize : 0
+      },
+      highlight : {
+
+          range: {"end": [end_highlight], "start": [start_highlight]},
+          alpha: "0.1",
+          stroke: false,
+
+      },
+      zoom : { interactive : false },
+      pan : { interactive : true,
+              frameRate: 60 },
+      grid : {
+        hoverable : true }
+      };
+
+  var main_plot = $.plot("#graph", graph_plots, graph_options);
+
+  // Add tooltips.
+  $("#graph").bind("plothover", function(e,p,i) {
+    update_tooltip(e, p, i, show_tooltip, graph_plots); });
+  bind_zoom_bar(main_plot);
+ 
+ 
+}
+
+// Show our overlay tooltip.
+g.current_tip_point = null;
+function show_tooltip(x, y, item, pos, graph_data) {
+
+    // Given the event handler item, get the graph metadata.
+    function extract_metadata(item, graph_data) {
+        var index = item.dataIndex;
+        var series_index = item.seriesIndex;
+        // Graph data is formatted as [x, y, meta_data].
+        var meta_data = item.series.data[series_index][2];
+        return meta_data;
+
+    }
+    var data = item.datapoint;
+    var meta_data = extract_metadata(item, graph_data);
+    var tip_body = '<div id="tooltip">';
+
+    if ("test_name" in meta_data) {
+        tip_body += "<b>Test:</b> " + meta_data.test_name + "<br>";
+    }
+
+    if ("label" in meta_data) {
+        tip_body += "<b>Revision:</b> " + meta_data.label + "<br>";
+    }
+    tip_body += "<b>Value:</b> " + data[1].toFixed(4) + "<br>";
+
+    if ("date" in meta_data) {
+        tip_body += "<b>Date:</b> " + meta_data.date;
+    }
+    tip_body += "</div>";
+    var tooltip_div = $(tip_body).css( {
+        position: 'absolute',
+        display: 'none',
+        top: y + 5,
+        left: x + 5,
+        border: '1px solid #fdd',
+        padding: '2px',
+        'background-color': '#fee',
+        opacity: 0.80
+    }).appendTo("body").fadeIn(200);
+
+    // Now make sure the tool tip is on the graph canvas.
+    var tt_position = tooltip_div.position();
+    var tt_offset = tooltip_div.offset();
+
+    var graph_div = $("#graph");
+    var graph_position = graph_div.position();
+
+    // The right edge of the graph.
+    var max_width = graph_position.left + graph_div.width();
+    // The right edge of the tool tip.
+    var tt_right = tt_position.left + tooltip_div.width();
+
+    if (tt_right > max_width) {
+        var diff = tt_right - max_width
+        var GRAPH_BORDER = 10;
+        var VISUAL_APPEAL = 10;
+        tooltip_div.css({'left' : tt_position.left - diff 
+                         - GRAPH_BORDER - VISUAL_APPEAL});
+    }
+
+}
+
+// Event handler function to update the tooltop.
+function update_tooltip(event, pos, item, show_fn, graph_data) {
+    if (!item) {
+        $("#tooltip").remove();
+        g.current_tip_point = null;
+        return;
+    }
+        
+    if (!g.current_tip_point || (g.current_tip_point[0] != item.datapoint[0] ||
+                                 g.current_tip_point[1] != item.datapoint[1])) {
+        $("#tooltip").remove();
+        g.current_tip_point = item.datapoint;
+        show_fn(pos.pageX, pos.pageY, item, pos, graph_data);
+    }
+}
+{% endblock %}
+
 {% block body %}
 
 
 <section id="Changes" />
 <h3>Recent Changes</h3>
+<table><td><tr>Data: More </tr><tr><input type="range" id="graph_range" value="1" onchange="update_graph()"></tr><tr> Less</tr></td></table>
+<div id="graph" style="height:250px"></div>
+<div id="zoombar" style="width:40px;z-index: 999;">
+    <button id="in" type="button" class="btn btn-default" style="width:100%;text-align:center;">+</button>
+    <br>
+    <button id="out" type="button" class="btn btn-default" style="width:100%; text-align:center;">-</button>
+    <br>
+    <button id="normalize" type="button" class="btn btn-default" style="width:100%; text-align:center;">%</button>
+</div>
+
 
 <form method="POST" action="{{ v4_url_for("v4_new_regressions") }}">
     {{ form.hidden_tag() }}
@@ -20,7 +209,7 @@
   
   <thead>
   <tr>
-    <th>X</th>
+    <th><button id="clear" type="button" class="btn btn-default" style="width:100%;text-align:center;" onclick="clear_checks()">X</button></th>
     <th>Machine</th>
     <th>Metric</th>
     <th>Test</th>
@@ -39,7 +228,13 @@
     {% for form_change in form.field_changes%}
         {% set fc = changes[loop.index -1] %}
     <tr>
-        <td>{{ form_change }}</td>
+        <td>
+            
+            <table>
+                <tr style="background:transparent;">
+                    <td style="background:transparent;"></td><td>{{ form_change }}</td>
+                </tr>
+            </table>
         <td>{{utils.render_machine(fc.ri.machine)}}</td>
         <td> {{ fc.ri.field.name }} </td>
          {% set graph_base=v4_url_for('v4_graph', highlight_run=fc.run.id) %}
@@ -64,12 +259,57 @@
 
 
 <script type="text/javascript">
+
+function clear_checks() {
+     $('input:checkbox').removeAttr('checked');
+     $('input:checkbox').trigger('change');
+}
+
+function register_checkboxes() {
+    $(':checkbox').change(function(){
+          var c = this.checked
+          var id = this.id;
+          var index = id.split("-")[1];
+          
+          if (c) {
+              var color = color_codes[index % color_codes.length];
+              var prev_cell = $(this).closest('td').prev();
+              prev_cell.css("background-color", color);
+              $.getJSON(changes[index]["url"], function(data) {
+                  new_graph_data_callback(data, index);
+                  });
+          } else {
+              is_checked[index] = false;
+              var prev_cell = $(this).closest('td').prev();
+              prev_cell.css("background-color", "transparent");
+          }
+          update_graph();
+      });
+  }
+
 $(document).ready( function () {
     $('#changes_table').DataTable({
-    "sDom": '<"top"if>rt<"bottom"Flp>'
+    "sDom": '<"top"if>rt<"bottom"Flp>',
+    "drawCallback": function( settings ) {
+        register_checkboxes();
+    }
   });
+  
+  for (var i = 0; i < changes.length; i++) {
+      is_checked[i] = false;
+  }
+      
+    register_checkboxes();
+    $('#normalize').click(function (e) {
+        normalize = !normalize;
+        update_graph();
+    }); 
+    update_graph();
+  
+    
 } );
 </script>
 
 
+
 {% endblock %}




More information about the llvm-commits mailing list