diff --git a/test/mjsunit/BUILD.gn b/test/mjsunit/BUILD.gn index 5a57640df6..184bf5b596 100644 --- a/test/mjsunit/BUILD.gn +++ b/test/mjsunit/BUILD.gn @@ -26,6 +26,7 @@ group("v8_mjsunit") { "../../tools/profile_view.mjs", "../../tools/splaytree.mjs", "../../tools/system-analyzer/helper.mjs", + "../../tools/system-analyzer/log/api.mjs", "../../tools/system-analyzer/log/code.mjs", "../../tools/system-analyzer/log/ic.mjs", "../../tools/system-analyzer/log/log.mjs", diff --git a/tools/csvparser.mjs b/tools/csvparser.mjs index e027d47384..c43ee4c4fc 100644 --- a/tools/csvparser.mjs +++ b/tools/csvparser.mjs @@ -62,7 +62,11 @@ export class CsvParser { } // Convert the selected escape sequence to a single character. let escapeChars = string.substring(pos, nextPos); - result += String.fromCharCode(parseInt(escapeChars, 16)); + if (escapeChars === '2C') { + result += ','; + } else { + result += String.fromCharCode(parseInt(escapeChars, 16)); + } } // Continue looking for the next escape sequence. diff --git a/tools/profile.mjs b/tools/profile.mjs index 4ca6e9cd7d..834bdec251 100644 --- a/tools/profile.mjs +++ b/tools/profile.mjs @@ -47,6 +47,7 @@ export class Script { source; // Map> lineToColumn = new Map(); + _entries = []; constructor(id) { this.id = id; @@ -62,6 +63,10 @@ export class Script { return this.source.length; } + get entries() { + return this._entries; + } + addSourcePosition(line, column, entry) { let sourcePosition = this.lineToColumn.get(line)?.get(column); if (sourcePosition === undefined) { @@ -69,6 +74,7 @@ export class Script { this._addSourcePosition(line, column, sourcePosition); } sourcePosition.addEntry(entry); + this._entries.push(entry); return sourcePosition; } @@ -83,10 +89,12 @@ export class Script { this.sourcePositions.push(sourcePosition); columnToSourcePosition.set(column, sourcePosition); } + + } -class SourceInfo{ +class SourceInfo { script; start; end; @@ -107,6 +115,10 @@ class SourceInfo{ setDisassemble(code) { this.disassemble = code; } + + getSourceCode() { + return this.script.source?.substring(this.start, this.end); + } } /** @@ -170,7 +182,7 @@ export class Profile { return this.CodeState.IGNITION; case '-': return this.CodeState.NATIVE_CONTEXT_INDEPENDENT; - case '=': + case '+': return this.CodeState.TURBOPROP; case '*': return this.CodeState.TURBOFAN; @@ -639,9 +651,14 @@ class DynamicFuncCodeEntry extends CodeEntry { constructor(size, type, func, state) { super(size, '', type); this.func = func; + func.addDynamicCode(this); this.state = state; } + getSourceCode() { + return this.source?.getSourceCode(); + } + static STATE_PREFIX = ["", "~", "-", "+", "*"]; getState() { return DynamicFuncCodeEntry.STATE_PREFIX[this.state]; @@ -675,10 +692,26 @@ class DynamicFuncCodeEntry extends CodeEntry { * @constructor */ class FunctionEntry extends CodeEntry { + + // Contains the list of generated code for this function. + _codeEntries = new Set(); + constructor(name) { super(0, name); } + addDynamicCode(code) { + if (code.func != this) { + throw new Error("Adding dynamic code to wrong function"); + } + this._codeEntries.add(code); + } + + getSourceCode() { + // All code entries should map to the same source positions. + return this._codeEntries.values().next().value.getSourceCode(); + } + /** * Returns node name. */ diff --git a/tools/system-analyzer/app-model.mjs b/tools/system-analyzer/app-model.mjs index 8aa99e9568..5d558e248b 100644 --- a/tools/system-analyzer/app-model.mjs +++ b/tools/system-analyzer/app-model.mjs @@ -17,6 +17,7 @@ class State { _mapTimeline; _deoptTimeline; _codeTimeline; + _apiTimeline; _minStartTime = Number.POSITIVE_INFINITY; _maxEndTime = Number.NEGATIVE_INFINITY; @@ -38,11 +39,13 @@ class State { } } - setTimelines(mapTimeline, icTimeline, deoptTimeline, codeTimeline) { + setTimelines( + mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline) { this._mapTimeline = mapTimeline; this._icTimeline = icTimeline; this._deoptTimeline = deoptTimeline; this._codeTimeline = codeTimeline; + this._apiTimeline = apiTimeline; for (let timeline of arguments) { if (timeline === undefined) return; this._minStartTime = Math.min(this._minStartTime, timeline.startTime); @@ -70,9 +73,14 @@ class State { return this._codeTimeline; } + get apiTimeline() { + return this._apiTimeline; + } + get timelines() { return [ - this.mapTimeline, this.icTimeline, this.deoptTimeline, this.codeTimeline + this._mapTimeline, this._icTimeline, this._deoptTimeline, + this._codeTimeline, this._apiTimeline ]; } diff --git a/tools/system-analyzer/index.html b/tools/system-analyzer/index.html index e5e509a695..85738ffe83 100644 --- a/tools/system-analyzer/index.html +++ b/tools/system-analyzer/index.html @@ -18,9 +18,7 @@ found in the LICENSE file. --> // Delay loading of the main App (async function() { let module = await import('./index.mjs'); - globalThis.app = new module.App("#log-file-reader", "#map-panel", "#map-stats-panel", - "#timeline-panel", "#ic-panel", "#map-track", "#ic-track", "#deopt-track", - "#code-track", "#source-panel", "#code-panel", "#tool-tip"); + globalThis.app = new module.App(); })(); @@ -104,6 +102,7 @@ found in the LICENSE file. --> +
@@ -159,16 +158,17 @@ found in the LICENSE file. --> --trace-maps -
Log - Maps
+
+ LogMaps +
--trace-ic
-
Log - - ICs
+
+ Log ICs +
--log-source-code @@ -181,6 +181,12 @@ found in the LICENSE file. -->
Log detailed generated generated code
+
+ + --log-api + +
+
Log various API uses.

Keyboard Shortcuts for Navigation

diff --git a/tools/system-analyzer/index.mjs b/tools/system-analyzer/index.mjs index 8a08b3da84..a42306200d 100644 --- a/tools/system-analyzer/index.mjs +++ b/tools/system-analyzer/index.mjs @@ -5,6 +5,8 @@ import {SourcePosition} from '../profile.mjs'; import {State} from './app-model.mjs'; +import {ApiLogEntry} from './log/api.mjs'; +import {DeoptLogEntry} from './log/code.mjs'; import {CodeLogEntry} from './log/code.mjs'; import {IcLogEntry} from './log/ic.mjs'; import {MapLogEntry} from './log/map.mjs'; @@ -17,24 +19,22 @@ class App { _view; _navigation; _startupPromise; - constructor( - fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId, icPanelId, - mapTrackId, icTrackId, deoptTrackId, codeTrackId, sourcePanelId, - codePanelId, toolTipId) { + constructor() { this._view = { __proto__: null, - logFileReader: $(fileReaderId), - icPanel: $(icPanelId), - mapPanel: $(mapPanelId), - mapStatsPanel: $(mapStatsPanelId), - timelinePanel: $(timelinePanelId), - mapTrack: $(mapTrackId), - icTrack: $(icTrackId), - deoptTrack: $(deoptTrackId), - codeTrack: $(codeTrackId), - sourcePanel: $(sourcePanelId), - codePanel: $(codePanelId), - toolTip: $(toolTipId), + logFileReader: $('#log-file-reader'), + mapPanel: $('#map-panel'), + mapStatsPanel: $('#map-stats-panel'), + timelinePanel: $('#timeline-panel'), + mapTrack: $('#map-track'), + icTrack: $('#ic-track'), + icPanel: $('#ic-panel'), + deoptTrack: $('#deopt-track'), + codePanel: $('#code-panel'), + codeTrack: $('#code-track'), + apiTrack: $('#api-track'), + sourcePanel: $('#source-panel'), + toolTip: $('#tool-tip'), }; this.toggleSwitch = $('.theme-switch input[type="checkbox"]'); this.toggleSwitch.addEventListener('change', (e) => this.switchTheme(e)); @@ -67,19 +67,61 @@ class App { } handleShowEntries(e) { - const entry = e.entries[0]; - if (entry instanceof MapLogEntry) { - this.showMapEntries(e.entries); - } else if (entry instanceof IcLogEntry) { - this.showIcEntries(e.entries); - } else if (entry instanceof SourcePosition) { - this.showSourcePositionEntries(e.entries); - } else if (e.entries[0] instanceof CodeLogEntry) { - this.showCodeEntries(e.entries); - } else { - throw new Error('Unknown selection type!'); - } e.stopPropagation(); + this.showEntries(e.entries); + } + + showEntries(entries) { + const groups = new Map(); + for (let entry of entries) { + const group = groups.get(entry.constructor); + if (group !== undefined) { + group.push(entry); + } else { + groups.set(entry.constructor, [entry]); + } + } + groups.forEach(entries => this.showEntriesOfSingleType(entries)); + } + + showEntriesOfSingleType(entries) { + switch (entries[0].constructor) { + case SourcePosition: + return this.showSourcePositions(entries); + case MapLogEntry: + return this.showMapEntries(entries); + case IcLogEntry: + return this.showIcEntries(entries); + case ApiLogEntry: + return this.showApiEntries(entries); + case CodeLogEntry: + return this.showCodeEntries(entries); + case DeoptLogEntry: + return this.showDeoptEntries(entries); + default: + throw new Error('Unknown selection type!'); + } + } + + handleShowEntryDetail(e) { + e.stopPropagation(); + const entry = e.entry; + switch (entry.constructor) { + case SourcePosition: + return this.selectSourcePosition(entry); + case MapLogEntry: + return this.selectMapLogEntry(entry); + case IcLogEntry: + return this.selectIcLogEntry(entry); + case ApiLogEntry: + return this.selectApiLogEntry(entry); + case CodeLogEntry: + return this.selectCodeLogEntry(entry); + case DeoptLogEntry: + return this.selectDeoptLogEntry(entry); + default: + throw new Error('Unknown selection type!'); + } } showMapEntries(entries) { @@ -94,17 +136,22 @@ class App { } showDeoptEntries(entries) { - // TODO: creat list panel. + // TODO: create list panel. this._state.selectedDeoptLogEntries = entries; } showCodeEntries(entries) { - // TODO: creat list panel + // TODO: create list panel this._state.selectedCodeLogEntries = entries; this._view.codePanel.selectedEntries = entries; } - showSourcePositionEntries(entries) { + showApiEntries(entries) { + // TODO: create list panel + this._state.selectedApiLogEntries = entries; + } + + showSourcePositions(entries) { // TODO: Handle multiple source position selection events this._view.sourcePanel.selectedSourcePositions = entries } @@ -120,32 +167,17 @@ class App { this.showIcEntries(this._state.icTimeline.selection ?? []); this.showDeoptEntries(this._state.deoptTimeline.selection ?? []); this.showCodeEntries(this._state.codeTimeline.selection ?? []); + this.showApiEntries(this._state.apiTimeline.selection ?? []); this._view.timelinePanel.timeSelection = {start, end}; } - handleShowEntryDetail(e) { - const entry = e.entry; - if (entry instanceof MapLogEntry) { - this.selectMapLogEntry(e.entry); - } else if (entry instanceof IcLogEntry) { - this.selectICLogEntry(e.entry); - } else if (entry instanceof SourcePosition) { - this.selectSourcePosition(e.entry); - } else if (e.entry instanceof CodeLogEntry) { - this.selectCodeLogEntry(e.entry); - } else { - throw new Error('Unknown selection type!'); - } - e.stopPropagation(); - } - selectMapLogEntry(entry) { this._state.map = entry; this._view.mapTrack.selectedEntry = entry; this._view.mapPanel.map = entry; } - selectICLogEntry(entry) { + selectIcLogEntry(entry) { this._state.ic = entry; this._view.icPanel.selectedEntry = [entry]; } @@ -155,6 +187,15 @@ class App { this._view.codePanel.entry = entry; } + selectDeoptLogEntry(entry) { + // TODO + } + + selectApiLogEntry(entry) { + this._state.apiLogEntry = entry; + this._view.apiTrack.selectedEntry = entry; + } + selectSourcePosition(sourcePositions) { if (!sourcePositions.script) return; this._view.sourcePanel.selectedSourcePositions = [sourcePositions]; @@ -183,8 +224,9 @@ class App { const icTimeline = processor.icTimeline; const deoptTimeline = processor.deoptTimeline; const codeTimeline = processor.codeTimeline; + const apiTimeline = processor.apiTimeline; this._state.setTimelines( - mapTimeline, icTimeline, deoptTimeline, codeTimeline); + mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline); // Transitions must be set before timeline for stats panel. this._view.mapPanel.timeline = mapTimeline; this._view.mapStatsPanel.transitions = @@ -208,6 +250,7 @@ class App { this._view.icTrack.data = this._state.icTimeline; this._view.deoptTrack.data = this._state.deoptTimeline; this._view.codeTrack.data = this._state.codeTimeline; + this._view.apiTrack.data = this._state.apiTimeline; } switchTheme(event) { diff --git a/tools/system-analyzer/log/api.mjs b/tools/system-analyzer/log/api.mjs new file mode 100644 index 0000000000..bc9b34fd9a --- /dev/null +++ b/tools/system-analyzer/log/api.mjs @@ -0,0 +1,14 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import {LogEntry} from './log.mjs'; + +export class ApiLogEntry extends LogEntry { + constructor(type, time, name) { + super(type, time); + this._name = name; + } + toString() { + return `Api(${this.type}): ${this._name}`; + } +} diff --git a/tools/system-analyzer/log/ic.mjs b/tools/system-analyzer/log/ic.mjs index 75eba3ec4e..8d26061892 100644 --- a/tools/system-analyzer/log/ic.mjs +++ b/tools/system-analyzer/log/ic.mjs @@ -14,7 +14,7 @@ export class IcLogEntry extends LogEntry { } else if (this.type.indexOf('Load') !== -1) { this.category = 'Load'; } - let parts = fn_file.split(' '); + const parts = fn_file.split(' '); this.functionName = parts[0]; this.file = parts[1]; let position = line + ':' + column; diff --git a/tools/system-analyzer/log/log.mjs b/tools/system-analyzer/log/log.mjs index c965124b39..16eecca8b7 100644 --- a/tools/system-analyzer/log/log.mjs +++ b/tools/system-analyzer/log/log.mjs @@ -3,19 +3,20 @@ // found in the LICENSE file. export class LogEntry { - _time; - _type; constructor(type, time) { - // TODO(zcankara) remove type and add empty getters to override this._time = time; this._type = type; + this.sourcePosition = undefined; } + get time() { return this._time; } + get type() { return this._type; } + // Returns an Array of all possible #type values. static get allTypes() { throw new Error('Not implemented.'); diff --git a/tools/system-analyzer/log/map.mjs b/tools/system-analyzer/log/map.mjs index 8a88328d92..3ad5832c74 100644 --- a/tools/system-analyzer/log/map.mjs +++ b/tools/system-analyzer/log/map.mjs @@ -34,17 +34,6 @@ define(Array.prototype, 'last', function() { // Map Log Events class MapLogEntry extends LogEntry { - id = -1; - edge = undefined; - children = []; - depth = 0; - _isDeprecated = false; - deprecatedTargets = null; - leftId = 0; - rightId = 0; - filePosition = ''; - script = ''; - description = ''; constructor(id, time) { if (!time) throw new Error('Invalid time'); // Use MapLogEntry.type getter instead of property, since we only know the @@ -52,6 +41,17 @@ class MapLogEntry extends LogEntry { super(undefined, time); this.id = id; MapLogEntry.set(id, this); + this.id = -1; + this.edge = undefined; + this.children = []; + this.depth = 0; + this._isDeprecated = false; + this.deprecatedTargets = null; + this.leftId = 0; + this.rightId = 0; + this.filePosition = ''; + this.script = ''; + this.description = ''; } toString() { diff --git a/tools/system-analyzer/processor.mjs b/tools/system-analyzer/processor.mjs index b783837650..2ec49449cb 100644 --- a/tools/system-analyzer/processor.mjs +++ b/tools/system-analyzer/processor.mjs @@ -5,6 +5,7 @@ import {LogReader, parseString, parseVarArgs} from '../logreader.mjs'; import {Profile} from '../profile.mjs'; +import {ApiLogEntry} from './log/api.mjs'; import {CodeLogEntry, DeoptLogEntry} from './log/code.mjs'; import {IcLogEntry} from './log/ic.mjs'; import {Edge, MapLogEntry} from './log/map.mjs'; @@ -18,7 +19,10 @@ export class Processor extends LogReader { _icTimeline = new Timeline(); _deoptTimeline = new Timeline(); _codeTimeline = new Timeline(); + _apiTimeline = new Timeline(); _formatPCRegexp = /(.*):[0-9]+:[0-9]+$/; + _lastTimestamp = 0; + _lastCodeLogEntry; MAJOR_VERSION = 7; MINOR_VERSION = 6; constructor(logString) { @@ -115,6 +119,10 @@ export class Processor extends LogReader { parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'StoreInArrayLiteralIC') }, + 'api': { + parsers: [parseString, parseVarArgs], + processor: this.processApiEvent + }, }; if (logString) this.processString(logString); } @@ -182,7 +190,21 @@ export class Processor extends LogReader { }); } + processV8Version(majorVersion, minorVersion) { + if ((majorVersion == this.MAJOR_VERSION && + minorVersion <= this.MINOR_VERSION) || + (majorVersion < this.MAJOR_VERSION)) { + window.alert( + `Unsupported version ${majorVersion}.${minorVersion}. \n` + + `Please use the matching tool for given the V8 version.`); + } + } + processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) { + this._lastTimestamp = timestamp; + if (timestamp == 5724567) { + console.log(start); + } let entry; let stateName = ''; if (maybe_func.length) { @@ -194,26 +216,20 @@ export class Processor extends LogReader { } else { entry = this._profile.addCode(type, name, timestamp, start, size); } - this._codeTimeline.push( - new CodeLogEntry(type + stateName, timestamp, kind, entry)); + this._lastCodeLogEntry = + new CodeLogEntry(type + stateName, timestamp, kind, entry); + this._codeTimeline.push(this._lastCodeLogEntry); } processCodeDeopt( timestamp, codeSize, instructionStart, inliningId, scriptOffset, deoptKind, deoptLocation, deoptReason) { - this._deoptTimeline.push(new DeoptLogEntry( + this._lastTimestamp = timestamp; + const logEntry = new DeoptLogEntry( deoptKind, timestamp, deoptReason, deoptLocation, scriptOffset, - instructionStart, codeSize, inliningId)); - } - - processV8Version(majorVersion, minorVersion) { - if ((majorVersion == this.MAJOR_VERSION && - minorVersion <= this.MINOR_VERSION) || - (majorVersion < this.MAJOR_VERSION)) { - window.alert( - `Unsupported version ${majorVersion}.${minorVersion}. \n` + - `Please use the matching tool for given the V8 version.`); - } + instructionStart, codeSize, inliningId); + this._deoptTimeline.push(logEntry); + this.addSourcePosition(this._profile.findEntry(instructionStart), logEntry); } processScriptSource(scriptId, url, source) { @@ -233,11 +249,24 @@ export class Processor extends LogReader { } processCodeSourceInfo( - start, script, startPos, endPos, sourcePositions, inliningPositions, + start, scriptId, startPos, endPos, sourcePositions, inliningPositions, inlinedFunctions) { this._profile.addSourcePositions( - start, script, startPos, endPos, sourcePositions, inliningPositions, + start, scriptId, startPos, endPos, sourcePositions, inliningPositions, inlinedFunctions); + let profileEntry = this._profile.findEntry(start); + if (profileEntry !== this._lastCodeLogEntry._entry) return; + this.addSourcePosition(profileEntry, this._lastCodeLogEntry); + this._lastCodeLogEntry = undefined; + } + + addSourcePosition(profileEntry, logEntry) { + let script = this.getProfileEntryScript(profileEntry); + const parts = profileEntry.getRawName().split(':'); + if (parts.length < 3) return; + const line = parseInt(parts[parts.length - 2]); + const column = parseInt(parts[parts.length - 1]); + logEntry.sourcePosition = script.addSourcePosition(line, column, logEntry); } processCodeDisassemble(start, kind, disassemble) { @@ -247,6 +276,7 @@ export class Processor extends LogReader { processPropertyIC( type, pc, time, line, column, old_state, new_state, map, key, modifier, slow_reason) { + this._lastTimestamp = time; let profileEntry = this._profile.findEntry(pc); let fnName = this.formatProfileEntry(profileEntry); let script = this.getProfileEntryScript(profileEntry); @@ -284,6 +314,7 @@ export class Processor extends LogReader { } processMap(type, time, from, to, pc, line, column, reason, name) { + this._lastTimestamp = time; const time_ = parseInt(time); if (type === 'Deprecate') return this.deprecateMap(type, time_, from); // Skip normalized maps that were cached so we don't introduce multiple @@ -318,12 +349,14 @@ export class Processor extends LogReader { } deprecateMap(type, time, id) { + this._lastTimestamp = time; this.getMapEntry(id, time).deprecate(); } processMapCreate(time, id) { // map-create events might override existing maps if the addresses get // recycled. Hence we do not check for existing maps. + this._lastTimestamp = time; this.createMapEntry(id, time); } @@ -334,6 +367,7 @@ export class Processor extends LogReader { } createMapEntry(id, time) { + this._lastTimestamp = time; const map = new MapLogEntry(id, time); this._mapTimeline.push(map); return map; @@ -357,6 +391,16 @@ export class Processor extends LogReader { return script; } + processApiEvent(name, varArgs) { + if (varArgs.length == 0) { + varArgs = [name]; + const index = name.indexOf(':'); + if (index > 0) name = name.substr(0, index); + } + this._apiTimeline.push( + new ApiLogEntry(name, this._lastTimestamp, varArgs[0])); + } + get icTimeline() { return this._icTimeline; } @@ -373,6 +417,10 @@ export class Processor extends LogReader { return this._codeTimeline; } + get apiTimeline() { + return this._apiTimeline; + } + get scripts() { return this._profile.scripts_.filter(script => script !== undefined); } diff --git a/tools/system-analyzer/view/code-panel.mjs b/tools/system-analyzer/view/code-panel.mjs index 555acb2b39..a9135ce133 100644 --- a/tools/system-analyzer/view/code-panel.mjs +++ b/tools/system-analyzer/view/code-panel.mjs @@ -25,6 +25,8 @@ DOM.defineCustomElement( set selectedEntries(entries) { this._selectedEntries = entries; + // TODO: add code selection dropdown + this._entry = entries.first(); this.update(); } diff --git a/tools/system-analyzer/view/source-panel.mjs b/tools/system-analyzer/view/source-panel.mjs index c2fdac616a..26969a79e2 100644 --- a/tools/system-analyzer/view/source-panel.mjs +++ b/tools/system-analyzer/view/source-panel.mjs @@ -105,7 +105,7 @@ DOM.defineCustomElement('view/source-panel', const option = this.scriptDropdown.options[this.scriptDropdown.selectedIndex]; this.script = option.script; - this.selectLogEntries(this._script.entries()); + this.selectLogEntries(this._script.entries); } handleSourcePositionClick(e) { @@ -124,21 +124,7 @@ DOM.defineCustomElement('view/source-panel', } selectLogEntries(logEntries) { - let icLogEntries = []; - let mapLogEntries = []; - for (const entry of logEntries) { - if (entry instanceof MapLogEntry) { - mapLogEntries.push(entry); - } else if (entry instanceof IcLogEntry) { - icLogEntries.push(entry); - } - } - if (icLogEntries.length > 0) { - this.dispatchEvent(new SelectionEvent(icLogEntries)); - } - if (mapLogEntries.length > 0) { - this.dispatchEvent(new SelectionEvent(mapLogEntries)); - } + this.dispatchEvent(new SelectionEvent(logEntries)); } }); diff --git a/tools/system-analyzer/view/timeline-panel-template.html b/tools/system-analyzer/view/timeline-panel-template.html index 2641c71441..3eea9c68a6 100644 --- a/tools/system-analyzer/view/timeline-panel-template.html +++ b/tools/system-analyzer/view/timeline-panel-template.html @@ -4,6 +4,11 @@ found in the LICENSE file. --> +

Timeline Panel

diff --git a/tools/system-analyzer/view/timeline/timeline-track-template.html b/tools/system-analyzer/view/timeline/timeline-track-template.html index 69c1886305..3c32a0a333 100644 --- a/tools/system-analyzer/view/timeline/timeline-track-template.html +++ b/tools/system-analyzer/view/timeline/timeline-track-template.html @@ -63,36 +63,40 @@ found in the LICENSE file. --> font-size: 10px; } - #legend { + .legend { position: relative; float: right; - width: 100%; - max-width: 280px; - padding-left: 20px; - padding-top: 10px; + height: calc(200px + 12px); + overflow-y: scroll; + margin-right: -10px; + padding-right: 2px; + } + + #legendTable { + width: 280px; border-collapse: collapse; } th, td { - width: 200px; - text-align: left; - padding-bottom: 3px; + padding: 1px 3px 2px 3px; } - /* right align numbers */ - #legend td:nth-of-type(4n+3), - #legend td:nth-of-type(4n+4) { - text-align: right; - } /* Center colors */ - #legend td:nth-of-type(4n+1) { - text-align: center;; + #legendTable td:nth-of-type(4n+1) { + text-align: center; + padding-top: 3px; } - - .legendTypeColumn { + /* Left align text*/ + #legendTable td:nth-of-type(4n+2) { + text-align: left; width: 100%; } + /* right align numbers */ + #legendTable td:nth-of-type(4n+3), + #legendTable td:nth-of-type(4n+4) { + text-align: right; + } .timeline { background-color: var(--timeline-background-color); @@ -120,17 +124,18 @@ found in the LICENSE file. --> position: absolute; } - - - - - - - - - - -
TypeCountPercent
+
+ + + + + + + + + +
TypeCountPercent
+
diff --git a/tools/system-analyzer/view/timeline/timeline-track.mjs b/tools/system-analyzer/view/timeline/timeline-track.mjs index a88e3d28a2..821a09e517 100644 --- a/tools/system-analyzer/view/timeline/timeline-track.mjs +++ b/tools/system-analyzer/view/timeline/timeline-track.mjs @@ -25,7 +25,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', constructor() { super(templateText); this._selectionHandler = new SelectionHandler(this); - this._legend = new Legend(this.$('#legend')); + this._legend = new Legend(this.$('#legendTable')); this._legend.onFilter = (type) => this._handleFilterTimeline(); this.timelineNode.addEventListener( 'scroll', e => this._handleTimelineScroll(e));