Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/_static/preview/assets/index-Cql2ltA1.js

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions docs/_static/preview/assets/index-DhaBtkCM.js

This file was deleted.

2 changes: 1 addition & 1 deletion docs/_static/preview/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pyinstrument Demo</title>
<script type="module" crossorigin src="./assets/index-DhaBtkCM.js"></script>
<script type="module" crossorigin src="./assets/index-Cql2ltA1.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-paBu1EOJ.css">
</head>
<body>
Expand Down
18 changes: 15 additions & 3 deletions html_renderer/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,20 @@
link.href = `https://fonts.googleapis.com/css?family=Source+Code+Pro:400,600|Source+Sans+Pro:400,600&display=swap`;
document.head.appendChild(link);

const rootFrame = session.rootFrame;
const duration = rootFrame?.time.toLocaleString(undefined, {maximumSignificantDigits: 3});
const rootFrames = session.rootFrames;
let maxTime = 0;
if (rootFrames != null) {
for (const thread_id of Object.keys(rootFrames)) {
const rootFrame = rootFrames[thread_id];
if (rootFrame != null) {
const t = rootFrame.time;
if (t > maxTime) {
maxTime = t;
}
}
}
}
const duration = maxTime.toLocaleString(undefined, {maximumSignificantDigits: 3});
let name
// let name = rootFrame?.function;
// if (name == '<module>') {
Expand All @@ -53,7 +65,7 @@
<Header session={session} />
</div>
<div class="body">
{#if !session.rootFrame}
{#if !session.rootFrames}
<div class="margins">
<div class="spacer" style="height: 20px;"></div>
<div class="error">
Expand Down
22 changes: 18 additions & 4 deletions html_renderer/src/components/CallStackView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,35 @@
}
});

let rootFrame: Frame|null
$: rootFrame = applyProcessors(session.rootFrame.cloneDeep(), $config.processors, $config.options)
let rootFrames: Frame[]|null
let clonedFrames: Frame[]|null = []
for (const thread_id of Object.keys(session.rootFrames)) {
clonedFrames[thread_id] = session.rootFrames[thread_id].cloneDeep()
}
$: rootFrames = applyProcessors(clonedFrames, $config.processors, $config.options)
</script>

