Changeset 180468 in webkit


Ignore:
Timestamp:
Feb 20, 2015 5:28:34 PM (9 years ago)
Author:
rniwa@webkit.org
Message:

Loading the perf dashboard takes multiple seconds
https://bugs.webkit.org/show_bug.cgi?id=141860

Reviewed by Andreas Kling.

This patch introduces the caches of JSON files returned by /api/ in /data/ directory. It also records
the last time test_runs rows associated with the requested platforms and metrics are inserted, updated,
or removed in the caches as well as the manifest JSON files ("last modified time"). Because the manifest
is regenerated each time a new test result is reported, the front end can compare last modified time in
the manifest file with that in a /api/runs JSON cache to detect the stale-ness.

More concretely, the front end first optimistically fetches the JSON in /data/. If the cache doesn't exit
or the last modified time in the cache doesn't match with that in the manifest file, it would fetch it
again via /api/runs. In the case the cache did exist, we render the charts based on the cache meanwhile.
This dramatically reduces the perceived latency for the page load since charts are drawn immediately using
the cache and we would only re-render the charts as new up-to-date JSON comes in.

This patch also changes the format of runs JSONs by pushing the exiting properties into 'configurations'
and adding 'lastModified' and 'elapsedTime' at the top level.

  • init-database.sql: Added config_runs_last_modified to test_configurations table as well as a trigger to

auto-update this column upon changes to test_runs table.

  • public/admin/test-configurations.php:

(add_run): Regenerate the manifest file to invalidate the /api/runs JSON cache.
(delete_run): Ditto.

  • public/api/runs.php:

(main): Fetch all columns of test_configurations table including config_runs_last_modified. Also generate
the cache in /data/ directory.
(RunsGenerator::construct): Compute the last modified time for this (platform, metric) pair.
(RunsGenerator::results): Put the old content in 'configurations' property and include 'lastModified' and
'elapsedTime' properties. 'elapsedTime' is added for debugging purposes.
(RunsGenerator::add_runs):
(RunsGenerator::parse_revisions_array):

  • public/include/db.php:

(CONFIG_DIR): Added.
(generate_data_file): Added based on ManifestGenerator::store.
(Database::to_js_time): Extracted from RunsGenerator::add_runs to share code.

  • public/include/json-header.php:

(echo_success): Renamed from success_json. Return the serialized JSON instead of echo'ing it so that we can
generate caches in /api/runs/.
(exit_with_success):

  • public/include/manifest.php:

(ManifestGenerator::generate): Added 'elapsedTime' property for the time taken to generate the manifest.
It seems like we're generating it in 200-300ms for now so that's good.
(ManifestGenerator::store): Uses generate_data_file.
(ManifestGenerator::platforms): Added 'lastModified' array to each platform entry. This array contains the
last modified time for each (platform, metric) pair.

  • public/index.html:

(fetchTest): Updated per the format change in runs JSON.

  • public/v2/app.js:

(App.Pane._fetch): Fetch the cached JSON first. Refetch the uncached version if instructed as such.
(App.Pane._updateChartData): Extracted from App.Pane._fetch.
(App.Pane._handleFetchErrors): Ditto.

  • public/v2/data.js:

(RunsData.fetchRuns): Takes the fourth argument indicating whether we should fetch the cached version or not.
The cached JSON is located in /data/ with the same filename. When fetching a cached JSON results in 404,
fulfill the promise with null as the result instead of rejecting it. The only client of this function which
sets useCache to true is App.Manifest.fetchRunsWithPlatformAndMetric, and it handles this special case.

  • public/v2/manifest.js:

