Changeset 179763 in webkit


Ignore:
Timestamp:
Feb 6, 2015 2:43:08 PM (9 years ago)
Author:
rniwa@webkit.org
Message:

New perf dashboard should have multiple dashboard pages
https://bugs.webkit.org/show_bug.cgi?id=141339

Reviewed by Chris Dumez.

Added the support for multiple dashboard pages. Also added the status of the latest data point.
e.g. "5% better than target"

  • public/v2/app.css: Tweaked the styles to work around the fact Ember.js creates empty script elements.

Also hid the border lines around charts on the dashboard page for a cleaner look.

  • public/v2/app.js:

(App.IndexRoute): Added. Navigate to /dashboard/<defaultDashboardName> once the manifest.json is loaded.
(App.IndexRoute.beforeModel): Added.
(App.DashboardRoute): Added.
(App.DashboardRoute.model): Added. Return the dashboard specified by the name.
(App.CustomDashboardRoute): Added. This route is used for a customized dashboard specified by "grid".
(App.CustomDashboardRoute.model): Create a dashboard model from "grid" query parameter.
(App.CustomDashboardRoute.renderTemplate): Use the dashboard template.
(App.DashboardController): Renamed from App.IndexController.
(App.DashboardController.modelChanged): Renamed from gridChanged. Removed the code to deal with "grid"
and "defaultDashboard" as these are taken care of by newly added routers.
(App.DashboardController.computeGrid): Renamed from updateGrid. No longer updates "grid" since this is
now done in actions.toggleEditMode.
(App.DashboardController.actions.toggleEditMode): Navigate to CustomDashboardRoute when start editing
an existing dashboard.

(App.Pane.computeStatus): Moved from App.PaneController so that to be called in App.Pane.latestStatus.
Also moved the code to compute the delta with respect to the previous data point from _updateDetails.
(App.Pane._relativeDifferentToLaterPointInTimeSeries): Ditto.
(App.Pane.latestStatus): Added. Used by the dashboard template to show the status of the latest result.

(App.createChartData): Added deltaFormatter to show less significant digits for differences.

(App.PaneController._updateDetails): Updated per changes to computeStatus.

  • public/v2/chart-pane.css: Added style rules for the status labels on the dashboard.
  • public/v2/data.js:

(TimeSeries.prototype.lastPoint): Added.

  • public/v2/index.html: Prefetch manifest.json as soon as possible, show the latest data points' status

on the dashboard, and enumerate all predefined dashboards.

  • public/v2/interactive-chart.js:

(App.InteractiveChartComponent._relayoutDataAndAxes): Slightly adjust the offset at which we show unit
for the dashboard page.

  • public/v2/manifest.js:

(App.Dashboard): Inherit from App.NameLabelModel now that each predefined dashboard has a name.
(App.MetricSerializer.normalizePayload): Parse all predefined dashboards instead of a single dashboard.
IDs are generated for each dashboard for forward compatibility.
(App.Manifest):
(App.Manifest.dashboardByName): Added.
(App.Manifest.defaultDashboardName): Added.
(App.Manifest._fetchedManifest): Create dashboard model objects for all predefined ones.

