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

Unified 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, 1 month 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/resources/profiler.html ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/resources/profiler.js
===================================================================
--- chrome/browser/resources/profiler.js (revision 111053)
+++ chrome/browser/resources/profiler.js (working copy)
@@ -5,15 +5,17 @@
var g_browserBridge;
var g_mainView;
+// TODO(eroman): The handling of "max" across snapshots is not correct.
+// For starters the browser needs to be aware to generate new maximums.
+// Secondly, we need to take into account the "max" of intermediary snapshots,
+// not just the terminal ones.
+
/**
* Main entry point called once the page has loaded.
*/
function onLoad() {
g_browserBridge = new BrowserBridge();
g_mainView = new MainView();
-
- // Ask the browser to send us the current data.
- g_browserBridge.sendGetData();
}
document.addEventListener('DOMContentLoaded', onLoad);
@@ -49,7 +51,9 @@
//--------------------------------------------------------------------------
receivedData: function(data) {
- g_mainView.addData(data);
+ // TODO(eroman): The browser should give an indication of which snapshot
+ // this data belongs to. For now we always assume it is for the latest.
+ g_mainView.addDataToSnapshot(data);
},
};
@@ -97,6 +101,11 @@
var RESET_DATA_LINK_ID = 'reset-data-link';
+ var TOGGLE_SNAPSHOTS_LINK_ID = 'snapshots-link';
+ var SNAPSHOTS_ROW = 'snapshots-row';
+ var SNAPSHOT_SELECTION_SUMMARY_ID = 'snapshot-selection-summary';
+ var TAKE_SNAPSHOT_BUTTON_ID = 'snapshot-button';
+
// --------------------------------------------------------------------------
// Row keys
// --------------------------------------------------------------------------
@@ -277,6 +286,14 @@
'Still_Alive',
]);
+ function diffFuncForCount(a, b) {
+ return b - a;
+ }
+
+ function diffFuncForMax(a, b) {
+ return b;
+ }
+
/**
* Enumerates information about various keys. Such as whether their data is
* expected to be numeric or is a string, a descriptive name (title) for the
@@ -309,6 +326,11 @@
* [sortDescending]: When first clicking on this column, we will default to
* sorting by |comparator| in ascending order. If this
* property is true, we will reverse that to descending.
+ * [diff]: Function to call to compute a "difference" value between
+ * parameters (a, b). This is used when calculating the difference
+ * between two snapshots. Diffing numeric quantities generally
+ * involves subtracting, but some fields like max may need to do
+ * something different.
*/
var KEY_PROPERTIES = [];
@@ -363,6 +385,7 @@
textPrinter: formatNumberAsText,
inputJsonKey: 'death_data.count',
aggregator: SumAggregator,
+ diff: diffFuncForCount,
};
KEY_PROPERTIES[KEY_QUEUE_TIME] = {
@@ -372,6 +395,7 @@
textPrinter: formatNumberAsText,
inputJsonKey: 'death_data.queue_ms',
aggregator: SumAggregator,
+ diff: diffFuncForCount,
};
KEY_PROPERTIES[KEY_MAX_QUEUE_TIME] = {
@@ -381,6 +405,7 @@
textPrinter: formatNumberAsText,
inputJsonKey: 'death_data.queue_ms_max',
aggregator: MaxAggregator,
+ diff: diffFuncForMax,
};
KEY_PROPERTIES[KEY_RUN_TIME] = {
@@ -390,6 +415,7 @@
textPrinter: formatNumberAsText,
inputJsonKey: 'death_data.run_ms',
aggregator: SumAggregator,
+ diff: diffFuncForCount,
};
KEY_PROPERTIES[KEY_AVG_RUN_TIME] = {
@@ -407,6 +433,7 @@
textPrinter: formatNumberAsText,
inputJsonKey: 'death_data.run_ms_max',
aggregator: MaxAggregator,
+ diff: diffFuncForMax,
};
KEY_PROPERTIES[KEY_AVG_QUEUE_TIME] = {
@@ -761,6 +788,17 @@
return (new Date()).getTime();
}
+ /**
+ * Toggle a node between hidden/invisible.
+ */
+ function toggleNodeDisplay(n) {
+ if (n.style.display == '') {
+ n.style.display = 'none';
+ } else {
+ n.style.display = '';
+ }
+ }
+
// --------------------------------------------------------------------------
// Functions that augment, bucket, and compute aggregates for the input data.
// --------------------------------------------------------------------------
@@ -769,9 +807,13 @@
* Adds new derived properties to row. Mutates the provided dictionary |e|.
*/
function augmentDataRow(e) {
+ computeDataRowAverages(e);
+ e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']';
+ }
+
+ function computeDataRowAverages(e) {
e[KEY_AVG_QUEUE_TIME] = e[KEY_QUEUE_TIME] / e[KEY_COUNT];
e[KEY_AVG_RUN_TIME] = e[KEY_RUN_TIME] / e[KEY_COUNT];
- e[KEY_SOURCE_LOCATION] = e[KEY_FILE_NAME] + ' [' + e[KEY_LINE_NUMBER] + ']';
}
/**
@@ -796,6 +838,26 @@
aggregates[key].consume(row);
}
+ function bucketIdenticalRows(rows, identityKeys, propertyGetterFunc) {
+ var identicalRows = {};
+ for (var i = 0; i < rows.length; ++i) {
+ var r = rows[i];
+
+ var rowIdentity = [];
+ for (var j = 0; j < identityKeys.length; ++j)
+ rowIdentity.push(propertyGetterFunc(r, identityKeys[j]));
+ rowIdentity = rowIdentity.join('\n');
+
+ var l = identicalRows[rowIdentity];
+ if (!l) {
+ l = [];
+ identicalRows[rowIdentity] = l;
+ }
+ l.push(r);
+ }
+ return identicalRows;
+ }
+
/**
* Merges the rows in |origRows|, by collapsing the columns listed in
* |mergeKeys|. Returns an array with the merged rows (in no particular
@@ -837,23 +899,9 @@
deleteValuesFromArray(aggregateKeys, IDENTITY_KEYS);
// Group all the identical rows together, bucketed into |identicalRows|.
- var identicalRows = {};
- for (var i = 0; i < origRows.length; ++i) {
- var e = origRows[i];
+ var identicalRows =
+ bucketIdenticalRows(origRows, identityKeys, propertyGetterFunc);
- var rowIdentity = [];
- for (var j = 0; j < identityKeys.length; ++j)
- rowIdentity.push(propertyGetterFunc(e, identityKeys[j]));
- rowIdentity = rowIdentity.join('\n');
-
- var l = identicalRows[rowIdentity];
- if (!l) {
- l = [];
- identicalRows[rowIdentity] = l;
- }
- l.push(e);
- }
-
var mergedRows = [];
// Merge the rows and save the results to |mergedRows|.
@@ -884,6 +932,85 @@
return mergedRows;
}
+ /**
+ * Takes two flat lists data1 and data2, and returns a new flat list which
+ * represents the difference between them. The exact meaning of "difference"
+ * is column specific, but for most numeric fields (like the count, or total
+ * time), it is found by subtracting.
+ *
+ * TODO(eroman): Some of this code is duplicated from mergeRows().
+ */
+ function subtractSnapshots(data1, data2) {
+ // These columns are computed from the other columns. We won't bother
+ // diffing/aggregating these, but rather will derive them again from the
+ // final row.
+ var COMPUTED_AGGREGATE_KEYS = [KEY_AVG_QUEUE_TIME, KEY_AVG_RUN_TIME];
+
+ // These are the keys which determine row equality. Since we are not doing
+ // any merging yet at this point, it is simply the list of all identity
+ // columns.
+ var identityKeys = IDENTITY_KEYS;
+
+ // The columns to compute via aggregation is everything else.
+ var aggregateKeys = ALL_KEYS.slice(0);
+ deleteValuesFromArray(aggregateKeys, IDENTITY_KEYS);
+ deleteValuesFromArray(aggregateKeys, COMPUTED_AGGREGATE_KEYS);
+
+ // Group all the identical rows for each list together.
+ var propertyGetterFunc = function(row, key) { return row[key]; };
+ var identicalRows1 =
+ bucketIdenticalRows(data1, identityKeys, propertyGetterFunc);
+ var identicalRows2 =
+ bucketIdenticalRows(data2, identityKeys, propertyGetterFunc);
+
+ var diffedRows = [];
+
+ for (var k in identicalRows2) {
+ var rows2 = identicalRows2[k];
+ var rows1 = identicalRows1[k];
+ if (rows1 == undefined)
+ rows1 = [];
+
+ var newRow = [];
+
+ // Copy over all the identity columns to the new row (since they
+ // were the same for each row matched).
+ for (var i = 0; i < identityKeys.length; ++i)
+ newRow[identityKeys[i]] = propertyGetterFunc(rows2[0], identityKeys[i]);
+
+ // The raw data for each snapshot *may* have contained duplicate rows, so
+ // smash them down into a single row using our aggregation functions.
+ var aggregates1 = initializeAggregates(aggregateKeys);
+ var aggregates2 = initializeAggregates(aggregateKeys);
+ for (var i = 0; i < rows1.length; ++i)
+ consumeAggregates(aggregates1, rows1[i]);
+ for (var i = 0; i < rows2.length; ++i)
+ consumeAggregates(aggregates2, rows2[i]);
+
+ // Finally, diff the two merged rows.
+ for (var aggregateKey in aggregates2) {
+ var a = aggregates1[aggregateKey].getValue();
+ var b = aggregates2[aggregateKey].getValue();
+
+ var diffFunc = KEY_PROPERTIES[aggregateKey].diff;
+ newRow[aggregateKey] = diffFunc(a, b);
+ }
+
+ if (newRow[KEY_COUNT] == 0) {
+ // If a row's count has gone to zero, it means there were no new
+ // occurrences of it in the second snapshot, so remove it.
+ continue;
+ }
+
+ // Since we excluded the averages during diffing phase, re-compute them
+ // using the diffed totals.
+ computeDataRowAverages(newRow);
+ diffedRows.push(newRow);
+ }
+
+ return diffedRows;
+ }
+
// --------------------------------------------------------------------------
// HTML drawing code
// --------------------------------------------------------------------------
@@ -1060,7 +1187,13 @@
}
MainView.prototype = {
- addData: function(data) {
+ addDataToSnapshot: function(data) {
+ // TODO(eroman): We need to know which snapshot this data belongs to!
+ // For now we assume it is the most recent snapshot.
+ var snapshotIndex = this.snapshots_.length - 1;
+
+ var snapshot = this.snapshots_[snapshotIndex];
+
var pid = data.process_id;
var ptype = data.process_type;
@@ -1094,42 +1227,120 @@
// Add our computed properties.
augmentDataRow(newRow);
- this.flatData_.push(newRow);
+ snapshot.flatData.push(newRow);
}
- // We may end up calling addData() repeatedly (once for each process).
- // To avoid this from slowing us down we do bulk updates on a timer.
- this.updateMergedDataSoon_();
+ if (!arrayToSet(this.getSelectedSnapshotIndexes_())[snapshotIndex]) {
+ // Optimization: If this snapshot is not a data dependency for the
+ // current display, then don't bother updating anything.
+ return;
+ }
+
+ // We may end up calling addDataToSnapshot_() repeatedly (once for each
+ // process). To avoid this from slowing us down we do bulk updates on a
+ // timer.
+ this.updateFlatDataSoon_();
},
- updateMergedDataSoon_: function() {
- if (this.updateMergedDataPending_) {
+ updateFlatDataSoon_: function() {
+ if (this.updateFlatDataPending_) {
// If a delayed task has already been posted to re-merge the data,
// then we don't need to do anything extra.
return;
}
- // Otherwise schedule updateMergeData_() to be called later. We want it to
+ // Otherwise schedule updateFlatData_() to be called later. We want it to
// be called no more than once every PROCESS_DATA_DELAY_MS milliseconds.
- if (this.lastUpdateMergedDataTime_ == undefined)
- this.lastUpdateMergedDataTime_ = 0;
+ if (this.lastUpdateFlatDataTime_ == undefined)
+ this.lastUpdateFlatDataTime_ = 0;
- var timeSinceLastMerge = getTimeMillis() - this.lastUpdateMergedDataTime_;
+ var timeSinceLastMerge = getTimeMillis() - this.lastUpdateFlatDataTime_;
var timeToWait = Math.max(0, PROCESS_DATA_DELAY_MS - timeSinceLastMerge);
var functionToRun = function() {
// Do the actual update.
- this.updateMergedData_();
+ this.updateFlatData_();
// Keep track of when we last ran.
- this.lastUpdateMergedDataTime_ = getTimeMillis();
- this.updateMergedDataPending_ = false;
+ this.lastUpdateFlatDataTime_ = getTimeMillis();
+ this.updateFlatDataPending_ = false;
}.bind(this);
- this.updateMergedDataPending_ = true;
+ this.updateFlatDataPending_ = true;
window.setTimeout(functionToRun, timeToWait);
},
+ /**
+ * Returns a list of the currently selected snapshots. This list is
+ * guaranteed to be of length 1 or 2.
+ */
+ getSelectedSnapshotIndexes_: function() {
+ var indexes = this.getSelectedSnapshotBoxes_();
+ for (var i = 0; i < indexes.length; ++i)
+ indexes[i] = indexes[i].__index;
+ return indexes;
+ },
+
+ /**
+ * Same as getSelectedSnapshotIndexes_(), only it returns the actual
+ * checkbox input DOM nodes rather than the snapshot ID.
+ */
+ getSelectedSnapshotBoxes_: function() {
+ // Figure out which snaphots to use for our data.
+ var boxes = [];
+ for (var i = 0; i < this.snapshots_.length; ++i) {
+ var box = this.getSnapshotCheckbox_(i);
+ if (box.checked)
+ boxes.push(box);
+ }
+ return boxes;
+ },
+
+ /**
+ * This function should be called any time a snapshot dependency for what is
+ * being displayed on the screen has changed. It will re-calculate the
+ * difference between the two snapshots and update flatData_.
+ */
+ updateFlatData_: function() {
+ var summaryDiv = $(SNAPSHOT_SELECTION_SUMMARY_ID);
+
+ var selectedSnapshots = this.getSelectedSnapshotIndexes_();
+ if (selectedSnapshots.length == 1) {
+ // If only one snapshot is chosen then we will display that snapshot's
+ // data in its entirety.
+ this.flatData_ = this.snapshots_[selectedSnapshots[0]].flatData;
+
+ // Don't bother displaying any text when just 1 snapshot is selected,
+ // since it is obvious what this should do.
+ summaryDiv.innerText = '';
+ } else if (selectedSnapshots.length == 2) {
+ // Otherwise if two snapshots were chosen, show the difference between
+ // them.
+ var snapshot1 = this.snapshots_[selectedSnapshots[0]];
+ var snapshot2 = this.snapshots_[selectedSnapshots[1]];
+
+ this.flatData_ =
+ subtractSnapshots(snapshot1.flatData, snapshot2.flatData);
+
+ var timeDeltaInSeconds =
+ ((snapshot2.time - snapshot1.time) / 1000).toFixed(0);
+
+ // Explain that what is being shown is the difference between two
+ // snapshots.
+ summaryDiv.innerText =
+ 'Showing the difference between snapshots #' +
+ selectedSnapshots[0] + ' and #' +
+ selectedSnapshots[1] + ' (' + timeDeltaInSeconds +
+ ' seconds worth of data)';
+ } else {
+ // This shouldn't be possible...
+ throw 'Unexpected number of selected snapshots';
+ }
+
+ // Recompute mergedData_ (since it is derived from flatData_)
+ this.updateMergedData_();
+ },
+
updateMergedData_: function() {
// Recompute mergedData_.
this.mergedData_ = mergeRows(this.flatData_,
@@ -1443,6 +1654,11 @@
},
init_: function() {
+ this.snapshots_ = [];
+
+ // Start fetching the data from the browser; this will be our snapshot #0.
+ this.takeSnapshot_();
+
// Data goes through the following pipeline:
// (1) Raw data received from browser, and transformed into our own
// internal row format (where properties are indexed by KEY_*
@@ -1475,24 +1691,108 @@
this.fillGroupingDropdowns_();
this.fillSortingDropdowns_();
- $(EDIT_COLUMNS_LINK_ID).onclick = this.toggleEditColumns_.bind(this);
+ $(EDIT_COLUMNS_LINK_ID).onclick =
+ toggleNodeDisplay.bind(null, $(EDIT_COLUMNS_ROW));
+ $(TOGGLE_SNAPSHOTS_LINK_ID).onclick =
+ toggleNodeDisplay.bind(null, $(SNAPSHOTS_ROW));
+
$(MERGE_SIMILAR_THREADS_CHECKBOX_ID).onchange =
this.onMergeSimilarThreadsCheckboxChanged_.bind(this);
$(RESET_DATA_LINK_ID).onclick =
g_browserBridge.sendResetData.bind(g_browserBridge);
+
+ $(TAKE_SNAPSHOT_BUTTON_ID).onclick = this.takeSnapshot_.bind(this);
},
- toggleEditColumns_: function() {
- var n = $(EDIT_COLUMNS_ROW);
- if (n.style.display == '') {
- n.style.display = 'none';
- } else {
- n.style.display = '';
+ takeSnapshot_: function() {
+ // Start a new empty snapshot. Make note of the current time, so we know
+ // when the snaphot was taken.
+ this.snapshots_.push({flatData: [], time: getTimeMillis()});
+
+ // Update the UI to reflect the new snapshot.
+ this.addSnapshotToList_(this.snapshots_.length - 1);
+
+ // Ask the browser for the profiling data. We will receive the data
+ // later through a callback to addDataToSnapshot_().
+ g_browserBridge.sendGetData();
+ },
+
+ getSnapshotCheckbox_: function(i) {
+ return $(this.getSnapshotCheckboxId_(i));
+ },
+
+ getSnapshotCheckboxId_: function(i) {
+ return 'snapshotCheckbox-' + i;
+ },
+
+ addSnapshotToList_: function(i) {
+ var tbody = $('snapshots-tbody');
+
+ var tr = addNode(tbody, 'tr');
+
+ var id = this.getSnapshotCheckboxId_(i);
+
+ var checkboxCell = addNode(tr, 'td');
+ var checkbox = addNode(checkboxCell, 'input');
+ checkbox.type = 'checkbox';
+ checkbox.id = id;
+ checkbox.__index = i;
+ checkbox.onclick = this.onSnapshotCheckboxChanged_.bind(this);
+
+ addNode(tr, 'td', '#' + i);
+
+ var labelCell = addNode(tr, 'td');
+ var l = addNode(labelCell, 'label');
+
+ var dateString = new Date(this.snapshots_[i].time).toLocaleString();
+ addText(l, dateString);
+ l.htmlFor = id;
+
+ // If we are on snapshot 0, make it the default.
+ if (i == 0) {
+ checkbox.checked = true;
+ checkbox.__time = getTimeMillis();
+ this.updateSnapshotCheckboxStyling_();
}
},
+ updateSnapshotCheckboxStyling_: function() {
+ for (var i = 0; i < this.snapshots_.length; ++i) {
+ var checkbox = this.getSnapshotCheckbox_(i);
+ checkbox.parentNode.parentNode.className =
+ checkbox.checked ? 'selected_snapshot' : '';
+ }
+ },
+
+ onSnapshotCheckboxChanged_: function(event) {
+ // Keep track of when we clicked this box (for when we need to uncheck
+ // older boxes).
+ event.target.__time = getTimeMillis();
+
+ // Find all the checked boxes. Either 1 or 2 can be checked. If a third
+ // was just checked, then uncheck one of the earlier ones so we only have
+ // 2.
+ var checked = this.getSelectedSnapshotBoxes_();
+ checked.sort(function(a, b) { return b.__time - a.__time; });
+ if (checked.length > 2) {
+ for (var i = 2; i < checked.length; ++i)
+ checked[i].checked = false;
+ checked.length = 2;
+ }
+
+ // We should always have at least 1 selection. Prevent the user from
+ // unselecting the final box.
+ if (checked.length == 0)
+ event.target.checked = true;
+
+ this.updateSnapshotCheckboxStyling_();
+
+ // Recompute flatData_ (since it is derived from selected snapshots).
+ this.updateFlatData_();
+ },
+
fillSelectionCheckboxes_: function(parent) {
this.selectionCheckboxes_ = {};
« 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