(App.DateArrayTransform): Added. Handles the array of last modified dates in platform objects.
(App.Platform.lastModifiedTimeForMetric): Added. Returns the last modified date in the manifest JSON.
(App.Manifest.fetchRunsWithPlatformAndMetric): Takes "useCache" like RunsData.fetchRuns. Set shouldRefetch
to true if response is null (the cache didn't exit) or the cache is out-of-date.
(App.Manifest._formatFetchedData): Extracted from App.Manifest.fetchRunsWithPlatformAndMetric.

  • run-tests.js:

(initializeDatabase): Avoid splitting function definitions in the middle.

  • tests/api-report.js: Added tests to verify that reporting new test results updates the last modified time

in test_configurations.

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

Legend:

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

    r180466 r180468  
     12015-02-20  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Loading the perf dashboard takes multiple seconds
     4        https://bugs.webkit.org/show_bug.cgi?id=141860
     5
     6        Reviewed by Andreas Kling.
     7
     8        This patch introduces the caches of JSON files returned by /api/ in /data/ directory. It also records
     9        the last time test_runs rows associated with the requested platforms and metrics are inserted, updated,
     10        or removed in the caches as well as the manifest JSON files ("last modified time"). Because the manifest
     11        is regenerated each time a new test result is reported, the front end can compare last modified time in
     12        the manifest file with that in a /api/runs JSON cache to detect the stale-ness.
     13
     14        More concretely, the front end first optimistically fetches the JSON in /data/. If the cache doesn't exit
     15        or the last modified time in the cache doesn't match with that in the manifest file, it would fetch it
     16        again via /api/runs. In the case the cache did exist, we render the charts based on the cache meanwhile.
     17        This dramatically reduces the perceived latency for the page load since charts are drawn immediately using
     18        the cache and we would only re-render the charts as new up-to-date JSON comes in.
     19
     20        This patch also changes the format of runs JSONs by pushing the exiting properties into 'configurations'
     21        and adding 'lastModified' and 'elapsedTime' at the top level.
     22
     23        * init-database.sql: Added config_runs_last_modified to test_configurations table as well as a trigger to
     24        auto-update this column upon changes to test_runs table.
     25
     26        * public/admin/test-configurations.php:
     27        (add_run): Regenerate the manifest file to invalidate the /api/runs JSON cache.
     28        (delete_run): Ditto.
     29
     30        * public/api/runs.php:
     31        (main): Fetch all columns of test_configurations table including config_runs_last_modified. Also generate
     32        the cache in /data/ directory.
     33        (RunsGenerator::__construct): Compute the last modified time for this (platform, metric) pair.
     34        (RunsGenerator::results): Put the old content in 'configurations' property and include 'lastModified' and
     35        'elapsedTime' properties. 'elapsedTime' is added for debugging purposes.
     36        (RunsGenerator::add_runs):
     37        (RunsGenerator::parse_revisions_array):
     38
     39        * public/include/db.php:
     40        (CONFIG_DIR): Added.
     41        (generate_data_file): Added based on ManifestGenerator::store.
     42        (Database::to_js_time): Extracted from RunsGenerator::add_runs to share code.
     43
     44        * public/include/json-header.php:
     45        (echo_success): Renamed from success_json. Return the serialized JSON instead of echo'ing it so that we can
     46        generate caches in /api/runs/.
     47        (exit_with_success):
     48
     49        * public/include/manifest.php:
     50        (ManifestGenerator::generate): Added 'elapsedTime' property for the time taken to generate the manifest.
     51        It seems like we're generating it in 200-300ms for now so that's good.
     52        (ManifestGenerator::store): Uses generate_data_file.
     53        (ManifestGenerator::platforms): Added 'lastModified' array to each platform entry. This array contains the
     54        last modified time for each (platform, metric) pair.
     55
     56        * public/index.html:
     57        (fetchTest): Updated per the format change in runs JSON.
     58
     59        * public/v2/app.js:
     60        (App.Pane._fetch): Fetch the cached JSON first. Refetch the uncached version if instructed as such.
     61        (App.Pane._updateChartData): Extracted from App.Pane._fetch.
     62        (App.Pane._handleFetchErrors): Ditto.
     63
     64        * public/v2/data.js:
     65        (RunsData.fetchRuns): Takes the fourth argument indicating whether we should fetch the cached version or not.
     66        The cached JSON is located in /data/ with the same filename. When fetching a cached JSON results in 404,
     67        fulfill the promise with null as the result instead of rejecting it. The only client of this function which
     68        sets useCache to true is App.Manifest.fetchRunsWithPlatformAndMetric, and it handles this special case.
     69
     70        * public/v2/manifest.js:
     71        (App.DateArrayTransform): Added. Handles the array of last modified dates in platform objects.
     72        (App.Platform.lastModifiedTimeForMetric): Added. Returns the last modified date in the manifest JSON.
     73        (App.Manifest.fetchRunsWithPlatformAndMetric): Takes "useCache" like RunsData.fetchRuns. Set shouldRefetch
     74        to true if response is null (the cache didn't exit) or the cache is out-of-date.
     75        (App.Manifest._formatFetchedData): Extracted from App.Manifest.fetchRunsWithPlatformAndMetric.
     76
     77        * run-tests.js:
     78        (initializeDatabase): Avoid splitting function definitions in the middle.
     79
     80        * tests/api-report.js: Added tests to verify that reporting new test results updates the last modified time
     81        in test_configurations.
     82
    1832015-02-20  Ryosuke Niwa  <rniwa@webkit.org>
    284
  • trunk/Websites/perf.webkit.org/init-database.sql

    r178234 r180468  
    3636CREATE TABLE repositories (
    3737    repository_id serial PRIMARY KEY,
     38    repository_parent integer REFERENCES repositories ON DELETE CASCADE,
    3839    repository_name varchar(64) NOT NULL,
    3940    repository_url varchar(1024),
    40     repository_blame_url varchar(1024));
     41    repository_blame_url varchar(1024),
     42    CONSTRAINT repository_name_must_be_unique UNIQUE(repository_parent, repository_name));
    4143
    4244CREATE TABLE bug_trackers (
     
    122124    config_type test_configuration_type NOT NULL,
    123125    config_is_in_dashboard boolean NOT NULL DEFAULT FALSE,
     126    config_runs_last_modified timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
    124127    CONSTRAINT configuration_must_be_unique UNIQUE(config_metric, config_platform, config_type));
    125128CREATE INDEX config_platform_index ON test_configurations(config_platform);
     
    144147    iteration_relative_time float,
    145148    PRIMARY KEY (iteration_run, iteration_order));
     149
     150CREATE OR REPLACE FUNCTION update_config_last_modified() RETURNS TRIGGER AS $update_config_last_modified$
     151    BEGIN
     152        IF TG_OP != 'DELETE' THEN
     153            UPDATE test_configurations SET config_runs_last_modified = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') WHERE config_id = NEW.run_config;
     154        ELSE
     155            UPDATE test_configurations SET config_runs_last_modified = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') WHERE config_id = OLD.run_config;
     156        END IF;
     157        RETURN NULL;
     158    END;
     159$update_config_last_modified$ LANGUAGE plpgsql;
     160
     161CREATE TRIGGER update_config_last_modified AFTER INSERT OR UPDATE OR DELETE ON test_runs
     162    FOR EACH ROW EXECUTE PROCEDURE update_config_last_modified();
    146163
    147164CREATE TABLE reports (
  • trunk/Websites/perf.webkit.org/public/admin/test-configurations.php

    r163688 r180468  
    3030    $db->commit_transaction();
    3131    notice("Added a baseline test run.");
     32
     33    regenerate_manifest();
    3234}
    3335
     
    6668
    6769    $db->commit_transaction();
     70
     71    regenerate_manifest();
    6872}
    6973
  • trunk/Websites/perf.webkit.org/public/api/runs.php

    r180000 r180468  
    3838    $platform_id = intval($parts[0]);
    3939    $metric_id = intval($parts[1]);
    40     $config_rows = $db->query_and_fetch_all('SELECT config_id, config_type, config_platform, config_metric
     40    $config_rows = $db->query_and_fetch_all('SELECT *
    4141        FROM test_configurations WHERE config_metric = $1 AND config_platform = $2', array($metric_id, $platform_id));
    4242    if (!$config_rows)
     
    5353    }
    5454
    55     $generator = new RunsGenerator();
     55    $generator = new RunsGenerator($config_rows);
    5656
    5757    foreach ($config_rows as $config) {
     
    6363    }
    6464
    65     exit_with_success($generator->results());
     65    $content = success_json($generator->results());
     66    if (!$test_group_id)
     67        generate_data_file("$platform_id-$metric_id.json", $content);
     68    echo $content;
    6669}
    6770
    6871class RunsGenerator {
    69     function __construct() {
     72    function __construct($config_rows) {
    7073        $this->results = array();
     74        $last_modified_times = array();
     75        foreach ($config_rows as $row)
     76            array_push($last_modified_times, Database::to_js_time($row['config_runs_last_modified']));
     77        $this->last_modified = max($last_modified_times);
     78        $this->start_time = microtime(true);
    7179    }
    7280
    73     function &results() { return $this->results; }
     81    function results() {
     82        return array(
     83            'configurations' => &$this->results,
     84            'lastModified' => $this->last_modified,
     85            'elapsedTime' => microtime(true) - $this->start_time);
     86    }
    7487
    7588    function add_runs($name, $raw_runs) {
     
    92105            'revisions' => self::parse_revisions_array($run['revisions']),
    93106            'build' => $run['build_id'],
    94             'buildTime' => strtotime($run['build_time']) * 1000,
     107            'buildTime' => Database::to_js_time($run['build_time']),
    95108            'buildNumber' => intval($run['build_number']),
    96109            'builder' => $run['build_builder']);
     
    105118            if (!$name_and_revision[0])
    106119                continue;
    107             $time = strtotime(trim($name_and_revision[2], '"')) * 1000;
     120            $time = Database::to_js_time(trim($name_and_revision[2], '"'));
    108121            $revisions[trim($name_and_revision[0], '"')] = array(trim($name_and_revision[1], '"'), $time);
    109122        }
  • trunk/Websites/perf.webkit.org/public/include/db.php

    r178234 r180468  
    2828$_config = NULL;
    2929
     30define('CONFIG_DIR', dirname(__FILE__) . '/../../');
     31
    3032function config($key) {
    3133    global $_config;
    3234    if (!$_config)
    33         $_config = json_decode(file_get_contents(dirname(__FILE__) . '/../../config.json'), true);
     35        $_config = json_decode(file_get_contents(CONFIG_DIR . 'config.json'), true);
    3436    return $_config[$key];
     37}
     38
     39function generate_data_file($filename, $content) {
     40    if (!assert(ctype_alnum(str_replace(array('-', '_', '.'), '', $filename))))
     41        return FALSE;
     42    return file_put_contents(CONFIG_DIR . config('dataDirectory') . '/' . $filename, $content);
    3543}
    3644
     
    5563    function is_true($value) {
    5664        return $value == 't';
     65    }
     66
     67    static function to_js_time($time_str) {
     68        return strtotime($time_str) * 1000;
    5769    }
    5870
  • trunk/Websites/perf.webkit.org/public/include/json-header.php

    r178234 r180468  
    1414}
    1515
    16 function echo_success($details = array()) {
     16function success_json($details = array()) {
    1717    $details['status'] = 'OK';
    1818    merge_additional_details($details);
    1919
    20     echo json_encode($details);
     20    return json_encode($details);
    2121}
    2222
    2323function exit_with_success($details = array()) {
    24     echo_success($details);
     24    echo success_json($details);
    2525    exit(0);
    2626}
  • trunk/Websites/perf.webkit.org/public/include/manifest.php

    r179764 r180468  
    1313
    1414    function generate() {
     15        $start_time = microtime(true);
     16
    1517        $config_table = $this->db->fetch_table('test_configurations');
    1618        $platform_table = $this->db->fetch_table('platforms');
     
    3537            'dashboards' => config('dashboards'),
    3638        );
    37         return $this->manifest;
     39
     40        $this->manifest['elapsedTime'] = (microtime(true) - $start_time) * 1000;
     41
     42        return TRUE;
    3843    }
    3944
    4045    function store() {
    41         return file_put_contents(self::MANIFEST_PATH, json_encode($this->manifest));
     46        return generate_data_file('manifest.json', json_encode($this->manifest));
    4247    }
    4348
     
    7883                    continue;
    7984
     85                $new_last_modified = array_get($config_row, 'config_runs_last_modified', 0);
     86                if ($new_last_modified)
     87                    $new_last_modified = strtotime($config_row['config_runs_last_modified']) * 1000;
     88
    8089                $platform = &array_ensure_item_has_array($platform_metrics, $config_row['config_platform']);
    81                 if (!in_array($config_row['config_metric'], $platform))
    82                     array_push($platform, $config_row['config_metric']);
     90                $metrics = &array_ensure_item_has_array($platform, 'metrics');
     91                $last_modified = &array_ensure_item_has_array($platform, 'last_modified');
     92
     93                $metric_id = $config_row['config_metric'];
     94                $index = array_search($metric_id, $metrics);
     95                if ($index === FALSE) {
     96                    array_push($metrics, $metric_id);
     97                    array_push($last_modified, $new_last_modified);
     98                } else
     99                    $last_modified[$index] = max($last_modified[$index], $new_last_modified);
    83100            }
    84101        }
     102        $configurations = array();
     103       
    85104        $platforms = array();
    86105        if ($platform_table) {
     
    89108                    continue;
    90109                $id = $platform_row['platform_id'];
    91                 if (array_key_exists($id, $platform_metrics))
    92                     $platforms[$id] = array('name' => $platform_row['platform_name'], 'metrics' => $platform_metrics[$id]);
     110                if (array_key_exists($id, $platform_metrics)) {
     111                    $platforms[$id] = array(
     112                        'name' => $platform_row['platform_name'],
     113                        'metrics' => $platform_metrics[$id]['metrics'],
     114                        'lastModified' => $platform_metrics[$id]['last_modified']);
     115                }
    93116            }
    94117        }
  • trunk/Websites/perf.webkit.org/public/index.html

    r175006 r180468  
    841841    }
    842842
    843     $.getJSON('api/runs/' + filename, function (data) {
     843    $.getJSON('api/runs/' + filename, function (response) {
     844        var data = response.configurations;
    844845        callback(createRunAndResults(data.current), createRunAndResults(data.baseline), createRunAndResults(data.target));
    845846    });
  • trunk/Websites/perf.webkit.org/public/v2/app.js

    r180451 r180468  
    350350            this.set('failure', metricId ? 'Invalid metric id:' + metricId : 'Metric id was not specified');
    351351        else {
    352             var self = this;
    353 
    354             App.Manifest.fetchRunsWithPlatformAndMetric(this.get('store'), platformId, metricId).then(function (result) {
    355                 self.set('platform', result.platform);
    356                 self.set('metric', result.metric);
    357                 self.set('chartData', result.data);
    358                 self._updateMovingAverageAndEnvelope();
    359             }, function (result) {
    360                 if (!result || typeof(result) === "string")
    361                     self.set('failure', 'Failed to fetch the JSON with an error: ' + result);
    362                 else if (!result.platform)
    363                     self.set('failure', 'Could not find the platform "' + platformId + '"');
    364                 else if (!result.metric)
    365                     self.set('failure', 'Could not find the metric "' + metricId + '"');
    366                 else
    367                     self.set('failure', 'An internal error');
    368             });
    369 
     352            var store = this.get('store');
     353            var updateChartData = this._updateChartData.bind(this);
     354            var handleErrors = this._handleFetchErrors.bind(this, platformId, metricId);
     355            var useCache = true;
     356            App.Manifest.fetchRunsWithPlatformAndMetric(store, platformId, metricId, null, useCache).then(function (result) {
     357                    updateChartData(result);
     358                    if (!result.shouldRefetch)
     359                        return;
     360
     361                    useCache = false;
     362                    App.Manifest.fetchRunsWithPlatformAndMetric(store, platformId, metricId, null, useCache)
     363                        .then(updateChartData, handleErrors);
     364                }, handleErrors);
    370365            this.fetchAnalyticRanges();
    371366        }
    372367    }.observes('platformId', 'metricId').on('init'),
     368    _updateChartData: function (result)
     369    {
     370        this.set('platform', result.platform);
     371        this.set('metric', result.metric);
     372        this.set('chartData', result.data);
     373        this._updateMovingAverageAndEnvelope();
     374    },
     375    _handleFetchErrors: function (platformId, metricId, result)
     376    {
     377        console.log(platformId, metricId, result)
     378        if (!result || typeof(result) === "string")
     379            this.set('failure', 'Failed to fetch the JSON with an error: ' + result);
     380        else if (!result.platform)
     381            this.set('failure', 'Could not find the platform "' + platformId + '"');
     382        else if (!result.metric)
     383            this.set('failure', 'Could not find the metric "' + metricId + '"');
     384        else
     385            this.set('failure', 'An internal error');
     386    },
    373387    fetchAnalyticRanges: function ()
    374388    {
  • trunk/Websites/perf.webkit.org/public/v2/data.js

    r180365 r180468  
    326326// FIXME: We need to devise a way to fetch runs in multiple chunks so that
    327327// we don't have to fetch the entire time series to just show the last 3 days.
    328 RunsData.fetchRuns = function (platformId, metricId, testGroupId)
    329 {
    330     var filename = platformId + '-' + metricId + '.json';
    331 
     328RunsData.fetchRuns = function (platformId, metricId, testGroupId, useCache)
     329{
     330    var url = useCache ? '../data/' : '../api/runs/';
     331
     332    url += platformId + '-' + metricId + '.json';
    332333    if (testGroupId)
    333         filename += '?testGroup=' + testGroupId;
     334        url += '?testGroup=' + testGroupId;
    334335
    335336    return new Ember.RSVP.Promise(function (resolve, reject) {
    336         $.getJSON('../api/runs/' + filename, function (data) {
    337             if (data.status != 'OK') {
    338                 reject(data.status);
     337        $.getJSON(url, function (response) {
     338            if (response.status != 'OK') {
     339                reject(response.status);
    339340                return;
    340341            }
    341             delete data.status;
    342 
     342            delete response.status;
     343
     344            var data = response.configurations;
    343345            for (var config in data)
    344346                data[config] = new RunsData(data[config]);
    345 
    346             resolve(data);
     347           
     348            if (response.lastModified)
     349                response.lastModified = new Date(response.lastModified);
     350
     351            resolve(response);
    347352        }).fail(function (xhr, status, error) {
    348             reject(xhr.status + (error ? ', ' + error : ''));
     353            if (xhr.status == 404 && useCache)
     354                resolve(null);
     355            else
     356                reject(xhr.status + (error ? ', ' + error : ''));
    349357        })
    350358    });
  • trunk/Websites/perf.webkit.org/public/v2/manifest.js

    r180333 r180468  
    3535    fullName: function ()
    3636    {
    37         return this.get('path').join(' \u220b ') /* &ni; */
     37        return this.get('path').join(' \u220b ') /* &in; */
    3838            + ' : ' + this.get('label');
    3939    }.property('path', 'label'),
     
    5555});
    5656
     57App.DateArrayTransform = DS.Transform.extend({
     58    deserialize: function (serialized)
     59    {
     60        return serialized.map(function (time) { return new Date(time); });
     61    }
     62});
     63
    5764App.Platform = App.NameLabelModel.extend({
    5865    _metricSet: null,
    5966    _testSet: null,
    6067    metrics: DS.hasMany('metric'),
     68    lastModified: DS.attr('dateArray'),
    6169    containsMetric: function (metric)
    6270    {
     
    6472            this._metricSet = new Ember.Set(this.get('metrics'));
    6573        return this._metricSet.contains(metric);
     74    },
     75    lastModifiedTimeForMetric: function (metric)
     76    {
     77        var index = this.get('metrics').indexOf(metric);
     78        if (index < 0)
     79            return null;
     80        return this.get('lastModified').objectAt(index);
    6681    },
    6782    containsTest: function (test)
     
    280295        this._defaultDashboardName = dashboards.length ? dashboards[0].get('name') : null;
    281296    },
    282     fetchRunsWithPlatformAndMetric: function (store, platformId, metricId, testGroupId)
    283     {
     297    fetchRunsWithPlatformAndMetric: function (store, platformId, metricId, testGroupId, useCache)
     298    {
     299        Ember.assert("Can't cache results for test groups", !(testGroupId && useCache));
     300        var self = this;
    284301        return Ember.RSVP.all([
    285             RunsData.fetchRuns(platformId, metricId, testGroupId),
     302            RunsData.fetchRuns(platformId, metricId, testGroupId, useCache),
    286303            this.fetch(store),
    287304        ]).then(function (values) {
    288             var runs = values[0];
     305            var response = values[0];
    289306
    290307            var platform = App.Manifest.platform(platformId);
    291308            var metric = App.Manifest.metric(metricId);
    292309
    293             var suffix = metric.get('name').match('([A-z][a-z]+|FrameRate)$')[0];
    294             var unit = {
    295                 'FrameRate': 'fps',
    296                 'Runs': '/s',
    297                 'Time': 'ms',
    298                 'Malloc': 'bytes',
    299                 'Heap': 'bytes',
    300                 'Allocations': 'bytes'
    301             }[suffix];
    302             var smallerIsBetter = unit != 'fps' && unit != '/s'; // Assume smaller is better for unit-less metrics.
    303 
    304             var useSI = unit == 'bytes';
    305             var unitSuffix = unit ? ' ' + unit : '';
    306             var deltaFormatterWithoutSign = useSI ? d3.format('.2s') : d3.format('.2g');
    307310            return {
    308311                platform: platform,
    309312                metric: metric,
    310                 data: {
    311                     current: runs.current.timeSeriesByCommitTime(),
    312                     baseline: runs.baseline ? runs.baseline.timeSeriesByCommitTime() : null,
    313                     target: runs.target ? runs.target.timeSeriesByCommitTime() : null,
    314                     unit: unit,
    315                     formatWithUnit: function (value) { return this.formatter(value) + unitSuffix; },
    316                     formatWithDeltaAndUnit: function (value, delta)
    317                     {
    318                         return this.formatter(value) + (delta && !isNaN(delta) ? ' \u00b1 ' + deltaFormatterWithoutSign(delta) : '') + unitSuffix;
    319                     },
    320                     formatter: useSI ? d3.format('.4s') : d3.format('.4g'),
    321                     deltaFormatter: useSI ? d3.format('+.2s') : d3.format('+.2g'),
    322                     smallerIsBetter: smallerIsBetter,
    323                 }
     313                data: response ? self._formatFetchedData(metric.get('name'), response.configurations) : null,
     314                shouldRefetch: !response || +response.lastModified < +platform.lastModifiedTimeForMetric(metric),
    324315            };
    325316        });
    326317    },
     318    _formatFetchedData: function (metricName, configurations)
     319    {
     320        var suffix = metricName.match('([A-z][a-z]+|FrameRate)$')[0];
     321        var unit = {
     322            'FrameRate': 'fps',
     323            'Runs': '/s',
     324            'Time': 'ms',
     325            'Malloc': 'bytes',
     326            'Heap': 'bytes',
     327            'Allocations': 'bytes'
     328        }[suffix];
     329
     330        var smallerIsBetter = unit != 'fps' && unit != '/s'; // Assume smaller is better for unit-less metrics.
     331
     332        var useSI = unit == 'bytes';
     333        var unitSuffix = unit ? ' ' + unit : '';
     334        var deltaFormatterWithoutSign = useSI ? d3.format('.2s') : d3.format('.2g');
     335
     336        return {
     337            current: configurations.current.timeSeriesByCommitTime(),
     338            baseline: configurations.baseline ? configurations.baseline.timeSeriesByCommitTime() : null,
     339            target: configurations.target ? configurations.target.timeSeriesByCommitTime() : null,
     340            unit: unit,
     341            formatWithUnit: function (value) { return this.formatter(value) + unitSuffix; },
     342            formatWithDeltaAndUnit: function (value, delta)
     343            {
     344                return this.formatter(value) + (delta && !isNaN(delta) ? ' \u00b1 ' + deltaFormatterWithoutSign(delta) : '') + unitSuffix;
     345            },
     346            formatter: useSI ? d3.format('.4s') : d3.format('.4g'),
     347            deltaFormatter: useSI ? d3.format('+.2s') : d3.format('+.2g'),
     348            smallerIsBetter: smallerIsBetter,
     349        };
     350    }
    327351}).create();
  • trunk/Websites/perf.webkit.org/run-tests.js

    r174459 r180468  
    200200    var firstError;
    201201    var queue = new TaskQueue();
    202     commaSeparatedSqlStatements.split(/;\s*/).forEach(function (statement) {
     202    commaSeparatedSqlStatements.split(/;\s*(?=CREATE|DROP)/).forEach(function (statement) {
    203203        queue.addTask(function (error, callback) {
    204204            client.query(statement, function (error) {
  • trunk/Websites/perf.webkit.org/tests/api-report.js

    r177614 r180468  
    688688        });
    689689    });
     690
     691    var reportsUpdatingDifferentTests = [
     692        [{
     693            "buildNumber": "123",
     694            "buildTime": "2013-02-28T10:12:03",
     695            "builderName": "someBuilder",
     696            "builderPassword": "somePassword",
     697            "platform": "Mountain Lion",
     698            "tests": {"test1": {"metrics": {"Time": {"current": 3}}}}
     699        }],
     700        [{
     701            "buildNumber": "124",
     702            "buildTime": "2013-02-28T11:31:21",
     703            "builderName": "someBuilder",
     704            "builderPassword": "somePassword",
     705            "platform": "Mountain Lion",
     706            "tests": {"test2": {"metrics": {"Time": {"current": 3}}}}
     707        }],
     708        [{
     709            "buildNumber": "125",
     710            "buildTime": "2013-02-28T12:45:34",
     711            "builderName": "someBuilder",
     712            "builderPassword": "somePassword",
     713            "platform": "Mountain Lion",
     714            "tests": {"test1": {"metrics": {"Time": {"current": 3}}}}
     715        }],
     716    ];
     717
     718    function fetchTestConfig(testName, metricName, callback) {
     719         queryAndFetchAll('SELECT * FROM tests, test_metrics, test_configurations WHERE test_id = metric_test AND metric_id = config_metric'
     720            + ' AND test_name = $1 AND metric_name = $2', [testName, metricName], function (runRows) {
     721                assert.equal(runRows.length, 1);
     722                callback(runRows[0]);
     723            });
     724    }
     725
     726    it("should update the last modified date of test configurations with new runs", function () {
     727        addBuilder(reportsUpdatingDifferentTests[0], function () {
     728            postJSON('/api/report/', reportsUpdatingDifferentTests[0], function (response) {
     729                assert.equal(response.statusCode, 200);
     730                fetchTestConfig('test1', 'Time', function (originalConfig) {
     731                    postJSON('/api/report/', reportsUpdatingDifferentTests[2], function (response) {
     732                        assert.equal(response.statusCode, 200);
     733                        fetchTestConfig('test1', 'Time', function (config) {
     734                            assert.notEqual(+originalConfig['config_runs_last_modified'], +config['config_runs_last_modified']);
     735                            notifyDone();
     736                        });
     737                    });
     738                });
     739            });
     740        });
     741    });
     742
     743    it("should update the last modified date of unrelated test configurations", function () {
     744        addBuilder(reportsUpdatingDifferentTests[0], function () {
     745            postJSON('/api/report/', reportsUpdatingDifferentTests[0], function (response) {
     746                assert.equal(response.statusCode, 200);
     747                fetchTestConfig('test1', 'Time', function (originalConfig) {
     748                    postJSON('/api/report/', reportsUpdatingDifferentTests[1], function (response) {
     749                        assert.equal(response.statusCode, 200);
     750                        fetchTestConfig('test1', 'Time', function (config) {
     751                            assert.equal(+originalConfig['config_runs_last_modified'], +config['config_runs_last_modified']);
     752                            notifyDone();
     753                        });
     754                    });
     755                });
     756            });
     757        });
     758    });
    690759});
Note: See TracChangeset for help on using the changeset viewer.