Location:
trunk/Websites/perf.webkit.org
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r179731 r179763  
     12015-02-06  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        New perf dashboard should have multiple dashboard pages
     4        https://bugs.webkit.org/show_bug.cgi?id=141339
     5
     6        Reviewed by Chris Dumez.
     7
     8        Added the support for multiple dashboard pages. Also added the status of the latest data point.
     9        e.g. "5% better than target"
     10
     11        * public/v2/app.css: Tweaked the styles to work around the fact Ember.js creates empty script elements.
     12        Also hid the border lines around charts on the dashboard page for a cleaner look.
     13
     14        * public/v2/app.js:
     15        (App.IndexRoute): Added. Navigate to /dashboard/<defaultDashboardName> once the manifest.json is loaded.
     16        (App.IndexRoute.beforeModel): Added.
     17        (App.DashboardRoute): Added.
     18        (App.DashboardRoute.model): Added. Return the dashboard specified by the name.
     19        (App.CustomDashboardRoute): Added. This route is used for a customized dashboard specified by "grid".
     20        (App.CustomDashboardRoute.model): Create a dashboard model from "grid" query parameter.
     21        (App.CustomDashboardRoute.renderTemplate): Use the dashboard template.
     22        (App.DashboardController): Renamed from App.IndexController.
     23        (App.DashboardController.modelChanged): Renamed from gridChanged. Removed the code to deal with "grid"
     24        and "defaultDashboard" as these are taken care of by newly added routers.
     25        (App.DashboardController.computeGrid): Renamed from updateGrid. No longer updates "grid" since this is
     26        now done in actions.toggleEditMode.
     27        (App.DashboardController.actions.toggleEditMode): Navigate to CustomDashboardRoute when start editing
     28        an existing dashboard.
     29
     30        (App.Pane.computeStatus): Moved from App.PaneController so that to be called in App.Pane.latestStatus.
     31        Also moved the code to compute the delta with respect to the previous data point from _updateDetails.
     32        (App.Pane._relativeDifferentToLaterPointInTimeSeries): Ditto.
     33        (App.Pane.latestStatus): Added. Used by the dashboard template to show the status of the latest result.
     34
     35        (App.createChartData): Added deltaFormatter to show less significant digits for differences.
     36
     37        (App.PaneController._updateDetails): Updated per changes to computeStatus.
     38
     39        * public/v2/chart-pane.css: Added style rules for the status labels on the dashboard.
     40
     41        * public/v2/data.js:
     42        (TimeSeries.prototype.lastPoint): Added.
     43
     44        * public/v2/index.html: Prefetch manifest.json as soon as possible, show the latest data points' status
     45        on the dashboard, and enumerate all predefined dashboards.
     46
     47        * public/v2/interactive-chart.js:
     48        (App.InteractiveChartComponent._relayoutDataAndAxes): Slightly adjust the offset at which we show unit
     49        for the dashboard page.
     50
     51        * public/v2/manifest.js:
     52        (App.Dashboard): Inherit from App.NameLabelModel now that each predefined dashboard has a name.
     53        (App.MetricSerializer.normalizePayload): Parse all predefined dashboards instead of a single dashboard.
     54        IDs are generated for each dashboard for forward compatibility.
     55        (App.Manifest):
     56        (App.Manifest.dashboardByName): Added.
     57        (App.Manifest.defaultDashboardName): Added.
     58        (App.Manifest._fetchedManifest): Create dashboard model objects for all predefined ones.
     59
    1602015-02-05  Ryosuke Niwa  <rniwa@webkit.org>
    261
  • trunk/Websites/perf.webkit.org/public/v2/app.css

    r179661 r179763  
    202202    line-height: 1rem;
    203203}
    204 #navigation li:not(:last-child) a {
     204#navigation li:not(:last-of-type) a {
    205205    border-top-right-radius: 0;
    206206    border-bottom-right-radius: 0;
    207207    border-right: 0;
    208208}
    209 #navigation li:not(:first-child) a {
     209#navigation li:not(:first-of-type) a {
    210210    border-top-left-radius: 0;
    211211    border-bottom-left-radius: 0;
     
    398398
    399399table.dashboard tbody td .chart {
    400     border: solid 1px #ddd;
     400    border: solid 0px #ddd;
    401401    border-radius: 0.5rem;
    402402    margin: 0.5rem 0.5rem;
  • trunk/Websites/perf.webkit.org/public/v2/app.js

    r179731 r179763  
    22
    33App.Router.map(function () {
     4    this.resource('customDashboard', {path: 'dashboard/custom'});
     5    this.resource('dashboard', {path: 'dashboard/:name'});
    46    this.resource('charts', {path: 'charts'});
    57    this.resource('analysis', {path: 'analysis'});
     
    5557});
    5658
    57 App.IndexController = Ember.Controller.extend({
     59App.IndexRoute = Ember.Route.extend({
     60    beforeModel: function ()
     61    {
     62        var self = this;
     63        App.Manifest.fetch(this.store).then(function () {
     64            self.transitionTo('dashboard', App.Manifest.defaultDashboardName());
     65        });
     66    },
     67});
     68
     69App.DashboardRoute = Ember.Route.extend({
     70    model: function (param)
     71    {
     72        return App.Manifest.fetch(this.store).then(function () {
     73            return App.Manifest.dashboardByName(param.name);
     74        });
     75    },
     76});
     77
     78App.CustomDashboardRoute = Ember.Route.extend({
     79    controllerName: 'dashboard',
     80    model: function (param)
     81    {
     82        return this.store.createRecord('dashboard', {serialized: param.grid});
     83    },
     84    renderTemplate: function()
     85    {
     86        this.render('dashboard');
     87    }
     88});
     89
     90App.DashboardController = Ember.Controller.extend({
    5891    queryParams: ['grid', 'numberOfDays'],
    59     _previousGrid: {},
    6092    headerColumns: [],
    6193    rows: [],
     
    6395    editMode: false,
    6496
    65     gridChanged: function ()
    66     {
    67         var grid = this.get('grid');
    68         if (grid === this._previousGrid)
    69             return;
    70 
    71         var dashboard = null;
    72         if (grid) {
    73             dashboard = this.store.createRecord('dashboard', {serialized: grid});
    74             if (!dashboard.get('headerColumns').length)
    75                 dashboard = null;
    76         }
    77         if (!dashboard)
    78             dashboard = App.Manifest.get('defaultDashboard');
     97    modelChanged: function ()
     98    {
     99        var dashboard = this.get('model');
    79100        if (!dashboard)
    80101            return;
     
    96117
    97118        this.set('emptyRow', new Array(columnCount));
    98     }.observes('grid', 'App.Manifest.defaultDashboard').on('init'),
    99 
    100     updateGrid: function()
     119    }.observes('model').on('init'),
     120
     121    computeGrid: function()
    101122    {
    102123        var headers = this.get('headerColumns').map(function (header) { return header.label; });
     
    107128            }));
    108129        }));
    109         this._previousGrid = JSON.stringify(table);
    110         this.set('grid', this._previousGrid);
     130        return JSON.stringify(table);
    111131    },
    112132
     
    174194        {
    175195            this.toggleProperty('editMode');
    176             if (!this.get('editMode'))
    177                 this.updateGrid();
     196            if (this.get('editMode'))
     197                this.transitionToRoute('dashboard', 'custom', {name: null, queryParams: {grid: this.computeGrid()}});
     198            else
     199                this.set('grid', this.computeGrid());
    178200        },
    179201    },
     
    363385            return !!id.match(/^[A-Za-z0-9_]+$/);
    364386        return false;
    365     }
     387    },
     388    computeStatus: function (currentPoint, previousPoint)
     389    {
     390        var chartData = this.get('chartData');
     391        var diffFromBaseline = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.baseline);
     392        var diffFromTarget = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.target);
     393
     394        var label = '';
     395        var className = '';
     396        var formatter = d3.format('.3p');
     397
     398        var smallerIsBetter = chartData.smallerIsBetter;
     399        if (diffFromBaseline !== undefined && diffFromBaseline > 0 == smallerIsBetter) {
     400            label = formatter(Math.abs(diffFromBaseline)) + ' ' + (smallerIsBetter ? 'above' : 'below') + ' baseline';
     401            className = 'worse';
     402        } else if (diffFromTarget !== undefined && diffFromTarget < 0 == smallerIsBetter) {
     403            label = formatter(Math.abs(diffFromTarget)) + ' ' + (smallerIsBetter ? 'below' : 'above') + ' target';
     404            className = 'better';
     405        } else if (diffFromTarget !== undefined)
     406            label = formatter(Math.abs(diffFromTarget)) + ' until target';
     407
     408        var valueDelta = previousPoint ? chartData.deltaFormatter(currentPoint.value - previousPoint.value) : null;
     409        if (valueDelta && valueDelta > 0)
     410            valueDelta = '+' + valueDelta;
     411
     412        return {className: className, label: label, currentValue: chartData.formatter(currentPoint.value), valueDelta: valueDelta};
     413    },
     414    _relativeDifferentToLaterPointInTimeSeries: function (currentPoint, timeSeries)
     415    {
     416        if (!currentPoint || !timeSeries)
     417            return undefined;
     418
     419        var referencePoint = timeSeries.findPointAfterTime(currentPoint.time);
     420        if (!referencePoint)
     421            return undefined;
     422
     423        return (currentPoint.value - referencePoint.value) / referencePoint.value;
     424    },
     425    latestStatus: function ()
     426    {
     427        var chartData = this.get('chartData');
     428        if (!chartData || !chartData.current)
     429            return null;
     430
     431        var lastPoint = chartData.current.lastPoint();
     432        if (!lastPoint)
     433            return null;
     434
     435        return this.computeStatus(lastPoint, chartData.current.previousPoint(lastPoint));
     436    }.property('chartData'),
    366437});
    367438
     
    375446        unit: data.unit,
    376447        formatter: data.useSI ? d3.format('.4s') : d3.format('.4g'),
     448        deltaFormatter: data.useSI ? d3.format('.2s') : d3.format('.2g'),
    377449        smallerIsBetter: data.smallerIsBetter,
    378450    };
     
    731803
    732804        var currentMeasurement;
    733         var oldMeasurement;
     805        var previousPoint;
    734806        if (currentPoint) {
    735807            currentMeasurement = currentPoint.measurement;
    736             var previousPoint = currentPoint.series.previousPoint(currentPoint);
    737             oldMeasurement = previousPoint ? previousPoint.measurement : null;
     808            previousPoint = currentPoint.series.previousPoint(currentPoint);
    738809        } else {
    739810            currentMeasurement = selectedPoints[selectedPoints.length - 1].measurement;
    740             oldMeasurement = selectedPoints[0].measurement;           
     811            previousPoint = selectedPoints[0];
    741812        }
     813        var oldMeasurement = previousPoint ? previousPoint.measurement : null;
    742814
    743815        var formattedRevisions = currentMeasurement.formattedRevisions(oldMeasurement);
     
    763835        }
    764836
    765         var chartData = this.get('chartData');
    766         var valueDiff = oldMeasurement ? chartData.formatter(currentMeasurement.mean() - oldMeasurement.mean()) : null;
    767         if (valueDiff && valueDiff > 0)
    768             valueDiff = '+' + valueDiff;
    769 
    770837        this.set('details', Ember.Object.create({
    771             status: this._computeStatus(currentPoint),
    772             currentValue: chartData.formatter(currentMeasurement.mean()),
    773             valueDiff: valueDiff,
     838            status: this.get('model').computeStatus(currentPoint, previousPoint),
    774839            buildNumber: buildNumber,
    775840            buildURL: buildURL,
     
    784849        this.set('cannotAnalyze', !this.get('newAnalysisTaskName') || !points || points.length < 2);
    785850    }.observes('newAnalysisTaskName'),
    786     _computeStatus: function (currentPoint)
    787     {
    788         var chartData = this.get('chartData');
    789 
    790         var diffFromBaseline = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.baseline);
    791         var diffFromTarget = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.target);
    792 
    793         var label = '';
    794         var className = '';
    795         var formatter = d3.format('.3p');
    796 
    797         var smallerIsBetter = chartData.smallerIsBetter;
    798         if (diffFromBaseline !== undefined && diffFromBaseline > 0 == smallerIsBetter) {
    799             label = formatter(Math.abs(diffFromBaseline)) + ' ' + (smallerIsBetter ? 'above' : 'below') + ' baseline';
    800             className = 'worse';
    801         } else if (diffFromTarget !== undefined && diffFromTarget < 0 == smallerIsBetter) {
    802             label = formatter(Math.abs(diffFromTarget)) + ' ' + (smallerIsBetter ? 'below' : 'above') + ' target';
    803             className = 'better';
    804         } else if (diffFromTarget !== undefined)
    805             label = formatter(Math.abs(diffFromTarget)) + ' until target';
    806 
    807         return {className: className, label: label};
    808     },
    809     _relativeDifferentToLaterPointInTimeSeries: function (currentPoint, timeSeries)
    810     {
    811         if (!currentPoint || !timeSeries)
    812             return undefined;
    813        
    814         var referencePoint = timeSeries.findPointAfterTime(currentPoint.time);
    815         if (!referencePoint)
    816             return undefined;
    817 
    818         return (currentPoint.value - referencePoint.value) / referencePoint.value;
    819     }
    820851});
    821852
  • trunk/Websites/perf.webkit.org/public/v2/chart-pane.css

    r179729 r179763  
    321321    stroke: #f66;
    322322}
    323 .chart-pane .status .worse {
     323.chart-pane .status .worse,
     324.dashboard-status .worse {
    324325    color: #c33;
    325326}
     
    328329    stroke: #66f;
    329330}
    330 .chart-pane .status .better {
     331.chart-pane .status .better,
     332.dashboard-status .better {
    331333    color: #33c;
     334}
     335
     336.dashboard-status .status-label {
     337    margin-left: 1rem;
    332338}
    333339
  • trunk/Websites/perf.webkit.org/public/v2/data.js

    r179710 r179763  
    406406TimeSeries.prototype.series = function () { return this._series; }
    407407
     408TimeSeries.prototype.lastPoint = function ()
     409{
     410    if (!this._series || !this._series.length)
     411        return null;
     412    return this._series[this._series.length - 1];
     413}
     414
    408415TimeSeries.prototype.previousPoint = function (point)
    409416{
  • trunk/Websites/perf.webkit.org/public/v2/index.html

    r179731 r179763  
    44    <meta charset="utf-8">
    55    <title>WebKit Performance Monitor (Beta)</title>
     6
     7    <link rel="prefetch" href="../data/manifest.json">
     8    <script type="application/json" src="../data/manifest.json"></script>
     9
     10    <link rel="stylesheet" href="app.css">
     11    <link rel="stylesheet" href="chart-pane.css">
     12
     13    <script src="js/jquery.min.js" defer></script>
    614    <script src="js/jquery.min.js" defer></script>
    715    <script src="js/handlebars.js" defer></script>
     
    1725    <script src="interactive-chart.js" defer></script>
    1826    <script src="commits-viewer.js" defer></script>
    19     <link rel="stylesheet" href="app.css">
    20     <link rel="stylesheet" href="chart-pane.css">
    21 
    22     <script type="text/x-handlebars" data-template-name="index">
     27
     28    <script type="text/x-handlebars" data-template-name="dashboard">
    2329        <header id="header">
    2430            {{partial "navbar"}}
     
    7985                            {{else}}
    8086                                {{#if chartData}}
     87                                    <div class="dashboard-status">
     88                                        {{#if latestStatus}}
     89                                            {{latestStatus.currentValue}} {{chartData.unit}}
     90                                            {{#if latestStatus.label}}
     91                                                <span {{bind-attr class=":status-label latestStatus.className"}}>{{latestStatus.label}}</span>
     92                                            {{/if}}
     93                                        {{/if}}
     94                                    </div>
    8195                                    {{#link-to 'charts' (query-params paneList=paneList since=controller.since)}}
    82                                     {{interactive-chart
    83                                         chartData=chartData
    84                                         domain=controller.sharedDomain
    85                                         enableSelection=false}}
     96                                        {{interactive-chart
     97                                            chartData=chartData
     98                                            domain=controller.sharedDomain
     99                                            enableSelection=false}}
    86100                                    {{/link-to}}
    87101                                {{else}}
     
    259273                    <th>Current</th>
    260274                    <td>
    261                         {{details.currentValue}} {{chartData.unit}}
    262                         {{#if details.valueDiff}}
    263                             ({{details.valueDiff}} {{chartData.unit}})
     275                        {{details.status.currentValue}} {{chartData.unit}}
     276                        {{#if details.status.valueDelta}}
     277                            ({{details.status.valueDelta}} {{chartData.unit}})
    264278                        {{/if}}
    265279                        {{#if details.status.label}}
     
    379393            <h1><a href="#">WebKit Perf Monitor</a></h1>
    380394            <ul>
    381                 {{#link-to 'index' tagName='li'}}
    382                     {{#link-to 'index'}}Dashboard{{/link-to}}
    383                 {{/link-to}}
     395                {{#each App.Manifest.dashboards}}
     396                    {{#if name}}
     397                        {{#link-to 'dashboard' name tagName='li'}}
     398                            {{#link-to 'dashboard' name}}{{label}}{{/link-to}}
     399                        {{/link-to}}
     400                    {{/if}}
     401                {{/each}}
    384402                {{#link-to 'charts' tagName='li'}}
    385403                    {{#link-to 'charts'}}Charts{{/link-to}}
  • trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js

    r179710 r179763  
    289289        if (this._yAxisUnitContainer)
    290290            this._yAxisUnitContainer.remove();
    291         var x = - 3 * this._rem;
     291        var x = - 3.2 * this._rem;
    292292        var y = this._contentHeight / 2;
    293293        this._yAxisUnitContainer = this._yAxisLabels.append("text")
  • trunk/Websites/perf.webkit.org/public/v2/manifest.js

    r179710 r179763  
    9393});
    9494
    95 App.Dashboard = App.Model.extend({
     95App.Dashboard = App.NameLabelModel.extend({
    9696    serialized: DS.attr('string'),
    9797    table: function ()
     
    153153            repositories: this._normalizeIdMap(payload['repositories']),
    154154            bugTrackers: this._normalizeIdMap(payload['bugTrackers']),
    155             dashboards: [{id: 1, serialized: JSON.stringify(payload['defaultDashboard'])}],
     155            dashboards: [],
    156156        };
    157157
     
    172172                test['metrics'] = [];
    173173            test['metrics'].push(metricId);
     174        }
     175
     176        var id = 1;
     177        var dashboardsInPayload = payload['dashboards'];
     178        for (var dashboardName in dashboardsInPayload) {
     179            results['dashboards'].push({
     180                id: id,
     181                name: dashboardName,
     182                serialized: JSON.stringify(dashboardsInPayload[dashboardName])
     183            });
     184            id++;
    174185        }
    175186
     
    212223    _builderById: {},
    213224    _repositoryById: {},
     225    _dashboardByName: {},
     226    _defaultDashboardName: null,
    214227    _fetchPromise: null,
    215228    fetch: function (store)
     
    224237    builder: function (id) { return this._builderById[id]; },
    225238    repository: function (id) { return this._repositoryById[id]; },
     239    dashboardByName: function (name) { return this._dashboardByName[name]; },
     240    defaultDashboardName: function () { return this._defaultDashboardName; },
    226241    _fetchedManifest: function (store)
    227242    {
     
    260275        this.set('bugTrackers', store.all('bugTracker').sortBy('name'));
    261276
    262         this.set('defaultDashboard', store.all('dashboard').objectAt(0));
     277        var dashboards = store.all('dashboard').sortBy('name');
     278        this.set('dashboards', dashboards);
     279        dashboards.forEach(function (dashboard) { self._dashboardByName[dashboard.get('name')] = dashboard; });
     280        this._defaultDashboardName = dashboards.length ? dashboards[0].get('name') : null;
    263281    },
    264282    fetchRunsWithPlatformAndMetric: function (store, platformId, metricId)
Note: See TracChangeset for help on using the changeset viewer.