Changeset 179878 in webkit


Ignore:
Timestamp:
Feb 10, 2015 1:39:41 PM (9 years ago)
Author:
rniwa@webkit.org
Message:

New perf dashboard should have the ability to overlay moving average with an envelope
https://bugs.webkit.org/show_bug.cgi?id=141438

Reviewed by Andreas Kling.

This patch adds three kinds of moving average strategies and two kinds of enveloping strategies:

Simple Moving Average - The moving average x̄_i of x_i is computed as the arithmetic mean of values
from x_(i - n) though x_(i + m) where n is a non-negative integer and m is a positive integer. It takes
n, backward window size, and m, forward window size, as an argument.

Cumulative Moving Average - x̄_i is computed as the arithmetic mean of all values x_0 though x_i.
That is, x̄_1 = x_1 and x̄_i = ((i - 1) * M_(i - 1) + x_i) / i for all i > 1.

Exponential Moving Average - x̄_i is computed as the weighted average of x_i and x̄_(i - 1) with α as
an argument specifying x_i's weight. To be precise, x̄_1 = x_1 and x̄_i = α * x_i + (α - 1) x̄_(i-1).

Average Difference - The enveloping delta d is computed as the arithmetic mean of the difference
between each x_i and x̄_i.

Moving Average Standard Deviation - d is computed like the standard deviation except the deviation
for each term is measured from the moving average instead of the sample arithmetic mean. i.e. it uses
the average of (x_i - x̄_i)2 as the "sample variance" instead of the conventional (x_i - x̄)2 where
x̄ is the sample mean of all x_i's. This change was necessary since our time series is non-stationary.

Each strategy is cloned for an App.Pane instance so that its parameterList can be configured per pane.
The configuration of the currently chosen strategies is saved in the query string for convenience.

Also added the "stat pane" to choose a moving average strategy and a enveloping strategy in each pane.

  • public/v2/app.css: Specify the fill color for all SVG groups in the pane toolbar icons.
  • public/v2/app.js:

(App.Pane._fetch): Delegate the creation of 'chartData' to _computeChartData.
(App.Pane.updateStatisticsTools): Added. Clones moving average and enveloping strategies for this pane.
(App.Pane._cloneStrategy): Added. Clones a strategy for a new pane.
(App.Pane._configureStrategy): Added. Finds and configures a strategy from the configuration retrieved
from the query string via ChartsController.
(App.Pane._computeChartData): Added. Creates chartData from fetchedData.
(App.Pane._computeMovingAverage): Added. Computes the moving average and the envelope.
(App.Pane._executeStrategy): Added.
(App.Pane._updateStrategyConfigIfNeeded): Pushes the strategy configurations to the query string via
ChartsController.
(App.ChartsController._parsePaneList): Merged the query string arguments for the range and point
selections, and added two new arguments for the moving average and the enveloping configurations.
(App.ChartsController._serializePaneList): Ditto.
(App.ChartsController._scheduleQueryStringUpdate): Observes strategy configurations.
(App.PaneController.actions.toggleBugsPane): Hides the stat pane.
(App.PaneController.actions.toggleSearchPane): Hides the stat pane.
(App.PaneController.actions.toggleStatPane): Added.

  • public/v2/chart-pane.css: Added CSS rules for the new stat pane. Also added .foreground class for the

current (as opposed to baseline and target) time series for when it's the most foreground graph without
moving average and its envelope overlapping on top of it.

  • public/v2/index.html: Added the templates for the stat pane and the corresponding icon (Σ).
  • public/v2/interactive-chart.js:

(App.InteractiveChartComponent.chartDataDidChange): Unset _totalWidth and _totalHeight to avoid exiting
early inside _updateDimensionsIfNeeded when chartData changes after the initial layout.
(App.InteractiveChartComponent.didInsertElement): Attach event listeners here instead of inside
_constructGraphIfPossible since that could be called multiple times on the same SVG element.
(App.InteractiveChartComponent._constructGraphIfPossible): Clear down the old SVG element we created
but don't bother removing individual paths and circles. Added the code to show the moving average time
series when there is one. Also add "foreground" class on SVG elements for the current time series when
we're not showing the moving average. chart-pane.css has been updated to "dim down" the current time
series when "foreground" is not set.
(App.InteractiveChartComponent._minMaxForAllTimeSeries): Take the moving average time series into
account when computing the y-axis range.
(App.InteractiveChartComponent._brushChanged): Removed 'selectionIsLocked' argument as it's useless.

  • public/v2/js/statistics.js:

(Statistics.MovingAverageStrategies): Added.
(Statistics.EnvelopingStrategies): Added.

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

Legend:

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

    r179766 r179878  
     12015-02-10  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        New perf dashboard should have the ability to overlay moving average with an envelope
     4        https://bugs.webkit.org/show_bug.cgi?id=141438
     5
     6        Reviewed by Andreas Kling.
     7
     8        This patch adds three kinds of moving average strategies and two kinds of enveloping strategies:
     9
     10        Simple Moving Average - The moving average x̄_i of x_i is computed as the arithmetic mean of values
     11        from x_(i - n) though x_(i + m) where n is a non-negative integer and m is a positive integer. It takes
     12        n, backward window size, and m, forward window size, as an argument.
     13
     14        Cumulative Moving Average - x̄_i is computed as the arithmetic mean of all values x_0 though x_i.
     15        That is, x̄_1 = x_1 and x̄_i = ((i - 1) * M_(i - 1) + x_i) / i for all i > 1.
     16
     17        Exponential Moving Average - x̄_i is computed as the weighted average of x_i and x̄_(i - 1) with α as
     18        an argument specifying x_i's weight. To be precise, x̄_1 = x_1 and x̄_i = α * x_i + (α - 1) x̄_(i-1).
     19
     20
     21        Average Difference - The enveloping delta d is computed as the arithmetic mean of the difference
     22        between each x_i and x̄_i.
     23
     24        Moving Average Standard Deviation - d is computed like the standard deviation except the deviation
     25        for each term is measured from the moving average instead of the sample arithmetic mean. i.e. it uses
     26        the average of (x_i - x̄_i)^2 as the "sample variance" instead of the conventional (x_i - x̄)^2 where
     27        x̄ is the sample mean of all x_i's. This change was necessary since our time series is non-stationary.
     28
     29
     30        Each strategy is cloned for an App.Pane instance so that its parameterList can be configured per pane.
     31        The configuration of the currently chosen strategies is saved in the query string for convenience.
     32
     33        Also added the "stat pane" to choose a moving average strategy and a enveloping strategy in each pane.
     34
     35        * public/v2/app.css: Specify the fill color for all SVG groups in the pane toolbar icons.
     36
     37        * public/v2/app.js:
     38        (App.Pane._fetch): Delegate the creation of 'chartData' to _computeChartData.
     39        (App.Pane.updateStatisticsTools): Added. Clones moving average and enveloping strategies for this pane.
     40        (App.Pane._cloneStrategy): Added. Clones a strategy for a new pane.
     41        (App.Pane._configureStrategy): Added. Finds and configures a strategy from the configuration retrieved
     42        from the query string via ChartsController.
     43        (App.Pane._computeChartData): Added. Creates chartData from fetchedData.
     44        (App.Pane._computeMovingAverage): Added. Computes the moving average and the envelope.
     45        (App.Pane._executeStrategy): Added.
     46        (App.Pane._updateStrategyConfigIfNeeded): Pushes the strategy configurations to the query string via
     47        ChartsController.
     48        (App.ChartsController._parsePaneList): Merged the query string arguments for the range and point
     49        selections, and added two new arguments for the moving average and the enveloping configurations.
     50        (App.ChartsController._serializePaneList): Ditto.
     51        (App.ChartsController._scheduleQueryStringUpdate): Observes strategy configurations.
     52        (App.PaneController.actions.toggleBugsPane): Hides the stat pane.
     53        (App.PaneController.actions.toggleSearchPane): Hides the stat pane.
     54        (App.PaneController.actions.toggleStatPane): Added.
     55
     56        * public/v2/chart-pane.css: Added CSS rules for the new stat pane. Also added .foreground class for the
     57        current (as opposed to baseline and target) time series for when it's the most foreground graph without
     58        moving average and its envelope overlapping on top of it.
     59
     60        * public/v2/index.html: Added the templates for the stat pane and the corresponding icon (Σ).
     61
     62        * public/v2/interactive-chart.js:
     63        (App.InteractiveChartComponent.chartDataDidChange): Unset _totalWidth and _totalHeight to avoid exiting
     64        early inside _updateDimensionsIfNeeded when chartData changes after the initial layout.
     65        (App.InteractiveChartComponent.didInsertElement): Attach event listeners here instead of inside
     66        _constructGraphIfPossible since that could be called multiple times on the same SVG element.
     67        (App.InteractiveChartComponent._constructGraphIfPossible): Clear down the old SVG element we created
     68        but don't bother removing individual paths and circles. Added the code to show the moving average time
     69        series when there is one. Also add "foreground" class on SVG elements for the current time series when
     70        we're not showing the moving average. chart-pane.css has been updated to "dim down" the current time
     71        series when "foreground" is not set.
     72        (App.InteractiveChartComponent._minMaxForAllTimeSeries): Take the moving average time series into
     73        account when computing the y-axis range.
     74        (App.InteractiveChartComponent._brushChanged): Removed 'selectionIsLocked' argument as it's useless.
     75
     76        * public/v2/js/statistics.js:
     77        (Statistics.MovingAverageStrategies): Added.
     78        (Statistics.EnvelopingStrategies): Added.
     79
    1802015-02-06  Ryosuke Niwa  <rniwa@webkit.org>
    281
  • trunk/Websites/perf.webkit.org/public/v2/app.css

    r179763 r179878  
    122122.icon-button g {
    123123    stroke: #ccc;
     124    fill: #ccc;
    124125}
    125126.icon-button:hover g {
    126127    stroke: #666;
     128    fill: #666;
    127129}
    128130.disabled .icon-button:hover g {
    129131    stroke: #ccc;
     132    fill: #ccc;
    130133}
    131134
  • trunk/Websites/perf.webkit.org/public/v2/app.js

    r179766 r179878  
    352352                self.set('platform', result.platform);
    353353                self.set('metric', result.metric);
    354                 self.set('chartData', App.createChartData(result));
     354                self.set('fetchedData', result);
     355                self._computeChartData();
    355356            }, function (result) {
    356357                if (!result || typeof(result) === "string")
     
    432433        return this.computeStatus(lastPoint, chartData.current.previousPoint(lastPoint));
    433434    }.property('chartData'),
     435    updateStatisticsTools: function ()
     436    {
     437        var movingAverageStrategies = Statistics.MovingAverageStrategies.map(this._cloneStrategy.bind(this));
     438        this.set('movingAverageStrategies', [{label: 'None'}].concat(movingAverageStrategies));
     439        this.set('chosenMovingAverageStrategy', this._configureStrategy(movingAverageStrategies, this.get('movingAverageConfig')));
     440
     441        var envelopingStrategies = Statistics.EnvelopingStrategies.map(this._cloneStrategy.bind(this));
     442        this.set('envelopingStrategies', [{label: 'None'}].concat(envelopingStrategies));
     443        this.set('chosenEnvelopingStrategy', this._configureStrategy(envelopingStrategies, this.get('envelopingConfig')));
     444    }.on('init'),
     445    _cloneStrategy: function (strategy)
     446    {
     447        var parameterList = (strategy.parameterList || []).map(function (param) { return Ember.Object.create(param); });
     448        return Ember.Object.create({
     449            id: strategy.id,
     450            label: strategy.label,
     451            description: strategy.description,
     452            parameterList: parameterList,
     453            execute: strategy.execute,
     454        });
     455    },
     456    _configureStrategy: function (strategies, config)
     457    {
     458        if (!config || !config[0])
     459            return null;
     460
     461        var id = config[0];
     462        var chosenStrategy = strategies.find(function (strategy) { return strategy.id == id });
     463        if (!chosenStrategy)
     464            return null;
     465
     466        for (var i = 0; i < chosenStrategy.parameters.length; i++)
     467            chosenStrategy.parameters[i] = parseFloat(config[i + 1]);
     468
     469        return chosenStrategy;
     470    },
     471    _computeChartData: function ()
     472    {
     473        if (!this.get('fetchedData'))
     474            return;
     475
     476        var chartData = App.createChartData(this.get('fetchedData'));
     477        chartData.movingAverage = this._computeMovingAverage(chartData);
     478
     479        this._updateStrategyConfigIfNeeded(this.get('chosenMovingAverageStrategy'), 'movingAverageConfig');
     480        this._updateStrategyConfigIfNeeded(this.get('chosenEnvelopingStrategy'), 'envelopingConfig');
     481
     482        this.set('chartData', chartData);
     483    }.observes('chosenMovingAverageStrategy', 'chosenMovingAverageStrategy.parameterList.@each.value',
     484        'chosenEnvelopingStrategy', 'chosenEnvelopingStrategy.parameterList.@each.value'),
     485    _computeMovingAverage: function (chartData)
     486    {
     487        var currentTimeSeriesData = chartData.current.series();
     488        var movingAverageStrategy = this.get('chosenMovingAverageStrategy');
     489        if (!movingAverageStrategy || !movingAverageStrategy.execute)
     490            return null;
     491
     492        var movingAverageValues = this._executeStrategy(movingAverageStrategy, currentTimeSeriesData);
     493        if (!movingAverageValues)
     494            return null;
     495
     496        var envelopeDelta = null;
     497        var envelopingStrategy = this.get('chosenEnvelopingStrategy');
     498        if (envelopingStrategy && envelopingStrategy.execute)
     499            envelopeDelta = this._executeStrategy(envelopingStrategy, currentTimeSeriesData, [movingAverageValues]);
     500       
     501        return new TimeSeries(currentTimeSeriesData.map(function (point, index) {
     502            var value = movingAverageValues[index];
     503            return {
     504                measurement: point.measurement,
     505                time: point.time,
     506                value: value,
     507                interval: envelopeDelta !== null ? [value - envelopeDelta, value + envelopeDelta] : null,
     508            }
     509        }));
     510    },
     511    _executeStrategy: function (strategy, currentTimeSeriesData, additionalArguments)
     512    {
     513        var parameters = (strategy.parameterList || []).map(function (param) {
     514            var parsed = parseFloat(param.value);
     515            return Math.min(param.max || Infinity, Math.max(param.min || -Infinity, isNaN(parsed) ? 0 : parsed));
     516        });
     517        parameters.push(currentTimeSeriesData.map(function (point) { return point.value }));
     518        return strategy.execute.apply(window, parameters.concat(additionalArguments));
     519    },
     520    _updateStrategyConfigIfNeeded: function (strategy, configName)
     521    {
     522        var config = null;
     523        if (strategy && strategy.execute)
     524            config = [strategy.id].concat((strategy.parameterList || []).map(function (param) { return param.value; }));
     525
     526        if (JSON.stringify(config) != JSON.stringify(this.get(configName)))
     527            this.set(configName, config);
     528    },
    434529});
    435530
     
    553648            return null;
    554649
    555         // Don't re-create all panes.
     650        // FIXME: Don't re-create all panes.
    556651        var self = this;
    557652        return parsedPaneList.map(function (paneInfo) {
    558653            var timeRange = null;
    559             if (paneInfo[3] && paneInfo[3] instanceof Array) {
    560                 var timeRange = paneInfo[3];
     654            var selectedItem = null;
     655            if (paneInfo[2] instanceof Array) {
     656                var timeRange = paneInfo[2];
    561657                try {
    562658                    timeRange = [new Date(timeRange[0]), new Date(timeRange[1])];
     
    564660                    console.log("Failed to parse the time range:", timeRange, error);
    565661                }
    566             }
     662            } else
     663                selectedItem = paneInfo[2];
     664
    567665            return App.Pane.create({
    568666                store: self.store,
     
    570668                platformId: paneInfo[0],
    571669                metricId: paneInfo[1],
    572                 selectedItem: paneInfo[2],
     670                selectedItem: selectedItem,
    573671                timeRange: timeRange,
    574                 timeRangeIsLocked: !!paneInfo[4],
     672                movingAverageConfig: paneInfo[3],
     673                envelopingConfig: paneInfo[4],
    575674            });
    576675        });
     
    581680        if (!panes.length)
    582681            return undefined;
     682        var self = this;
    583683        return App.encodePrettifiedJSON(panes.map(function (pane) {
    584684            return [
    585685                pane.get('platformId'),
    586686                pane.get('metricId'),
    587                 pane.get('selectedItem'),
    588                 pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : null,
    589                 !!pane.get('timeRangeIsLocked'),
     687                pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : pane.get('selectedItem'),
     688                pane.get('movingAverageConfig'),
     689                pane.get('envelopingConfig'),
    590690            ];
    591691        }));
     
    595695    {
    596696        Ember.run.debounce(this, '_updateQueryString', 1000);
    597     }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem',
    598         'panes.@each.timeRange', 'panes.@each.timeRangeIsLocked'),
     697    }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem', 'panes.@each.timeRange',
     698        'panes.@each.movingAverageConfig', 'panes.@each.envelopingConfig'),
    599699
    600700    _updateQueryString: function ()
     
    712812        toggleBugsPane: function ()
    713813        {
    714             if (this.toggleProperty('showingAnalysisPane'))
     814            if (this.toggleProperty('showingAnalysisPane')) {
    715815                this.set('showingSearchPane', false);
     816                this.set('showingStatPane', false);
     817            }
    716818        },
    717819        createAnalysisTask: function ()
     
    744846            if (!model.get('commitSearchRepository'))
    745847                model.set('commitSearchRepository', App.Manifest.repositoriesWithReportedCommits[0]);
    746             if (this.toggleProperty('showingSearchPane'))
     848            if (this.toggleProperty('showingSearchPane')) {
    747849                this.set('showingAnalysisPane', false);
     850                this.set('showingStatPane', false);
     851            }
    748852        },
    749853        searchCommit: function () {
    750854            var model = this.get('model');
    751855            model.searchCommit(model.get('commitSearchRepository'), model.get('commitSearchKeyword'));               
     856        },
     857        toggleStatPane: function ()
     858        {
     859            if (this.toggleProperty('showingStatPane')) {
     860                this.set('showingSearchPane', false);
     861                this.set('showingAnalysisPane', false);
     862            }
    752863        },
    753864        zoomed: function (selection)
     
    787898        if (App.domainsAreEqual(newSelection, this.get('mainPlotDomain')))
    788899            return;
    789         this.set('mainPlotDomain', newSelection);
     900        this.set('mainPlotDomain', newSelection || this.get('overviewDomain'));
    790901        this.set('overviewSelection', newSelection);
    791902    }.observes('parentController.sharedZoom').on('init'),
  • trunk/Websites/perf.webkit.org/public/v2/chart-pane.css

    r179763 r179878  
    5151}
    5252
     53.chart-pane a.stat-button {
     54    display: inline-block;
     55    position: absolute;
     56    right: 3.15rem;
     57    top: 0.55rem;
     58}
     59
    5360.chart-pane a.bugs-button {
    5461    display: inline-block;
     
    6572}
    6673
    67 .search-pane, .analysis-pane {
     74.popup-pane {
    6875    position: absolute;
    6976    top: 1.7rem;
    7077    border: 1px solid #bbb;
    71     padding: 0;
     78    font-size: 0.8rem;
     79    padding: 0.2rem;
    7280    border-radius: 0.5rem;
    7381    display: table;
     
    7583}
    7684
     85.popup-pane.hidden {
     86    display: none;
     87}
     88
     89.stat-pane {
     90    right: 2.6rem;
     91    padding: 0;
     92}
     93
     94.stat-pane fieldset {
     95    border: solid 1px #ccc;
     96    border-radius: 0.5rem;
     97    margin: 0.2rem;
     98    padding: 0;
     99}
     100
     101.stat-option {
     102    margin: 0;
     103    padding: 0;
     104    font-size: 0.8rem;
     105}
     106
     107.stat-option h1 {
     108    font-size: inherit;
     109    line-height: 0.8rem;
     110    padding: 0.3rem 0.5rem;
     111    margin: 0;
     112    border-top: solid 1px #ccc;
     113    border-bottom: solid 1px #ccc;
     114}
     115
     116.stat-option:first-child h1 {
     117    border-top: none;
     118}
     119
     120.stat-option > * {
     121    display: block;
     122    margin: 0.1rem 0.5rem 0.1rem 1rem;
     123}
     124
     125.stat-option input {
     126    width: 4rem;
     127}
     128
     129.stat-option p {
     130    max-width: 15rem;
     131}
     132
    77133.analysis-pane {
    78134    right: 1.3rem;
    79135}
    80136
    81 .analysis-pane table {
     137.analysis-pane > * {
    82138    margin: 0.2rem;
    83     font-size: 0.8rem;
    84 }
    85 
    86 .analysis-pane th {
    87     font-weight: normal;
    88139}
    89140
    90141.search-pane {
    91142    right: 0rem;
    92 }
    93 
    94 .analysis-pane.hidden,
    95 .search-pane.hidden {
    96     display: none;
    97143}
    98144
     
    104150    border-top-right-radius: 0.5rem;
    105151    border-bottom-right-radius: 0.5rem;
    106     padding: 0.5rem;
    107     font-size: 1rem;
     152    padding: 0.2rem;
     153    font-size: 0.8rem;
    108154    margin: 0;
    109155}
     
    112158    display: table-cell;
    113159    vertical-align: middle;
    114     padding: 0 0.5rem;
     160    padding: 0;
    115161}
    116162
     
    304350
    305351.chart .dot {
     352    fill: #ccc;
     353    stroke: none;
     354}
     355.chart .dot.foreground {
    306356    fill: #666;
    307     stroke: none;
    308357}
    309358
     
    313362    opacity: 0.8;
    314363}
     364.chart path.area.foreground {
     365}
    315366
    316367.chart path.current {
     368    stroke: #ccc;
     369}
     370
     371.chart path.current.foreground {
    317372    stroke: #999;
     373}
     374
     375.chart path.movingAverage {
     376    stroke: #363;
     377    fill: none;
     378    opacity: 0.8;
     379}
     380
     381.chart path.envelope {
     382    stroke: none;
     383    fill: #6c6;
     384    opacity: 0.4;
    318385}
    319386
  • trunk/Websites/perf.webkit.org/public/v2/index.html

    r179763 r179878  
    148148                    <h1 {{action "toggleDetails"}}>{{metric.fullName}} - {{ platform.name}}</h1>
    149149                    <a href="#" title="Close" class="close-button" {{action "close"}}>{{partial "close-button"}}</a>
     150                    {{#if movingAverageStrategies}}
     151                        <a href="#" title="Statistical Tools" class="stat-button" {{action "toggleStatPane"}}>{{partial "stat-button"}}</a>
     152                    {{/if}}
    150153                    {{#if App.Manifest.bugTrackers}}
    151154                        <a href="#" title="Analysis" class="bugs-button" {{action "toggleBugsPane"}}>
     
    174177                            selection=timeRange
    175178                            selectedPoints=selectedPoints
    176                             selectionIsLocked=timeRangeIsLocked
    177179                            markedPoints=markedPoints
    178180                            zoom="zoomed"}}
     
    201203                </div>
    202204
    203                 <form {{bind-attr class=":search-pane showingSearchPane::hidden"}}>
     205                <div {{bind-attr class=":popup-pane :analysis-pane showingAnalysisPane::hidden"}}>
     206                    <label>Name: {{input type=text value=newAnalysisTaskName}}</label>
     207                    <button {{action "createAnalysisTask"}} {{bind-attr disabled=cannotAnalyze}}>Analyze</button>
     208                </div>
     209
     210                <form {{bind-attr class=":popup-pane :search-pane showingSearchPane::hidden"}}>
    204211                    <span class="repositories">
    205212                        {{view Ember.Select
     
    212219                </form>
    213220
    214                 <div {{bind-attr class=":analysis-pane showingAnalysisPane::hidden"}}>
    215                     <table>
    216                         <tbody>
    217                             <tr>
    218                                 <th>
    219                                     <label>Name: {{input type=text value=newAnalysisTaskName}}</label>
    220                                     <button {{action "createAnalysisTask"}} {{bind-attr disabled=cannotAnalyze}}>Analyze</button>
    221                                 </th>
    222                             </tr>
    223                         </tbody>
    224                     </table>
    225                 </div>
    226 
     221                {{partial "stat-pane"}}
    227222            </section>
    228223        {{/each}}
     
    357352    </script>
    358353
     354    <script type="text/x-handlebars" data-template-name="stat-button">
     355        <svg class="stat-button icon-button" viewBox="10 0 110 100">
     356            <g stroke="none" stroke-width="0" fill="black">
     357                <path id="upper-sigma" d="M 5 5 H 95 V 40 h -10 c -5 -20 -5 -20 -25 -20 H 35 L 60 50 l -20 0" />
     358                <use xlink:href="#upper-sigma" transform="translate(0, 100) scale(1, -1)" />
     359            </g>
     360        </svg>
     361    </script>
     362
     363    <script type="text/x-handlebars" data-template-name="stat-pane">
     364        <section {{bind-attr class=":popup-pane :stat-pane showingStatPane::hidden"}}>
     365            <section class="stat-option">
     366                <h1>Moving average</h1>
     367                <label>Type: {{view Ember.Select
     368                    content=movingAverageStrategies
     369                    optionValuePath='content'
     370                    optionLabelPath='content.label'
     371                    selection=chosenMovingAverageStrategy}}</label>
     372                {{#each chosenMovingAverageStrategy.parameterList}}
     373                    <label>{{label}}: {{input type="number" value=value min=min max=max step=step}}</label>
     374                {{/each}}
     375            </section>
     376            {{#if chosenMovingAverageStrategy.execute}}
     377                <section class="stat-option">
     378                    <h1>Envelope</h1>
     379                    <label>Type: {{view Ember.Select
     380                        content=envelopingStrategies
     381                        optionValuePath='content'
     382                        optionLabelPath='content.label'
     383                        selection=chosenEnvelopingStrategy}}</label>
     384                    {{#if chosenEnvelopingStrategy.description}}
     385                        <p class="description">{{chosenEnvelopingStrategy.description}}</p>
     386                    {{/if}}
     387                    {{#each chosenEnvelopingStrategy.parameterList}}
     388                        <label>{{label}}: <input type="number" {{bind-attr value=value min=min max=max step=step}}></label>
     389                    {{/each}}
     390                </section>
     391            {{/if}}
     392        </section>
     393    </script>
     394
    359395    <script type="text/x-handlebars" data-template-name="analysis-button">
    360396        <svg class="analysis-button icon-button" viewBox="0 0 100 100">
    361             <g stroke="black" stroke-width="15">
     397            <g stroke="black" fill="black" stroke-width="15">
    362398                <circle cx="50" cy="50" r="40" fill="transparent"/>
    363399                <line x1="50" y1="25" x2="50" y2="55"/>
    364                 <circle cx="50" cy="67.5" r="2.5" fill="transparent"/>
     400                <circle cx="50" cy="67.5" r="10" stroke="none"/>
    365401            </g>
    366402        </svg>
  • trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js

    r179763 r179878  
    1919            return;
    2020        this._needsConstruction = true;
     21        this._totalWidth = undefined;
     22        this._totalHeight = undefined;
    2123        this._constructGraphIfPossible(chartData);
    2224    }.observes('chartData').on('init'),
     
    2628        if (chartData)
    2729            this._constructGraphIfPossible(chartData);
     30
     31        if (this.get('interactive')) {
     32            var element = this.get('element');
     33            this._attachEventListener(element, "mousemove", this._mouseMoved.bind(this));
     34            this._attachEventListener(element, "mouseleave", this._mouseLeft.bind(this));
     35            this._attachEventListener(element, "mousedown", this._mouseDown.bind(this));
     36            this._attachEventListener($(element).parents("[tabindex]"), "keydown", this._keyPressed.bind(this));
     37        }
    2838    },
    2939    willClearRender: function ()
     
    4858        this._y = d3.scale.linear();
    4959
    50         // FIXME: Tear down the old SVG element.
     60        if (this._svgElement)
     61            this._svgElement.remove();
    5162        this._svgElement = d3.select(element).append("svg")
    5263                .attr("width", "100%")
     
    8798            .y1(function(point) { return point.interval ? yScale(point.interval[1]) : null; });
    8899
    89         if (this._paths)
    90             this._paths.forEach(function (path) { path.remove(); });
    91100        this._paths = [];
    92         if (this._areas)
    93             this._areas.forEach(function (area) { area.remove(); });
    94101        this._areas = [];
    95         if (this._dots)
    96             this._dots.forEach(function (dot) { dots.remove(); });
    97102        this._dots = [];
    98         if (this._highlights)
    99             this._highlights.remove();
    100103        this._highlights = null;
    101104
     
    104107        this._baselineTimeSeries = chartData.baseline;
    105108        this._targetTimeSeries = chartData.target;
     109        this._movingAverageTimeSeries = chartData.movingAverage;
    106110
    107111        this._yAxisUnit = chartData.unit;
     
    120124        }
    121125
     126        var foregroundClass = this._movingAverageTimeSeries ? '' : ' foreground';
    122127        this._areas.push(this._clippedContainer
    123128            .append("path")
    124129            .datum(this._currentTimeSeriesData)
    125             .attr("class", "area"));
     130            .attr("class", "area" + foregroundClass));
    126131
    127132        this._paths.push(this._clippedContainer
    128133            .append("path")
    129134            .datum(this._currentTimeSeriesData)
    130             .attr("class", "current"));
     135            .attr("class", "current" + foregroundClass));
    131136
    132137        this._dots.push(this._clippedContainer
     
    134139                .data(this._currentTimeSeriesData)
    135140            .enter().append("circle")
    136                 .attr("class", "dot")
     141                .attr("class", "dot" + foregroundClass)
    137142                .attr("r", this.get('chartPointRadius') || 1));
    138143
     144        if (this._movingAverageTimeSeries) {
     145            this._paths.push(this._clippedContainer
     146                .append("path")
     147                .datum(this._movingAverageTimeSeries.series())
     148                .attr("class", "movingAverage"));
     149            this._areas.push(this._clippedContainer
     150                .append("path")
     151                .datum(this._movingAverageTimeSeries.series())
     152                .attr("class", "envelope"));
     153        }
     154
    139155        if (this.get('interactive')) {
    140             this._attachEventListener(element, "mousemove", this._mouseMoved.bind(this));
    141             this._attachEventListener(element, "mouseleave", this._mouseLeft.bind(this));
    142             this._attachEventListener(element, "mousedown", this._mouseDown.bind(this));
    143             this._attachEventListener($(element).parents("[tabindex]"), "keydown", this._keyPressed.bind(this));
    144 
    145156            this._currentItemLine = this._clippedContainer
    146157                .append("line")
     
    332343        var baselineRange = this._baselineTimeSeries ? this._baselineTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
    333344        var targetRange = this._targetTimeSeries ? this._targetTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
     345        var movingAverageRange = this._movingAverageTimeSeries ? this._movingAverageTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
    334346        return [
    335             Math.min(currentRange[0], baselineRange[0], targetRange[0]),
    336             Math.max(currentRange[1], baselineRange[1], targetRange[1]),
     347            Math.min(currentRange[0], baselineRange[0], targetRange[0], movingAverageRange[0]),
     348            Math.max(currentRange[1], baselineRange[1], targetRange[1], movingAverageRange[1]),
    337349        ];
    338350    },
     
    379391                return;
    380392
    381             this.set('selectionIsLocked', false);
    382393            this._setCurrentSelection(undefined);
    383394
     
    392403        }
    393404
    394         this.set('selectionIsLocked', true);
    395405        this._setCurrentSelection(this._brush.extent());
    396406    },
  • trunk/Websites/perf.webkit.org/public/v2/js/statistics.js

    r174477 r179878  
    100100    };
    101101
     102    this.MovingAverageStrategies = [
     103        {
     104            id: 1,
     105            label: 'Simple Moving Average',
     106            parameterList: [
     107                {label: "Backward window size", value: 5, min: 2, step: 1},
     108                {label: "Forward window size", value: 3, min: 0, step: 1}
     109            ],
     110            execute: function (backwardWindowSize, forwardWindowSize, values) {
     111                var averages = new Array(values.length);
     112                // We use naive O(n^2) algorithm for simplicy as well as to avoid accumulating round-off errors.
     113                for (var i = 0; i < values.length; i++) {
     114                    var sum = 0;
     115                    var count = 0;
     116                    for (var j = i - backwardWindowSize; j < i + backwardWindowSize; j++) {
     117                        if (j >= 0 && j < values.length) {
     118                            sum += values[j];
     119                            count++;
     120                        }
     121                    }
     122                    averages[i] = sum / count;
     123                }
     124                return averages;
     125            },
     126
     127        },
     128        {
     129            id: 2,
     130            label: 'Cumulative Moving Average',
     131            execute: function (values) {
     132                var averages = new Array(values.length);
     133                var sum = 0;
     134                for (var i = 0; i < values.length; i++) {
     135                    sum += values[i];
     136                    averages[i] = sum / (i + 1);
     137                }
     138                return averages;
     139            }
     140        },
     141        {
     142            id: 3,
     143            label: 'Exponential Moving Average',
     144            parameterList: [{label: "Smoothing factor", value: 0.1, min: 0.001, max: 0.9}],
     145            execute: function (smoothingFactor, values) {
     146                if (!values.length || typeof(smoothingFactor) !== "number")
     147                    return null;
     148
     149                var averages = new Array(values.length);
     150                var movingAverage = 0;
     151                averages[0] = values[0];
     152                for (var i = 1; i < values.length; i++)
     153                    averages[i] = smoothingFactor * values[i] + (1 - smoothingFactor) * averages[i - 1];
     154                return averages;
     155            }
     156        },
     157    ];
     158
     159    this.EnvelopingStrategies = [
     160        {
     161            id: 100,
     162            label: 'Average Difference',
     163            description: 'The average difference between consecutive values.',
     164            execute: function (values, movingAverages) {
     165                if (values.length < 1)
     166                    return NaN;
     167
     168                var diff = 0;
     169                for (var i = 1; i < values.length; i++)
     170                    diff += Math.abs(values[i] - values[i - 1]);
     171
     172                return diff / values.length;
     173            }
     174        },
     175        {
     176            id: 101,
     177            label: 'Moving Average Standard Deviation',
     178            description: 'The square root of the average deviation from the moving average with Bessel\'s correction.',
     179            execute: function (values, movingAverages) {
     180                if (values.length < 1)
     181                    return NaN;
     182
     183                var diffSquareSum = 0;
     184                for (var i = 1; i < values.length; i++) {
     185                    var diff = (values[i] - movingAverages[i]);
     186                    diffSquareSum += diff * diff;
     187                }
     188
     189                return Math.sqrt(diffSquareSum / (values.length - 1));
     190            }
     191        },
     192    ];
    102193})();
    103194
Note: See TracChangeset for help on using the changeset viewer.