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. -->
-