Commit 00b30232 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools] Improve system-analyzer profiler panel

Bug: v8:10644
Change-Id: Ie14c5055a4d24d064def7435fee2cde480844e8e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3717985Reviewed-by: 's avatarPatrick Thier <pthier@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81352}
parent e95a3e31
......@@ -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(' ');
}
......
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