diff --git a/tools/js/helper.mjs b/tools/js/helper.mjs index 1da31b7e55..25333b407c 100644 --- a/tools/js/helper.mjs +++ b/tools/js/helper.mjs @@ -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); +} diff --git a/tools/system-analyzer/log/code.mjs b/tools/system-analyzer/log/code.mjs index 50536b2942..ff2305e212 100644 --- a/tools/system-analyzer/log/code.mjs +++ b/tools/system-analyzer/log/code.mjs @@ -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`; } diff --git a/tools/system-analyzer/processor.mjs b/tools/system-analyzer/processor.mjs index 3575ea15b7..c291d9894d 100644 --- a/tools/system-analyzer/processor.mjs +++ b/tools/system-analyzer/processor.mjs @@ -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); } diff --git a/tools/system-analyzer/profiling.mjs b/tools/system-analyzer/profiling.mjs index 04a4183d49..215bf9a3fd 100644 --- a/tools/system-analyzer/profiling.mjs +++ b/tools/system-analyzer/profiling.mjs @@ -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; } } diff --git a/tools/system-analyzer/view/profiler-panel-template.html b/tools/system-analyzer/view/profiler-panel-template.html index 255489fa5d..cdf7f8ea88 100644 --- a/tools/system-analyzer/view/profiler-panel-template.html +++ b/tools/system-analyzer/view/profiler-panel-template.html @@ -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; + } + @@ -106,7 +120,7 @@ found in the LICENSE file. -->

Profiler

- + @@ -126,7 +140,7 @@ found in the LICENSE file. --> Type Name - SourcePostion + SourcePosition diff --git a/tools/system-analyzer/view/profiler-panel.mjs b/tools/system-analyzer/view/profiler-panel.mjs index a7c9b2fd0f..b262a7029e 100644 --- a/tools/system-analyzer/view/profiler-panel.mjs +++ b/tools/system-analyzer/view/profiler-panel.mjs @@ -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(`${node.totalCount()}`); const totalPercent = (node.totalCount() / totalEntries * 100).toFixed(1); buffer.push(`${totalPercent}%`); - buffer.push(''); + if (node.isLeaf()) { + buffer.push(''); + } else { + buffer.push('▸'); + } if (typeof codeEntry === 'number') { buffer.push(''); buffer.push(`${codeEntry}`); buffer.push(''); } else { const logEntry = codeEntry.logEntry; + let sourcePositionString = logEntry.sourcePosition?.toString() ?? ''; + if (logEntry.type == 'SHARED_LIB') { + sourcePositionString = logEntry.name; + } buffer.push(`${logEntry.type}`); - buffer.push(`${logEntry.name}`); - buffer.push(`${logEntry.sourcePosition?.toString() ?? ''}`); + buffer.push( + `${simpleHtmlEscape(logEntry.shortName)}`); + buffer.push( + `${simpleHtmlEscape(sourcePositionString)}`); } buffer.push(''); } @@ -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()) { diff --git a/tools/system-analyzer/view/timeline/timeline-overview-template.html b/tools/system-analyzer/view/timeline/timeline-overview-template.html index 14bd305da7..4a0b68c9f0 100644 --- a/tools/system-analyzer/view/timeline/timeline-overview-template.html +++ b/tools/system-analyzer/view/timeline/timeline-overview-template.html @@ -10,7 +10,7 @@ found in the LICENSE file. --> - + - + - + - + - + diff --git a/tools/system-analyzer/view/timeline/timeline-overview.mjs b/tools/system-analyzer/view/timeline/timeline-overview.mjs index 8004ac9fad..6086192fa9 100644 --- a/tools/system-analyzer/view/timeline/timeline-overview.mjs +++ b/tools/system-analyzer/view/timeline/timeline-overview.mjs @@ -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(' '); }