Unreviewed, fix a few tests that became flaky after r230919.
[WebKit.git] / Websites / perf.webkit.org / public / api / measurement-set.php
1 <?php
2
3 require('../include/json-header.php');
4
5 function main() {
6     $program_start_time = microtime(true);
7
8     $arguments = validate_arguments($_GET, array(
9         'platform' => 'int?',
10         'metric' => 'int?',
11         'analysisTask' => 'int?'));
12
13     $platform_id = $arguments['platform'];
14     $metric_id = $arguments['metric'];
15     $task_id = $arguments['analysisTask'];
16     if (!(($platform_id && $metric_id && !$task_id) || ($task_id && !$platform_id && !$metric_id)))
17         exit_with_error('AmbiguousRequest');
18
19     $db = new Database;
20     if (!$db->connect())
21         exit_with_error('DatabaseConnectionFailure');
22
23     if ($task_id) {
24         $fetcher = new AnalysisResultsFetcher($db, $task_id);
25         exit_with_success($fetcher->fetch());
26     }
27
28     $fetcher = new MeasurementSetFetcher($db);
29     if (!$fetcher->fetch_config_list($platform_id, $metric_id)) {
30         exit_with_error('ConfigurationNotFound',
31             array('platform' => $platform_id, 'metric' => $metric_id));
32     }
33
34     if ($fetcher->at_end()) {
35         header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
36         exit(404);
37     }
38
39     $cluster_count = 0;
40     while (!$fetcher->at_end()) {
41         $content = $fetcher->fetch_next_cluster();
42         $cluster_count++;
43         if ($fetcher->at_end()) {
44             $cache_filename = "measurement-set-$platform_id-$metric_id.json";
45             $content['clusterCount'] = $cluster_count;
46             $content['elapsedTime'] = (microtime(true) - $program_start_time) * 1000;
47         } else
48             $cache_filename = "measurement-set-$platform_id-$metric_id-{$content['endTime']}.json";
49
50         $json = success_json($content);
51         generate_data_file($cache_filename, $json);
52     }
53
54     echo $json;
55 }
56
57 define('DAY', 24 * 3600 * 1000);
58 define('YEAR', 365.24 * DAY);
59 define('MONTH', 30 * DAY);
60
61 class MeasurementSetFetcher {
62     function __construct($db) {
63         $this->db = $db;
64         $this->queries = NULL;
65
66         // Each cluster contains data points between two commit time
67         // as well as a point immediately before and a point immediately after these points.
68         // Clusters are fetched in chronological order.
69         $start_time = config('clusterStart');
70         $size = config('clusterSize');
71         $this->cluster_start = mktime($start_time[3], $start_time[4], 0, $start_time[1], $start_time[2], $start_time[0]) * 1000;
72         $this->next_cluster_start = $this->cluster_start;
73         $this->next_cluster_results = NULL;
74         $this->cluster_size = $size[0] * YEAR + $size[1] * MONTH + $size[2] * DAY;
75         $this->last_modified = 0;
76
77         $this->start_time = microtime(TRUE);
78     }
79
80     function fetch_config_list($platform_id, $metric_id) {
81         $config_rows = $this->db->query_and_fetch_all('SELECT *
82             FROM test_configurations WHERE config_metric = $1 AND config_platform = $2',
83             array($metric_id, $platform_id));
84         $this->config_rows = $config_rows;
85         if (!$config_rows)
86             return FALSE;
87
88         $this->queries = array();
89         $this->next_cluster_results = array();
90         $min_commit_time = microtime(TRUE) * 1000;
91         foreach ($config_rows as &$config_row) {
92             $query = $this->execute_query($config_row['config_id']);
93
94             $this->last_modified = max($this->last_modified, Database::to_js_time($config_row['config_runs_last_modified']));
95
96             $measurement_row = $this->db->fetch_next_row($query);
97             if ($measurement_row) {
98                 $commit_time = 0;
99                 $formatted_row = self::format_run($measurement_row, $commit_time);
100                 $this->next_cluster_results[$config_row['config_type']] = array($formatted_row);
101                 $min_commit_time = min($min_commit_time, $commit_time);
102             } else
103                 $query = NULL;
104
105             $this->queries[$config_row['config_type']] = $query;
106         }
107
108         while ($this->next_cluster_start + $this->cluster_size < $min_commit_time)
109             $this->next_cluster_start += $this->cluster_size;
110
111         return TRUE;
112     }
113
114     function at_end() {
115         if ($this->queries === NULL)
116             return FALSE;
117         foreach ($this->queries as $name => &$query) {
118             if ($query)
119                 return FALSE;
120         }
121         return TRUE;
122     }
123     
124     function fetch_next_cluster() {
125         assert($this->queries);
126
127         $results_by_config = array();
128         $current_cluster_start = $this->next_cluster_start;
129         $this->next_cluster_start += $this->cluster_size;
130
131         foreach ($this->queries as $name => &$query) {
132             assert($this->next_cluster_start);
133
134             $carry_over = array_get($this->next_cluster_results, $name);
135             if ($carry_over)
136                 $results_by_config[$name] = $carry_over;
137             else
138                 $results_by_config[$name] = array();
139
140             if (!$query)
141                 continue;
142
143             while ($row = $this->db->fetch_next_row($query)) {
144                 $commit_time = NULL;
145                 $formatted_row = self::format_run($row, $commit_time);
146                 array_push($results_by_config[$name], $formatted_row);
147                 $row_belongs_to_next_cluster = $commit_time > $this->next_cluster_start;
148                 if ($row_belongs_to_next_cluster)
149                     break;
150             }
151
152             $reached_end = !$row;
153             if ($reached_end)
154                 $this->queries[$name] = NULL;
155             else {
156                 $this->next_cluster_results[$name] = array_slice($results_by_config[$name], -2);
157             }
158         }
159
160         return array(
161             'clusterStart' => $this->cluster_start,
162             'clusterSize' => $this->cluster_size,
163             'configurations' => &$results_by_config,
164             'formatMap' => self::format_map(),
165             'startTime' => $current_cluster_start,
166             'endTime' => $this->next_cluster_start,
167             'lastModified' => $this->last_modified);
168     }
169
170     function execute_query($config_id) {
171         return $this->db->query('
172             SELECT test_runs.*, build_id, build_number, build_builder, build_time,
173             array_agg((commit_id, commit_repository, commit_revision, commit_order, extract(epoch from commit_time at time zone \'utc\') * 1000)) AS revisions,
174             extract(epoch from max(commit_time at time zone \'utc\')) * 1000 AS revision_time
175                 FROM builds
176                     LEFT OUTER JOIN build_commits ON commit_build = build_id
177                     LEFT OUTER JOIN commits ON build_commit = commit_id, test_runs
178                 WHERE run_build = build_id AND run_config = $1 AND NOT EXISTS (SELECT * FROM build_requests WHERE request_build = build_id)
179                 GROUP BY build_id, build_builder, build_number, build_time, build_latest_revision, build_slave,
180                     run_id, run_config, run_build, run_iteration_count_cache, run_mean_cache, run_sum_cache, run_square_sum_cache, run_marked_outlier
181                 ORDER BY revision_time, build_time', array($config_id));
182     }
183
184     static function format_map()
185     {
186         return array('id', 'mean', 'iterationCount', 'sum', 'squareSum', 'markedOutlier', 'revisions',
187             'commitTime', 'build', 'buildTime', 'buildNumber', 'builder');
188     }
189
190     private static function format_run(&$run, &$commit_time) {
191         $commit_time = intval($run['revision_time']);
192         $build_time = Database::to_js_time($run['build_time']);
193         if (!$commit_time)
194             $commit_time = $build_time;
195         return array(
196             intval($run['run_id']),
197             floatval($run['run_mean_cache']),
198             intval($run['run_iteration_count_cache']),
199             floatval($run['run_sum_cache']),
200             floatval($run['run_square_sum_cache']),
201             Database::is_true($run['run_marked_outlier']),
202             self::parse_revisions_array($run['revisions']),
203             $commit_time,
204             intval($run['build_id']),
205             $build_time,
206             $run['build_number'],
207             intval($run['build_builder']));
208     }
209
210     private static function parse_revisions_array(&$postgres_array) {
211         // e.g. {"(<commit-id>,<repository-id>,<revision>,<order>,\"2012-10-16 14:53:00\")","(<commit-id>,<repository-id>,<revision>,<order>,)",
212         // "(<commit-id>,<repository-id>,<revision>,,)", "(<commit-id>,<repository-id>,<revision>,,\"2012-10-16 14:53:00\")"}
213         $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
214         $revisions = array();
215         foreach ($outer_array as $item) {
216             $name_and_revision = explode(',', trim($item, '()'));
217             if (!$name_and_revision[0])
218                 continue;
219             $commit_id = intval(trim($name_and_revision[0], '"'));
220             $repository_id = intval(trim($name_and_revision[1], '"'));
221             $revision = trim($name_and_revision[2], '"');
222             $trimmed_order = trim($name_and_revision[3], '"');
223             $order = strlen($trimmed_order) ? intval($trimmed_order) : NULL;
224             $time = intval(trim($name_and_revision[4], '"'));
225             array_push($revisions, array($commit_id, $repository_id, $revision, $order, $time));
226         }
227         return $revisions;
228     }
229 }
230
231 class AnalysisResultsFetcher {
232
233     function __construct($db, $task_id) {
234         $this->db = $db;
235         $this->task_id = $task_id;
236         $this->build_to_commits = array();
237     }
238
239     function fetch()
240     {
241         $start_time = microtime(TRUE);
242
243         // Fetch commmits separately from test_runs since number of builds is much smaller than number of runs here.
244         $this->fetch_commits();
245
246         $query = $this->db->query('SELECT test_runs.*, builds.*, test_configurations.*
247             FROM builds,
248                 test_runs JOIN test_configurations ON run_config = config_id,
249                 build_requests JOIN analysis_test_groups ON request_group = testgroup_id
250             WHERE run_build = build_id AND build_id = request_build
251                 AND testgroup_task = $1 AND run_config = config_id', array($this->task_id));
252
253         $results = array();
254         while ($row = $this->db->fetch_next_row($query))
255             array_push($results, $this->format_measurement($row));
256
257         return array(
258             'formatMap' => self::format_map(),
259             'measurements' => $results,
260             'elapsedTime' => (microtime(TRUE) - $start_time) * 1000);
261     }
262
263     function fetch_commits()
264     {
265         $query = $this->db->query('SELECT commit_id, commit_build, commit_repository, commit_revision, commit_time
266             FROM commits, build_commits, build_requests, analysis_test_groups
267             WHERE commit_id = build_commit AND commit_build = request_build
268                 AND request_group = testgroup_id AND testgroup_task = $1', array($this->task_id));
269         while ($row = $this->db->fetch_next_row($query)) {
270             $commit_time = Database::to_js_time($row['commit_time']);
271             array_push(array_ensure_item_has_array($this->build_to_commits, $row['commit_build']),
272                 array($row['commit_id'], $row['commit_repository'], $row['commit_revision'], $commit_time));
273         }
274     }
275
276     function format_measurement($row)
277     {
278         $build_id = $row['build_id'];
279         return array(
280             intval($row['run_id']),
281             floatval($row['run_mean_cache']),
282             intval($row['run_iteration_count_cache']),
283             floatval($row['run_sum_cache']),
284             floatval($row['run_square_sum_cache']),
285             $this->build_to_commits[$build_id],
286             intval($build_id),
287             Database::to_js_time($row['build_time']),
288             $row['build_number'],
289             intval($row['build_builder']),
290             intval($row['config_metric']),
291             $row['config_type']);
292     }
293
294     static function format_map()
295     {
296         return array('id', 'mean', 'iterationCount', 'sum', 'squareSum', 'revisions',
297             'build', 'buildTime', 'buildNumber', 'builder', 'metric', 'configType');
298     }
299 }
300
301 main();
302
303 ?>