[tools][system-analyzer] Various improvements

- Change Group.prototype.size to .length
- Use window.requestAnimationFrame when streaming-loading files to show
  the loading animation
- Limit width of the timeline-track legend and add 'title' attribute
  to show the full text when cropped
- Add duration for selected timeline events in timeline-track legend
- Better error message when the local symbol server is not available

Bug: v8:10644
Change-Id: Icdf2042341c9355ecb55e2fd8e6a4fa0feb5968f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3003151
Reviewed-by: Patrick Thier <pthier@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75549}
This commit is contained in:
Camillo Bruni 2021-07-05 10:01:09 +02:00 committed by V8 LUCI CQ
parent 3e1d2221ac
commit 212d6678e7
9 changed files with 68 additions and 30 deletions

View File

@ -63,21 +63,21 @@ export class Group {
constructor(key, id, parentTotal, entries) {
this.key = key;
this.id = id;
this.count = 1;
this.entries = entries;
this.length = entries.length;
this.parentTotal = parentTotal;
}
get percent() {
return this.count / this.parentTotal * 100;
return this.length / this.parentTotal * 100;
}
add() {
this.count++;
this.length++;
}
addEntry(entry) {
this.count++;
this.length++;
this.entries.push(entry);
}
}
@ -87,6 +87,7 @@ export function groupBy(array, keyFunction, collect = false) {
if (keyFunction === undefined) keyFunction = each => each;
const keyToGroup = new Map();
const groups = [];
const sharedEmptyArray = [];
let id = 0;
// This is performance critical, resorting to for-loop
for (let each of array) {
@ -96,8 +97,7 @@ export function groupBy(array, keyFunction, collect = false) {
collect ? group.addEntry(each) : group.add();
continue;
}
let entries = undefined;
if (collect) entries = [each];
let entries = collect ? [each] : sharedEmptyArray;
group = new Group(key, id++, array.length, entries);
groups.push(group);
keyToGroup.set(key, group);

View File

@ -207,8 +207,13 @@ class Timeline {
return minIndex;
}
getBreakdown(keyFunction) {
if (keyFunction) return groupBy(this._values, keyFunction);
getBreakdown(keyFunction, collect = false) {
if (keyFunction || collect) {
if (!keyFunction) {
keyFunction = each => each.type;
}
return groupBy(this._values, keyFunction, collect);
}
if (this._breakdown === undefined) {
this._breakdown = groupBy(this._values, each => each.type);
}

View File

@ -458,7 +458,7 @@ export function gradientStopsFromGroups(
const stops = [];
for (let group of groups) {
const color = colorFn(group.key);
increment += group.count;
increment += group.length;
const height = (increment / totalLength * kMaxHeight) | 0;
stops.push(`${color} ${lastHeight}${kUnit} ${height}${kUnit}`)
lastHeight = height;

View File

@ -176,7 +176,7 @@ DOM.defineCustomElement('view/list-panel',
_render(groups, table) {
let last;
new LazyTable(table, groups, group => {
if (last && last.count < group.count) {
if (last && last.count < group.length) {
console.log(last, group);
}
last = group;
@ -185,7 +185,7 @@ DOM.defineCustomElement('view/list-panel',
const details = tr.appendChild(DOM.td('', 'toggle'));
details.onclick = this._detailsClickHandler;
tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
tr.appendChild(DOM.td(group.count, 'count'));
tr.appendChild(DOM.td(group.length, 'count'));
const valueTd = tr.appendChild(DOM.td(group.key?.toString(), 'key'));
if (App.isClickable(group.key)) {
tr.onclick = this._logEntryClickHandler;

View File

@ -66,12 +66,11 @@ DOM.defineCustomElement('view/log-file-reader',
}
this.fileReader.blur();
this.root.className = 'loading';
this.asyncReadFile(file);
// Delay the loading a bit to allow for CSS animations to happen.
window.requestAnimationFrame(() => this.asyncReadFile(file));
}
async asyncReadFile(file) {
// Delay the loading a bit to allow for CSS animations to happen.
await delay(5);
const decoder = globalThis.TextDecoderStream;
if (decoder) {
await this._streamFile(file, decoder);

View File

@ -151,10 +151,10 @@ DOM.defineCustomElement('view/script-panel',
const entries = sourcePosition.entries;
let text = groupBy(entries, each => each.constructor, true)
.map(group => {
let text = `${group.key.name}: ${group.count}\n`
let text = `${group.key.name}: ${group.length}\n`
text += groupBy(group.entries, each => each.type, true)
.map(group => {
return ` - ${group.key}: ${group.count}`;
return ` - ${group.key}: ${group.length}`;
})
.join('\n');
return text;

View File

@ -5,7 +5,7 @@
import {delay} from '../../helper.mjs';
import {kChunkHeight, kChunkVisualWidth, kChunkWidth} from '../../log/map.mjs';
import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs';
import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs';
import {CSSColor, DOM, formatDurationMicros, SVG, V8CustomElement} from '../helper.mjs';
export const kTimelineHeight = 200;
@ -242,8 +242,8 @@ export class TimelineTrackBase extends V8CustomElement {
let lastHeight = kTimelineHeight;
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
if (group.count == 0) break;
const height = (group.count / chunk.size() * kHeight) | 0;
if (group.length == 0) break;
const height = (group.length / chunk.size() * kHeight) | 0;
lastHeight -= height;
const color = this._legend.colorForType(group.key);
buffer += `<rect x=${chunkIndex * kChunkWidth} y=${lastHeight} height=${
@ -511,6 +511,7 @@ class Legend {
constructor(table) {
this._table = table;
this._enableDuration = false;
}
set timeline(timeline) {
@ -548,10 +549,12 @@ class Legend {
update() {
const tbody = DOM.tbody();
const missingTypes = new Set(this._typesFilters.keys());
this.selection.getBreakdown().forEach(group => {
tbody.appendChild(this._addTypeRow(group));
missingTypes.delete(group.key);
});
this._checkDurationField();
this.selection.getBreakdown(undefined, this._enableDuration)
.forEach(group => {
tbody.appendChild(this._addTypeRow(group));
missingTypes.delete(group.key);
});
missingTypes.forEach(key => tbody.appendChild(this._row('', key, 0, '0%')));
if (this._timeline.selection) {
tbody.appendChild(
@ -561,12 +564,26 @@ class Legend {
this._table.tBodies[0].replaceWith(tbody);
}
_row(color, type, count, percent) {
_checkDurationField() {
if (this._enableDuration) return;
const example = this.selection.at(0);
if (!example || !('duration' in example)) return;
this._enableDuration = true;
this._table.tHead.appendChild(DOM.td('Duration'));
this._table.tHead.appendChild(DOM.td(''));
}
_row(colorNode, type, count, countPercent, duration, durationPercent) {
const row = DOM.tr();
row.appendChild(DOM.td(color));
row.appendChild(DOM.td(type));
row.appendChild(DOM.td(colorNode));
const typeCell = row.appendChild(DOM.td(type));
typeCell.setAttribute('title', type);
row.appendChild(DOM.td(count.toString()));
row.appendChild(DOM.td(percent));
row.appendChild(DOM.td(countPercent));
if (this._enableDuration) {
row.appendChild(DOM.td(formatDurationMicros(duration ?? 0)));
row.appendChild(DOM.td(durationPercent ?? '0%'));
}
return row
}
@ -579,8 +596,17 @@ class Legend {
colorDiv.style.borderColor = color;
colorDiv.style.backgroundColor = CSSColor.backgroundImage;
}
let percent = `${(group.count / this.selection.length * 100).toFixed(1)}%`;
const row = this._row(colorDiv, group.key, group.count, percent);
let duration = 0;
if (this._enableDuration) {
const entries = group.entries;
for (let i = 0; i < entries.length; i++) {
duration += entries[i].duration;
}
}
let countPercent =
`${(group.length / this.selection.length * 100).toFixed(1)}%`;
const row = this._row(
colorDiv, group.key, group.length, countPercent, duration, '');
row.className = 'clickable';
row.onclick = this._typeClickHandler;
row.data = group.key;

View File

@ -93,6 +93,9 @@ found in the LICENSE file. -->
#legendTable td:nth-of-type(4n+2) {
text-align: left;
width: 100%;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
}
/* right align numbers */
#legendTable td:nth-of-type(4n+3),
@ -225,7 +228,7 @@ found in the LICENSE file. -->
<td></td>
<td>Type</td>
<td>Count</td>
<td>Percent</td>
<td></td>
</tr>
</thead>
<tbody></tbody>

View File

@ -138,6 +138,10 @@ class CppEntriesProvider {
let json;
try {
response = await fetch(url);
if (response.status == 404) {
throw new Error(
`Local symbol server returned 404: ${await response.text()}`);
}
json = await response.json();
if (json.error) console.warn(json.error);
} catch (e) {
@ -145,6 +149,7 @@ class CppEntriesProvider {
// Assume that the local symbol server is not reachable.
console.error("Disabling remote symbol loading:", e);
this._isEnabled = false;
return;
}
}
this._handleRemoteSymbolsResult(json);