OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 var g_browserBridge; | 5 var g_browserBridge; |
6 var g_mainView; | 6 var g_mainView; |
7 | 7 |
8 // TODO(eroman): Don't repeat the work of grouping, sorting, merging on every | |
9 // redraw. Rather do it only once when one of its dependencies | |
10 // change and cache the result. | |
11 | |
12 /** | 8 /** |
13 * Main entry point called once the page has loaded. | 9 * Main entry point called once the page has loaded. |
14 */ | 10 */ |
15 function onLoad() { | 11 function onLoad() { |
16 g_browserBridge = new BrowserBridge(); | 12 g_browserBridge = new BrowserBridge(); |
17 g_mainView = new MainView(); | 13 g_mainView = new MainView(); |
18 | 14 |
19 // Ask the browser to send us the current data. | 15 // Ask the browser to send us the current data. |
20 g_browserBridge.sendGetData(); | 16 g_browserBridge.sendGetData(); |
21 } | 17 } |
(...skipping 694 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
716 return path; | 712 return path; |
717 | 713 |
718 return path.substr(lastSlash + 1); | 714 return path.substr(lastSlash + 1); |
719 } | 715 } |
720 | 716 |
721 // -------------------------------------------------------------------------- | 717 // -------------------------------------------------------------------------- |
722 // Functions that augment, bucket, and compute aggregates for the input data. | 718 // Functions that augment, bucket, and compute aggregates for the input data. |
723 // -------------------------------------------------------------------------- | 719 // -------------------------------------------------------------------------- |
724 | 720 |
725 /** | 721 /** |
726 * Selects all the data in |rows| which are matched by |filterFunc|, and | |
727 * buckets the results using |entryToGroupKeyFunc|. For each bucket aggregates | |
728 * are computed, and the results are sorted. | |
729 * | |
730 * Returns a dictionary whose keys are the group name, and the value is an | |
731 * objected containing two properties: |rows| and |aggregates|. | |
732 */ | |
733 function prepareData(rows, entryToGroupKeyFunc, filterFunc, sortingFunc) { | |
734 var groupedData = {}; | |
735 | |
736 for (var i = 0; i < rows.length; ++i) { | |
737 var e = rows[i]; | |
738 | |
739 if (!filterFunc(e)) | |
740 continue; // Not matched by our filter, discard the row. | |
741 | |
742 var groupKey = entryToGroupKeyFunc(e); | |
743 | |
744 var groupData = groupedData[groupKey]; | |
745 if (!groupData) { | |
746 groupData = { | |
747 aggregates: initializeAggregates(ALL_KEYS), | |
748 rows: [], | |
749 }; | |
750 groupedData[groupKey] = groupData; | |
751 } | |
752 | |
753 // Add the row to our list. | |
754 groupData.rows.push(e); | |
755 | |
756 // Update aggregates for each column. | |
757 consumeAggregates(groupData.aggregates, e); | |
758 } | |
759 | |
760 // Sort all the data. | |
761 for (var groupKey in groupedData) | |
762 groupedData[groupKey].rows.sort(sortingFunc); | |
763 | |
764 return groupedData; | |
765 } | |
766 | |
767 /** | |
768 * Adds new derived properties to row. Mutates the provided dictionary |e|. | 722 * Adds new derived properties to row. Mutates the provided dictionary |e|. |
769 */ | 723 */ |
770 function augmentDataRow(e) { | 724 function augmentDataRow(e) { |
771 e[KEY_AVG_QUEUE_TIME] = e[KEY_QUEUE_TIME] / e[KEY_COUNT]; | 725 e[KEY_AVG_QUEUE_TIME] = e[KEY_QUEUE_TIME] / e[KEY_COUNT]; |
772 e[KEY_AVG_RUN_TIME] = e[KEY_RUN_TIME] / e[KEY_COUNT]; | 726 e[KEY_AVG_RUN_TIME] = e[KEY_RUN_TIME] / e[KEY_COUNT]; |
773 e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']'; | 727 e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']'; |
774 } | 728 } |
775 | 729 |
776 /** | 730 /** |
777 * Creates and initializes an aggregator object for each key in |columns|. | 731 * Creates and initializes an aggregator object for each key in |columns|. |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
906 addNode(parent, 'i', ' and '); | 860 addNode(parent, 'i', ' and '); |
907 var e = groupKey[i]; | 861 var e = groupKey[i]; |
908 addNode(parent, 'b', getNameForKey(e.key) + ' = '); | 862 addNode(parent, 'b', getNameForKey(e.key) + ' = '); |
909 addNode(parent, 'span', e.value); | 863 addNode(parent, 'span', e.value); |
910 } | 864 } |
911 } | 865 } |
912 | 866 |
913 /** | 867 /** |
914 * Renders the information for a particular group. | 868 * Renders the information for a particular group. |
915 */ | 869 */ |
916 function drawGroup(parent, groupKey, groupData, columns, | 870 function drawGroup(parent, groupData, columns, |
917 columnOnClickHandler, currentSortKeys) { | 871 columnOnClickHandler, currentSortKeys) { |
918 var div = addNode(parent, 'div'); | 872 var div = addNode(parent, 'div'); |
919 div.className = 'group-container'; | 873 div.className = 'group-container'; |
920 | 874 |
921 drawGroupTitle(div, groupKey); | 875 drawGroupTitle(div, groupData.key); |
922 | 876 |
923 var table = addNode(div, 'table'); | 877 var table = addNode(div, 'table'); |
924 | 878 |
925 drawDataTable(table, groupData, columns, columnOnClickHandler, | 879 drawDataTable(table, groupData, columns, columnOnClickHandler, |
926 currentSortKeys); | 880 currentSortKeys); |
927 } | 881 } |
928 | 882 |
929 /** | 883 /** |
930 * Renders a row that describes all the aggregate values for |columns|. | 884 * Renders a row that describes all the aggregate values for |columns|. |
931 */ | 885 */ |
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1204 // When resetting the data, it is possible for the backend to give us | 1158 // When resetting the data, it is possible for the backend to give us |
1205 // counts of "0". There is no point adding these rows (in fact they | 1159 // counts of "0". There is no point adding these rows (in fact they |
1206 // will cause us to do divide by zeros when calculating averages and | 1160 // will cause us to do divide by zeros when calculating averages and |
1207 // stuff), so we skip past them. | 1161 // stuff), so we skip past them. |
1208 continue; | 1162 continue; |
1209 } | 1163 } |
1210 | 1164 |
1211 // Add our computed properties. | 1165 // Add our computed properties. |
1212 augmentDataRow(newRow); | 1166 augmentDataRow(newRow); |
1213 | 1167 |
1214 this.allData_.push(newRow); | 1168 this.flatData_.push(newRow); |
1215 } | 1169 } |
1216 | 1170 |
| 1171 // Recompute the merged data based on flatData_. |
| 1172 this.updateMergedData_(); |
| 1173 }, |
| 1174 |
| 1175 updateMergedData_: function() { |
| 1176 // Recompute mergedData_. |
| 1177 this.mergedData_ = mergeRows(this.flatData_, |
| 1178 this.getMergeColumns_(), |
| 1179 this.shouldMergeSimilarThreads_()); |
| 1180 |
| 1181 // Recompute filteredData_ (since it is derived from mergedData_) |
| 1182 this.updateFilteredData_(); |
| 1183 }, |
| 1184 |
| 1185 updateFilteredData_: function() { |
| 1186 // Recompute filteredData_. |
| 1187 this.filteredData_ = []; |
| 1188 var filterFunc = this.getFilterFunction_(); |
| 1189 for (var i = 0; i < this.mergedData_.length; ++i) { |
| 1190 var r = this.mergedData_[i]; |
| 1191 if (!filterFunc(r)) { |
| 1192 // Not matched by our filter, discard. |
| 1193 continue; |
| 1194 } |
| 1195 this.filteredData_.push(r); |
| 1196 } |
| 1197 |
| 1198 // Recompute groupedData_ (since it is derived from filteredData_) |
| 1199 this.updateGroupedData_(); |
| 1200 }, |
| 1201 |
| 1202 updateGroupedData_: function() { |
| 1203 // Recompute groupedData_. |
| 1204 var groupKeyToData = {}; |
| 1205 var entryToGroupKeyFunc = this.getGroupingFunction_(); |
| 1206 for (var i = 0; i < this.filteredData_.length; ++i) { |
| 1207 var r = this.filteredData_[i]; |
| 1208 |
| 1209 var groupKey = entryToGroupKeyFunc(r); |
| 1210 |
| 1211 var groupData = groupKeyToData[groupKey]; |
| 1212 if (!groupData) { |
| 1213 groupData = { |
| 1214 key: JSON.parse(groupKey), |
| 1215 aggregates: initializeAggregates(ALL_KEYS), |
| 1216 rows: [], |
| 1217 }; |
| 1218 groupKeyToData[groupKey] = groupData; |
| 1219 } |
| 1220 |
| 1221 // Add the row to our list. |
| 1222 groupData.rows.push(r); |
| 1223 |
| 1224 // Update aggregates for each column. |
| 1225 consumeAggregates(groupData.aggregates, r); |
| 1226 } |
| 1227 this.groupedData_ = groupKeyToData; |
| 1228 |
| 1229 // Figure out a display order for the groups themselves. |
| 1230 this.sortedGroupKeys_ = getDictionaryKeys(groupKeyToData); |
| 1231 this.sortedGroupKeys_.sort(this.getGroupSortingFunction_()); |
| 1232 |
| 1233 // Sort the group data. |
| 1234 this.sortGroupedData_(); |
| 1235 }, |
| 1236 |
| 1237 sortGroupedData_: function() { |
| 1238 var sortingFunc = this.getSortingFunction_(); |
| 1239 for (var k in this.groupedData_) |
| 1240 this.groupedData_[k].rows.sort(sortingFunc); |
| 1241 |
| 1242 // Every cached data dependency is now up to date, all that is left is |
| 1243 // to actually draw the result. |
1217 this.redrawData_(); | 1244 this.redrawData_(); |
1218 }, | 1245 }, |
1219 | 1246 |
1220 redrawData_: function() { | 1247 getVisibleColumnKeys_: function() { |
1221 // Eliminate columns which we are merging on. | |
1222 var mergedKeys = this.getMergeColumns_(); | |
1223 var data = mergeRows( | |
1224 this.allData_, mergedKeys, this.shouldMergeSimilarThreads_()); | |
1225 | |
1226 // Figure out what columns to include, based on the selected checkboxes. | 1248 // Figure out what columns to include, based on the selected checkboxes. |
1227 var columns = this.getSelectionColumns_(); | 1249 var columns = this.getSelectionColumns_(); |
1228 deleteValuesFromArray(columns, mergedKeys); | |
1229 | 1250 |
1230 // Group, aggregate, filter, and sort the data. | 1251 // Eliminate columns which we are merging on. |
1231 var groupedData = prepareData( | 1252 deleteValuesFromArray(columns, this.getMergeColumns_()); |
1232 data, this.getGroupingFunction_(), this.getFilterFunction_(), | |
1233 this.getSortingFunction_()); | |
1234 | 1253 |
1235 // Figure out a display order for the groups. | 1254 // Eliminate columns which we are grouped on. |
1236 var groupKeys = getDictionaryKeys(groupedData); | 1255 if (this.sortedGroupKeys_.length > 0) { |
1237 groupKeys.sort(this.getGroupSortingFunction_()); | |
1238 | |
1239 // Clear the results div, sine we may be overwriting older data. | |
1240 var parent = $(RESULTS_DIV_ID); | |
1241 parent.innerHTML = ''; | |
1242 | |
1243 if (groupKeys.length > 0) { | |
1244 // The grouping will be the the same for each so just pick the first. | 1256 // The grouping will be the the same for each so just pick the first. |
1245 var randomGroupKey = JSON.parse(groupKeys[0]); | 1257 var randomGroupKey = this.groupedData_[this.sortedGroupKeys_[0]].key; |
1246 | 1258 |
1247 // The grouped properties are going to be the same for each row in our, | 1259 // The grouped properties are going to be the same for each row in our, |
1248 // table, so avoid drawing them in our table! | 1260 // table, so avoid drawing them in our table! |
1249 var keysToExclude = [] | 1261 var keysToExclude = [] |
1250 | 1262 |
1251 for (var i = 0; i < randomGroupKey.length; ++i) | 1263 for (var i = 0; i < randomGroupKey.length; ++i) |
1252 keysToExclude.push(randomGroupKey[i].key); | 1264 keysToExclude.push(randomGroupKey[i].key); |
1253 columns = columns.slice(0); | 1265 columns = columns.slice(0); |
1254 deleteValuesFromArray(columns, keysToExclude); | 1266 deleteValuesFromArray(columns, keysToExclude); |
1255 } | 1267 } |
1256 | 1268 |
| 1269 return columns; |
| 1270 }, |
| 1271 |
| 1272 redrawData_: function() { |
| 1273 // Clear the results div, sine we may be overwriting older data. |
| 1274 var parent = $(RESULTS_DIV_ID); |
| 1275 parent.innerHTML = ''; |
| 1276 |
| 1277 var columns = this.getVisibleColumnKeys_(); |
| 1278 |
1257 var columnOnClickHandler = this.onClickColumn_.bind(this); | 1279 var columnOnClickHandler = this.onClickColumn_.bind(this); |
1258 | 1280 |
1259 // Draw each group. | 1281 // Draw each group. |
1260 for (var i = 0; i < groupKeys.length; ++i) { | 1282 for (var i = 0; i < this.sortedGroupKeys_.length; ++i) { |
1261 var groupKeyString = groupKeys[i]; | 1283 var groupData = this.groupedData_[this.sortedGroupKeys_[i]]; |
1262 var groupData = groupedData[groupKeyString]; | 1284 drawGroup(parent, groupData, columns, |
1263 var groupKey = JSON.parse(groupKeyString); | |
1264 | |
1265 drawGroup(parent, groupKey, groupData, columns, | |
1266 columnOnClickHandler, this.currentSortKeys_); | 1285 columnOnClickHandler, this.currentSortKeys_); |
1267 } | 1286 } |
1268 }, | 1287 }, |
1269 | 1288 |
1270 init_: function() { | 1289 init_: function() { |
1271 this.allData_ = []; | 1290 // Data goes through the following pipeline: |
| 1291 // (1) Raw data received from browser, and transformed into our own |
| 1292 // internal row format (where properties are indexed by KEY_* |
| 1293 // constants.) |
| 1294 // (2) We "augment" each row by adding some extra computed columns |
| 1295 // (like averages). |
| 1296 // (3) The rows are merged using current merge settings. |
| 1297 // (4) The rows that don't match current search expression are |
| 1298 // tossed out. |
| 1299 // (5) The rows are organized into "groups" based on current settings, |
| 1300 // and aggregate values are computed for each resulting group. |
| 1301 // (6) The rows within each group are sorted using current settings. |
| 1302 // (7) The grouped rows are drawn to the screen. |
| 1303 this.flatData_ = []; |
| 1304 this.mergedData_ = []; |
| 1305 this.filteredData_ = []; |
| 1306 this.groupedData_ = {}; |
| 1307 this.sortedGroupKeys_ = []; |
| 1308 |
1272 this.fillSelectionCheckboxes_($(COLUMN_TOGGLES_CONTAINER_ID)); | 1309 this.fillSelectionCheckboxes_($(COLUMN_TOGGLES_CONTAINER_ID)); |
1273 this.fillMergeCheckboxes_($(COLUMN_MERGE_TOGGLES_CONTAINER_ID)); | 1310 this.fillMergeCheckboxes_($(COLUMN_MERGE_TOGGLES_CONTAINER_ID)); |
1274 | 1311 |
1275 $(FILTER_SEARCH_ID).onsearch = this.onChangedFilter_.bind(this); | 1312 $(FILTER_SEARCH_ID).onsearch = this.onChangedFilter_.bind(this); |
1276 | 1313 |
1277 this.currentSortKeys_ = INITIAL_SORT_KEYS.slice(0); | 1314 this.currentSortKeys_ = INITIAL_SORT_KEYS.slice(0); |
1278 this.currentGroupingKeys_ = INITIAL_GROUP_KEYS.slice(0); | 1315 this.currentGroupingKeys_ = INITIAL_GROUP_KEYS.slice(0); |
1279 | 1316 |
1280 this.fillGroupingDropdowns_(); | 1317 this.fillGroupingDropdowns_(); |
1281 this.fillSortingDropdowns_(); | 1318 this.fillSortingDropdowns_(); |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1378 if (i < this.currentSortKeys_.length) { | 1415 if (i < this.currentSortKeys_.length) { |
1379 var key = this.currentSortKeys_[i]; | 1416 var key = this.currentSortKeys_[i]; |
1380 setSelectedOptionByValue(select, key); | 1417 setSelectedOptionByValue(select, key); |
1381 } | 1418 } |
1382 } | 1419 } |
1383 }, | 1420 }, |
1384 | 1421 |
1385 onChangedGrouping_: function(select, i) { | 1422 onChangedGrouping_: function(select, i) { |
1386 updateKeyListFromDropdown(this.currentGroupingKeys_, i, select); | 1423 updateKeyListFromDropdown(this.currentGroupingKeys_, i, select); |
1387 this.fillGroupingDropdowns_(); | 1424 this.fillGroupingDropdowns_(); |
1388 this.redrawData_(); | 1425 this.updateGroupedData_(); |
1389 }, | 1426 }, |
1390 | 1427 |
1391 onChangedSorting_: function(select, i) { | 1428 onChangedSorting_: function(select, i) { |
1392 updateKeyListFromDropdown(this.currentSortKeys_, i, select); | 1429 updateKeyListFromDropdown(this.currentSortKeys_, i, select); |
1393 this.fillSortingDropdowns_(); | 1430 this.fillSortingDropdowns_(); |
1394 this.redrawData_(); | 1431 this.sortGroupedData_(); |
1395 }, | 1432 }, |
1396 | 1433 |
1397 onSelectCheckboxChanged_: function() { | 1434 onSelectCheckboxChanged_: function() { |
1398 this.redrawData_(); | 1435 this.redrawData_(); |
1399 }, | 1436 }, |
1400 | 1437 |
1401 onMergeCheckboxChanged_: function() { | 1438 onMergeCheckboxChanged_: function() { |
1402 this.redrawData_(); | 1439 this.updateMergedData_(); |
1403 }, | 1440 }, |
1404 | 1441 |
1405 onMergeSimilarThreadsCheckboxChanged_: function() { | 1442 onMergeSimilarThreadsCheckboxChanged_: function() { |
1406 this.redrawData_(); | 1443 this.updateMergedData_(); |
1407 }, | 1444 }, |
1408 | 1445 |
1409 onChangedFilter_: function() { | 1446 onChangedFilter_: function() { |
1410 this.redrawData_(); | 1447 this.updateFilteredData_(); |
1411 }, | 1448 }, |
1412 | 1449 |
1413 /** | 1450 /** |
1414 * When left-clicking a column, change the primary sort order to that | 1451 * When left-clicking a column, change the primary sort order to that |
1415 * column. If we were already sorted on that column then reverse the order. | 1452 * column. If we were already sorted on that column then reverse the order. |
1416 * | 1453 * |
1417 * When alt-clicking, add a secondary sort column. Similarly, if | 1454 * When alt-clicking, add a secondary sort column. Similarly, if |
1418 * alt-clicking a column which was already being sorted on, reverse its | 1455 * alt-clicking a column which was already being sorted on, reverse its |
1419 * order. | 1456 * order. |
1420 */ | 1457 */ |
(...skipping 28 matching lines...) Expand all Loading... |
1449 // make it so. | 1486 // make it so. |
1450 this.currentSortKeys_ = [key]; | 1487 this.currentSortKeys_ = [key]; |
1451 } else { | 1488 } else { |
1452 // If the column we left-clicked was already our primary column (and | 1489 // If the column we left-clicked was already our primary column (and |
1453 // we just reversed it), remove any secondary sorts. | 1490 // we just reversed it), remove any secondary sorts. |
1454 this.currentSortKeys_.length = 1; | 1491 this.currentSortKeys_.length = 1; |
1455 } | 1492 } |
1456 } | 1493 } |
1457 | 1494 |
1458 this.fillSortingDropdowns_(); | 1495 this.fillSortingDropdowns_(); |
1459 this.redrawData_(); | 1496 this.sortGroupedData_(); |
1460 }, | 1497 }, |
1461 | 1498 |
1462 getSortingFunction_: function() { | 1499 getSortingFunction_: function() { |
1463 var sortKeys = this.currentSortKeys_.slice(0); | 1500 var sortKeys = this.currentSortKeys_.slice(0); |
1464 | 1501 |
1465 // Eliminate the empty string keys (which means they were unspecified). | 1502 // Eliminate the empty string keys (which means they were unspecified). |
1466 deleteValuesFromArray(sortKeys, ['']); | 1503 deleteValuesFromArray(sortKeys, ['']); |
1467 | 1504 |
1468 // If no sort is specified, use our default sort. | 1505 // If no sort is specified, use our default sort. |
1469 if (sortKeys.length == 0) | 1506 if (sortKeys.length == 0) |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1552 groupKey.push(entry); | 1589 groupKey.push(entry); |
1553 } | 1590 } |
1554 | 1591 |
1555 return JSON.stringify(groupKey); | 1592 return JSON.stringify(groupKey); |
1556 }; | 1593 }; |
1557 }, | 1594 }, |
1558 }; | 1595 }; |
1559 | 1596 |
1560 return MainView; | 1597 return MainView; |
1561 })(); | 1598 })(); |
OLD | NEW |