Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (203708 => 203709)
--- trunk/Websites/perf.webkit.org/ChangeLog 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2016-07-26 02:45:13 UTC (rev 203709)
@@ -1,3 +1,119 @@
+2016-07-23 Ryosuke Niwa <rn...@webkit.org>
+
+ Perf dashboard should show the list of a pending A/B testing jobs
+ https://bugs.webkit.org/show_bug.cgi?id=160138
+
+ Rubber-stamped by Chris Dumez.
+
+ Add a page to show the list of A/B testing build requests per triggerable. Ideally, we would like to
+ see a queue per builder but that would require changes to database tables and syncing scripts.
+
+ Because this page is most useful when the analysis task with which each build request is associated,
+ JSON API at /api/build-requests/ has been modified to return the analysis task ID for each request.
+
+ Also streamlined the page that shows the list of analysis tasks per Chris' feedback by consolidating
+ "Bisecting" and "Identified" into "Investigated" and moving the toolbar from the upper left corner
+ inside the heading to right beneath the heading above the table. Also made the category page serialize
+ the filter an user had typed in so that reloading the page doesn't clear it.
+
+ * public/api/analysis-tasks.php:
+ (fetch_associated_data_for_tasks): Removed 'category' from the list of columns returned as the notion
+ of 'category' is only relevant in UI, and it's better computed in the front-end.
+ (format_task): Ditto.
+ (determine_category): Deleted.
+
+ * public/api/test-groups.php:
+ (main):
+
+ * public/include/build-requests-fetcher.php:
+ (BuildRequestsFetcher::fetch_for_task): Include the analysis task ID in the rows.
+ (BuildRequestsFetcher::fetch_for_group): Ditto. Ditto.
+ (BuildRequestsFetcher::fetch_incomplete_requests_for_triggerable): Ditto.
+ (BuildRequestsFetcher::results_internal): Ditto.
+
+ * public/v3/index.html:
+
+ * public/v3/main.js:
+ (main): Create a newly introduced BuildRequestQueuePage as a subpage of AnalysisCategoryPage.
+
+ * public/v3/components/ratio-bar-graph.js:
+ (RatioBarGraph.prototype.update): Fixed a bogus assertion here. ratio can be any number. The coercion
+ into [-1, 1] is done inside RatioBarGraph's render() function.
+
+ * public/v3/models/analysis-task.js:
+ (AnalysisTask.prototype.category): Moved the code to compute the analysis task's category from
+ determine_category in analysis-tasks.php. Also merged "bisecting" and "identified" into "investigated".
+ (AnalysisTask.categories): Merged "bisecting" and "identified" into "investigated".
+
+ * public/v3/models/build-request.js:
+ (BuildRequest): Remember the triggerable and the analysis task associated with this request as well as
+ the time at when this request was created.
+ (BuildRequest.prototype.analysisTaskId): Added.
+ (BuildRequest.prototype.statusLabel): Use a shorter label: "Waiting" for "pending" status.
+ (BuildRequest.prototype.createdAt): Added.
+ (BuildRequest.prototype.waitingTime): Added. Returns a human readable time duration since the creation
+ of this build request such as "2 hours 21 minutes" against a reference time.
+ (BuildRequest.fetchTriggerables): Added.
+ (BuildRequest.cachedRequestsForTriggerableID): Added. Used when navigating back to
+
+ * public/v3/pages/analysis-category-page.js:
+ (AnalysisCategoryPage): Construct AnalysisCategoryToolbar and store it in this._categoryToolbar since it
+ no longer inherits from Toolbar class, which PageWithHeading recognizes and stores.
+ (AnalysisCategoryPage.prototype.title):
+ (AnalysisCategoryPage.prototype.serializeState): Added.
+ (AnalysisCategoryPage.prototype.stateForCategory): Added. Include the filter in the serialization.
+ (AnalysisCategoryPage.prototype.updateFromSerializedState): Restore the filter from the URL state.
+ (AnalysisCategoryPage.prototype.filterDidChange): Added. Called by AnalysisCategoryToolbar to update
+ the URL state in addition to calling render() as done previously via setFilterCallback.
+ (AnalysisCategoryPage.prototype.render): Always call _categoryToolbar.render() since the hyperlinks for
+ the category pages now include the filter, which can be updated in each call.
+ (AnalysisCategoryPage.cssTemplate):
+
+ * public/v3/pages/analysis-category-toolbar.js:
+ (AnalysisCategoryToolbar): Inherits from ComponentBase instead of Toolbar since AnalysisCategoryToolbar
+ no longer works with Heading class unlike other subclasses of Toolbar class.
+ (AnalysisCategoryToolbar.prototype.setCategoryPage): Added.
+ (AnalysisCategoryToolbar.prototype.setFilterCallback): Deleted.
+ (AnalysisCategoryToolbar.prototype.setFilter): Added. Used to restore from a serialized URL state.
+ (AnalysisCategoryToolbar.prototype.render): Don't recreate the input element as it clears the value as
+ well as the selection of the element. Also use AnalysisCategoryPage's stateForCategory to serialize the
+ category name and the current filter for each hyperlink.
+ (AnalysisCategoryToolbar.prototype._filterMayHaveChanged): Now takes an boolean argument specifying
+ whether the URL state should be updated or not. We update the URL only when a change event is fired to
+ avoid constantly updating it while an user is still typing.
+ (AnalysisCategoryToolbar.cssTemplate): Added.
+ (AnalysisCategoryToolbar.htmlTemplate): Added a button to open the newly added queue page.
+
+ * public/v3/pages/build-request-queue-page.js:
+ (BuildRequestQueuePage): Added.
+ (BuildRequestQueuePage.prototype.routeName): Added.
+ (BuildRequestQueuePage.prototype.pageTitle): Added.
+ (BuildRequestQueuePage.prototype.open): Added. Fetch open build requests for every triggerables using
+ the same API as the syncing scripts.
+ (BuildRequestQueuePage.prototype.render): Added.
+ (BuildRequestQueuePage.prototype._constructBuildRequestTable): Added. Construct a table for the list of
+ pending, scheduled or running build requests in the order syncing scripts would see. Note that the list
+ of build requests returned by /api/build-requests/* can contain completed, canceled, or failed requests
+ since the JSON returns all build requests associated with each test group if one of the requests of the
+ group have not finished. This helps syncing scripts picking the right builder for A/B testing when it
+ had previously been unloaded or crashed in the middle of processing a test group. This characteristics
+ of the API actually helps us here because we can reliably compute the total number of build requests in
+ the group. The first half of this function does this counting as well as collapses all but the first
+ unfinished build requests into a "contraction" row, which just shows the number of build requests that
+ are remaining in the group.
+ (BuildRequestQueuePage.cssTemplate): Added.
+ (BuildRequestQueuePage.htmlTemplate): Added.
+
+ * public/v3/pages/summary-page.js:
+ (SummaryPage.prototype.open): Use one-day median instead of seven-day median to compute the status.
+ (SummaryPageConfigurationGroup): Initialize _ratio to NaN. This was causing assertion failures in
+ RatioBarGraph's update() while measurement sets are being fetched.
+
+ * server-tests/api-build-requests-tests.js: Updated the tests per change in BuildRequest's statusLabel.
+ * unit-tests/analysis-task-tests.js: Ditto.
+ * unit-tests/test-groups-tests.js: Ditto.
+ * unit-tests/build-request-tests.js: Added tests for BuildRequest's waitingTime.
+
2016-07-22 Ryosuke Niwa <rn...@webkit.org>
REGRESSION(r203035): Marking points as an outlier no longer updates charts
Modified: trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php 2016-07-26 02:45:13 UTC (rev 203709)
@@ -83,7 +83,6 @@
$task = &$task_by_id[$build_count['task']];
$task['buildRequestCount'] = $build_count['total'];
$task['finishedBuildRequestCount'] = $build_count['finished'];
- $task['category'] = determine_category($task);
}
return array('analysisTasks' => $tasks, 'bugs' => $bugs, 'commits' => $commits);
@@ -103,7 +102,6 @@
'startRunTime' => Database::to_js_time($task_row['task_start_run_time']),
'endRun' => $task_row['task_end_run'],
'endRunTime' => Database::to_js_time($task_row['task_end_run_time']),
- 'category' => null,
'result' => $task_row['task_result'],
'needed' => $task_row['task_needed'] ? Database::is_true($task_row['task_needed']) : null,
'bugs' => array(),
@@ -112,20 +110,6 @@
);
}
-function determine_category($task) {
- $category = 'unconfirmed';
-
- $result = $task['result'];
- if ($result == 'unchanged' || $result == 'inconclusive' || $task['fixes'] || ($result == 'progression' && $task['causes']))
- $category = 'closed';
- else if ($task['causes'])
- $category = 'identified';
- else if ($result)
- $category = 'bisecting';
-
- return $category;
-}
-
main(array_key_exists('PATH_INFO', $_SERVER) ? explode('/', trim($_SERVER['PATH_INFO'], '/')) : array());
?>
Modified: trunk/Websites/perf.webkit.org/public/api/test-groups.php (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/api/test-groups.php 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/api/test-groups.php 2016-07-26 02:45:13 UTC (rev 203709)
@@ -19,7 +19,7 @@
if (!$group)
exit_with_error('GroupNotFound', array('id' => $group_id));
$test_groups = array($group);
- $build_requests_fetcher->fetch_for_group($group_id);
+ $build_requests_fetcher->fetch_for_group($group['testgroup_task'], $group_id);
} else {
$task_id = array_get($_GET, 'task');
if (!$task_id)
Modified: trunk/Websites/perf.webkit.org/public/include/build-requests-fetcher.php (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/include/build-requests-fetcher.php 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/include/build-requests-fetcher.php 2016-07-26 02:45:13 UTC (rev 203709)
@@ -13,21 +13,23 @@
}
function fetch_for_task($task_id) {
- $this->rows = $this->db->query_and_fetch_all('SELECT *
+ $this->rows = $this->db->query_and_fetch_all('SELECT *, testgroup_task as task_id
FROM build_requests LEFT OUTER JOIN builds ON request_build = build_id, analysis_test_groups
WHERE request_group = testgroup_id AND testgroup_task = $1
ORDER BY request_group, request_order', array($task_id));
}
- function fetch_for_group($test_group_id) {
+ function fetch_for_group($task_id, $test_group_id) {
$this->rows = $this->db->query_and_fetch_all('SELECT *
FROM build_requests LEFT OUTER JOIN builds ON request_build = build_id
WHERE request_group = $1 ORDER BY request_order', array($test_group_id));
+ foreach ($this->rows as &$row)
+ $row['task_id'] = $task_id;
}
function fetch_incomplete_requests_for_triggerable($triggerable_id) {
- $this->rows = $this->db->query_and_fetch_all('SELECT * FROM build_requests,
- (SELECT testgroup_id, (case when testgroup_author is not null then 0 else 1 end) as author_order, testgroup_created_at
+ $this->rows = $this->db->query_and_fetch_all('SELECT *, test_groups.testgroup_task as task_id FROM build_requests,
+ (SELECT testgroup_id, testgroup_task, (case when testgroup_author is not null then 0 else 1 end) as author_order, testgroup_created_at
FROM analysis_test_groups WHERE EXISTS
(SELECT 1 FROM build_requests WHERE testgroup_id = request_group AND request_status
IN (\'pending\', \'scheduled\', \'running\'))) AS test_groups
@@ -64,6 +66,7 @@
array_push($requests, array(
'id' => $row['request_id'],
+ 'task' => $row['task_id'],
'triggerable' => $row['request_triggerable'],
'test' => $resolve_ids ? $test_path_resolver->path_for_test($test_id) : $test_id,
'platform' => $resolve_ids ? $id_to_platform_name[$platform_id] : $platform_id,
Modified: trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -11,7 +11,7 @@
update(ratio, label, showWarningIcon)
{
- console.assert(isNaN(ratio) || (ratio >= -1 && ratio <= 1));
+ console.assert(typeof(ratio) == 'number');
this._ratio = ratio;
this._label = label;
this._showWarningIcon = showWarningIcon;
Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/index.html 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html 2016-07-26 02:45:13 UTC (rev 203709)
@@ -101,6 +101,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
Modified: trunk/Websites/perf.webkit.org/public/v3/main.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/main.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/main.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -30,6 +30,9 @@
var analysisTaskPage = new AnalysisTaskPage();
analysisTaskPage.setParentPage(analysisCategoryPage);
+ var buildRequestQueuePage = new BuildRequestQueuePage();
+ buildRequestQueuePage.setParentPage(analysisCategoryPage);
+
var heading = new Heading(manifest.siteTitle);
heading.addPageGroup([summaryPage, chartsPage, analysisCategoryPage].filter(function (page) { return page; }));
@@ -42,6 +45,7 @@
router.addPage(chartsPage);
router.addPage(createAnalysisTaskPage);
router.addPage(analysisTaskPage);
+ router.addPage(buildRequestQueuePage);
router.addPage(analysisCategoryPage);
for (var page of dashboardPages)
router.addPage(page);
Modified: trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -71,7 +71,7 @@
fixes() { return this._fixes; }
platform() { return this._platform; }
metric() { return this._metric; }
- category() { return this._category; }
+
changeType() { return this._changeType; }
updateName(newName) { return this._updateRemoteState({name: newName}); }
@@ -146,12 +146,25 @@
});
}
+ category()
+ {
+ var category = 'unconfirmed';
+
+ if (this._changeType == 'unchanged' || this._changeType == 'inconclusive'
+ || (this._changeType == 'regression' && this._fixes.length)
+ || (this._changeType == 'progression' && (this._causes.length || this._fixes.length)))
+ category = 'closed';
+ else if (this._causes.length || this._fixes.length || this._changeType == 'regression' || this._changeType == 'progression')
+ category = 'investigated';
+
+ return category;
+ }
+
static categories()
{
return [
'unconfirmed',
- 'bisecting',
- 'identified',
+ 'investigated',
'closed'
];
}
Modified: trunk/Websites/perf.webkit.org/public/v3/models/build-request.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -5,6 +5,8 @@
constructor(id, object)
{
super(id, object);
+ this._triggerable = object.triggerable;
+ this._analysisTaskId = object.task;
this._testGroupId = object.testGroupId;
console.assert(!object.testGroup || object.testGroup instanceof TestGroup);
this._testGroup = object.testGroup;
@@ -20,6 +22,7 @@
this._status = object.status;
this._statusUrl = object.url;
this._buildId = object.build;
+ this._createdAt = new Date(object.createdAt);
this._result = null;
}
@@ -33,6 +36,7 @@
this._buildId = object.build;
}
+ analysisTaskId() { return this._analysisTaskId; }
testGroupId() { return this._testGroupId; }
testGroup() { return this._testGroup; }
platform() { return this._platform; }
@@ -49,7 +53,7 @@
{
switch (this._status) {
case 'pending':
- return 'Waiting to be scheduled';
+ return 'Waiting';
case 'scheduled':
return 'Scheduled';
case 'running':
@@ -65,7 +69,45 @@
statusUrl() { return this._statusUrl; }
buildId() { return this._buildId; }
+ createdAt() { return this._createdAt; }
+ waitingTime(referenceTime)
+ {
+ const units = [
+ {unit: 'week', length: 7 * 24 * 3600},
+ {unit: 'day', length: 24 * 3600},
+ {unit: 'hour', length: 3600},
+ {unit: 'minute', length: 60},
+ ];
+
+ var diff = (referenceTime - this.createdAt()) / 1000;
+
+ var indexOfFirstSmallEnoughUnit = units.length - 1;
+ for (var i = 0; i < units.length; i++) {
+ if (diff > 1.5 * units[i].length) {
+ indexOfFirstSmallEnoughUnit = i;
+ break;
+ }
+ }
+
+ var label = '';
+ var lastUnit = false;
+ for (var i = indexOfFirstSmallEnoughUnit; !lastUnit; i++) {
+ lastUnit = i == indexOfFirstSmallEnoughUnit + 1 || i == units.length - 1;
+ var length = units[i].length;
+ var valueForUnit = lastUnit ? Math.round(diff / length) : Math.floor(diff / length);
+
+ var unit = units[i].unit + (valueForUnit == 1 ? '' : 's');
+ if (label)
+ label += ' ';
+ label += `${valueForUnit} ${unit}`;
+
+ diff = diff - valueForUnit * length;
+ }
+
+ return label;
+ }
+
result() { return this._result; }
setResult(result)
{
@@ -73,6 +115,21 @@
this._testGroup.didSetResult(this);
}
+ static fetchTriggerables()
+ {
+ return this.cachedFetch('/api/triggerables/').then(function (response) {
+ return response.triggerables.map(function (entry) { return {id: entry.id, name: entry.name}; });
+ });
+ }
+
+ // FIXME: Create a real model object for triggerables.
+ static cachedRequestsForTriggerableID(id)
+ {
+ return this.all().filter(function (request) {
+ return request._triggerable == id;
+ });
+ }
+
static fetchForTriggerable(triggerable)
{
return RemoteAPI.getJSONWithStatus('/api/build-requests/' + triggerable).then(function (data) {
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-page.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-page.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-page.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -2,8 +2,9 @@
class AnalysisCategoryPage extends PageWithHeading {
constructor()
{
- super('Analysis', new AnalysisCategoryToolbar);
- this.toolbar().setFilterCallback(this.render.bind(this));
+ super('Analysis');
+ this._categoryToolbar = this.content().querySelector('analysis-category-toolbar').component();
+ this._categoryToolbar.setCategoryPage(this);
this._renderedList = false;
this._renderedFilter = false;
this._fetched = false;
@@ -12,7 +13,7 @@
title()
{
- var category = this.toolbar().currentCategory();
+ var category = this._categoryToolbar.currentCategory();
return (category ? category.charAt(0).toUpperCase() + category.slice(1) + ' ' : '') + 'Analysis Tasks';
}
routeName() { return 'analysis'; }
@@ -30,26 +31,50 @@
super.open(state);
}
+ serializeState()
+ {
+ return this.stateForCategory(this._categoryToolbar.currentCategory());
+ }
+
+ stateForCategory(category)
+ {
+ var state = {category: category};
+ var filter = this._categoryToolbar.filter();
+ if (filter)
+ state.filter = filter;
+ return state;
+ }
+
updateFromSerializedState(state, isOpen)
{
if (state.category instanceof Set)
state.category = Array.from(state.category.values())[0];
+ if (state.filter instanceof Set)
+ state.filter = Array.from(state.filter.values())[0];
- if (this.toolbar().setCategoryIfValid(state.category))
+ if (this._categoryToolbar.setCategoryIfValid(state.category))
this._renderedList = false;
+ if (state.filter)
+ this._categoryToolbar.setFilter(state.filter);
+
if (!isOpen)
this.render();
}
+ filterDidChange(shouldUpdateState)
+ {
+ this.render();
+ if (shouldUpdateState)
+ this.scheduleUrlStateUpdate();
+ }
+
render()
{
Instrumentation.startMeasuringTime('AnalysisCategoryPage', 'render');
- if (!this._renderedList) {
- super.render();
- this.toolbar().render();
- }
+ super.render();
+ this._categoryToolbar.render();
if (this._errorMessage) {
console.assert(!this._fetched);
@@ -69,7 +94,7 @@
this._renderedList = true;
}
- var filter = this.toolbar().filter();
+ var filter = this._categoryToolbar.filter();
if (filter || this._renderedFilter) {
Instrumentation.startMeasuringTime('AnalysisCategoryPage', 'filterByKeywords');
var keywordList = filter ? filter.toLowerCase().split(/\s+/) : [];
@@ -98,7 +123,7 @@
Instrumentation.startMeasuringTime('AnalysisCategoryPage', 'reconstructTaskList');
console.assert(this.router());
- var currentCategory = this.toolbar().currentCategory();
+ var currentCategory = this._categoryToolbar.currentCategory();
var tasks = AnalysisTask.all().filter(function (task) {
return task.category() == currentCategory;
@@ -164,6 +189,7 @@
static htmlTemplate()
{
return `
+ <div class="toolbar-container"><analysis-category-toolbar></analysis-category-toolbar></div>
<div class="analysis-task-category">
<table>
<thead>
@@ -184,6 +210,10 @@
static cssTemplate()
{
return `
+ .toolbar-container {
+ text-align: center;
+ }
+
.analysis-task-category {
width: calc(100% - 2rem);
margin: 1rem;
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-toolbar.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-toolbar.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-toolbar.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -1,63 +1,57 @@
-class AnalysisCategoryToolbar extends Toolbar {
- constructor()
+class AnalysisCategoryToolbar extends ComponentBase {
+ constructor(categoryPage)
{
super('analysis-category-toolbar');
+ this._categoryPage = null;
this._categories = AnalysisTask.categories();
this._currentCategory = null;
this._filter = null;
- this._filterCallback = null;
this.setCategoryIfValid(null);
+
+ this._filterInput = this.content().querySelector('input');
+ this._filterInput._oninput_ = this._filterMayHaveChanged.bind(this, false);
+ this._filterInput._onchange_ = this._filterMayHaveChanged.bind(this, true);
}
+ setCategoryPage(categoryPage) { this._categoryPage = categoryPage; }
currentCategory() { return this._currentCategory; }
-
filter() { return this._filter; }
- setFilterCallback(callback)
- {
- console.assert(!callback || callback instanceof Function);
- this._filterCallback = callback;
- }
+ setFilter(filter) { this._filter = filter; }
render()
{
- var router = this.router();
+ if (!this._categoryPage)
+ return;
+
+ var router = this._categoryPage.router();
console.assert(router);
- var currentPage = router.currentPage();
- console.assert(currentPage instanceof AnalysisCategoryPage);
-
super.render();
var element = ComponentBase.createElement;
var link = ComponentBase.createLink;
- var input = element('input',
- {
- oninput: this._filterMayHaveChanged.bind(this),
- onchange: this._filterMayHaveChanged.bind(this),
- });
- if (this._filter != null)
- input.value = this._filter;
+ if (this._filterInput.value != this._filter)
+ this._filterInput.value = this._filter;
var currentCategory = this._currentCategory;
- this.renderReplace(this.content().querySelector('.analysis-task-category-toolbar'), [
- element('ul', {class: 'buttoned-toolbar'},
- this._categories.map(function (category) {
- return element('li',
- {class: category == currentCategory ? 'selected' : null},
- link(category, router.url(currentPage.routeName(), {category: category})));
- })),
- input]);
+ var categoryPage = this._categoryPage;
+ this.renderReplace(this.content().querySelector('.analysis-task-category-toolbar'),
+ this._categories.map(function (category) {
+ return element('li',
+ {class: category == currentCategory ? 'selected' : null},
+ link(category, router.url(categoryPage.routeName(), categoryPage.stateForCategory(category))));
+ }));
}
- _filterMayHaveChanged(event)
+ _filterMayHaveChanged(shouldUpdateState, event)
{
var input = event.target;
var oldFilter = this._filter;
this._filter = input.value;
- if (this._filter != oldFilter && this._filterCallback)
- this._filterCallback(this._filter);
+ if (this._filter != oldFilter && this._categoryPage || shouldUpdateState)
+ this._categoryPage.filterDidChange(shouldUpdateState);
}
setCategoryIfValid(category)
@@ -67,17 +61,28 @@
if (this._categories.indexOf(category) < 0)
return false;
this._currentCategory = category;
-
- var filterDidChange = !!this._filter;
- this._filter = null;
- if (filterDidChange && this._filterCallback)
- this._filterCallback(this._filter);
-
return true;
}
+ static cssTemplate()
+ {
+ return Toolbar.cssTemplate() + `
+ .queue-toolbar {
+ position: absolute;
+ right: 1rem;
+ }
+ `
+ }
+
static htmlTemplate()
{
- return `<div class="buttoned-toolbar analysis-task-category-toolbar"></div>`;
+ return `
+ <ul class="analysis-task-category-toolbar buttoned-toolbar"></ul>
+ <input type="text">
+ <ul class="buttoned-toolbar queue-toolbar">
+ <li><a href=""
+ </ul>`;
}
}
+
+ComponentBase.defineElement('analysis-category-toolbar', AnalysisCategoryToolbar);
Added: trunk/Websites/perf.webkit.org/public/v3/pages/build-request-queue-page.js (0 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/pages/build-request-queue-page.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/build-request-queue-page.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -0,0 +1,166 @@
+
+class BuildRequestQueuePage extends PageWithHeading {
+ constructor()
+ {
+ super('build-request-queue-page');
+ this._triggerables = [];
+ }
+
+ routeName() { return 'analysis/queue'; }
+ pageTitle() { return 'Build Request Queue'; }
+
+ open(state)
+ {
+ var self = this;
+ BuildRequest.fetchTriggerables().then(function (list) {
+ self._triggerables = list.map(function (entry) {
+ var triggerable = {name: entry.name, buildRequests: BuildRequest.cachedRequestsForTriggerableID(entry.id)};
+
+ BuildRequest.fetchForTriggerable(entry.name).then(function (requests) {
+ triggerable.buildRequests = requests;
+ self.render();
+ });
+
+ return triggerable;
+ });
+ self.render();
+ });
+
+ AnalysisTask.fetchAll().then(function () {
+ self.render();
+ });
+
+ super.open(state);
+ }
+
+ render()
+ {
+ super.render();
+
+ var referenceTime = Date.now();
+ this.renderReplace(this.content().querySelector('.triggerable-list'),
+ this._triggerables.map(this._constructBuildRequestTable.bind(this, referenceTime)));
+ }
+
+ _constructBuildRequestTable(referenceTime, triggerable)
+ {
+ if (!triggerable.buildRequests.length)
+ return [];
+
+ var rowList = [];
+ var previousRow = null;
+ var requestCount = 0;
+ var requestCountForGroup = {};
+ for (var request of triggerable.buildRequests) {
+ var groupId = request.testGroupId();
+ if (groupId in requestCountForGroup)
+ requestCountForGroup[groupId]++;
+ else
+ requestCountForGroup[groupId] = 1
+
+ if (request.hasFinished())
+ continue;
+
+ requestCount++;
+ if (previousRow && previousRow.request.testGroupId() == groupId) {
+ if (previousRow.contraction)
+ previousRow.count++;
+ else
+ rowList.push({contraction: true, request: previousRow.request, count: 1});
+ } else
+ rowList.push({contraction: false, request: request, count: null});
+ previousRow = rowList[rowList.length - 1];
+ }
+
+ var element = ComponentBase.createElement;
+ var link = ComponentBase.createLink;
+ var router = this.router();
+ return element('table', {class: 'build-request-table'}, [
+ element('caption', `${triggerable.name}: ${requestCount} pending requests`),
+ element('thead', [
+ element('td', 'Request ID'),
+ element('td', 'Platform'),
+ element('td', 'Test'),
+ element('td', 'Analysis Task'),
+ element('td', 'Group'),
+ element('td', 'Order'),
+ element('td', 'Status'),
+ element('td', 'Waiting Time'),
+ ]),
+ element('tbody', rowList.map(function (entry) {
+ if (entry.contraction) {
+ return element('tr', {class: 'contraction'}, [
+ element('td', {colspan: 8}, `${entry.count} additional requests`)
+ ]);
+ }
+
+ var request = entry.request;
+ var taskId = request.analysisTaskId();
+ var task = AnalysisTask.findById(taskId);
+ return element('tr', [
+ element('td', {class: 'request-id'}, request.id()),
+ element('td', {class: 'platform'}, request.platform().name()),
+ element('td', {class: 'test'}, request.test().fullName()),
+ element('td', {class: 'task'}, !task ? taskId : link(task.name(), router.url(`analysis/task/${task.id()}`))),
+ element('td', {class: 'test-group'}, request.testGroupId()),
+ element('td', {class: 'order'}, `${request.order() + 1} of ${requestCountForGroup[request.testGroupId()]}`),
+ element('td', {class: 'status'}, request.statusLabel()),
+ element('td', {class: 'wait'}, request.waitingTime(referenceTime))]);
+ }))]);
+ }
+
+ static htmlTemplate()
+ {
+ return `<div class="triggerable-list"></div>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ .triggerable-list {
+ margin: 1rem;
+ }
+
+ .build-request-table {
+ border-collapse: collapse;
+ border: solid 0px #ccc;
+ font-size: 0.9rem;
+ margin-bottom: 2rem;
+ }
+
+ .build-request-table caption {
+ text-align: left;
+ font-size: 1.2rem;
+ margin: 1rem 0 0.5rem 0;
+ color: #c93;
+ }
+
+ .build-request-table td {
+ padding: 0.2rem;
+ }
+
+ .build-request-table td:not(.test):not(.task) {
+ white-space: nowrap;
+ }
+
+ .build-request-table .contraction {
+ text-align: center;
+ color: #999;
+ }
+
+ .build-request-table tr:not(.contraction) td {
+ border-top: solid 1px #eee;
+ }
+
+ .build-request-table tr:last-child td {
+ border-bottom: solid 1px #eee;
+ }
+
+ .build-request-table thead {
+ font-weight: inherit;
+ color: #c93;
+ }
+ `;
+ }
+
+}
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -33,7 +33,7 @@
super.open(state);
var current = Date.now();
- var timeRange = [current - 7 * 24 * 3600 * 1000, current];
+ var timeRange = [current - 24 * 3600 * 1000, current];
for (var group of this._configGroups)
group.fetchAndComputeSummary(timeRange).then(this.render.bind(this));
}
@@ -250,7 +250,7 @@
this._measurementSets = [];
this._configurationList = [];
this._setToRatio = new Map;
- this._ratio = null;
+ this._ratio = NaN;
this._label = null;
this._missingPlatforms = new Set;
this._platformsWithoutBaseline = new Set;
Modified: trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -177,7 +177,7 @@
assert.ok(!buildRequests[0].hasFinished());
assert.ok(!buildRequests[0].hasStarted());
assert.ok(buildRequests[0].isPending());
- assert.equal(buildRequests[0].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[0].statusLabel(), 'Waiting');
assert.equal(buildRequests[1].id(), 701);
assert.equal(buildRequests[1].testGroupId(), 600);
@@ -188,7 +188,7 @@
assert.ok(!buildRequests[1].hasFinished());
assert.ok(!buildRequests[1].hasStarted());
assert.ok(buildRequests[1].isPending());
- assert.equal(buildRequests[1].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[1].statusLabel(), 'Waiting');
assert.equal(buildRequests[2].id(), 702);
assert.equal(buildRequests[2].testGroupId(), 600);
@@ -199,7 +199,7 @@
assert.ok(!buildRequests[2].hasFinished());
assert.ok(!buildRequests[2].hasStarted());
assert.ok(buildRequests[2].isPending());
- assert.equal(buildRequests[2].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[2].statusLabel(), 'Waiting');
assert.equal(buildRequests[3].id(), 703);
assert.equal(buildRequests[3].testGroupId(), 600);
@@ -210,7 +210,7 @@
assert.ok(!buildRequests[3].hasFinished());
assert.ok(!buildRequests[3].hasStarted());
assert.ok(buildRequests[3].isPending());
- assert.equal(buildRequests[3].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[3].statusLabel(), 'Waiting');
let osx = Repository.findById(9);
assert.equal(osx.name(), 'OS X');
Modified: trunk/Websites/perf.webkit.org/unit-tests/analysis-task-tests.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/unit-tests/analysis-task-tests.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/unit-tests/analysis-task-tests.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -175,7 +175,7 @@
assert.ok(task.hasResults());
assert.ok(task.hasPendingRequests());
assert.equal(task.requestLabel(), '6 of 14');
- assert.equal(task.category(), 'identified');
+ assert.equal(task.category(), 'investigated');
assert.equal(task.changeType(), 'regression');
assert.equal(task.startMeasurementId(), 37117949);
assert.equal(task.startTime(), 1454444458791);
Added: trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js (0 => 203709)
--- trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js (rev 0)
+++ trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -0,0 +1,158 @@
+'use strict';
+
+const assert = require('assert');
+
+require('../tools/js/v3-models.js');
+let MockModels = require('./resources/mock-v3-models.js').MockModels;
+
+function sampleBuildRequestData()
+{
+ return {
+ "buildRequests": [{
+ "id": "16985",
+ "triggerable": "3",
+ "test": "844",
+ "platform": "31",
+ "testGroup": "2128",
+ "order": "0",
+ "rootSet": "4255",
+ "status": "pending",
+ "url": null,
+ "build": null,
+ "createdAt": 1458688514000
+ }],
+ "rootSets": [{
+ "id": "4255",
+ "roots": ["87832", "93116"]
+ }, {
+ "id": "4256",
+ "roots": ["87832", "96336"]
+ }],
+ "roots": [{
+ "id": "87832",
+ "repository": "9",
+ "revision": "10.11 15A284",
+ "time": 0
+ }, {
+ "id": "93116",
+ "repository": "11",
+ "revision": "191622",
+ "time": 1445945816878
+ }, {
+ "id": "87832",
+ "repository": "9",
+ "revision": "10.11 15A284",
+ "time": 0
+ }, {
+ "id": "96336",
+ "repository": "11",
+ "revision": "192736",
+ "time": 1448225325650
+ }],
+ "status": "OK"
+ };
+}
+
+describe('TestGroup', function () {
+ MockModels.inject();
+
+ describe('waitingTime', function () {
+ it('should return "0 minutes" when the reference time is identically equal to createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '0 minutes');
+ });
+
+ it('should return "1 minute" when the reference time is exactly one minute head of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 60 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '1 minute');
+ });
+
+ it('should return "1 minute" when the reference time is 75 seconds head of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 75 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '1 minute');
+ });
+
+ it('should return "2 minutes" when the reference time is 118 seconds head of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 118 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '2 minutes');
+ });
+
+ it('should return "75 minutes" when the reference time is 75 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 75 * 60 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '75 minutes');
+ });
+
+ it('should return "1 hour 58 minutes" when the reference time is 118 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 118 * 60 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '1 hour 58 minutes');
+ });
+
+ it('should return "3 hours 2 minutes" when the reference time is 182 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - 182 * 60 * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '3 hours 2 minutes');
+ });
+
+ it('should return "27 hours 14 minutes" when the reference time is 27 hours 14 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - (27 * 3600 + 14 * 60) * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '27 hours 14 minutes');
+ });
+
+ it('should return "2 days 3 hours" when the reference time is 51 hours 14 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - (51 * 3600 + 14 * 60) * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '2 days 3 hours');
+ });
+
+ it('should return "2 days 0 hours" when the reference time is 48 hours 1 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - (48 * 3600 + 1 * 60) * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '2 days 0 hours');
+ });
+
+ it('should return "2 days 2 hours" when the reference time is 49 hours 59 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - (49 * 3600 + 59 * 60) * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '2 days 2 hours');
+ });
+
+ it('should return "2 weeks 6 days" when the reference time is 20 days 5 hours 21 minutes ahead of createdAt', function () {
+ const data = ""
+ const now = Date.now();
+ data.buildRequests[0].createdAt = now - ((20 * 24 + 5) * 3600 + 21 * 60) * 1000;
+ const request = BuildRequest.constructBuildRequestsFromData(data)[0];
+ assert.equal(request.waitingTime(now), '2 weeks 6 days');
+ });
+
+ });
+
+});
\ No newline at end of file
Modified: trunk/Websites/perf.webkit.org/unit-tests/test-groups-tests.js (203708 => 203709)
--- trunk/Websites/perf.webkit.org/unit-tests/test-groups-tests.js 2016-07-26 02:40:04 UTC (rev 203708)
+++ trunk/Websites/perf.webkit.org/unit-tests/test-groups-tests.js 2016-07-26 02:45:13 UTC (rev 203709)
@@ -140,7 +140,7 @@
assert.ok(!buildRequests[0].hasFinished());
assert.ok(!buildRequests[0].hasStarted());
assert.ok(buildRequests[0].isPending());
- assert.equal(buildRequests[0].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[0].statusLabel(), 'Waiting');
assert.equal(buildRequests[0].buildId(), null);
assert.equal(buildRequests[0].result(), null);
@@ -149,7 +149,7 @@
assert.ok(!buildRequests[1].hasFinished());
assert.ok(!buildRequests[1].hasStarted());
assert.ok(buildRequests[1].isPending());
- assert.equal(buildRequests[1].statusLabel(), 'Waiting to be scheduled');
+ assert.equal(buildRequests[1].statusLabel(), 'Waiting');
assert.equal(buildRequests[1].buildId(), null);
assert.equal(buildRequests[1].result(), null);
});