Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: chrome/browser/resources/profiler.js

Issue 8666005: Add snapshotting support to about:profiler. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: add comment about max Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/resources/profiler.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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): The handling of "max" across snapshots is not correct.
9 // For starters the browser needs to be aware to generate new maximums.
10 // Secondly, we need to take into account the "max" of intermediary snapshots,
11 // not just the terminal ones.
12
8 /** 13 /**
9 * Main entry point called once the page has loaded. 14 * Main entry point called once the page has loaded.
10 */ 15 */
11 function onLoad() { 16 function onLoad() {
12 g_browserBridge = new BrowserBridge(); 17 g_browserBridge = new BrowserBridge();
13 g_mainView = new MainView(); 18 g_mainView = new MainView();
14
15 // Ask the browser to send us the current data.
16 g_browserBridge.sendGetData();
17 } 19 }
18 20
19 document.addEventListener('DOMContentLoaded', onLoad); 21 document.addEventListener('DOMContentLoaded', onLoad);
20 22
21 /** 23 /**
22 * This class provides a "bridge" for communicating between the javascript and 24 * This class provides a "bridge" for communicating between the javascript and
23 * the browser. Used as a singleton. 25 * the browser. Used as a singleton.
24 */ 26 */
25 var BrowserBridge = (function() { 27 var BrowserBridge = (function() {
26 'use strict'; 28 'use strict';
(...skipping 15 matching lines...) Expand all
42 44
43 sendResetData: function() { 45 sendResetData: function() {
44 chrome.send('resetData'); 46 chrome.send('resetData');
45 }, 47 },
46 48
47 //-------------------------------------------------------------------------- 49 //--------------------------------------------------------------------------
48 // Messages received from the browser. 50 // Messages received from the browser.
49 //-------------------------------------------------------------------------- 51 //--------------------------------------------------------------------------
50 52
51 receivedData: function(data) { 53 receivedData: function(data) {
52 g_mainView.addData(data); 54 // TODO(eroman): The browser should give an indication of which snapshot
55 // this data belongs to. For now we always assume it is for the latest.
56 g_mainView.addDataToSnapshot(data);
53 }, 57 },
54 }; 58 };
55 59
56 return BrowserBridge; 60 return BrowserBridge;
57 })(); 61 })();
58 62
59 /** 63 /**
60 * This class handles the presentation of our profiler view. Used as a 64 * This class handles the presentation of our profiler view. Used as a
61 * singleton. 65 * singleton.
62 */ 66 */
(...skipping 27 matching lines...) Expand all
90 94
91 // The container node to show/hide when toggling the column checkboxes. 95 // The container node to show/hide when toggling the column checkboxes.
92 var EDIT_COLUMNS_ROW = 'edit-columns-row'; 96 var EDIT_COLUMNS_ROW = 'edit-columns-row';
93 97
94 // The checkbox which controls whether things like "Worker Threads" and 98 // The checkbox which controls whether things like "Worker Threads" and
95 // "PAC threads" will be merged together. 99 // "PAC threads" will be merged together.
96 var MERGE_SIMILAR_THREADS_CHECKBOX_ID = 'merge-similar-threads-checkbox'; 100 var MERGE_SIMILAR_THREADS_CHECKBOX_ID = 'merge-similar-threads-checkbox';
97 101
98 var RESET_DATA_LINK_ID = 'reset-data-link'; 102 var RESET_DATA_LINK_ID = 'reset-data-link';
99 103
104 var TOGGLE_SNAPSHOTS_LINK_ID = 'snapshots-link';
105 var SNAPSHOTS_ROW = 'snapshots-row';
106 var SNAPSHOT_SELECTION_SUMMARY_ID = 'snapshot-selection-summary';
107 var TAKE_SNAPSHOT_BUTTON_ID = 'snapshot-button';
108
100 // -------------------------------------------------------------------------- 109 // --------------------------------------------------------------------------
101 // Row keys 110 // Row keys
102 // -------------------------------------------------------------------------- 111 // --------------------------------------------------------------------------
103 112
104 // Each row of our data is an array of values rather than a dictionary. This 113 // Each row of our data is an array of values rather than a dictionary. This
105 // avoids some overhead from repeating the key string multiple times, and 114 // avoids some overhead from repeating the key string multiple times, and
106 // speeds up the property accesses a bit. The following keys are well-known 115 // speeds up the property accesses a bit. The following keys are well-known
107 // indexes into the array for various properties. 116 // indexes into the array for various properties.
108 // 117 //
109 // Note that the declaration order will also define the default display order. 118 // Note that the declaration order will also define the default display order.
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 var threadNameComparator = 279 var threadNameComparator =
271 createLexicographicComparatorWithExceptions([ 280 createLexicographicComparatorWithExceptions([
272 'CrBrowserMain', 281 'CrBrowserMain',
273 'Chrome_IOThread', 282 'Chrome_IOThread',
274 'Chrome_FileThread', 283 'Chrome_FileThread',
275 'Chrome_HistoryThread', 284 'Chrome_HistoryThread',
276 'Chrome_DBThread', 285 'Chrome_DBThread',
277 'Still_Alive', 286 'Still_Alive',
278 ]); 287 ]);
279 288
289 function diffFuncForCount(a, b) {
290 return b - a;
291 }
292
293 function diffFuncForMax(a, b) {
294 return b;
295 }
296
280 /** 297 /**
281 * Enumerates information about various keys. Such as whether their data is 298 * Enumerates information about various keys. Such as whether their data is
282 * expected to be numeric or is a string, a descriptive name (title) for the 299 * expected to be numeric or is a string, a descriptive name (title) for the
283 * property, and what function should be used to aggregate the property when 300 * property, and what function should be used to aggregate the property when
284 * displayed in a column. 301 * displayed in a column.
285 * 302 *
286 * -------------------------------------- 303 * --------------------------------------
287 * The following properties are required: 304 * The following properties are required:
288 * -------------------------------------- 305 * --------------------------------------
289 * 306 *
(...skipping 12 matching lines...) Expand all
302 * [comparator]: A comparator function for sorting this column. 319 * [comparator]: A comparator function for sorting this column.
303 * [textPrinter]: A function that transforms values into the user-displayed 320 * [textPrinter]: A function that transforms values into the user-displayed
304 * text shown in the UI. If unspecified, will default to the 321 * text shown in the UI. If unspecified, will default to the
305 * "toString()" function. 322 * "toString()" function.
306 * [cellAlignment]: The horizonal alignment to use for columns of this 323 * [cellAlignment]: The horizonal alignment to use for columns of this
307 * property (for instance 'right'). If unspecified will 324 * property (for instance 'right'). If unspecified will
308 * default to left alignment. 325 * default to left alignment.
309 * [sortDescending]: When first clicking on this column, we will default to 326 * [sortDescending]: When first clicking on this column, we will default to
310 * sorting by |comparator| in ascending order. If this 327 * sorting by |comparator| in ascending order. If this
311 * property is true, we will reverse that to descending. 328 * property is true, we will reverse that to descending.
329 * [diff]: Function to call to compute a "difference" value between
330 * parameters (a, b). This is used when calculating the difference
331 * between two snapshots. Diffing numeric quantities generally
332 * involves subtracting, but some fields like max may need to do
333 * something different.
312 */ 334 */
313 var KEY_PROPERTIES = []; 335 var KEY_PROPERTIES = [];
314 336
315 KEY_PROPERTIES[KEY_PROCESS_ID] = { 337 KEY_PROPERTIES[KEY_PROCESS_ID] = {
316 name: 'PID', 338 name: 'PID',
317 cellAlignment: 'right', 339 cellAlignment: 'right',
318 aggregator: UniquifyAggregator, 340 aggregator: UniquifyAggregator,
319 }; 341 };
320 342
321 KEY_PROPERTIES[KEY_PROCESS_TYPE] = { 343 KEY_PROPERTIES[KEY_PROCESS_TYPE] = {
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 aggregator: UniquifyAggregator, 378 aggregator: UniquifyAggregator,
357 }; 379 };
358 380
359 KEY_PROPERTIES[KEY_COUNT] = { 381 KEY_PROPERTIES[KEY_COUNT] = {
360 name: 'Count', 382 name: 'Count',
361 cellAlignment: 'right', 383 cellAlignment: 'right',
362 sortDescending: true, 384 sortDescending: true,
363 textPrinter: formatNumberAsText, 385 textPrinter: formatNumberAsText,
364 inputJsonKey: 'death_data.count', 386 inputJsonKey: 'death_data.count',
365 aggregator: SumAggregator, 387 aggregator: SumAggregator,
388 diff: diffFuncForCount,
366 }; 389 };
367 390
368 KEY_PROPERTIES[KEY_QUEUE_TIME] = { 391 KEY_PROPERTIES[KEY_QUEUE_TIME] = {
369 name: 'Total queue time', 392 name: 'Total queue time',
370 cellAlignment: 'right', 393 cellAlignment: 'right',
371 sortDescending: true, 394 sortDescending: true,
372 textPrinter: formatNumberAsText, 395 textPrinter: formatNumberAsText,
373 inputJsonKey: 'death_data.queue_ms', 396 inputJsonKey: 'death_data.queue_ms',
374 aggregator: SumAggregator, 397 aggregator: SumAggregator,
398 diff: diffFuncForCount,
375 }; 399 };
376 400
377 KEY_PROPERTIES[KEY_MAX_QUEUE_TIME] = { 401 KEY_PROPERTIES[KEY_MAX_QUEUE_TIME] = {
378 name: 'Max queue time', 402 name: 'Max queue time',
379 cellAlignment: 'right', 403 cellAlignment: 'right',
380 sortDescending: true, 404 sortDescending: true,
381 textPrinter: formatNumberAsText, 405 textPrinter: formatNumberAsText,
382 inputJsonKey: 'death_data.queue_ms_max', 406 inputJsonKey: 'death_data.queue_ms_max',
383 aggregator: MaxAggregator, 407 aggregator: MaxAggregator,
408 diff: diffFuncForMax,
384 }; 409 };
385 410
386 KEY_PROPERTIES[KEY_RUN_TIME] = { 411 KEY_PROPERTIES[KEY_RUN_TIME] = {
387 name: 'Total run time', 412 name: 'Total run time',
388 cellAlignment: 'right', 413 cellAlignment: 'right',
389 sortDescending: true, 414 sortDescending: true,
390 textPrinter: formatNumberAsText, 415 textPrinter: formatNumberAsText,
391 inputJsonKey: 'death_data.run_ms', 416 inputJsonKey: 'death_data.run_ms',
392 aggregator: SumAggregator, 417 aggregator: SumAggregator,
418 diff: diffFuncForCount,
393 }; 419 };
394 420
395 KEY_PROPERTIES[KEY_AVG_RUN_TIME] = { 421 KEY_PROPERTIES[KEY_AVG_RUN_TIME] = {
396 name: 'Avg run time', 422 name: 'Avg run time',
397 cellAlignment: 'right', 423 cellAlignment: 'right',
398 sortDescending: true, 424 sortDescending: true,
399 textPrinter: formatNumberAsText, 425 textPrinter: formatNumberAsText,
400 aggregator: AvgAggregator.create(KEY_RUN_TIME, KEY_COUNT), 426 aggregator: AvgAggregator.create(KEY_RUN_TIME, KEY_COUNT),
401 }; 427 };
402 428
403 KEY_PROPERTIES[KEY_MAX_RUN_TIME] = { 429 KEY_PROPERTIES[KEY_MAX_RUN_TIME] = {
404 name: 'Max run time', 430 name: 'Max run time',
405 cellAlignment: 'right', 431 cellAlignment: 'right',
406 sortDescending: true, 432 sortDescending: true,
407 textPrinter: formatNumberAsText, 433 textPrinter: formatNumberAsText,
408 inputJsonKey: 'death_data.run_ms_max', 434 inputJsonKey: 'death_data.run_ms_max',
409 aggregator: MaxAggregator, 435 aggregator: MaxAggregator,
436 diff: diffFuncForMax,
410 }; 437 };
411 438
412 KEY_PROPERTIES[KEY_AVG_QUEUE_TIME] = { 439 KEY_PROPERTIES[KEY_AVG_QUEUE_TIME] = {
413 name: 'Avg queue time', 440 name: 'Avg queue time',
414 cellAlignment: 'right', 441 cellAlignment: 'right',
415 sortDescending: true, 442 sortDescending: true,
416 textPrinter: formatNumberAsText, 443 textPrinter: formatNumberAsText,
417 aggregator: AvgAggregator.create(KEY_QUEUE_TIME, KEY_COUNT), 444 aggregator: AvgAggregator.create(KEY_QUEUE_TIME, KEY_COUNT),
418 }; 445 };
419 446
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after
754 return path.substr(lastSlash + 1); 781 return path.substr(lastSlash + 1);
755 } 782 }
756 783
757 /** 784 /**
758 * Returns the current time in milliseconds since unix epoch. 785 * Returns the current time in milliseconds since unix epoch.
759 */ 786 */
760 function getTimeMillis() { 787 function getTimeMillis() {
761 return (new Date()).getTime(); 788 return (new Date()).getTime();
762 } 789 }
763 790
791 /**
792 * Toggle a node between hidden/invisible.
793 */
794 function toggleNodeDisplay(n) {
795 if (n.style.display == '') {
796 n.style.display = 'none';
797 } else {
798 n.style.display = '';
799 }
800 }
801
764 // -------------------------------------------------------------------------- 802 // --------------------------------------------------------------------------
765 // Functions that augment, bucket, and compute aggregates for the input data. 803 // Functions that augment, bucket, and compute aggregates for the input data.
766 // -------------------------------------------------------------------------- 804 // --------------------------------------------------------------------------
767 805
768 /** 806 /**
769 * Adds new derived properties to row. Mutates the provided dictionary |e|. 807 * Adds new derived properties to row. Mutates the provided dictionary |e|.
770 */ 808 */
771 function augmentDataRow(e) { 809 function augmentDataRow(e) {
772 e[KEY_AVG_QUEUE_TIME] = e[KEY_QUEUE_TIME] / e[KEY_COUNT]; 810 computeDataRowAverages(e);
773 e[KEY_AVG_RUN_TIME] = e[KEY_RUN_TIME] / e[KEY_COUNT];
774 e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']'; 811 e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']';
775 } 812 }
776 813
814 function computeDataRowAverages(e) {
815 e[KEY_AVG_QUEUE_TIME] = e[KEY_QUEUE_TIME] / e[KEY_COUNT];
816 e[KEY_AVG_RUN_TIME] = e[KEY_RUN_TIME] / e[KEY_COUNT];
817 }
818
777 /** 819 /**
778 * Creates and initializes an aggregator object for each key in |columns|. 820 * Creates and initializes an aggregator object for each key in |columns|.
779 * Returns an array whose keys are values from |columns|, and whose 821 * Returns an array whose keys are values from |columns|, and whose
780 * values are Aggregator instances. 822 * values are Aggregator instances.
781 */ 823 */
782 function initializeAggregates(columns) { 824 function initializeAggregates(columns) {
783 var aggregates = []; 825 var aggregates = [];
784 826
785 for (var i = 0; i < columns.length; ++i) { 827 for (var i = 0; i < columns.length; ++i) {
786 var key = columns[i]; 828 var key = columns[i];
787 var aggregatorFactory = KEY_PROPERTIES[key].aggregator; 829 var aggregatorFactory = KEY_PROPERTIES[key].aggregator;
788 aggregates[key] = aggregatorFactory.create(key); 830 aggregates[key] = aggregatorFactory.create(key);
789 } 831 }
790 832
791 return aggregates; 833 return aggregates;
792 } 834 }
793 835
794 function consumeAggregates(aggregates, row) { 836 function consumeAggregates(aggregates, row) {
795 for (var key in aggregates) 837 for (var key in aggregates)
796 aggregates[key].consume(row); 838 aggregates[key].consume(row);
797 } 839 }
798 840
841 function bucketIdenticalRows(rows, identityKeys, propertyGetterFunc) {
842 var identicalRows = {};
843 for (var i = 0; i < rows.length; ++i) {
844 var r = rows[i];
845
846 var rowIdentity = [];
847 for (var j = 0; j < identityKeys.length; ++j)
848 rowIdentity.push(propertyGetterFunc(r, identityKeys[j]));
849 rowIdentity = rowIdentity.join('\n');
850
851 var l = identicalRows[rowIdentity];
852 if (!l) {
853 l = [];
854 identicalRows[rowIdentity] = l;
855 }
856 l.push(r);
857 }
858 return identicalRows;
859 }
860
799 /** 861 /**
800 * Merges the rows in |origRows|, by collapsing the columns listed in 862 * Merges the rows in |origRows|, by collapsing the columns listed in
801 * |mergeKeys|. Returns an array with the merged rows (in no particular 863 * |mergeKeys|. Returns an array with the merged rows (in no particular
802 * order). 864 * order).
803 * 865 *
804 * If |mergeSimilarThreads| is true, then threads with a similar name will be 866 * If |mergeSimilarThreads| is true, then threads with a similar name will be
805 * considered equivalent. For instance, "WorkerThread-1" and "WorkerThread-2" 867 * considered equivalent. For instance, "WorkerThread-1" and "WorkerThread-2"
806 * will be remapped to "WorkerThread-*". 868 * will be remapped to "WorkerThread-*".
807 */ 869 */
808 function mergeRows(origRows, mergeKeys, mergeSimilarThreads) { 870 function mergeRows(origRows, mergeKeys, mergeSimilarThreads) {
(...skipping 21 matching lines...) Expand all
830 // considered identical to another row. 892 // considered identical to another row.
831 var identityKeys = IDENTITY_KEYS.slice(0); 893 var identityKeys = IDENTITY_KEYS.slice(0);
832 deleteValuesFromArray(identityKeys, mergeKeys); 894 deleteValuesFromArray(identityKeys, mergeKeys);
833 895
834 // Set |aggregateKeys| to everything else, since we will be aggregating 896 // Set |aggregateKeys| to everything else, since we will be aggregating
835 // their value as part of the merge. 897 // their value as part of the merge.
836 var aggregateKeys = ALL_KEYS.slice(0); 898 var aggregateKeys = ALL_KEYS.slice(0);
837 deleteValuesFromArray(aggregateKeys, IDENTITY_KEYS); 899 deleteValuesFromArray(aggregateKeys, IDENTITY_KEYS);
838 900
839 // Group all the identical rows together, bucketed into |identicalRows|. 901 // Group all the identical rows together, bucketed into |identicalRows|.
840 var identicalRows = {}; 902 var identicalRows =
841 for (var i = 0; i < origRows.length; ++i) { 903 bucketIdenticalRows(origRows, identityKeys, propertyGetterFunc);
842 var e = origRows[i];
843
844 var rowIdentity = [];
845 for (var j = 0; j < identityKeys.length; ++j)
846 rowIdentity.push(propertyGetterFunc(e, identityKeys[j]));
847 rowIdentity = rowIdentity.join('\n');
848
849 var l = identicalRows[rowIdentity];
850 if (!l) {
851 l = [];
852 identicalRows[rowIdentity] = l;
853 }
854 l.push(e);
855 }
856 904
857 var mergedRows = []; 905 var mergedRows = [];
858 906
859 // Merge the rows and save the results to |mergedRows|. 907 // Merge the rows and save the results to |mergedRows|.
860 for (var k in identicalRows) { 908 for (var k in identicalRows) {
861 // We need to smash the list |l| down to a single row... 909 // We need to smash the list |l| down to a single row...
862 var l = identicalRows[k]; 910 var l = identicalRows[k];
863 911
864 var newRow = []; 912 var newRow = [];
865 mergedRows.push(newRow); 913 mergedRows.push(newRow);
(...skipping 11 matching lines...) Expand all
877 consumeAggregates(aggregates, l[i]); 925 consumeAggregates(aggregates, l[i]);
878 926
879 // Suck out the data generated by the aggregators. 927 // Suck out the data generated by the aggregators.
880 for (var aggregateKey in aggregates) 928 for (var aggregateKey in aggregates)
881 newRow[aggregateKey] = aggregates[aggregateKey].getValue(); 929 newRow[aggregateKey] = aggregates[aggregateKey].getValue();
882 } 930 }
883 931
884 return mergedRows; 932 return mergedRows;
885 } 933 }
886 934
935 /**
936 * Takes two flat lists data1 and data2, and returns a new flat list which
937 * represents the difference between them. The exact meaning of "difference"
938 * is column specific, but for most numeric fields (like the count, or total
939 * time), it is found by subtracting.
940 *
941 * TODO(eroman): Some of this code is duplicated from mergeRows().
942 */
943 function subtractSnapshots(data1, data2) {
944 // These columns are computed from the other columns. We won't bother
945 // diffing/aggregating these, but rather will derive them again from the
946 // final row.
947 var COMPUTED_AGGREGATE_KEYS = [KEY_AVG_QUEUE_TIME, KEY_AVG_RUN_TIME];
948
949 // These are the keys which determine row equality. Since we are not doing
950 // any merging yet at this point, it is simply the list of all identity
951 // columns.
952 var identityKeys = IDENTITY_KEYS;
953
954 // The columns to compute via aggregation is everything else.
955 var aggregateKeys = ALL_KEYS.slice(0);
956 deleteValuesFromArray(aggregateKeys, IDENTITY_KEYS);
957 deleteValuesFromArray(aggregateKeys, COMPUTED_AGGREGATE_KEYS);
958
959 // Group all the identical rows for each list together.
960 var propertyGetterFunc = function(row, key) { return row[key]; };
961 var identicalRows1 =
962 bucketIdenticalRows(data1, identityKeys, propertyGetterFunc);
963 var identicalRows2 =
964 bucketIdenticalRows(data2, identityKeys, propertyGetterFunc);
965
966 var diffedRows = [];
967
968 for (var k in identicalRows2) {
969 var rows2 = identicalRows2[k];
970 var rows1 = identicalRows1[k];
971 if (rows1 == undefined)
972 rows1 = [];
973
974 var newRow = [];
975
976 // Copy over all the identity columns to the new row (since they
977 // were the same for each row matched).
978 for (var i = 0; i < identityKeys.length; ++i)
979 newRow[identityKeys[i]] = propertyGetterFunc(rows2[0], identityKeys[i]);
980
981 // The raw data for each snapshot *may* have contained duplicate rows, so
982 // smash them down into a single row using our aggregation functions.
983 var aggregates1 = initializeAggregates(aggregateKeys);
984 var aggregates2 = initializeAggregates(aggregateKeys);
985 for (var i = 0; i < rows1.length; ++i)
986 consumeAggregates(aggregates1, rows1[i]);
987 for (var i = 0; i < rows2.length; ++i)
988 consumeAggregates(aggregates2, rows2[i]);
989
990 // Finally, diff the two merged rows.
991 for (var aggregateKey in aggregates2) {
992 var a = aggregates1[aggregateKey].getValue();
993 var b = aggregates2[aggregateKey].getValue();
994
995 var diffFunc = KEY_PROPERTIES[aggregateKey].diff;
996 newRow[aggregateKey] = diffFunc(a, b);
997 }
998
999 if (newRow[KEY_COUNT] == 0) {
1000 // If a row's count has gone to zero, it means there were no new
1001 // occurrences of it in the second snapshot, so remove it.
1002 continue;
1003 }
1004
1005 // Since we excluded the averages during diffing phase, re-compute them
1006 // using the diffed totals.
1007 computeDataRowAverages(newRow);
1008 diffedRows.push(newRow);
1009 }
1010
1011 return diffedRows;
1012 }
1013
887 // -------------------------------------------------------------------------- 1014 // --------------------------------------------------------------------------
888 // HTML drawing code 1015 // HTML drawing code
889 // -------------------------------------------------------------------------- 1016 // --------------------------------------------------------------------------
890 1017
891 function getTextValueForProperty(key, value) { 1018 function getTextValueForProperty(key, value) {
892 if (value == undefined) { 1019 if (value == undefined) {
893 // A value may be undefined as a result of having merging rows. We 1020 // A value may be undefined as a result of having merging rows. We
894 // won't actually draw it, but this might be called by the filter. 1021 // won't actually draw it, but this might be called by the filter.
895 return ''; 1022 return '';
896 } 1023 }
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after
1053 // Make sure we have a definition for each key. 1180 // Make sure we have a definition for each key.
1054 for (var k = BEGIN_KEY; k < END_KEY; ++k) { 1181 for (var k = BEGIN_KEY; k < END_KEY; ++k) {
1055 if (!KEY_PROPERTIES[k]) 1182 if (!KEY_PROPERTIES[k])
1056 throw 'KEY_PROPERTIES[] not defined for key: ' + k; 1183 throw 'KEY_PROPERTIES[] not defined for key: ' + k;
1057 } 1184 }
1058 1185
1059 this.init_(); 1186 this.init_();
1060 } 1187 }
1061 1188
1062 MainView.prototype = { 1189 MainView.prototype = {
1063 addData: function(data) { 1190 addDataToSnapshot: function(data) {
1191 // TODO(eroman): We need to know which snapshot this data belongs to!
1192 // For now we assume it is the most recent snapshot.
1193 var snapshotIndex = this.snapshots_.length - 1;
1194
1195 var snapshot = this.snapshots_[snapshotIndex];
1196
1064 var pid = data.process_id; 1197 var pid = data.process_id;
1065 var ptype = data.process_type; 1198 var ptype = data.process_type;
1066 1199
1067 // Augment each data row with the process information. 1200 // Augment each data row with the process information.
1068 var rows = data.list; 1201 var rows = data.list;
1069 for (var i = 0; i < rows.length; ++i) { 1202 for (var i = 0; i < rows.length; ++i) {
1070 // Transform the data from a dictionary to an array. This internal 1203 // Transform the data from a dictionary to an array. This internal
1071 // representation is more compact and faster to access. 1204 // representation is more compact and faster to access.
1072 var origRow = rows[i]; 1205 var origRow = rows[i];
1073 var newRow = []; 1206 var newRow = [];
(...skipping 13 matching lines...) Expand all
1087 // When resetting the data, it is possible for the backend to give us 1220 // When resetting the data, it is possible for the backend to give us
1088 // counts of "0". There is no point adding these rows (in fact they 1221 // counts of "0". There is no point adding these rows (in fact they
1089 // will cause us to do divide by zeros when calculating averages and 1222 // will cause us to do divide by zeros when calculating averages and
1090 // stuff), so we skip past them. 1223 // stuff), so we skip past them.
1091 continue; 1224 continue;
1092 } 1225 }
1093 1226
1094 // Add our computed properties. 1227 // Add our computed properties.
1095 augmentDataRow(newRow); 1228 augmentDataRow(newRow);
1096 1229
1097 this.flatData_.push(newRow); 1230 snapshot.flatData.push(newRow);
1098 } 1231 }
1099 1232
1100 // We may end up calling addData() repeatedly (once for each process). 1233 if (!arrayToSet(this.getSelectedSnapshotIndexes_())[snapshotIndex]) {
1101 // To avoid this from slowing us down we do bulk updates on a timer. 1234 // Optimization: If this snapshot is not a data dependency for the
1102 this.updateMergedDataSoon_(); 1235 // current display, then don't bother updating anything.
1236 return;
1237 }
1238
1239 // We may end up calling addDataToSnapshot_() repeatedly (once for each
1240 // process). To avoid this from slowing us down we do bulk updates on a
1241 // timer.
1242 this.updateFlatDataSoon_();
1103 }, 1243 },
1104 1244
1105 updateMergedDataSoon_: function() { 1245 updateFlatDataSoon_: function() {
1106 if (this.updateMergedDataPending_) { 1246 if (this.updateFlatDataPending_) {
1107 // If a delayed task has already been posted to re-merge the data, 1247 // If a delayed task has already been posted to re-merge the data,
1108 // then we don't need to do anything extra. 1248 // then we don't need to do anything extra.
1109 return; 1249 return;
1110 } 1250 }
1111 1251
1112 // Otherwise schedule updateMergeData_() to be called later. We want it to 1252 // Otherwise schedule updateFlatData_() to be called later. We want it to
1113 // be called no more than once every PROCESS_DATA_DELAY_MS milliseconds. 1253 // be called no more than once every PROCESS_DATA_DELAY_MS milliseconds.
1114 1254
1115 if (this.lastUpdateMergedDataTime_ == undefined) 1255 if (this.lastUpdateFlatDataTime_ == undefined)
1116 this.lastUpdateMergedDataTime_ = 0; 1256 this.lastUpdateFlatDataTime_ = 0;
1117 1257
1118 var timeSinceLastMerge = getTimeMillis() - this.lastUpdateMergedDataTime_; 1258 var timeSinceLastMerge = getTimeMillis() - this.lastUpdateFlatDataTime_;
1119 var timeToWait = Math.max(0, PROCESS_DATA_DELAY_MS - timeSinceLastMerge); 1259 var timeToWait = Math.max(0, PROCESS_DATA_DELAY_MS - timeSinceLastMerge);
1120 1260
1121 var functionToRun = function() { 1261 var functionToRun = function() {
1122 // Do the actual update. 1262 // Do the actual update.
1123 this.updateMergedData_(); 1263 this.updateFlatData_();
1124 // Keep track of when we last ran. 1264 // Keep track of when we last ran.
1125 this.lastUpdateMergedDataTime_ = getTimeMillis(); 1265 this.lastUpdateFlatDataTime_ = getTimeMillis();
1126 this.updateMergedDataPending_ = false; 1266 this.updateFlatDataPending_ = false;
1127 }.bind(this); 1267 }.bind(this);
1128 1268
1129 this.updateMergedDataPending_ = true; 1269 this.updateFlatDataPending_ = true;
1130 window.setTimeout(functionToRun, timeToWait); 1270 window.setTimeout(functionToRun, timeToWait);
1131 }, 1271 },
1132 1272
1273 /**
1274 * Returns a list of the currently selected snapshots. This list is
1275 * guaranteed to be of length 1 or 2.
1276 */
1277 getSelectedSnapshotIndexes_: function() {
1278 var indexes = this.getSelectedSnapshotBoxes_();
1279 for (var i = 0; i < indexes.length; ++i)
1280 indexes[i] = indexes[i].__index;
1281 return indexes;
1282 },
1283
1284 /**
1285 * Same as getSelectedSnapshotIndexes_(), only it returns the actual
1286 * checkbox input DOM nodes rather than the snapshot ID.
1287 */
1288 getSelectedSnapshotBoxes_: function() {
1289 // Figure out which snaphots to use for our data.
1290 var boxes = [];
1291 for (var i = 0; i < this.snapshots_.length; ++i) {
1292 var box = this.getSnapshotCheckbox_(i);
1293 if (box.checked)
1294 boxes.push(box);
1295 }
1296 return boxes;
1297 },
1298
1299 /**
1300 * This function should be called any time a snapshot dependency for what is
1301 * being displayed on the screen has changed. It will re-calculate the
1302 * difference between the two snapshots and update flatData_.
1303 */
1304 updateFlatData_: function() {
1305 var summaryDiv = $(SNAPSHOT_SELECTION_SUMMARY_ID);
1306
1307 var selectedSnapshots = this.getSelectedSnapshotIndexes_();
1308 if (selectedSnapshots.length == 1) {
1309 // If only one snapshot is chosen then we will display that snapshot's
1310 // data in its entirety.
1311 this.flatData_ = this.snapshots_[selectedSnapshots[0]].flatData;
1312
1313 // Don't bother displaying any text when just 1 snapshot is selected,
1314 // since it is obvious what this should do.
1315 summaryDiv.innerText = '';
1316 } else if (selectedSnapshots.length == 2) {
1317 // Otherwise if two snapshots were chosen, show the difference between
1318 // them.
1319 var snapshot1 = this.snapshots_[selectedSnapshots[0]];
1320 var snapshot2 = this.snapshots_[selectedSnapshots[1]];
1321
1322 this.flatData_ =
1323 subtractSnapshots(snapshot1.flatData, snapshot2.flatData);
1324
1325 var timeDeltaInSeconds =
1326 ((snapshot2.time - snapshot1.time) / 1000).toFixed(0);
1327
1328 // Explain that what is being shown is the difference between two
1329 // snapshots.
1330 summaryDiv.innerText =
1331 'Showing the difference between snapshots #' +
1332 selectedSnapshots[0] + ' and #' +
1333 selectedSnapshots[1] + ' (' + timeDeltaInSeconds +
1334 ' seconds worth of data)';
1335 } else {
1336 // This shouldn't be possible...
1337 throw 'Unexpected number of selected snapshots';
1338 }
1339
1340 // Recompute mergedData_ (since it is derived from flatData_)
1341 this.updateMergedData_();
1342 },
1343
1133 updateMergedData_: function() { 1344 updateMergedData_: function() {
1134 // Recompute mergedData_. 1345 // Recompute mergedData_.
1135 this.mergedData_ = mergeRows(this.flatData_, 1346 this.mergedData_ = mergeRows(this.flatData_,
1136 this.getMergeColumns_(), 1347 this.getMergeColumns_(),
1137 this.shouldMergeSimilarThreads_()); 1348 this.shouldMergeSimilarThreads_());
1138 1349
1139 // Recompute filteredData_ (since it is derived from mergedData_) 1350 // Recompute filteredData_ (since it is derived from mergedData_)
1140 this.updateFilteredData_(); 1351 this.updateFilteredData_();
1141 }, 1352 },
1142 1353
(...skipping 293 matching lines...) Expand 10 before | Expand all | Expand 10 after
1436 } else { 1647 } else {
1437 settings = {limit: INITIAL_GROUP_ROW_LIMIT}; 1648 settings = {limit: INITIAL_GROUP_ROW_LIMIT};
1438 } 1649 }
1439 if (opt_create) 1650 if (opt_create)
1440 this.groupDisplaySettings_[groupKey] = settings; 1651 this.groupDisplaySettings_[groupKey] = settings;
1441 } 1652 }
1442 return settings; 1653 return settings;
1443 }, 1654 },
1444 1655
1445 init_: function() { 1656 init_: function() {
1657 this.snapshots_ = [];
1658
1659 // Start fetching the data from the browser; this will be our snapshot #0.
1660 this.takeSnapshot_();
1661
1446 // Data goes through the following pipeline: 1662 // Data goes through the following pipeline:
1447 // (1) Raw data received from browser, and transformed into our own 1663 // (1) Raw data received from browser, and transformed into our own
1448 // internal row format (where properties are indexed by KEY_* 1664 // internal row format (where properties are indexed by KEY_*
1449 // constants.) 1665 // constants.)
1450 // (2) We "augment" each row by adding some extra computed columns 1666 // (2) We "augment" each row by adding some extra computed columns
1451 // (like averages). 1667 // (like averages).
1452 // (3) The rows are merged using current merge settings. 1668 // (3) The rows are merged using current merge settings.
1453 // (4) The rows that don't match current search expression are 1669 // (4) The rows that don't match current search expression are
1454 // tossed out. 1670 // tossed out.
1455 // (5) The rows are organized into "groups" based on current settings, 1671 // (5) The rows are organized into "groups" based on current settings,
(...skipping 12 matching lines...) Expand all
1468 this.fillMergeCheckboxes_($(COLUMN_MERGE_TOGGLES_CONTAINER_ID)); 1684 this.fillMergeCheckboxes_($(COLUMN_MERGE_TOGGLES_CONTAINER_ID));
1469 1685
1470 $(FILTER_SEARCH_ID).onsearch = this.onChangedFilter_.bind(this); 1686 $(FILTER_SEARCH_ID).onsearch = this.onChangedFilter_.bind(this);
1471 1687
1472 this.currentSortKeys_ = INITIAL_SORT_KEYS.slice(0); 1688 this.currentSortKeys_ = INITIAL_SORT_KEYS.slice(0);
1473 this.currentGroupingKeys_ = INITIAL_GROUP_KEYS.slice(0); 1689 this.currentGroupingKeys_ = INITIAL_GROUP_KEYS.slice(0);
1474 1690
1475 this.fillGroupingDropdowns_(); 1691 this.fillGroupingDropdowns_();
1476 this.fillSortingDropdowns_(); 1692 this.fillSortingDropdowns_();
1477 1693
1478 $(EDIT_COLUMNS_LINK_ID).onclick = this.toggleEditColumns_.bind(this); 1694 $(EDIT_COLUMNS_LINK_ID).onclick =
1695 toggleNodeDisplay.bind(null, $(EDIT_COLUMNS_ROW));
1696
1697 $(TOGGLE_SNAPSHOTS_LINK_ID).onclick =
1698 toggleNodeDisplay.bind(null, $(SNAPSHOTS_ROW));
1479 1699
1480 $(MERGE_SIMILAR_THREADS_CHECKBOX_ID).onchange = 1700 $(MERGE_SIMILAR_THREADS_CHECKBOX_ID).onchange =
1481 this.onMergeSimilarThreadsCheckboxChanged_.bind(this); 1701 this.onMergeSimilarThreadsCheckboxChanged_.bind(this);
1482 1702
1483 $(RESET_DATA_LINK_ID).onclick = 1703 $(RESET_DATA_LINK_ID).onclick =
1484 g_browserBridge.sendResetData.bind(g_browserBridge); 1704 g_browserBridge.sendResetData.bind(g_browserBridge);
1705
1706 $(TAKE_SNAPSHOT_BUTTON_ID).onclick = this.takeSnapshot_.bind(this);
1485 }, 1707 },
1486 1708
1487 toggleEditColumns_: function() { 1709 takeSnapshot_: function() {
1488 var n = $(EDIT_COLUMNS_ROW); 1710 // Start a new empty snapshot. Make note of the current time, so we know
1489 if (n.style.display == '') { 1711 // when the snaphot was taken.
1490 n.style.display = 'none'; 1712 this.snapshots_.push({flatData: [], time: getTimeMillis()});
1491 } else { 1713
1492 n.style.display = ''; 1714 // Update the UI to reflect the new snapshot.
1715 this.addSnapshotToList_(this.snapshots_.length - 1);
1716
1717 // Ask the browser for the profiling data. We will receive the data
1718 // later through a callback to addDataToSnapshot_().
1719 g_browserBridge.sendGetData();
1720 },
1721
1722 getSnapshotCheckbox_: function(i) {
1723 return $(this.getSnapshotCheckboxId_(i));
1724 },
1725
1726 getSnapshotCheckboxId_: function(i) {
1727 return 'snapshotCheckbox-' + i;
1728 },
1729
1730 addSnapshotToList_: function(i) {
1731 var tbody = $('snapshots-tbody');
1732
1733 var tr = addNode(tbody, 'tr');
1734
1735 var id = this.getSnapshotCheckboxId_(i);
1736
1737 var checkboxCell = addNode(tr, 'td');
1738 var checkbox = addNode(checkboxCell, 'input');
1739 checkbox.type = 'checkbox';
1740 checkbox.id = id;
1741 checkbox.__index = i;
1742 checkbox.onclick = this.onSnapshotCheckboxChanged_.bind(this);
1743
1744 addNode(tr, 'td', '#' + i);
1745
1746 var labelCell = addNode(tr, 'td');
1747 var l = addNode(labelCell, 'label');
1748
1749 var dateString = new Date(this.snapshots_[i].time).toLocaleString();
1750 addText(l, dateString);
1751 l.htmlFor = id;
1752
1753 // If we are on snapshot 0, make it the default.
1754 if (i == 0) {
1755 checkbox.checked = true;
1756 checkbox.__time = getTimeMillis();
1757 this.updateSnapshotCheckboxStyling_();
1493 } 1758 }
1494 }, 1759 },
1495 1760
1761 updateSnapshotCheckboxStyling_: function() {
1762 for (var i = 0; i < this.snapshots_.length; ++i) {
1763 var checkbox = this.getSnapshotCheckbox_(i);
1764 checkbox.parentNode.parentNode.className =
1765 checkbox.checked ? 'selected_snapshot' : '';
1766 }
1767 },
1768
1769 onSnapshotCheckboxChanged_: function(event) {
1770 // Keep track of when we clicked this box (for when we need to uncheck
1771 // older boxes).
1772 event.target.__time = getTimeMillis();
1773
1774 // Find all the checked boxes. Either 1 or 2 can be checked. If a third
1775 // was just checked, then uncheck one of the earlier ones so we only have
1776 // 2.
1777 var checked = this.getSelectedSnapshotBoxes_();
1778 checked.sort(function(a, b) { return b.__time - a.__time; });
1779 if (checked.length > 2) {
1780 for (var i = 2; i < checked.length; ++i)
1781 checked[i].checked = false;
1782 checked.length = 2;
1783 }
1784
1785 // We should always have at least 1 selection. Prevent the user from
1786 // unselecting the final box.
1787 if (checked.length == 0)
1788 event.target.checked = true;
1789
1790 this.updateSnapshotCheckboxStyling_();
1791
1792 // Recompute flatData_ (since it is derived from selected snapshots).
1793 this.updateFlatData_();
1794 },
1795
1496 fillSelectionCheckboxes_: function(parent) { 1796 fillSelectionCheckboxes_: function(parent) {
1497 this.selectionCheckboxes_ = {}; 1797 this.selectionCheckboxes_ = {};
1498 1798
1499 var onChangeFunc = this.onSelectCheckboxChanged_.bind(this); 1799 var onChangeFunc = this.onSelectCheckboxChanged_.bind(this);
1500 1800
1501 for (var i = 0; i < ALL_TABLE_COLUMNS.length; ++i) { 1801 for (var i = 0; i < ALL_TABLE_COLUMNS.length; ++i) {
1502 var key = ALL_TABLE_COLUMNS[i]; 1802 var key = ALL_TABLE_COLUMNS[i];
1503 var checkbox = addLabeledCheckbox(parent, getNameForKey(key)); 1803 var checkbox = addLabeledCheckbox(parent, getNameForKey(key));
1504 checkbox.checked = true; 1804 checkbox.checked = true;
1505 checkbox.onchange = onChangeFunc; 1805 checkbox.onchange = onChangeFunc;
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
1748 groupKey.push(entry); 2048 groupKey.push(entry);
1749 } 2049 }
1750 2050
1751 return JSON.stringify(groupKey); 2051 return JSON.stringify(groupKey);
1752 }; 2052 };
1753 }, 2053 },
1754 }; 2054 };
1755 2055
1756 return MainView; 2056 return MainView;
1757 })(); 2057 })();
OLDNEW
« no previous file with comments | « chrome/browser/resources/profiler.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698