Skip to content

Commit

Permalink
Use DOM elements as highlights to make large recordings faster
Browse files Browse the repository at this point in the history
  • Loading branch information
laffra committed Jul 27, 2023
1 parent 7776b14 commit d4f7871
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 31 deletions.
17 changes: 12 additions & 5 deletions dashboard/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def mousedown(self, event):
from dashboard.dialog import dialog
self.dragX = event.originalEvent.pageX
self.dragY = event.originalEvent.pageY
self.hideHighlights()

def isDragging(self):
return self.dragX != 0
Expand Down Expand Up @@ -80,14 +81,19 @@ def mouseleave(self, event):
self.dragY = 0

def mouseup(self, event):
self.dragX = 0
self.dragY = 0
if self.offsetY > 0:
if self.isDragging():
self.dragX = 0
self.dragY = 0
elif self.offsetY > 0:
self.offsetY = 0
self.redraw()

def hideHighlights(self):
js.jQuery(".highlight").css("left", 10000)

def mousewheel(self, event):
event.preventDefault()
self.hideHighlights()
x = event.originalEvent.offsetX
y = event.originalEvent.offsetY
self.zoom(x, y, 2 if event.originalEvent.wheelDelta > 0 else 0.5, event)
Expand Down Expand Up @@ -128,14 +134,15 @@ def width(self, width=0):
def height(self, height=0):
return self.canvas.attr("height", height) if height else float(self.canvas.attr("height") or 0)

@profiler.profile("Canvas.toScreenX")
def toScreenX(self, x):
return x * self.scaleX + self.offsetX

def toScreenY(self, y):
return y * self.scaleY + self.offsetY

def fromScreenX(self, x):
return x - self.offsetX / self.scaleX

@profiler.profile("Canvas.toScreenDimension")
def toScreenDimension(self, w):
return w * self.scaleX

Expand Down
76 changes: 56 additions & 20 deletions dashboard/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,27 @@ def __init__(self, flameElementId, timelineElementId):
self.markers = []
self.design = Design([])
self.tips = Tips([])
self.flameCanvas = self.createCanvas(self.flameElementId, self.clickFlame, self.dragFlame, self.zoomFlame, self.flameMousemove, fixedScaleY=True)
self.timelineCanvas = self.createCanvas(self.timelineElementId, self.clickTimeline, self.dragTimeline, self.zoomTimeline, self.timelineMousemove, fixedY=True, fixedScaleY=True)
self.flameCanvas = self.createCanvas(self.flameElementId, self.drawFlame, self.clickFlame, self.dragFlame, self.zoomFlame, self.flameMousemove, fixedScaleY=True)
self.timelineCanvas = self.createCanvas(self.timelineElementId, self.drawTimeline, self.clickTimeline, self.dragTimeline, self.zoomTimeline, self.timelineMousemove, fixedY=True, fixedScaleY=True)
js.jQuery(".tabs").on("tabsactivate", pyodide.ffi.create_proxy(lambda event, ui: self.activateTab(event, ui)))
js.jQuery("#filter").val(getFilterFromUrl())

def dragFlame(self, dx, dy):
self.timelineCanvas.drag(dx, dy)
CallView.positionThreads(self.flameCanvas)
self.drawTimeline()

def zoomFlame(self, x, y, scale):
self.timelineCanvas.zoom(x, y, scale)
self.drawTimeline()

def dragTimeline(self, dx, dy):
self.flameCanvas.drag(dx, dy)
self.drawFlame()

def zoomTimeline(self, x, y, scale):
self.flameCanvas.zoom(x, y, scale)
self.drawFlame()

def resize(self, width, height):
timelineHeight = config.TIMELINE_OFFSET_Y + config.TIMELINE_HEIGHT
Expand All @@ -70,8 +74,8 @@ def resize(self, width, height):
self.flameCanvas.height(height - timelineHeight)
self.redraw()

def createCanvas(self, elementId, click, drag, zoom, mousemove, fixedY=False, fixedScaleY=False):
return (canvas.Canvas(elementId, self.redraw, drag, zoom, minOffsetX=48, minOffsetY=0, fixedY=fixedY, fixedScaleY=fixedScaleY)
def createCanvas(self, elementId, redraw, click, drag, zoom, mousemove, fixedY=False, fixedScaleY=False):
return (canvas.Canvas(elementId, redraw, drag, zoom, minOffsetX=48, minOffsetY=0, fixedY=fixedY, fixedScaleY=fixedScaleY)
.on("mousemove", mousemove)
.on("click", click))

Expand All @@ -82,6 +86,10 @@ def activateTab(self, event, ui):
def reset(self):
self.timelineCanvas.reset()
self.flameCanvas.reset()
self.hover = None
CallView.reset()
StatusView.reset()
MarkerView.reset()

@profiler.profile("Flamegraph.convertLog")
def convertLog(self, log):
Expand Down Expand Up @@ -114,8 +122,9 @@ def load(self, log):
@profiler.report("Redrawing the whole UI.")
def redraw(self, event=None):
if self.currentTab == "Timeline":
self.draw()
debug("Draw", profiler.getTime("Flamegraph.draw"))
self.drawTimeline()
self.drawFlame()
debug("Draw Flamegraph", profiler.getTime("Flamegraph.draw"))
elif self.currentTab == "Design":
js.jQuery("#debug").html("")
self.design = Design(self.calls)
Expand Down Expand Up @@ -147,25 +156,55 @@ def fixNewline(s):
</tr>
"""

@profiler.profile("Flamegraph.draw")
def draw(self):
self.flameCanvas.clear("#DDD")
self.timelineCanvas.clear("#DDD")
def loading(self, name):
self.clear()
self.timelineCanvas.text(-20, 20, f"Loading {name}...", color="#BBB", font="16px Arial")

def clear(self, canvas=None):
if canvas:
canvas.clear("#222")
else:
self.flameCanvas.clear("#222")
self.timelineCanvas.clear("#222")
dialog.hide()
js.jQuery(".highlight").css("left", 10000)

@profiler.report("Flamegraph.drawTimeline")
def drawTimeline(self, event=None):
self.clear(self.timelineCanvas)
self.hover = None
self.drawStatuses()
self.drawMarkers()
self.timeline.draw(self.timelineCanvas)

@profiler.profile("Flamegraph.drawStatuses")
def drawStatuses(self):
canvasScaleX = self.flameCanvas.scaleX
canvasOffsetX = self.flameCanvas.offsetX
canvasWidth = self.flameCanvas.width()
StatusView.drawAll(self.timelineCanvas, [
view for view in self.sampleStatuses()
if not view.offscreen(canvasScaleX, canvasOffsetX, canvasWidth)
])
self.timeline.draw(self.timelineCanvas)
CallView.drawAll(self.flameCanvas, [
view for view in self.calls

@profiler.profile("Flamegraph.drawMarkers")
def drawMarkers(self):
canvasScaleX = self.flameCanvas.scaleX
canvasOffsetX = self.flameCanvas.offsetX
canvasWidth = self.flameCanvas.width()
MarkerView.drawAll(self.timelineCanvas, [
view for view in self.markers
if not view.offscreen(canvasScaleX, canvasOffsetX, canvasWidth)
])
MarkerView.drawAll(self.timelineCanvas, [
view for view in self.markers

@profiler.report("Flamegraph.drawFlame")
def drawFlame(self, event=None):
self.clear(self.flameCanvas)
canvasScaleX = self.flameCanvas.scaleX
canvasOffsetX = self.flameCanvas.offsetX
canvasWidth = self.flameCanvas.width()
CallView.drawAll(self.flameCanvas, [
view for view in self.calls
if not view.offscreen(canvasScaleX, canvasOffsetX, canvasWidth)
])

Expand All @@ -180,7 +219,6 @@ def sampleStatuses(self):
statuses.append(self.statuses[-1])
return statuses


def flameMousemove(self, event):
self.mousemoveCanvas(self.flameCanvas, self.calls, event)

Expand Down Expand Up @@ -230,10 +268,7 @@ def setUrl(log=None):


def showLog(log):
dialog.hide()
CallView.reset()
StatusView.reset()
MarkerView.reset()
flamegraph.clear()
flamegraph.reset()
loadLog(log)
setUrl(log)
Expand All @@ -242,6 +277,7 @@ def showLog(log):


def loadLog(name):
flamegraph.loading(name)
url = f"zip/{name}"
js.jQuery.get(url, pyodide.ffi.create_proxy(lambda data, status, xhr: showFlamegraph(data)))

Expand Down
27 changes: 24 additions & 3 deletions dashboard/views/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,33 @@ def highlightCall(self, link):
def mouseenter(self, x, y):
View.mouseenter(self, x, y)
if self.threadId in self.showThreads and self.canvas.toScreenDimension(self.w) > self.minWidth:
self.canvas.rect(self.x, self.y, self.w, self.h, 2, "red")
(js.jQuery("#call-highlight-top")
.css("left", self.canvas.toScreenX(self.x))
.css("top", self.canvas.toScreenY(self.y) + config.FLAME_OFFSET_Y + config.TIMELINE_HEIGHT + 2)
.css("width", self.canvas.toScreenDimension(self.w))
.css("height", 0)
.appendTo(self.canvas.canvas.parent()))
(js.jQuery("#call-highlight-bottom")
.css("left", self.canvas.toScreenX(self.x))
.css("top", self.canvas.toScreenY(self.y) + config.FLAME_OFFSET_Y + config.TIMELINE_HEIGHT + config.LINE_HEIGHT + 2)
.css("width", self.canvas.toScreenDimension(self.w))
.css("height", 0)
.appendTo(self.canvas.canvas.parent()))
(js.jQuery("#call-highlight-left")
.css("left", self.canvas.toScreenX(self.x))
.css("top", self.canvas.toScreenY(self.y) + config.FLAME_OFFSET_Y + config.TIMELINE_HEIGHT + 2)
.css("width", 0)
.css("height", config.LINE_HEIGHT)
.appendTo(self.canvas.canvas.parent()))
(js.jQuery("#call-highlight-right")
.css("left", self.canvas.toScreenX(self.x) + self.canvas.toScreenDimension(self.w))
.css("top", self.canvas.toScreenY(self.y) + config.FLAME_OFFSET_Y + config.TIMELINE_HEIGHT + 2)
.css("width", 0)
.css("height", config.LINE_HEIGHT)
.appendTo(self.canvas.canvas.parent()))

def mouseleave(self, x, y):
View.mouseleave(self, x, y)
if self.threadId in self.showThreads and self.canvas.toScreenDimension(self.w) > self.minWidth:
self.canvas.redraw()

def getCpu(self):
stats = [
Expand Down
8 changes: 5 additions & 3 deletions dashboard/views/marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ def mouseenter(self, x, y):
self.select()

def select(self):
self.canvas.redraw()
self.canvas.rect(self.x, self.y, self.w, self.h, color="white")
(js.jQuery("#marker-highlight")
.css("left", self.canvas.toScreenX(self.x))
.css("top", self.canvas.toScreenY(self.y) + 42)
.click(pyodide.ffi.create_proxy(lambda event: self.click(0, 0)))
.appendTo(self.canvas.canvas.parent()))

def mouseleave(self, x, y):
View.mouseleave(self, x, y)
self.canvas.redraw()

def formatStack(self, full=True):
def shortFile(filename):
Expand Down
21 changes: 21 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@
left: 0;
}

#marker-highlight {
width: 36px;
height: 36px;
position: absolute;
background-color: transparent;
border: 1px solid white;
}

#call-highlight-top, #call-highlight-bottom, #call-highlight-left, #call-highlight-right {
position: absolute;
background-color: transparent;
border: 2px solid red;
width: 1px;
height: 1px;
}

.loading {
width: 16px;
height: 16px;
Expand Down Expand Up @@ -516,6 +532,11 @@
<img id="marker-error" width=32 height=32
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAApVBMVEVHcEy5u7vAxcXo6eni5uaAgIApKSkODQ25vb307e2+wcHy7u6ztLSpqqr27e0AAADm6Ojw7OwAAAAAAAAAAAAAAAAAAAD/AAD/Fhb/kpL/Bwf/AwP/NDT/Cwv/aGj91tb/6en/dnb/fHz/8PD90ND/gID/v7//4uL/Ozv/QUH/ISH/Kyv/trb/n5/07Oz/ZWX/iIj/SUn/WFj929v/UlL/h4f/iYmyi9arAAAAF3RSTlMAjIzq/SkMB4H8lfp7Zv4h6vczWmE7ZyvsseAAAAFLSURBVDjLfZPpdoMgEEZNF83SNGm6jLhiiSyKsRrT93+0YpMmiNjvB8fDvcIwqONc4s4etMw8x4g3X1Wf11QvT96InwK4JkaGYfCRofjXgBuGhQ8MK9eMCX4z1qtvK/811kq4S32YSHGc98JeCQUvII5UYijE+VHN+umfIFLCkogRgoVIcSoiTAgtNaHaiyTxG7nvCko5lQ3Ja4w04cSyKIAQUShxCy2rMQcpw5vgI0ZQABUNuUKCZThPBiuoejIc9UKJu34FYtTQIpGpVysaxFSWkjY4GZ6iOxKCYkAygI5h1nDG+z5oWxSNKhLKKASI2hJi7muCpZPhefR/O7k+ZKG900G+6u/CXT7bjSBfzFxn2uj55YuxGhq3GqHOLYbBR8aI98YB1cklNVqM/z13ub2/Zns+n2E8avGcibib3Wbj/JPd28fr+2DmB1EMSVxgvTPGAAAAAElFTkSuQmCC">
</div>
<div id="marker-highlight" class="highlight"></div>
<div id="call-highlight-left" class="highlight"></div>
<div id="call-highlight-top" class="highlight"></div>
<div id="call-highlight-bottom" class="highlight"></div>
<div id="call-highlight-right" class="highlight"></div>
<a id="logo" class="logo" target=_blank href=https://github.com/micrologai/microlog>
Microlog
<img src="microlog/images/icons/microlog.png"></img>
Expand Down

0 comments on commit d4f7871

Please sign in to comment.