aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-09-30 15:06:25 +0000
committerGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-09-30 15:06:25 +0000
commitafaad3dd7002feb6e69983ddc4e5148d7803baed (patch)
tree9933a754bc7a94efa3d88ff348d8647e91a758ed
parent5e49738c99f573b2e5ba342f6ec8bfa3bcf125e4 (diff)
HTTP GM results viewer: server now returns category summaries along with testData
(SkipBuildbotRuns) R=borenet@google.com Review URL: https://codereview.chromium.org/25045003 git-svn-id: http://skia.googlecode.com/svn/trunk@11520 2bbb7eff-a529-9590-31e7-b0007b416f81
-rwxr-xr-xgm/rebaseline_server/results.py119
-rwxr-xr-xgm/rebaseline_server/server.py8
-rw-r--r--gm/rebaseline_server/static/loader.js8
-rw-r--r--gm/rebaseline_server/static/view.html55
4 files changed, 129 insertions, 61 deletions
diff --git a/gm/rebaseline_server/results.py b/gm/rebaseline_server/results.py
index 0c50d26397..1336097f1f 100755
--- a/gm/rebaseline_server/results.py
+++ b/gm/rebaseline_server/results.py
@@ -31,6 +31,9 @@ if GM_DIRECTORY not in sys.path:
import gm_json
IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
+CATEGORIES_TO_SUMMARIZE = [
+ 'builder', 'test', 'config', 'resultType',
+]
class Results(object):
""" Loads actual and expected results from all builders, supplying combined
@@ -44,24 +47,49 @@ class Results(object):
"""
self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root)
self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root)
- self._all_results = self._Combine()
+ self._all_results = Results._Combine(
+ actual_builder_dicts=self._actual_builder_dicts,
+ expected_builder_dicts=self._expected_builder_dicts)
def GetAll(self):
- """Return results of all tests, as a list in this form:
+ """Return results of all tests, as a dictionary in this form:
- [
+ {
+ "categories": # dictionary of categories listed in
+ # CATEGORIES_TO_SUMMARIZE, with the number of times
+ # each value appears within its category
{
- "builder": "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug",
- "test": "bigmatrix",
- "config": "8888",
- "resultType": "failed",
- "expectedHashType": "bitmap-64bitMD5",
- "expectedHashDigest": "10894408024079689926",
- "actualHashType": "bitmap-64bitMD5",
- "actualHashDigest": "2409857384569",
- },
- ...
- ]
+ "resultType": # category name
+ {
+ "failed": 29, # category value and total number found of that value
+ "failure-ignored": 948,
+ "no-comparison": 4502,
+ "succeeded": 38609,
+ },
+ "builder":
+ {
+ "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug": 1286,
+ "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release": 1134,
+ ...
+ },
+ ... # other categories from CATEGORIES_TO_SUMMARIZE
+ }, # end of "categories" dictionary
+
+ "testData": # list of test results, with a dictionary for each
+ [
+ {
+ "builder": "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug",
+ "test": "bigmatrix",
+ "config": "8888",
+ "resultType": "failed",
+ "expectedHashType": "bitmap-64bitMD5",
+ "expectedHashDigest": "10894408024079689926",
+ "actualHashType": "bitmap-64bitMD5",
+ "actualHashDigest": "2409857384569",
+ },
+ ...
+ ], # end of "testData" list
+ }
"""
return self._all_results
@@ -84,15 +112,21 @@ class Results(object):
meta_dict[builder] = gm_json.LoadFromFile(fullpath)
return meta_dict
- def _Combine(self):
- """Returns a list of all tests, across all builders, based on the
- contents of self._actual_builder_dicts and self._expected_builder_dicts .
- Returns the list in the same form needed for GetAllResults().
+ @staticmethod
+ def _Combine(actual_builder_dicts, expected_builder_dicts):
+ """Gathers the results of all tests, across all builders (based on the
+ contents of actual_builder_dicts and expected_builder_dicts)
+ and returns it in a list in the same form needed for self.GetAll().
+
+ This is a static method, because once we start refreshing results
+ asynchronously, we need to make sure we are not corrupting the object's
+ member variables.
"""
- all_tests = []
- for builder in sorted(self._actual_builder_dicts.keys()):
+ test_data = []
+ category_dict = {}
+ for builder in sorted(actual_builder_dicts.keys()):
actual_results_for_this_builder = (
- self._actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
+ actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
for result_type in sorted(actual_results_for_this_builder.keys()):
results_of_this_type = actual_results_for_this_builder[result_type]
if not results_of_this_type:
@@ -102,7 +136,7 @@ class Results(object):
try:
# TODO(epoger): assumes a single allowed digest per test
expected_image = (
- self._expected_builder_dicts
+ expected_builder_dicts
[builder][gm_json.JSONKEY_EXPECTEDRESULTS]
[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]
[0])
@@ -159,13 +193,8 @@ class Results(object):
else:
updated_result_type = result_type
- # TODO(epoger): For now, don't include succeeded results.
- # There are so many of them that they make the client too slow.
- if updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
- continue
-
(test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
- all_tests.append({
+ results_for_this_test = {
"builder": builder,
"test": test,
"config": config,
@@ -174,5 +203,35 @@ class Results(object):
"actualHashDigest": str(actual_image[1]),
"expectedHashType": expected_image[0],
"expectedHashDigest": str(expected_image[1]),
- })
- return all_tests
+ }
+ Results._AddToCategoryDict(category_dict, results_for_this_test)
+
+ # TODO(epoger): For now, don't include succeeded results in the raw
+ # data. There are so many of them that they make the client too slow.
+ if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
+ test_data.append(results_for_this_test)
+ return {"categories": category_dict, "testData": test_data}
+
+ @staticmethod
+ def _AddToCategoryDict(category_dict, test_results):
+ """Add test_results to the category dictionary we are building
+ (see documentation of self.GetAll() for the format of this dictionary).
+
+ params:
+ category_dict: category dict-of-dicts to add to; modify this in-place
+ test_results: test data with which to update category_list, in a dict:
+ {
+ "category_name": "category_value",
+ "category_name": "category_value",
+ ...
+ }
+ """
+ for category in CATEGORIES_TO_SUMMARIZE:
+ category_value = test_results.get(category)
+ if not category_value:
+ continue # test_results did not include this category, keep going
+ if not category_dict.get(category):
+ category_dict[category] = {}
+ if not category_dict[category].get(category_value):
+ category_dict[category][category_value] = 0
+ category_dict[category][category_value] += 1
diff --git a/gm/rebaseline_server/server.py b/gm/rebaseline_server/server.py
index 5b81d8c2e2..34c70f4f4d 100755
--- a/gm/rebaseline_server/server.py
+++ b/gm/rebaseline_server/server.py
@@ -214,10 +214,10 @@ def main():
'to access this server. WARNING: doing so will '
'allow users on other hosts to modify your '
'GM expectations!'))
- parser.add_argument('--port',
- help=('Which TCP port to listen on for HTTP requests; '
- 'defaults to %(default)s'),
- default=DEFAULT_PORT)
+ parser.add_argument('--port', type=int,
+ help=('Which TCP port to listen on for HTTP requests; '
+ 'defaults to %(default)s'),
+ default=DEFAULT_PORT)
args = parser.parse_args()
global _SERVER
_SERVER = Server(expectations_dir=args.expectations_dir,
diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js
index 68da73afda..d08fdccbf6 100644
--- a/gm/rebaseline_server/static/loader.js
+++ b/gm/rebaseline_server/static/loader.js
@@ -1,7 +1,7 @@
/*
* Loader:
- * Reads GM result reports written out by results_loader.py, and imports
- * their data into $scope.results .
+ * Reads GM result reports written out by results.py, and imports
+ * them into $scope.categories and $scope.testData .
*/
var Loader = angular.module(
'Loader',
@@ -12,8 +12,10 @@ Loader.controller(
function($scope, $http) {
$http.get("/results/all").then(
function(response) {
- $scope.results = response.data;
+ $scope.categories = response.data.categories;
+ $scope.testData = response.data.testData;
$scope.sortColumn = 'test';
+ $scope.showResultsOfType = 'failed';
}
);
}
diff --git a/gm/rebaseline_server/static/view.html b/gm/rebaseline_server/static/view.html
index 89ef538271..5913e5c2ca 100644
--- a/gm/rebaseline_server/static/view.html
+++ b/gm/rebaseline_server/static/view.html
@@ -15,6 +15,9 @@
--export mode
-->
+ <!-- TODO(epoger): Add some indication of how old the
+ expected/actual data is -->
+
Settings:
<ul>
<!-- TODO(epoger): Now that we get multiple result types in a single
@@ -24,45 +27,49 @@
<li>show results of type
<select ng-model="showResultsOfType"
ng-init="showResultsOfType='failed'">
- <option>failed</option>
- <option>failure-ignored</option>
- <!--
- <option>no-comparison</option>
-
- TODO(epoger): For now, I have disabled viewing the
- no-comparison results because there are so many of them, and
- the browser takes forever to download all the images. Maybe
- we should use some sort of lazy-loading technique
- (e.g. http://www.appelsiini.net/projects/lazyload ), so that
- the images are only loaded as they become viewable...
- -->
- <!--
- <option>succeeded</option>
-
+ <option ng-repeat="(resultType, count) in categories['resultType']"
+ value="{{resultType}}">
+ {{resultType}} ({{count}})
+ </option>
+ </select>
+ <!--
TODO(epoger): See results.py: for now, I have disabled
returning succeeded tests as part of the JSON, because it
makes the returned JSON too big (and slows down the client).
- -->
- </select>
+
+ Also, we should use some sort of lazy-loading technique
+ (e.g. http://www.appelsiini.net/projects/lazyload ), so that
+ the images are only loaded as they become viewable...
+ that will help with long lists like resultType='no-comparison'.
+ -->
+ <br>
+ TODO(epoger): 'no-comparison' will probably take forever;
+ see HTML source for details
+ <br>
+ TODO(epoger): 'succeeded' will not show any results;
+ see HTML source for details
</li>
<li>image size
- <input type="text" ng-model="imageSize" ng-init="imageSize=100"
+ <input type="text" ng-model="imageSizePending"
+ ng-init="imageSizePending=100; imageSize=100"
maxlength="4"/>
+ <button ng:click="imageSize=imageSizePending">apply</button>
</li>
</ul>
<p>
+ Click on the column header radio buttons to re-sort by that column...<br>
<!-- TODO(epoger): Show some sort of "loading" message, instead of
an empty table, while the data is loading. Otherwise, if there are
a lot of failures and it takes a long time to load them, the user
might think there are NO failures and leave the page! -->
<table border="1">
<tr>
- <th ng:click="sortColumn='builder'">Builder</th>
- <th ng:click="sortColumn='test'">Test</th>
- <th ng:click="sortColumn='config'">Config</th>
- <th ng:click="sortColumn='expectedHashDigest'">Expected Image</th>
- <th ng:click="sortColumn='actualHashDigest'">Actual Image</th>
+ <th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="builder">Builder</input></th>
+ <th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="test">Test</input></th>
+ <th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="config">Config</input></th>
+ <th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="expectedHashDigest">Expected Image</input></th>
+ <th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="actualHashDigest">Actual Image</input></th>
<!-- TODO(epoger): Add more columns, such as...
pixel diff
notes/bugs
@@ -71,7 +78,7 @@
</tr>
<!-- TODO(epoger): improve the column sorting, as per
http://jsfiddle.net/vojtajina/js64b/14/ -->
- <tr ng-repeat="result in results | filter: { resultType: showResultsOfType } | orderBy: sortColumn">
+ <tr ng-repeat="result in testData | filter: { resultType: showResultsOfType } | orderBy: sortColumn">
<td>{{result.builder}}</td>
<td>{{result.test}}</td>
<td>{{result.config}}</td>