<div class="call-stack-view" bind:this={element}>
<div class="scroll-inner" bind:this={scrollInnerElement}>
{#if !rootFrame}
{#if !rootFrames}
<div class="margins">
<div class="error">
All frames were filtered out.
</div>
</div>
{:else}
<div class="call-stack-margins">
<FrameView frame={rootFrame} rootFrame={rootFrame} />
{#each Object.entries(rootFrames) as [threadId, rootFrame]}
{#if !rootFrame}
<div class="margins">
<div class="error">
All frames were filtered out.
</div>
</div>
{:else}
<FrameView frame={rootFrame} rootFrame={rootFrame} />
{/if}
{/each}
</div>
{/if}
</div>
Expand Down
91 changes: 59 additions & 32 deletions html_renderer/src/components/TimelineCanvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ export interface TimelineFrame {
className: string
filePathShort: string | null
}

interface TimelineFrameMap {
[thread_id: string]: TimelineFrame[]
}

export default class TimelineCanvasView extends CanvasView {
zoom: number = 1 // pixels per second
startT: number = 0 // seconds
yOffset: number = 0 // pixels
frames: TimelineFrame[] = []
frames: TimelineFrameMap = {} // first index is per thread_id
isZoomedIn: boolean = false

tooltipContainer: HTMLElement
Expand Down Expand Up @@ -75,32 +80,42 @@ export default class TimelineCanvasView extends CanvasView {
super.destroy()
}

_rootFrame: Frame | null = null
maxDepth = 0
setRootFrame(rootFrame: Frame) {
this._rootFrame = rootFrame
this.frames = []
_rootFrames: Frame[] | null = null
maxDepth: int[] = []
maxY: int = 0
setRootFrames(rootFrames: Frame[]) {
this._rootFrames = rootFrames
this.frames = {}
this._frameMaxT = undefined
this.maxDepth = 0
this._collectFrames(rootFrame, 0)
this.maxDepth = []
this.maxY = 0
for (const thread_id of Object.keys(rootFrames)) {
let rootFrame = rootFrames[thread_id]
this._collectFrames(rootFrame, thread_id, 0)
this.maxY += this.maxDepth[thread_id] + 1
}
this.fitContents()
this.setNeedsRedraw()
}

_collectFrames(frame: Frame, depth: number) {
this.frames.push({
_collectFrames(frame: Frame, thread_id: string, depth: number) {
if (!this.frames.hasOwnProperty(thread_id)) {
this.frames[thread_id] = []
this.maxDepth[thread_id] = 0
}
this.frames[thread_id].push({
frame,
depth,
isApplicationCode: frame.isApplicationCode,
library: frame.library,
className: frame.className,
filePathShort: frame.filePathShort,
})
this.maxDepth = Math.max(this.maxDepth, depth)
this.maxDepth[thread_id] = Math.max(this.maxDepth[thread_id], depth)
for (const child of frame.children) {
if (child.identifier !== SELF_TIME_FRAME_IDENTIFIER) {
// we don't render self time frames
this._collectFrames(child, depth + 1)
this._collectFrames(child, thread_id, depth + 1)
}
}
}
Expand All @@ -114,7 +129,7 @@ export default class TimelineCanvasView extends CanvasView {
name: this.frameName(timelineFrame),
time: timelineFrame.frame.time,
selfTime: this.frameSelfTime(timelineFrame),
totalTime: this._rootFrame?.time ?? 1e-12,
totalTime: this._rootFrames[timelineFrame.frame.thread_id]?.time ?? 1e-12,
location: `${timelineFrame.filePathShort}:${timelineFrame.frame.lineNo}`,
locationColor: this.colorForFrame(timelineFrame),
}
Expand Down Expand Up @@ -197,8 +212,12 @@ export default class TimelineCanvasView extends CanvasView {
this.drawAxes(ctx)

// draw frames
for (const frame of this.frames) {
this.drawFrame(ctx, frame)
let depthOffset = 0
for (const thread_id of Object.keys(this.frames)) {
for (const frame of this.frames[thread_id]) {
this.drawFrame(ctx, frame, depthOffset)
}
depthOffset += 1 + this.maxDepth[thread_id]
}

ctx.globalAlpha = 1
Expand Down Expand Up @@ -286,8 +305,8 @@ export default class TimelineCanvasView extends CanvasView {
}
}

drawFrame(ctx: CanvasRenderingContext2D, timelineFrame: TimelineFrame) {
const { x, y, w, h } = this.frameDims(timelineFrame)
drawFrame(ctx: CanvasRenderingContext2D, timelineFrame: TimelineFrame, depthOffset: int) {
const { x, y, w, h } = this.frameDims(timelineFrame, depthOffset)
const endX = x + w
if (endX < 0 || x > this.width) {
// offscreen
Expand Down Expand Up @@ -340,10 +359,12 @@ export default class TimelineCanvasView extends CanvasView {
_assignLibraryOrder() {
const librariesTotalTime: Record<string, number> = {}

for (const timelineFrame of this.frames) {
const frame = timelineFrame.frame
const library = frame.library ?? ''
librariesTotalTime[library] = (librariesTotalTime[library] || 0) + timelineFrame.frame.time
for (const thread_id of Object.keys(this.frames)) {
for (const timelineFrame of this.frames[thread_id]) {
const frame = timelineFrame.frame
const library = frame.library ?? ''
librariesTotalTime[library] = (librariesTotalTime[library] || 0) + timelineFrame.frame.time
}
}

const libraries = Object.keys(librariesTotalTime)
Expand Down Expand Up @@ -405,13 +426,17 @@ export default class TimelineCanvasView extends CanvasView {
_frameMaxT: number|undefined
get frameMaxT() {
if (this._frameMaxT === undefined) {
this._frameMaxT = this.frames.reduce((max, frame) => Math.max(max, frame.frame.startTime + frame.frame.time), 0)
this._frameMaxT = 0
for (const thread_id of Object.keys(this.frames)) {
this._frameMaxT = Math.max(this._frameMaxT,
this.frames[thread_id].reduce((max, frame) => Math.max(max, frame.frame.startTime + frame.frame.time), 0))
}
}
return this._frameMaxT
}

get maxYOffset() {
return Math.max(0, (this.maxDepth+1) * FRAME_PITCH + Y_MARGIN*2 + Y_FRAME_INSET - this.height)
return Math.max(0, (this.maxY+1) * FRAME_PITCH + Y_MARGIN*2 + Y_FRAME_INSET - this.height)
}

get minZoom() {
Expand Down Expand Up @@ -450,16 +475,16 @@ export default class TimelineCanvasView extends CanvasView {
this.startT = maxStartT
}

if (this.yOffset < 0) {
this.yOffset = 0
}
//if (this.yOffset < 0) {
// this.yOffset = 0
//}
if (this.yOffset > this.maxYOffset) {
this.yOffset = this.maxYOffset
}
}

frameDims(timelineFrame: TimelineFrame): { x: number; y: number; w: number; h: number } {
const y = timelineFrame.depth * FRAME_PITCH + Y_MARGIN + Y_FRAME_INSET - this.yOffset
frameDims(timelineFrame: TimelineFrame, depthOffset: int): { x: number; y: number; w: number; h: number } {
const y = (depthOffset + timelineFrame.depth) * FRAME_PITCH + Y_MARGIN + Y_FRAME_INSET - this.yOffset
const h = FRAME_HEIGHT
let x = this.xForT(timelineFrame.frame.startTime)
const endX = this.xForT(timelineFrame.frame.startTime + timelineFrame.frame.time)
Expand Down Expand Up @@ -507,10 +532,12 @@ export default class TimelineCanvasView extends CanvasView {
}

hitTest(loc: {x: number, y: number}): TimelineFrame | null {
for (const frame of this.frames) {
const { x: frameX, y: frameY, w, h } = this.frameDims(frame)
if (loc.x >= frameX && loc.x <= frameX + w && loc.y >= frameY && loc.y <= frameY + h) {
return frame
for (const thread_id of Object.keys(this.frames)) {
for (const frame of this.frames[thread_id]) {
const { x: frameX, y: frameY, w, h } = this.frameDims(frame)
if (loc.x >= frameX && loc.x <= frameX + w && loc.y >= frameY && loc.y <= frameY + h) {
return frame
}
}
}
return null
Expand Down
12 changes: 8 additions & 4 deletions html_renderer/src/components/TimelineView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
return {processors, options}
})

let rootFrame: Frame|null
$: rootFrame = applyProcessors(session.rootFrame.cloneDeep(), $config.processors, $config.options)
let rootFrames: Frame[]|null
let clonedFrames: Frame[]|null = []
for (const thread_id of Object.keys(session.rootFrames)) {
clonedFrames[thread_id] = session.rootFrames[thread_id].cloneDeep()
}
$: rootFrames = applyProcessors(clonedFrames, $config.processors, $config.options)

let rootElement: HTMLDivElement|null = null
let timelineCanvasView: TimelineCanvasView|null = null
Expand All @@ -32,8 +36,8 @@
timelineCanvasView?.destroy()
})

$: if (rootFrame && timelineCanvasView) {
timelineCanvasView.setRootFrame(rootFrame)
$: if (rootFrames && timelineCanvasView) {
timelineCanvasView.setRootFrames(rootFrames)
}
</script>

Expand Down
4 changes: 3 additions & 1 deletion html_renderer/src/lib/dataTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface SessionData {
session: {
start_time: number;
thread_start_times: {[thread_id: string] : number};
duration: number;
min_interval: number;
max_interval: number;
Expand All @@ -11,10 +12,11 @@ export interface SessionData {
sys_path: string;
sys_prefixes: string[];
};
frame_tree: FrameData;
frame_trees: FrameData[];
}

export interface FrameData {
thread_id: string;
identifier: string;
time: number;
attributes: {[name: string]: number};
Expand Down
3 changes: 2 additions & 1 deletion html_renderer/src/lib/model/Frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default class Frame {
) {
this.identifier = data.identifier
this._identifierParts = this.identifier.split(IDENTIFIER_SEP)
this.startTime = data.startTime ?? 0
this.startTime = data.startTime ?? context.threadStartTime(data.thread_id)
this.time = data.time ?? 0
this.attributes = data.attributes ?? {}
this.context = context
Expand Down Expand Up @@ -233,4 +233,5 @@ export default class Frame {
interface FrameContext {
shortenPath(path: string): string
sysPrefixes: string[]
threadStartTime(thread_id: string): number
}
16 changes: 14 additions & 2 deletions html_renderer/src/lib/model/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import Frame from "./Frame";

export default class Session {
startTime: number;
threadStartTimes: {[thread_id: string]: number};
duration: number;
minInterval: number;
maxInterval: number;
sampleCount: number;
target_description: string;
cpuTime: number;
rootFrame: Frame;
rootFrames: {[thread_id: string]: Frame};
sysPath: string;
sysPrefixes: string[];

constructor(data: SessionData) {
this.startTime = data.session.start_time;
this.threadStartTimes = data.session.thread_start_times;
this.duration = data.session.duration;
this.minInterval = data.session.min_interval;
this.maxInterval = data.session.max_interval;
Expand All @@ -23,7 +25,10 @@ export default class Session {
this.cpuTime = data.session.cpu_time;
this.sysPath = data.session.sys_path;
this.sysPrefixes = data.session.sys_prefixes
this.rootFrame = new Frame(data.frame_tree, this)
this.rootFrames = {}
for (const thread_id of Object.keys(data.frame_trees)) {
this.rootFrames[thread_id] = new Frame(data.frame_trees[thread_id], this)
}
}

_shortenPathCache: {[path: string]: string} = {}
Expand All @@ -47,6 +52,13 @@ export default class Session {
this._shortenPathCache[path] = result
return result
}

threadStartTime(thread_id: string): number {
if (this.threadStartTimes.hasOwnProperty(thread_id)) {
return this.threadStartTimes[thread_id]
}
return 0
}
}

function pathSplit(path: string): string[] {
Expand Down
Loading