[tools] Improve system-analyzer profiler panel
Bug: v8:10644 Change-Id: Ie14c5055a4d24d064def7435fee2cde480844e8e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3717985 Reviewed-by: Patrick Thier <pthier@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/main@{#81352}
This commit is contained in:
parent
e95a3e3182
commit
00b30232ae
@ -66,3 +66,12 @@ export function defer() {
|
||||
p.reject = reject_func;
|
||||
return p;
|
||||
}
|
||||
|
||||
const kSimpleHtmlEscapeRegexp = /[\&\n><]/g;
|
||||
function escaperFn(char) {
|
||||
return `&#${char.charCodeAt(0)};`;
|
||||
}
|
||||
|
||||
export function simpleHtmlEscape(string) {
|
||||
return string.replace(kSimpleHtmlEscapeRegexp, escaperFn);
|
||||
}
|
||||
|
@ -123,6 +123,10 @@ export class CodeLogEntry extends CodeLikeLogEntry {
|
||||
return this._kindName === 'Unopt';
|
||||
}
|
||||
|
||||
get isScript() {
|
||||
return this._type.startsWith('Script');
|
||||
}
|
||||
|
||||
get kindName() {
|
||||
return this._kindName;
|
||||
}
|
||||
@ -131,6 +135,14 @@ export class CodeLogEntry extends CodeLikeLogEntry {
|
||||
return this._entry.functionName ?? this._entry.getRawName();
|
||||
}
|
||||
|
||||
get shortName() {
|
||||
if (this.isScript) {
|
||||
let url = this.sourcePosition?.script?.name ?? '';
|
||||
return url.substring(url.lastIndexOf('/') + 1);
|
||||
}
|
||||
return this.functionName;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._entry.size;
|
||||
}
|
||||
@ -242,6 +254,11 @@ export class BaseCPPLogEntry extends CodeLikeLogEntry {
|
||||
return this._entry.name;
|
||||
}
|
||||
|
||||
get shortName() {
|
||||
let name = this.name;
|
||||
return name.substring(name.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `SharedLib`;
|
||||
}
|
||||
|
@ -342,23 +342,23 @@ export class Processor extends LogReader {
|
||||
processCodeCreation(
|
||||
type, kind, timestamp, start, size, nameAndPosition, maybe_func) {
|
||||
this._lastTimestamp = timestamp;
|
||||
let entry;
|
||||
let profilerEntry;
|
||||
let stateName = '';
|
||||
if (maybe_func.length) {
|
||||
const funcAddr = parseInt(maybe_func[0]);
|
||||
stateName = maybe_func[1] ?? '';
|
||||
const state = Profile.parseState(maybe_func[1]);
|
||||
entry = this._profile.addFuncCode(
|
||||
profilerEntry = this._profile.addFuncCode(
|
||||
type, nameAndPosition, timestamp, start, size, funcAddr, state);
|
||||
} else {
|
||||
entry = this._profile.addAnyCode(
|
||||
profilerEntry = this._profile.addAnyCode(
|
||||
type, nameAndPosition, timestamp, start, size);
|
||||
}
|
||||
const name = nameAndPosition.slice(0, nameAndPosition.indexOf(' '));
|
||||
this._lastCodeLogEntry = new CodeLogEntry(
|
||||
type + stateName, timestamp,
|
||||
Profile.getKindFromState(Profile.parseState(stateName)), kind, name,
|
||||
entry);
|
||||
profilerEntry);
|
||||
this._codeTimeline.push(this._lastCodeLogEntry);
|
||||
}
|
||||
|
||||
|
@ -194,6 +194,10 @@ export class ProfileNode {
|
||||
return this.ticksAndPosition.length / 2;
|
||||
}
|
||||
|
||||
isLeaf() {
|
||||
return this.selfCount() == this.totalCount();
|
||||
}
|
||||
|
||||
totalDuration() {
|
||||
let duration = 0;
|
||||
for (let entry of this.ticksAndPosition) duration += entry.duration;
|
||||
@ -259,7 +263,7 @@ export class Flame {
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._logEntry.name;
|
||||
return this._logEntry?.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,20 @@ found in the LICENSE file. -->
|
||||
.fsMain {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
#table .nm {
|
||||
font-family: var(--code-font);
|
||||
font-size: var(--code-font-size);
|
||||
}
|
||||
#table .aC { /* arrow closed */
|
||||
transition: transform 0.2s ease-out 0s;
|
||||
user-select: none;
|
||||
}
|
||||
#table .aO { /* arrow opened */
|
||||
transform: rotate(90deg);
|
||||
transition: transform 0.2s ease-out 0s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -106,7 +120,7 @@ found in the LICENSE file. -->
|
||||
<label class="panelCloserLabel" for="closer">▼</label>
|
||||
<h2>Profiler</h2>
|
||||
<div class="selection">
|
||||
<input type="radio" id="show-all" name="selectionType" value="all">
|
||||
<input type="radio" id="show-all" name="selectionType" value="all" checked >
|
||||
<label for="show-all">All</label>
|
||||
<input type="radio" id="show-timerange" name="selectionType" value="timerange">
|
||||
<label for="show-timerange">Time Range</label>
|
||||
@ -126,7 +140,7 @@ found in the LICENSE file. -->
|
||||
<td></td>
|
||||
<td>Type</td>
|
||||
<td>Name</td>
|
||||
<td>SourcePostion</td>
|
||||
<td>SourcePosition</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -3,13 +3,13 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {CodeEntry} from '../../codemap.mjs';
|
||||
import {delay} from '../helper.mjs';
|
||||
import {delay, simpleHtmlEscape} from '../helper.mjs';
|
||||
import {DeoptLogEntry} from '../log/code.mjs';
|
||||
import {TickLogEntry} from '../log/tick.mjs';
|
||||
import {Flame, FlameBuilder, ProfileNode} from '../profiling.mjs';
|
||||
import {Timeline} from '../timeline.mjs';
|
||||
|
||||
import {ToolTipEvent} from './events.mjs';
|
||||
import {FocusEvent, SelectRelatedEvent, ToolTipEvent} from './events.mjs';
|
||||
import {CollapsableElement, CSSColor, DOM, LazyTable} from './helper.mjs';
|
||||
import {Track} from './timeline/timeline-overview.mjs';
|
||||
|
||||
@ -79,7 +79,9 @@ DOM.defineCustomElement('view/profiler-panel',
|
||||
|
||||
_update() {
|
||||
this._profileNodeMap = new Map();
|
||||
const entries = this._displayedLogEntries?.values ?? [];
|
||||
const entries = this._displayedLogEntries ?
|
||||
(this._displayedLogEntries.values ?? []) :
|
||||
(this._timeline?.values ?? []);
|
||||
let totalDuration = 0;
|
||||
let totalEntries = 0;
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
@ -124,16 +126,26 @@ DOM.defineCustomElement('view/profiler-panel',
|
||||
buffer.push(`<td class=r >${node.totalCount()}</td>`);
|
||||
const totalPercent = (node.totalCount() / totalEntries * 100).toFixed(1);
|
||||
buffer.push(`<td class=r >${totalPercent}%</td>`);
|
||||
buffer.push('<td></td>');
|
||||
if (node.isLeaf()) {
|
||||
buffer.push('<td></td>');
|
||||
} else {
|
||||
buffer.push('<td class=aC >▸</td>');
|
||||
}
|
||||
if (typeof codeEntry === 'number') {
|
||||
buffer.push('<td></td>');
|
||||
buffer.push(`<td>${codeEntry}</td>`);
|
||||
buffer.push('<td></td>');
|
||||
} else {
|
||||
const logEntry = codeEntry.logEntry;
|
||||
let sourcePositionString = logEntry.sourcePosition?.toString() ?? '';
|
||||
if (logEntry.type == 'SHARED_LIB') {
|
||||
sourcePositionString = logEntry.name;
|
||||
}
|
||||
buffer.push(`<td>${logEntry.type}</td>`);
|
||||
buffer.push(`<td>${logEntry.name}</td>`);
|
||||
buffer.push(`<td>${logEntry.sourcePosition?.toString() ?? ''}</td>`);
|
||||
buffer.push(
|
||||
`<td class=nm >${simpleHtmlEscape(logEntry.shortName)}</td>`);
|
||||
buffer.push(
|
||||
`<td class=sp >${simpleHtmlEscape(sourcePositionString)}</td>`);
|
||||
}
|
||||
buffer.push('</tr>');
|
||||
}
|
||||
@ -158,6 +170,27 @@ DOM.defineCustomElement('view/profiler-panel',
|
||||
return;
|
||||
}
|
||||
const profileNode = this._profileNodes[dataId];
|
||||
const className = e.target.className;
|
||||
if (className == 'aC') {
|
||||
e.target.className = 'aO';
|
||||
return;
|
||||
} else if (className == 'aO') {
|
||||
e.target.className = 'aC';
|
||||
return;
|
||||
} else if (className == 'sp' || className == 'nm') {
|
||||
// open source position
|
||||
const codeEntry = profileNode?.codeEntry;
|
||||
if (codeEntry) {
|
||||
if (e.shiftKey) {
|
||||
this.dispatchEvent(new SelectRelatedEvent(codeEntry));
|
||||
return;
|
||||
} else if (codeEntry.sourcePosition) {
|
||||
this.dispatchEvent(new FocusEvent(codeEntry.sourcePosition));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default operation: show overview
|
||||
this._updateOverview(profileNode);
|
||||
this._updateFlameChart(profileNode);
|
||||
}
|
||||
@ -170,7 +203,7 @@ DOM.defineCustomElement('view/profiler-panel',
|
||||
const mainCode = profileNode.codeEntry;
|
||||
const secondaryCodeEntries = [];
|
||||
const deopts = [];
|
||||
const codeCreation = [mainCode.logEntry];
|
||||
const codeCreation = typeof mainCode == 'number' ? [] : [mainCode.logEntry];
|
||||
if (mainCode.func?.codeEntries.size > 1) {
|
||||
for (let dynamicCode of mainCode.func.codeEntries) {
|
||||
for (let related of dynamicCode.logEntry.relatedEntries()) {
|
||||
|
@ -10,7 +10,7 @@ found in the LICENSE file. -->
|
||||
<style>
|
||||
:host {
|
||||
--selection-height: 5px;
|
||||
--total-height: 30px;
|
||||
--total-height: 50px;
|
||||
}
|
||||
#svg {
|
||||
width: 100%;
|
||||
@ -19,7 +19,7 @@ found in the LICENSE file. -->
|
||||
top: 0px;
|
||||
}
|
||||
.marker {
|
||||
width: 1px;
|
||||
width: 2px;
|
||||
y: var(--selection-height);
|
||||
height: calc(var(--total-height) - var(--selection-height));
|
||||
}
|
||||
@ -50,7 +50,7 @@ found in the LICENSE file. -->
|
||||
}
|
||||
</style>
|
||||
|
||||
<svg id="svg" viewBox="0 1 800 30" preserveAspectRatio=none>
|
||||
<svg id="svg" viewBox="0 1 800 50" preserveAspectRatio=none>
|
||||
<defs>
|
||||
<pattern id="pattern1" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||
<path d="M-1,1 l2,-2
|
||||
@ -58,16 +58,16 @@ found in the LICENSE file. -->
|
||||
M3,5 l2,-2" stroke="white"/>
|
||||
</pattern>
|
||||
<mask id="mask1">
|
||||
<rect width="800" height="20" fill="url(#pattern1)" />
|
||||
<rect width="800" height="40" fill="url(#pattern1)" />
|
||||
</mask>
|
||||
</defs>
|
||||
<rect id="filler" width="800" fill-opacity="0"/>
|
||||
<g id="content"></g>
|
||||
<svg id="selection">
|
||||
<line x1="0%" y1="0" x2="0%" y2="30" />
|
||||
<line x1="0%" y1="0" x2="0%" y2="50" />
|
||||
<rect class="top" x="0%" width="100%"></rect>
|
||||
<rect class="bottom" x="0%" width="100%"></rect>
|
||||
<line x1="100%" y1="0" x2="100%" y2="30" />
|
||||
<line x1="100%" y1="0" x2="100%" y2="50" />
|
||||
</svg>
|
||||
<line id="indicator" x1="0" y1="0" x2="0" y2="30" />
|
||||
<line id="indicator" x1="0" y1="0" x2="0" y2="50" />
|
||||
</svg>
|
||||
|
@ -51,7 +51,7 @@ export class Track {
|
||||
|
||||
const kHorizontalPixels = 800;
|
||||
const kMarginHeight = 5;
|
||||
const kHeight = 20;
|
||||
const kHeight = 40;
|
||||
|
||||
DOM.defineCustomElement('view/timeline/timeline-overview',
|
||||
(templateText) =>
|
||||
@ -150,8 +150,7 @@ DOM.defineCustomElement('view/timeline/timeline-overview',
|
||||
const freq = new Frequency(this._timeline);
|
||||
freq.collect(track, this._countCallback);
|
||||
const path = SVG.path('continuousTrack');
|
||||
let vScale = kHeight / freq.max();
|
||||
path.setAttribute('d', freq.toSVG(vScale));
|
||||
path.setAttribute('d', freq.toSVG(kHeight));
|
||||
path.setAttribute('fill', track.color);
|
||||
if (index != 0) path.setAttribute('mask', `url(#mask${index})`)
|
||||
return path;
|
||||
@ -161,8 +160,9 @@ DOM.defineCustomElement('view/timeline/timeline-overview',
|
||||
const group = SVG.g();
|
||||
for (let entry of track.logEntries) {
|
||||
const x = entry.time * this._timeToPixel;
|
||||
const kWidth = 2;
|
||||
const rect = SVG.rect('marker');
|
||||
rect.setAttribute('x', x);
|
||||
rect.setAttribute('x', x - (kWidth / 2));
|
||||
rect.setAttribute('fill', track.color);
|
||||
rect.data = entry;
|
||||
group.appendChild(rect);
|
||||
@ -198,13 +198,12 @@ function smoothingKernel(size) {
|
||||
}
|
||||
|
||||
class Frequency {
|
||||
_smoothenedData;
|
||||
|
||||
constructor(timeline) {
|
||||
this._size = kHorizontalPixels;
|
||||
this._timeline = timeline;
|
||||
this._data = new Int16Array(this._size + kernel.length);
|
||||
this._max = 0;
|
||||
this._max = undefined;
|
||||
this._smoothenedData = undefined;
|
||||
}
|
||||
|
||||
collect(track, sumFn) {
|
||||
@ -232,10 +231,11 @@ class Frequency {
|
||||
}
|
||||
|
||||
max() {
|
||||
if (this._max !== undefined) return this._max;
|
||||
let max = 0;
|
||||
this._smoothenedData = new Float32Array(this._size);
|
||||
for (let start = 0; start < this._size; start++) {
|
||||
let value = 0
|
||||
let value = 0;
|
||||
for (let i = 0; i < kernel.length; i++) {
|
||||
value += this._data[start + i] * kernel[i];
|
||||
}
|
||||
@ -246,12 +246,17 @@ class Frequency {
|
||||
return this._max;
|
||||
}
|
||||
|
||||
toSVG(vScale = 1) {
|
||||
const buffer = ['M 0 0'];
|
||||
let prevY = 0;
|
||||
toSVG(height) {
|
||||
const vScale = height / this.max();
|
||||
const initialY = height;
|
||||
const buffer = [
|
||||
'M 0',
|
||||
initialY,
|
||||
];
|
||||
let prevY = initialY;
|
||||
let usedPrevY = false;
|
||||
for (let i = 0; i < this._size; i++) {
|
||||
const y = (this._smoothenedData[i] * vScale) | 0;
|
||||
const y = height - (this._smoothenedData[i] * vScale) | 0;
|
||||
if (y == prevY) {
|
||||
usedPrevY = false;
|
||||
continue;
|
||||
@ -262,7 +267,7 @@ class Frequency {
|
||||
usedPrevY = true;
|
||||
}
|
||||
if (!usedPrevY) buffer.push('L', this._size - 1, prevY);
|
||||
buffer.push('L', this._size - 1, 0);
|
||||
buffer.push('L', this._size - 1, initialY);
|
||||
buffer.push('Z');
|
||||
return buffer.join(' ');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user