Commit 592df384 authored by Alexei Filippov's avatar Alexei Filippov Committed by Commit Bot

DevTools: Speed up aggregated stats pie chart calculation for timeline.

Precalculate and cache running accumulated value for each category, then
use a binary search to find values for right and left bounds and subtract
them.

BUG=874116

Change-Id: I3195ce4c38d52d048d453c5f999fb7005f0982ff
Reviewed-on: https://chromium-review.googlesource.com/1175241
Commit-Queue: Alexei Filippov <alph@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583150}
parent e22cfe66
...@@ -1057,36 +1057,38 @@ Timeline.TimelineUIUtils = class { ...@@ -1057,36 +1057,38 @@ Timeline.TimelineUIUtils = class {
static statsForTimeRange(events, startTime, endTime) { static statsForTimeRange(events, startTime, endTime) {
if (!events.length) if (!events.length)
return {'idle': endTime - startTime}; return {'idle': endTime - startTime};
const symbol = Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol;
Timeline.TimelineUIUtils._buildRangeStatsCacheIfNeeded(events);
const before = findCachedStatsAfterTime(startTime); buildRangeStatsCacheIfNeeded(events);
const statsBefore = const aggregatedStats = subtractStats(aggregatedStatsAtTime(endTime), aggregatedStatsAtTime(startTime));
subtractStats(before.stats, Timeline.TimelineUIUtils._slowStatsForTimeRange(events, startTime, before.time));
const after = findCachedStatsAfterTime(endTime);
const statsAfter =
subtractStats(after.stats, Timeline.TimelineUIUtils._slowStatsForTimeRange(events, endTime, after.time));
const aggregatedStats = subtractStats(statsAfter, statsBefore);
const aggregatedTotal = Object.values(aggregatedStats).reduce((a, b) => a + b, 0); const aggregatedTotal = Object.values(aggregatedStats).reduce((a, b) => a + b, 0);
aggregatedStats['idle'] = Math.max(0, endTime - startTime - aggregatedTotal); aggregatedStats['idle'] = Math.max(0, endTime - startTime - aggregatedTotal);
return aggregatedStats; return aggregatedStats;
/** /**
* @param {number} atTime * @param {number} time
* @return {!{time: number, stats: !Object<string, number>}} * @return {!Object}
*/ */
function findCachedStatsAfterTime(atTime) { function aggregatedStatsAtTime(time) {
let index = events.lowerBound(atTime, (time, event) => time - (event.endTime || event.startTime)); const stats = {};
while (index < events.length && !events[index][symbol]) const cache = events[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol];
index++; for (const category in cache) {
if (index === events.length) { const categoryCache = cache[category];
const lastEvent = events.peekLast(); const index = categoryCache.time.upperBound(time);
return {time: lastEvent.endTime || lastEvent.startTime, stats: events[symbol]}; let value;
if (index === 0) {
value = 0;
} else if (index === categoryCache.time.length) {
value = categoryCache.value.peekLast();
} else {
const t0 = categoryCache.time[index - 1];
const t1 = categoryCache.time[index];
const v0 = categoryCache.value[index - 1];
const v1 = categoryCache.value[index];
value = v0 + (v1 - v0) * (time - t0) / (t1 - t0);
}
stats[category] = value;
} }
const event = events[index]; return stats;
return {time: event.endTime || event.startTime, stats: event[symbol]};
} }
/** /**
...@@ -1100,82 +1102,84 @@ Timeline.TimelineUIUtils = class { ...@@ -1100,82 +1102,84 @@ Timeline.TimelineUIUtils = class {
result[key] -= b[key]; result[key] -= b[key];
return result; return result;
} }
}
/**
* @param {!Array<!SDK.TracingModel.Event>} events
* @param {number} startTime
* @param {number} endTime
*/
static _slowStatsForTimeRange(events, startTime, endTime) {
/** @type {!Object<string, number>} */
const stats = {};
const ownTimes = [];
TimelineModel.TimelineModel.forEachEvent(
events, onStartEvent, onEndEvent, undefined, startTime, endTime, Timeline.TimelineUIUtils._filterForStats());
/** /**
* @param {!SDK.TracingModel.Event} e * @param {!Array<!SDK.TracingModel.Event>} events
*/ */
function onStartEvent(e) { function buildRangeStatsCacheIfNeeded(events) {
const duration = Math.min(e.endTime, endTime) - Math.max(e.startTime, startTime); if (events[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol])
if (ownTimes.length) return;
ownTimes[ownTimes.length - 1] -= duration;
ownTimes.push(duration);
}
/** // aggeregatedStats is a map by categories. For each category there's an array
* @param {!SDK.TracingModel.Event} e // containing sorted time points which records accumulated value of the category.
*/ const aggregatedStats = {};
function onEndEvent(e) { const categoryStack = [];
const category = Timeline.TimelineUIUtils.eventStyle(e).category.name; let lastTime = 0;
stats[category] = (stats[category] || 0) + ownTimes.pop(); TimelineModel.TimelineModel.forEachEvent(
} events, onStartEvent, onEndEvent, undefined, undefined, undefined, filterForStats());
return stats;
} /**
* @return {function(!SDK.TracingModel.Event):boolean}
*/
function filterForStats() {
const visibleEventsFilter = Timeline.TimelineUIUtils.visibleEventsFilter();
return event => visibleEventsFilter.accept(event) || SDK.TracingModel.isTopLevelEvent(event);
}
/** /**
* @return {function(!SDK.TracingModel.Event):boolean} * @param {string} category
*/ * @param {number} time
static _filterForStats() { */
const visibleEventsFilter = Timeline.TimelineUIUtils.visibleEventsFilter(); function updateCategory(category, time) {
return event => visibleEventsFilter.accept(event) || SDK.TracingModel.isTopLevelEvent(event); let statsArrays = aggregatedStats[category];
} if (!statsArrays) {
statsArrays = {time: [], value: []};
aggregatedStats[category] = statsArrays;
}
if (statsArrays.time.length && statsArrays.time.peekLast() === time)
return;
const lastValue = statsArrays.value.length ? statsArrays.value.peekLast() : 0;
statsArrays.value.push(lastValue + time - lastTime);
statsArrays.time.push(time);
}
/** /**
* @param {!Array<!SDK.TracingModel.Event>} events * @param {?string} from
*/ * @param {?string} to
static _buildRangeStatsCacheIfNeeded(events) { * @param {number} time
if (events[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol]) */
return; function categoryChange(from, to, time) {
if (from)
updateCategory(from, time);
lastTime = time;
if (to)
updateCategory(to, time);
}
const aggregatedStats = {}; /**
const ownTimes = []; * @param {!SDK.TracingModel.Event} e
TimelineModel.TimelineModel.forEachEvent( */
events, onStartEvent, onEndEvent, undefined, undefined, undefined, Timeline.TimelineUIUtils._filterForStats()); function onStartEvent(e) {
const category = Timeline.TimelineUIUtils.eventStyle(e).category.name;
const parentCategory = categoryStack.length ? categoryStack.peekLast() : null;
if (category !== parentCategory)
categoryChange(parentCategory, category, e.startTime);
categoryStack.push(category);
}
/** /**
* @param {!SDK.TracingModel.Event} e * @param {!SDK.TracingModel.Event} e
*/ */
function onStartEvent(e) { function onEndEvent(e) {
if (ownTimes.length) const category = categoryStack.pop();
ownTimes[ownTimes.length - 1] -= e.duration; const parentCategory = categoryStack.length ? categoryStack.peekLast() : null;
ownTimes.push(e.duration); if (category !== parentCategory)
} categoryChange(category, parentCategory, e.endTime);
}
/** const obj = /** @type {!Object} */ (events);
* @param {!SDK.TracingModel.Event} e obj[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol] = aggregatedStats;
*/
function onEndEvent(e) {
const category = Timeline.TimelineUIUtils.eventStyle(e).category.name;
aggregatedStats[category] = (aggregatedStats[category] || 0) + ownTimes.pop();
if (!ownTimes.length)
e[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol] = Object.assign({}, aggregatedStats);
} }
const obj = /** @type {!Object} */ (events);
obj[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol] = Object.assign({}, aggregatedStats);
} }
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment