Perf dashboard always re-generate measurement set JSON
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Jul 2016 03:25:59 +0000 (03:25 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Jul 2016 03:25:59 +0000 (03:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=159951

Reviewed by Chris Dumez.

The bug was caused by manifest.json reporting the last modified date of a measurement set in floating point,
and a measurement set JSON reporting it as an integer. Fixed the bug by always using an integer.

* public/api/measurement-set.php:
(main): Return 404 when the results is empty.
(MeasurementSetFetcher::execute_query): Use "extract(epoch from commit_time)" like ManifestGenerator to improve
the generation speed. This is ~10% runtime improvement.
(MeasurementSetFetcher::format_map): Updated to reflect the above change.
(MeasurementSetFetcher::parse_revisions_array): Ditto.

* public/include/manifest.php:
(ManifestGenerator::platforms): Fixed the bug by coercing lastModified to integer (instead of float).

* server-tests/api-measurement-set-tests.js: Added a test case for returning empty results, and a test case for
making sure lastModified dates in manifest.json and measurement sets match.

* tools/js/remote.js:
(RemoteAPI.prototype.sendHttpRequest): Reject the promise when HTTP status code is not 200.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@203486 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Websites/perf.webkit.org/ChangeLog
Websites/perf.webkit.org/public/api/measurement-set.php
Websites/perf.webkit.org/public/include/manifest.php
Websites/perf.webkit.org/server-tests/api-measurement-set-tests.js
Websites/perf.webkit.org/tools/js/remote.js

index b1eed94e1f92becd0e89da56b145de45aefd74d6..390593c8f30d9df4f9f4c679c2cfcd4e9fdcccc3 100644 (file)
@@ -1,3 +1,29 @@
+2016-07-19  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Perf dashboard always re-generate measurement set JSON
+        https://bugs.webkit.org/show_bug.cgi?id=159951
+
+        Reviewed by Chris Dumez.
+
+        The bug was caused by manifest.json reporting the last modified date of a measurement set in floating point,
+        and a measurement set JSON reporting it as an integer. Fixed the bug by always using an integer.
+
+        * public/api/measurement-set.php:
+        (main): Return 404 when the results is empty.
+        (MeasurementSetFetcher::execute_query): Use "extract(epoch from commit_time)" like ManifestGenerator to improve
+        the generation speed. This is ~10% runtime improvement.
+        (MeasurementSetFetcher::format_map): Updated to reflect the above change.
+        (MeasurementSetFetcher::parse_revisions_array): Ditto.
+
+        * public/include/manifest.php:
+        (ManifestGenerator::platforms): Fixed the bug by coercing lastModified to integer (instead of float).
+
+        * server-tests/api-measurement-set-tests.js: Added a test case for returning empty results, and a test case for
+        making sure lastModified dates in manifest.json and measurement sets match.
+
+        * tools/js/remote.js:
+        (RemoteAPI.prototype.sendHttpRequest): Reject the promise when HTTP status code is not 200.
+
 2016-07-09  Ryosuke Niwa  <rniwa@webkit.org>
 
         Perf dashboard can consume 50-70% of CPU on MacBook even if user is not interacting at all
index 2dedbf3670665fba04f9b107bb1781da67f0f3bb..bac82ba5781a13d098300397981b24a6dec6ed0e 100644 (file)
@@ -31,6 +31,11 @@ function main() {
             array('platform' => $platform_id, 'metric' => $metric_id));
     }
 
+    if ($fetcher->at_end()) {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
+        exit(404);
+    }
+
     $cluster_count = 0;
     while (!$fetcher->at_end()) {
         $content = $fetcher->fetch_next_cluster();
@@ -164,9 +169,9 @@ class MeasurementSetFetcher {
 
     function execute_query($config_id) {
         return $this->db->query('
-            SELECT test_runs.*, builds.*,
-            array_agg((commit_id, commit_repository, commit_revision, commit_time)) AS revisions,
-            max(commit_time) AS revision_time, max(commit_order) AS revision_order
+            SELECT test_runs.*, build_id, build_number, build_builder, build_time,
+            array_agg((commit_id, commit_repository, commit_revision, extract(epoch from commit_time) * 1000)) AS revisions,
+            extract(epoch from max(commit_time)) * 1000 AS revision_time, max(commit_order) AS revision_order
                 FROM builds
                     LEFT OUTER JOIN build_commits ON commit_build = build_id
                     LEFT OUTER JOIN commits ON build_commit = commit_id, test_runs
@@ -181,7 +186,7 @@ class MeasurementSetFetcher {
     }
 
     private static function format_run(&$run, &$commit_time) {
-        $commit_time = Database::to_js_time($run['revision_time']);
+        $commit_time = intval($run['revision_time']);
         $build_time = Database::to_js_time($run['build_time']);
         if (!$commit_time)
             $commit_time = $build_time;
@@ -211,7 +216,7 @@ class MeasurementSetFetcher {
             $commit_id = intval(trim($name_and_revision[0], '"'));
             $repository_id = intval(trim($name_and_revision[1], '"'));
             $revision = trim($name_and_revision[2], '"');
-            $time = Database::to_js_time(trim($name_and_revision[3], '"'));
+            $time = intval(trim($name_and_revision[3], '"'));
             array_push($revisions, array($commit_id, $repository_id, $revision, $time));
         }
         return $revisions;
index b6e25d53a4d6218bdc5f5f00295c7f56b58b3794..d8b8be458210429e54e2bc50463e6111dee7d4d9 100644 (file)
@@ -106,7 +106,7 @@ class ManifestGenerator {
                 }
 
                 array_push($current_platform_entry['metrics'], $metric_row['metric_id']);
-                array_push($current_platform_entry['last_modified'], $metric_row['last_modified']);
+                array_push($current_platform_entry['last_modified'], intval($metric_row['last_modified']));
             }
         }
         $configurations = array();
index cdeeaf2e35bfb5fff16640dce9500444d58c9c87..030f6c805a126315d5588b8885bd68308ccb910a 100644 (file)
@@ -2,6 +2,9 @@
 
 const assert = require('assert');
 
+require('../tools/js/v3-models.js');
+
+const MockData = require('./resources/mock-data.js');
 const TestServer = require('./resources/test-server.js');
 const addBuilderForReport = require('./resources/common-operations.js').addBuilderForReport;
 const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
@@ -11,6 +14,10 @@ describe("/api/measurement-set", function () {
     TestServer.inject();
     connectToDatabaseInEveryTest();
 
+    beforeEach(function () {
+        MockData.resetV3Models();
+    });
+
     function queryPlatformAndMetric(platformName, metricName)
     {
         const db = TestServer.database();
@@ -68,7 +75,7 @@ describe("/api/measurement-set", function () {
         "revisions": {
             "WebKit": {
                 "revision": "144000",
-                "timestamp": clusterTime(10.3).toISOString(),
+                "timestamp": clusterTime(10.35645364537).toISOString(),
             },
         },
         "builderName": "someBuilder",
@@ -184,6 +191,38 @@ describe("/api/measurement-set", function () {
         }).catch(done);
     });
 
+    it("should be able to return an empty report", function (done) {
+        const db = TestServer.database();
+        Promise.all([
+            db.insert('tests', {id: 1, name: 'SomeTest'}),
+            db.insert('tests', {id: 2, name: 'SomeOtherTest'}),
+            db.insert('tests', {id: 3, name: 'ChildTest', parent: 1}),
+            db.insert('tests', {id: 4, name: 'GrandChild', parent: 3}),
+            db.insert('aggregators', {id: 200, name: 'Total'}),
+            db.insert('test_metrics', {id: 5, test: 1, name: 'Time'}),
+            db.insert('test_metrics', {id: 6, test: 2, name: 'Time', aggregator: 200}),
+            db.insert('test_metrics', {id: 7, test: 2, name: 'Malloc', aggregator: 200}),
+            db.insert('test_metrics', {id: 8, test: 3, name: 'Time'}),
+            db.insert('test_metrics', {id: 9, test: 4, name: 'Time'}),
+            db.insert('platforms', {id: 23, name: 'iOS 9 iPhone 5s'}),
+            db.insert('platforms', {id: 46, name: 'Trunk Mavericks'}),
+            db.insert('test_configurations', {id: 101, metric: 5, platform: 46, type: 'current'}),
+            db.insert('test_configurations', {id: 102, metric: 6, platform: 46, type: 'current'}),
+            db.insert('test_configurations', {id: 103, metric: 7, platform: 46, type: 'current'}),
+            db.insert('test_configurations', {id: 104, metric: 8, platform: 46, type: 'current'}),
+            db.insert('test_configurations', {id: 105, metric: 9, platform: 46, type: 'current'}),
+            db.insert('test_configurations', {id: 106, metric: 5, platform: 23, type: 'current'}),
+            db.insert('test_configurations', {id: 107, metric: 5, platform: 23, type: 'baseline'}),
+        ]).then(function () {
+            return TestServer.remoteAPI().getJSONWithStatus(`/api/measurement-set/?platform=46&metric=5`).then(function (response) {
+                assert.equal(response.statusCode, 404);
+            }, function (error) {
+                assert.equal(error, 404);
+                done();
+            });
+        }).catch(done);
+    });
+
     it("should be able to retrieve a reported value", function (done) {
         addBuilderForReport(reportWithBuildTime[0]).then(function () {
             return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
@@ -379,7 +418,7 @@ describe("/api/measurement-set", function () {
     });
 
 
-    it("should create cache results", function (done) {
+    it("should create cached results", function (done) {
         const remote = TestServer.remoteAPI();
         let cachePrefix;
         addBuilderForReport(reportWithBuildTime[0]).then(function () {
@@ -409,4 +448,26 @@ describe("/api/measurement-set", function () {
         }).catch(done);
     });
 
+    it("should use lastModified timestamp identical to that in the manifest file", function (done) {
+        const remote = TestServer.remoteAPI();
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return remote.postJSON('/api/report/', reportWithRevision);
+        }).then(function () {
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
+        }).then(function (primaryCluster) {
+            return remote.getJSONWithStatus('/api/manifest').then(function (content) {
+                const manifest = Manifest._didFetchManifest(content);
+
+                const platform = Platform.findByName('Mountain Lion');
+                assert.equal(Metric.all().length, 1);
+                const metric = Metric.all()[0];
+                assert.equal(platform.lastModified(metric), primaryCluster['lastModified']);
+
+                done();
+            });
+        }).catch(done);
+    });
+
 });
index e16e5f8203680233afc47956386430af5e74b1fb..d588b5f4d58a53f7534eb893158a91a2facc3bf5 100644 (file)
@@ -119,6 +119,11 @@ class RemoteAPI {
                 response.setEncoding('utf8');
                 response.on('data', function (chunk) { responseText += chunk; });
                 response.on('end', function () {
+                    if (response.statusCode != 200) {
+                        reject(response.statusCode);
+                        return;
+                    }
+
                     if ('set-cookie' in response.headers) {
                         for (const cookie of response.headers['set-cookie']) {
                             var nameValue = cookie.split('=')