Created
November 5, 2019 21:32
-
-
Save yalue/c10ce129097d621173afd4c096fa9e22 to your computer and use it in GitHub Desktop.
A modified version of view_blocksbysm.py that takes a --min-sm and --max-sm command-line argument.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This script reads all JSON result files and uses matplotlib to display a | |
# timeline indicating when blocks and threads from multiple jobs were run on | |
# GPU, including which SM they ran on. For this to work, all result filenames | |
# must end in .json. | |
# | |
# Usage: python view_blocksbysm.py [results directory (default: ./results)] | |
import argparse | |
import glob | |
import json | |
import math | |
import re | |
import sys | |
from graphics import * | |
# Using threads vs shared memory | |
SM_THREADS = 2048 | |
SM_SHARED_MEM = 65536 | |
#Y_VAL_SOURCE = "sharedmem" | |
Y_VAL_SOURCE = "threads" | |
if Y_VAL_SOURCE == "threads": | |
MAX_YVAL = float(SM_THREADS) | |
elif Y_VAL_SOURCE == "sharedmem": | |
MAX_YVAL = float(SM_SHARED_MEM) | |
else: | |
# invalid! | |
MAX_YVAL = 0 | |
################################################### | |
# Drawing # | |
################################################### | |
# Colors: http://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm | |
idToColorMap = {0: 'azure', | |
1: 'light pink', | |
2: 'LightGoldenrod2', | |
3: 'DarkSeaGreen1', | |
4: 'MediumPurple1', | |
5: 'light gray', | |
6: 'orange', | |
7: 'gray32', | |
8: 'turquoise3', | |
9: 'light pink', | |
10: 'light blue', | |
11: 'LightGoldenrod2', | |
12: 'light sea green', | |
13: 'MediumPurple1', | |
14: 'gray68', | |
15: 'orange', | |
16: 'gray32', | |
17: 'turquoise3', | |
18: 'light pink', | |
19: 'light blue', | |
20: 'LightGoldenrod2', | |
21: 'brown', | |
22: 'DarkOliveGreen', | |
23: 'deep pink', | |
24: 'gold1', | |
25: 'gold4'} | |
patternColorToBgColorMap = {"light pink": "misty rose", | |
"azure": "sky blue", | |
"LightGoldenrod2": "light yellow", | |
"DarkSeaGreen1": "SeaGreen3", | |
"MediumPurple1": "lavender", | |
"light gray": "gray68", | |
"orange": "navajo white", | |
"gray32": "gray48", | |
"turquoise3": "turquoise1", | |
"brown": "beige", | |
"DarkOliveGreen": "light gray", | |
"deep pink": "light salmon", | |
"gold1": "light yellow", | |
"gold4": "light slate gray", | |
} | |
patternColorToArrowColorMap = {"light pink": "IndianRed3", | |
"azure": "SteelBlue2", | |
"LightGoldenrod2": "LightGoldenrod3", | |
"DarkSeaGreen1": "SpringGreen4", | |
"MediumPurple1": "MediumPurple1", | |
"light gray": "gray68", | |
"orange": "dark orange", | |
"gray32": "gray32", | |
"turquoise3": "turquoise3", | |
"brown": "brown", | |
"DarkOliveGreen": "DarkOliveGreen", | |
"deep pink": "deep pink", | |
"gold1": "gold1", | |
"gold4": "gold4"} | |
BUFFER_TOP = 32 | |
BUFFER_BOTTOM = 68 | |
BUFFER_LEFT = 52 | |
BUFFER_RIGHT = 8 | |
LEGEND_HEIGHT_BASE = 32 # pixels per row | |
BUFFER_LEGEND = 4 | |
LEGEND_BOX_SIZE = 20 | |
USE_PATTERNS = True | |
USE_BOLD_FONT = True | |
LINE_WIDTH = 2 | |
ARROW_WIDTH = 2 | |
class Pattern(object): | |
def __init__(self): | |
self.objs = [] | |
def draw(self, canvas): | |
for obj in self.objs: | |
obj.draw(canvas) | |
class HorizontalLinePattern(Pattern): | |
LINE_SPACING = 10 | |
def __init__(self, rect, color, numLines = None): | |
Pattern.__init__(self) | |
p1x = rect.getP1().x | |
p2x = rect.getP2().x | |
ymin = min(rect.getP1().y, rect.getP2().y) | |
ymax = max(rect.getP1().y, rect.getP2().y) | |
h = ymax - ymin | |
if numLines == None: | |
numLines = int(h / self.LINE_SPACING) | |
dy = h / float(numLines + 1) | |
for i in range(1, numLines+1): | |
pos = ymin + dy * i | |
line = Line(Point(p1x, pos), Point(p2x, pos)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
class VerticalLinePattern(Pattern): | |
LINE_SPACING = 10 | |
def __init__(self, rect, color, numLines = None): | |
Pattern.__init__(self) | |
p1y = rect.getP1().y | |
p2y = rect.getP2().y | |
xmin = min(rect.getP1().x, rect.getP2().x) | |
xmax = max(rect.getP1().x, rect.getP2().x) | |
w = xmax - xmin | |
if numLines == None: | |
numLines = int(w / self.LINE_SPACING) | |
dx = w / float(numLines + 1) | |
for i in range(1, numLines+1): | |
pos = xmin + dx * i | |
line = Line(Point(pos, p1y), Point(pos, p2y)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
class LeftDiagonalLinePattern(Pattern): | |
LINE_SPACING = 10 | |
def __init__(self, rect, color, numLines = None): | |
Pattern.__init__(self) | |
xmin = int(min(rect.getP1().x, rect.getP2().x)) | |
xmax = int(max(rect.getP1().x, rect.getP2().x)) | |
ymin = int(min(rect.getP1().y, rect.getP2().y)) | |
ymax = int(max(rect.getP1().y, rect.getP2().y)) | |
totalDist = (ymax - ymin) + (xmax - xmin) | |
if numLines == None: | |
numLines = int(totalDist / self.LINE_SPACING) | |
ddist = int(totalDist / float(numLines + 1)) | |
ycount = int((ymax-ymin) / ddist) | |
xcount = int((xmax-xmin) / ddist) | |
for i in range(ycount+1): | |
x1 = xmin | |
y1 = ymin + ddist * i | |
y2 = ymax | |
x2 = (y2 - y1) + x1 | |
if x2 > xmax: | |
y2 -= x2 - xmax | |
x2 = xmax | |
line = Line(Point(x1, y1), Point(x2, y2)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
for i in range(1, xcount+1): | |
x1 = xmin + ddist * i | |
y1 = ymin | |
x2 = (ymax - y1) + x1 | |
y2 = ymax | |
if x2 > xmax: | |
y2 -= x2 - xmax | |
x2 = xmax | |
line = Line(Point(x1, y1), Point(x2, y2)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
class RightDiagonalLinePattern(Pattern): | |
LINE_SPACING = 10 | |
def __init__(self, rect, color, numLines = None): | |
Pattern.__init__(self) | |
xmin = int(min(rect.getP1().x, rect.getP2().x)) | |
xmax = int(max(rect.getP1().x, rect.getP2().x)) | |
ymin = int(min(rect.getP1().y, rect.getP2().y)) | |
ymax = int(max(rect.getP1().y, rect.getP2().y)) | |
totalDist = (ymax - ymin) + (xmax - xmin) | |
if numLines == None: | |
numLines = int(totalDist / self.LINE_SPACING) | |
ddist = int(totalDist / float(numLines + 1)) | |
ycount = int((ymax-ymin) / ddist) | |
xcount = int((xmax-xmin) / ddist) | |
for i in range(ycount+1): | |
x2 = xmax | |
y2 = ymin + ddist * i + 1 | |
y1 = ymax | |
x1 = xmax - (ymax - y2) | |
if x1 < xmin: | |
y1 -= xmin - x1 | |
x1 = xmin | |
line = Line(Point(x1, y1), Point(x2, y2)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
for i in range(1, xcount+1): | |
x2 = xmax - ddist * i | |
y2 = ymin | |
y1 = ymax | |
x1 = x2 - (y1 - ymin) | |
if x1 < xmin: | |
y1 -= xmin - x1 | |
x1 = xmin | |
line = Line(Point(x1, y1), Point(x2, y2)) | |
line.setOutline(color) | |
line.setWidth(LINE_WIDTH) | |
self.objs.append(line) | |
idToPatternMap = {0: HorizontalLinePattern, | |
1: RightDiagonalLinePattern, | |
2: VerticalLinePattern, | |
3: LeftDiagonalLinePattern, | |
4: VerticalLinePattern, | |
5: RightDiagonalLinePattern, | |
6: LeftDiagonalLinePattern, | |
7: HorizontalLinePattern, | |
8: VerticalLinePattern, | |
9: RightDiagonalLinePattern, | |
10: LeftDiagonalLinePattern, | |
11: HorizontalLinePattern, | |
12: VerticalLinePattern, | |
13: RightDiagonalLinePattern, | |
14: LeftDiagonalLinePattern, | |
15: HorizontalLinePattern, | |
16: VerticalLinePattern, | |
17: RightDiagonalLinePattern, | |
18: LeftDiagonalLinePattern, | |
19: HorizontalLinePattern, | |
20: VerticalLinePattern, | |
21: RightDiagonalLinePattern, | |
22: LeftDiagonalLinePattern, | |
23: HorizontalLinePattern, | |
24: VerticalLinePattern, | |
25: LeftDiagonalLinePattern, | |
26: RightDiagonalLinePattern} | |
class PlotRect(Rectangle): | |
def __init__(self, w, h): | |
# Bottom left | |
p1x = BUFFER_LEFT | |
p1y = h - BUFFER_BOTTOM | |
# Top right | |
p2x = w - BUFFER_RIGHT | |
p2y = BUFFER_TOP + LEGEND_HEIGHT + BUFFER_LEGEND | |
Rectangle.__init__(self, Point(p1x, p1y), Point(p2x, p2y)) | |
self.setFill("white") | |
self.setWidth(LINE_WIDTH) | |
class BlockSMRect(object): | |
def __init__(self, block, firstTime, totalTime, totalNumSms, w, h, color, patternType, otherThreads): | |
self.build_rectangle(block, firstTime, totalTime, totalNumSms, w, h, color, patternType, otherThreads) | |
self.build_label(block, firstTime, totalTime, totalNumSms, w, h, color, otherThreads) | |
def build_rectangle(self, block, firstTime, totalTime, totalNumSms, w, h, color, patternType, otherThreads): | |
# Height is fraction of an SM: 2048 threads/SM, with block.numThreads threads in block | |
plotHeight = h - BUFFER_TOP - LEGEND_HEIGHT - BUFFER_LEGEND - BUFFER_BOTTOM | |
plotBottom = h - BUFFER_BOTTOM | |
smHeight = plotHeight / totalNumSms | |
smBottom = plotBottom - int((block.sm) * smHeight) | |
if Y_VAL_SOURCE == "threads": | |
blockHeight = smHeight / MAX_YVAL * block.numThreads | |
else: | |
blockHeight = smHeight / MAX_YVAL * block.sharedMemCount | |
otherHeight = smHeight / MAX_YVAL * otherThreads | |
blockBottom = smBottom - otherHeight | |
blockTop = blockBottom - blockHeight | |
p1x = int(float(block.start - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
p1y = blockBottom | |
p2x = int(float(block.end - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
p2y = blockTop | |
self.block = Rectangle(Point(p1x, p1y), Point(p2x, p2y)) | |
self.block.setWidth(LINE_WIDTH) | |
if patternType == None: | |
self.block.setFill(color) | |
self.pattern = None | |
else: | |
self.block.setFill(patternColorToBgColorMap[color]) | |
self.pattern = patternType(self.block, color) | |
# Draw just the outline, in case the pattern covers it | |
self.outline = Rectangle(Point(p1x, p1y), Point(p2x, p2y)) | |
self.outline.setWidth(LINE_WIDTH) | |
self.outline.setFill("") | |
def build_label(self, block, firstTime, totalTime, totalNumSms, w, h, color, otherThreads): | |
# Height is fraction of an SM: 2048 threads/SM, with block.numThreads threads in block | |
plotHeight = h - BUFFER_TOP - LEGEND_HEIGHT - BUFFER_LEGEND - BUFFER_BOTTOM | |
plotBottom = h - BUFFER_BOTTOM | |
smHeight = plotHeight / totalNumSms | |
smBottom = plotBottom - int((block.sm) * smHeight) | |
if Y_VAL_SOURCE == "threads": | |
blockHeight = smHeight / MAX_YVAL * block.numThreads | |
else: | |
blockHeight = smHeight / MAX_YVAL * block.sharedMemCount | |
otherHeight = smHeight / MAX_YVAL * otherThreads | |
blockBottom = smBottom - otherHeight | |
blockTop = blockBottom - blockHeight | |
blockLeft = int(float(block.start - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
blockRight = int(float(block.end - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
px = (blockLeft + blockRight) / 2 | |
py = (blockTop + blockBottom) / 2 | |
kernelName = block.kernelName | |
self.label = Text(Point(px, py), "%s: %s" % (kernelName, block.id)) | |
if USE_BOLD_FONT: | |
self.label.setStyle("bold") | |
self.label.setSize(14) | |
def draw(self, canvas): | |
self.block.draw(canvas) | |
if self.pattern != None: | |
self.pattern.draw(canvas) | |
self.outline.draw(canvas) | |
self.label.draw(canvas) | |
class KernelReleaseMarker(object): | |
def __init__(self, kernel, firstTime, totalTime, totalNumSms, w, h, color, patternType, idx): | |
self.lines = [] | |
self.arrow_height = 20 | |
self.arrow_width = int(0.3 * self.arrow_height) | |
# If the start and end of the release times are close together, | |
# just build a small straight arrow | |
if float(kernel.releaseTimeEnd - kernel.releaseTimeStart) / (totalTime - firstTime) <= \ | |
(4.5 * self.arrow_width) / w: | |
self.build_marker(kernel, firstTime, totalTime, totalNumSms, w, h, color, patternType, idx) | |
# Otherwise, build a long |__^ arrow to show their differences | |
else: | |
self.build_double_marker(kernel, firstTime, totalTime, totalNumSms, w, h, color, patternType, idx) | |
def build_marker(self, kernel, firstTime, totalTime, totalNumSms, w, h, color, patternType, idx): | |
releaseTime = kernel.releaseTime | |
if patternType != None: | |
color = patternColorToArrowColorMap[color] | |
mh = self.arrow_height | |
mw = self.arrow_width / 2 | |
px = int(float(releaseTime - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
p1y = h - BUFFER_BOTTOM + mh + idx * mh | |
p2y = p1y + mh | |
minx = px - 3*mw | |
maxx = px + 3*mw | |
midlx = px - mw | |
midrx = px + mw | |
midy = p1y + int(0.45 * mh) | |
# /\ | |
# / \ | |
line = Line(Point(minx, midy), Point(px, p1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(px, p1y), Point(maxx, midy)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
# _ _ | |
line = Line(Point(minx, midy), Point(midlx, midy)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(midrx, midy), Point(maxx, midy)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
# | | | |
# | | | |
# |_| | |
line = Line(Point(midlx, midy), Point(midlx, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(midrx, midy), Point(midrx, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(midlx, p2y), Point(midrx, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
def build_double_marker(self, kernel, firstTime, totalTime, totalNumSms, w, h, color, patternType, idx): | |
releaseTimeStart = kernel.releaseTimeStart | |
releaseTimeEnd = kernel.releaseTimeEnd | |
if patternType != None: | |
color = patternColorToArrowColorMap[color] | |
mh = self.arrow_height | |
mw = self.arrow_width / 2 | |
p1x = int(float(releaseTimeStart - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
p2x = int(float(releaseTimeEnd - firstTime) / totalTime * (w-BUFFER_LEFT-BUFFER_RIGHT)) + BUFFER_LEFT | |
p1y = h - BUFFER_BOTTOM + mh + idx * mh | |
p2y = p1y + mh | |
min1x = p1x - mw | |
max1x = p1x + mw | |
min2x = p2x - 3*mw | |
max2x = p2x + 3*mw | |
mid2lx = p2x - mw | |
mid2rx = p2x + mw | |
mid1y = p2y - 2*mw | |
mid2y = p1y + int(0.45 * mh) | |
# __ | |
# | | | |
# | |_________ | |
# |_____________ | |
line = Line(Point(min1x, p1y), Point(max1x, p1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(min1x, p1y), Point(min1x, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(max1x, p1y), Point(max1x, mid1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(min1x, p2y), Point(mid2rx, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(max1x, mid1y), Point(mid2lx, mid1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
# /\ | |
# / \ | |
line = Line(Point(min2x, mid2y), Point(p2x, p1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(p2x, p1y), Point(max2x, mid2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
# _ _ | |
line = Line(Point(min2x, mid2y), Point(mid2lx, mid2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(mid2rx, mid2y), Point(max2x, mid2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
# | | | |
# | | | |
# | | |
line = Line(Point(mid2lx, mid2y), Point(mid2lx, mid1y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
line = Line(Point(mid2rx, mid2y), Point(mid2rx, p2y)) | |
line.setWidth(ARROW_WIDTH) | |
line.setFill(color) | |
self.lines.append(line) | |
def draw(self, canvas): | |
for line in self.lines: | |
line.draw(canvas) | |
class Title(object): | |
def __init__(self, w, h, name): | |
self.build_title(w, h, name) | |
def build_title(self, w, h, name): | |
px = w / 2 | |
py = int(BUFFER_TOP - 16) | |
self.title = Text(Point(px, py), name) | |
self.title.setSize(14) | |
if USE_BOLD_FONT: | |
self.title.setStyle("bold") | |
def draw(self, canvas): | |
self.title.draw(canvas) | |
class LegendBox(object): | |
def __init__(self, posx, posy, w, h, i): | |
self.rect = Rectangle(Point(posx - w/2, posy - h/2), Point(posx + w/2, posy + h/2)) | |
self.rect.setWidth(LINE_WIDTH) | |
if USE_PATTERNS: | |
color = idToColorMap[i] | |
self.rect.setFill(patternColorToBgColorMap[color]) | |
patternType = idToPatternMap[i] | |
if patternType == HorizontalLinePattern: | |
self.pattern = patternType(self.rect, color, 2) | |
elif patternType == VerticalLinePattern: | |
self.pattern = patternType(self.rect, color, 2) | |
elif patternType == LeftDiagonalLinePattern: | |
self.pattern = patternType(self.rect, color, 3) | |
elif patternType == RightDiagonalLinePattern: | |
self.pattern = patternType(self.rect, color, 3) | |
else: | |
color = patternColorToArrowColorMap[idToColorMap[i]] | |
self.rect.setFill(color) | |
self.pattern = None | |
self.outline = Rectangle(self.rect.p1, self.rect.p2) | |
self.outline.setWidth(LINE_WIDTH) | |
self.outline.setFill("") | |
def draw(self, canvas): | |
self.rect.draw(canvas) | |
if self.pattern != None: | |
self.pattern.draw(canvas) | |
self.outline.draw(canvas) | |
class Legend(object): | |
def __init__(self, w, h, benchmark): | |
self.build_rectangle(w, h) | |
self.build_labels(w, h, benchmark) | |
def build_rectangle(self, w, h): | |
# Top left | |
p1x = BUFFER_LEFT | |
p1y = BUFFER_TOP | |
# Bottom right | |
p2x = w - BUFFER_RIGHT | |
p2y = p1y + LEGEND_HEIGHT | |
self.outline = Rectangle(Point(p1x, p1y), Point(p2x, p2y)) | |
self.outline.setFill("white") | |
self.outline.setOutline("black") | |
self.outline.setWidth(LINE_WIDTH) | |
def build_labels(self, w, h, benchmark): | |
self.boxes = [] | |
self.labels = [] | |
for i in range(len(benchmark.streams)): | |
stream = benchmark.streams[i] | |
self.build_label(w, h, stream, i, len(benchmark.streams)) | |
def build_label(self, w, h, stream, i, n): | |
boxSize = LEGEND_BOX_SIZE | |
numPerCol = math.ceil(n / 2.0) | |
if i < numPerCol: | |
# Left half | |
row = i | |
left = BUFFER_LEFT | |
right = left + (w - BUFFER_LEFT - BUFFER_RIGHT) / 2 | |
midx = (left + right) / 2 | |
else: | |
# Right half | |
row = i - numPerCol | |
left = BUFFER_LEFT + (w - BUFFER_LEFT - BUFFER_RIGHT) / 2 | |
right = w - BUFFER_RIGHT | |
midx = (left + right) / 2 | |
top = BUFFER_TOP + (LEGEND_HEIGHT / numPerCol) * row | |
bottom = top + (LEGEND_HEIGHT / numPerCol) | |
midy = (top + bottom) / 2 | |
# Build the box | |
box = LegendBox(left + 18, midy, boxSize, boxSize, i) | |
self.boxes.append(box) | |
# Build the label | |
s = stream.label | |
px = 18 + boxSize - 4 | |
label = Text(Point(left + px, midy), "Stream %d (%s)" % (i+1, s)) # TODO: generalize | |
if USE_BOLD_FONT: | |
label.setStyle("bold") | |
label.config["anchor"] = 'w' # hack to left-align labels | |
self.labels.append(label) | |
def draw(self, canvas): | |
self.outline.draw(canvas) | |
for box in self.boxes: | |
box.draw(canvas) | |
for label in self.labels: | |
label.draw(canvas) | |
class XAxis(object): | |
def __init__(self, firstTime, totalTime, w, h): | |
self.build_axis(w, h) | |
self.calculate_tick_time(totalTime) | |
self.build_tick_marks(totalTime, w, h) | |
self.build_labels(totalTime, w, h) | |
def build_axis(self, w, h): | |
# Draw a thin black horizontal line | |
p1x = BUFFER_LEFT | |
p2x = w - BUFFER_RIGHT | |
py = h - BUFFER_BOTTOM | |
self.axis = Line(Point(p1x, py), Point(p2x, py)) | |
self.axis.setFill("black") | |
self.axis.setWidth(LINE_WIDTH) | |
def calculate_tick_time(self, totalTime): | |
if totalTime <= 2.0: | |
self.tick_time = 0.1 | |
elif totalTime <= 4.0: | |
self.tick_time = 0.2 | |
else: | |
self.tick_time = 0.5 | |
def build_tick_marks(self, totalTime, w, h): | |
# Put a tick every 0.1 seconds | |
plotWidth = w - BUFFER_LEFT - BUFFER_RIGHT | |
numTicks = int(math.ceil(totalTime / self.tick_time)) | |
self.ticks = [] | |
for i in range(1, numTicks): | |
# Top of plot area | |
px = BUFFER_LEFT + (i * self.tick_time / totalTime) * plotWidth | |
p1y = BUFFER_TOP + LEGEND_HEIGHT + BUFFER_LEGEND + 1 | |
p2y = BUFFER_TOP + LEGEND_HEIGHT + BUFFER_LEGEND + 5 | |
tick = Line(Point(px, p1y), Point(px, p2y)) | |
tick.setFill("black") | |
tick.setWidth(LINE_WIDTH) | |
self.ticks.append(tick) | |
# Bottom of plot area | |
px = BUFFER_LEFT + (i * self.tick_time / totalTime) * plotWidth | |
p1y = h - BUFFER_BOTTOM - 0 | |
p2y = h - BUFFER_BOTTOM - 4 | |
tick = Line(Point(px, p1y), Point(px, p2y)) | |
tick.setFill("black") | |
tick.setWidth(LINE_WIDTH) | |
self.ticks.append(tick) | |
def build_labels(self, totalTime, w, h): | |
# Put a label every tick mark | |
plotWidth = w - BUFFER_LEFT - BUFFER_RIGHT | |
numTicks = int(math.ceil(totalTime / self.tick_time)) | |
self.labels = [] | |
for i in range(1, numTicks): | |
px = BUFFER_LEFT + (i * self.tick_time / totalTime) * plotWidth | |
py = h - int(BUFFER_BOTTOM * 0.9) | |
label = Text(Point(px, py), "%.1f" % (i * self.tick_time)) | |
label.setSize(10) | |
if USE_BOLD_FONT: | |
label.setStyle("bold") | |
self.labels.append(label) | |
# Give the axis a label | |
px = w / 2 | |
py = h - (BUFFER_BOTTOM * 0.6) | |
label = Text(Point(px, py), "Time (seconds)") | |
if USE_BOLD_FONT: | |
label.setStyle("bold") | |
self.labels.append(label) | |
def draw(self, canvas): | |
self.axis.draw(canvas) | |
for tick in self.ticks: | |
tick.draw(canvas) | |
for label in self.labels: | |
label.draw(canvas) | |
class YAxis(Rectangle): | |
def __init__(self, totalNumSms, firstTime, totalTime, w, h, baseSM): | |
self.build_axis(w, h) | |
self.build_grid_lines(totalNumSms, w, h) | |
self.build_labels(totalNumSms, w, h, baseSM) | |
def build_axis(self, w, h): | |
# Draw a thin black vertical line | |
px = BUFFER_LEFT | |
p1y = h - BUFFER_BOTTOM | |
p2y = BUFFER_TOP + LEGEND_HEIGHT + BUFFER_LEGEND | |
self.axis = Line(Point(px, p1y), Point(px, p2y)) | |
self.axis.setFill("black") | |
self.axis.setWidth(LINE_WIDTH) | |
def build_grid_lines(self, totalNumSms, w, h): | |
# Put a horizontal line between each SM | |
plotHeight = h - BUFFER_TOP - LEGEND_HEIGHT - BUFFER_LEGEND - BUFFER_BOTTOM | |
plotBottom = h - BUFFER_BOTTOM | |
smHeight = plotHeight / totalNumSms | |
self.gridlines = [] | |
for i in range(1, totalNumSms): | |
py = plotBottom - i * smHeight | |
p1x = BUFFER_LEFT | |
p2x = w - BUFFER_RIGHT | |
line = Line(Point(p1x, py), Point(p2x, py)) | |
line.setFill("black") | |
line.setWidth(LINE_WIDTH) | |
self.gridlines.append(line) | |
def build_labels(self, totalNumSms, w, h, baseSMNumber): | |
plotHeight = h - BUFFER_TOP - LEGEND_HEIGHT - BUFFER_LEGEND - BUFFER_BOTTOM | |
plotBottom = h - BUFFER_BOTTOM | |
smHeight = plotHeight / totalNumSms | |
self.labels = [] | |
for i in range(totalNumSms): | |
px = int(BUFFER_LEFT - 25) | |
py = plotBottom - i * smHeight - int(0.5 * smHeight) | |
label = Text(Point(px, py), "SM %d" % (i + baseSMNumber,)) | |
label.setSize(12) | |
if USE_BOLD_FONT: | |
label.setStyle("bold") | |
self.labels.append(label) | |
def draw(self, canvas): | |
self.axis.draw(canvas) | |
for line in self.gridlines: | |
line.draw(canvas) | |
for label in self.labels: | |
label.draw(canvas) | |
class ResizingCanvasFrame(CanvasFrame): | |
def __init__(self, parent, width, height, redraw_callback): | |
CanvasFrame.__init__(self, parent, width, height) | |
self.parent = parent | |
self.parent.resizable(True, True) | |
self.canvas.pack(fill="both", expand=True) | |
self.canvas.bind("<Configure>", self.on_resize) | |
self.canvas.config(highlightthickness=0) | |
self.redrawCallback = redraw_callback | |
def on_resize(self, event): | |
self.redrawCallback(event.width, event.height) | |
def clear_canvas(self): | |
self.canvas.delete("all") | |
class BlockSMDisplay(): | |
INIT_WIDTH = 800 | |
INIT_HEIGHT = 600 | |
def __init__(self, win, benchmark, min_sm, max_sm): | |
self.setup(win, self.INIT_WIDTH, self.INIT_HEIGHT, benchmark, min_sm, max_sm) | |
self.draw_benchmark() | |
def setup(self, win, width, height, benchmark, min_sm, max_sm): | |
self.width = width | |
self.height = height | |
self.firstTime = 0.0 | |
self.totalTime = (benchmark.get_end() - self.firstTime) * 1.05 | |
# Create a canvas | |
self.canvas = ResizingCanvasFrame(win, self.width, self.height, self.redraw) | |
self.canvas.setBackground("light gray") | |
self.benchmark = benchmark | |
self.baseSm = min_sm | |
self.min_sm = min_sm | |
self.max_sm = max_sm | |
if len(benchmark.streams) > 0: | |
tmp = self.benchmark.streams[0].maxResidentThreads / 2048 | |
if tmp < max_sm: | |
max_sm = tmp | |
self.numSms = max_sm - min_sm | |
if self.numSms == 0: | |
print("Error: The plot won't show any SMs.") | |
exit(1) | |
self.name = self.benchmark.streams[0].scenarioName | |
def redraw(self, width, height): | |
self.canvas.clear_canvas() | |
self.width = width | |
self.height = height | |
self.draw_benchmark() | |
def draw_benchmark(self): | |
global LEGEND_HEIGHT | |
if len(self.benchmark.streams) == 0: return | |
# Calculate the legend height based on the # of streams (two columns) | |
LEGEND_HEIGHT = (len(self.benchmark.streams) + 1) / 2 * LEGEND_HEIGHT_BASE | |
# Draw the plot area | |
self.draw_plot_area() | |
# Draw each kernel | |
smBase = [[] for j in range(self.numSms)] | |
releaseDict = {} | |
for i in range(len(self.benchmark.streams)): | |
color = idToColorMap[i] if USE_PATTERNS else patternColorToArrowColorMap[idToColorMap[i]] | |
patternType = idToPatternMap[i] if USE_PATTERNS else None | |
self.draw_stream(self.benchmark.streams[i], color, patternType, i, smBase, releaseDict) | |
# Draw the title, legend, and axes | |
self.draw_title() | |
self.draw_legend() | |
self.draw_axes() | |
def draw_plot_area(self): | |
pr = PlotRect(self.width, self.height) | |
pr.draw(self.canvas) | |
def draw_stream(self, stream, color, patternType, i, smBase, releaseDict): | |
# Draw each kernel in the stream | |
for kernel in stream.kernels: | |
self.draw_kernel(kernel, color, patternType, i, smBase, releaseDict) | |
def draw_kernel(self, kernel, color, patternType, i, smBase, releaseDict): | |
kernelBlocksIdxs = range(len(kernel.blocks)) # allows for easy reordering, if necessary | |
# Draw each block of the kernel | |
for blockIdx in kernelBlocksIdxs: | |
block = kernel.blocks[blockIdx] | |
if (block.sm < self.min_sm) or (block.sm >= self.max_sm - 1): | |
continue | |
# Calculate competing threadcount | |
otherThreads = 0 | |
myOverlap = [(0, self.totalTime, 0, block.sm, "", -1)] | |
for interval in smBase[block.sm - self.min_sm]: | |
if interval[0] < block.end and interval[1] > block.start: | |
# Find the sub-interval of my own that this overlaps for | |
intervalStart = max(interval[0], block.start) | |
intervalEnd = min(interval[1], block.end) | |
# Find any interval I've already built up overlap for | |
myoverlappingintervals = [] | |
for myinterval in myOverlap: | |
if myinterval[0] < intervalEnd and myinterval[1] > intervalStart: | |
myoverlappingintervals.append(myinterval) | |
# For each interval I already know about that I overlap with, | |
# consider a few cases | |
for myinterval in myoverlappingintervals: | |
# Check if my interval completely encloses this new one (in that case, | |
# split at the beginning and end of this new one) | |
if myinterval[0] <= intervalStart and myinterval[1] >= intervalEnd: | |
myOverlap.remove(myinterval) | |
myOverlap.append((myinterval[0], intervalStart, myinterval[2], myinterval[3], myinterval[4], myinterval[5])) | |
myOverlap.append((intervalStart, intervalEnd, myinterval[2] + interval[2], myinterval[3], myinterval[4], myinterval[5])) | |
myOverlap.append((intervalEnd, myinterval[1], myinterval[2], myinterval[3], myinterval[4], myinterval[5])) | |
# Otherwise, check if my interval starts before this new one | |
# (in that case, split on the start time) | |
elif myinterval[0] <= intervalStart: | |
myOverlap.remove(myinterval) | |
myOverlap.append((myinterval[0], intervalStart, myinterval[2], myinterval[3], myinterval[4], myinterval[5])) | |
myOverlap.append((intervalStart, myinterval[1], myinterval[2] + interval[2], myinterval[3], myinterval[4], myinterval[5])) | |
# Otherwise, check if my interval ends after this new one | |
# (then, split on the end time) | |
elif myinterval[1] >= intervalEnd: | |
myOverlap.remove(myinterval) | |
myOverlap.append((myinterval[0], intervalEnd, myinterval[2] + interval[2], myinterval[3], myinterval[4], myinterval[5])) | |
myOverlap.append((intervalEnd, myinterval[1], myinterval[2], myinterval[3], myinterval[4], myinterval[5])) | |
for interval in myOverlap: | |
otherThreads = max(otherThreads, interval[2]) | |
br = BlockSMRect(block, self.firstTime, self.totalTime, self.numSms, | |
self.width, self.height, color, patternType, otherThreads) | |
br.draw(self.canvas) | |
if Y_VAL_SOURCE == "threads": | |
smBase[block.sm - self.min_sm].append((block.start, block.end, block.numThreads, block.sm, | |
block.kernelName, block.id)) | |
elif Y_VAL_SOURCE == "sharedmem": | |
smBase[block.sm - self.min_sm].append((block.start, block.end, block.sharedMemCount, block.sm, | |
block.kernelName, block.id)) | |
else: | |
pass # invalid! | |
# Draw a marker for the kernel release time | |
releaseBucket = int(kernel.releaseTime / 0.02) | |
releaseIdx = releaseDict.get(releaseBucket, 0) | |
releaseDict[releaseBucket] = releaseIdx + 1 | |
krm = KernelReleaseMarker(kernel, self.firstTime, self.totalTime, | |
self.numSms, self.width, self.height, color, patternType, releaseIdx) | |
krm.draw(self.canvas) | |
def draw_title(self): | |
title = Title(self.width, self.height, self.name) | |
title.draw(self.canvas) | |
def draw_legend(self): | |
legend = Legend(self.width, self.height, self.benchmark) | |
legend.draw(self.canvas) | |
def draw_axes(self): | |
xaxis = XAxis(self.firstTime, self.totalTime, self.width, self.height) | |
xaxis.draw(self.canvas) | |
yaxis = YAxis(self.numSms, self.firstTime, self.totalTime, self.width, self.height, self.baseSm) | |
yaxis.draw(self.canvas) | |
################################################### | |
# Data # | |
################################################### | |
class Block(object): | |
def __init__(self, startTime, endTime, numThreads, smId, threadId, blockId, kernel): | |
self.start = startTime | |
self.end = endTime | |
self.numThreads = numThreads | |
self.sm = smId | |
self.thread = threadId | |
self.id = blockId | |
self.kernelName = kernel.kernelName | |
self.sharedMemCount = kernel.sharedMemCount | |
self.kernel = kernel | |
class Kernel(object): | |
def __init__(self, stream, kernelInfoDict): | |
self.parse_kernel(stream, kernelInfoDict) | |
def parse_kernel(self, stream, kernelInfoDict): | |
self.blocks = [] | |
self.scenarioName = stream.scenarioName | |
self.label = stream.label | |
self.tid = stream.tid | |
self.maxResidentThreads = stream.maxResidentThreads | |
self.releaseTimeStart = kernelInfoDict["cuda_launch_times"][0] | |
self.releaseTimeEnd = kernelInfoDict["cuda_launch_times"][1] | |
self.releaseTime = self.releaseTimeStart | |
self.kernelName = kernelInfoDict["kernel_name"] | |
self.blockCount = kernelInfoDict["block_count"] | |
self.threadCount = kernelInfoDict["thread_count"] | |
self.sharedMemCount = kernelInfoDict["shared_memory"] # int, in bytes | |
self.blockStarts = [] | |
self.blockEnds = [] | |
self.blockSms = [] | |
for i in range(self.blockCount): | |
self.blockStarts.append(kernelInfoDict["block_times"][i*2]) | |
self.blockEnds.append(kernelInfoDict["block_times"][i*2+1]) | |
self.blockSms.append(kernelInfoDict["block_smids"][i]) | |
block = Block(self.blockStarts[-1], self.blockEnds[-1], self.threadCount, | |
self.blockSms[-1], self.tid, i, self) | |
self.blocks.append(block) | |
def get_start(self): | |
start = None | |
for block in self.blocks: | |
if start == None or block.start < start: | |
start = block.start | |
return start | |
def get_end(self): | |
end = None | |
for block in self.blocks: | |
if end == None or block.end > end: | |
end = block.end | |
return end | |
class Stream(object): | |
def __init__(self, benchmark): | |
self.parse_benchmark(benchmark) | |
def parse_benchmark(self, benchmark): | |
self.blocks = [] | |
self.scenarioName = benchmark["scenario_name"] | |
self.label = benchmark.get("label", "") # string | |
self.tid = 0 | |
if "TID" in benchmark: | |
self.tid = benchmark["TID"] # string | |
self.releaseTime = benchmark["release_time"] # float | |
self.maxResidentThreads = benchmark["max_resident_threads"] # int | |
times = benchmark["times"][1:] | |
self.kernels = [] | |
for time in times: | |
if "cpu_times" in time: | |
continue | |
self.kernels.append(Kernel(self, time)) | |
def get_start(self): | |
start = None | |
for kernel in self.kernels: | |
if start == None or kernel.get_start() < start: | |
start = kernel.get_start() | |
return start | |
def get_end(self): | |
end = None | |
for kernel in self.kernels: | |
if end == None or kernel.get_end() > end: | |
end = kernel.get_end() | |
return end | |
class Benchmark(object): | |
def __init__(self, name, benchmarks): | |
self.name = name | |
self.parse_benchmark(benchmarks) | |
def parse_benchmark(self, benchmarks): | |
self.streams = [] | |
for benchmark in benchmarks: | |
self.streams.append(Stream(benchmark)) | |
def sort_key(o): | |
def tryint(s): | |
try: | |
return int(s) | |
except: | |
return s | |
return [tryint(c) for c in re.split(r'([0-9]+)', o.label)] | |
self.streams.sort(key = sort_key) | |
def get_start(self): | |
return min([s.get_start() for s in self.streams]) | |
def get_end(self): | |
return max([s.get_end() for s in self.streams]) | |
def get_block_intervals(name, benchmarks): | |
return Benchmark(name, benchmarks) | |
def plot_scenario(benchmarks, name, min_sm, max_sm): | |
"""Takes a list of parsed benchmark results and a scenario name and | |
generates a plot showing the timeline of benchmark behaviors for the | |
specific scenario.""" | |
win = Window("Block Execution by SM") | |
graph = BlockSMDisplay(win, get_block_intervals(name, benchmarks), min_sm, | |
max_sm) | |
win.mainloop() | |
def show_plots(filenames, min_sm, max_sm): | |
"""Takes a list of filenames, and generates one plot per scenario found in | |
the files.""" | |
# Parse the files | |
parsed_files = [] | |
for name in filenames: | |
with open(name) as f: | |
parsed_files.append(json.loads(f.read())) | |
# Group the files by scenario | |
scenarios = {} | |
for benchmark in parsed_files: | |
scenario = benchmark["scenario_name"] | |
if not scenario in scenarios: | |
scenarios[scenario] = [] | |
scenarios[scenario].append(benchmark) | |
# Plot the scenarios | |
for scenario in scenarios: | |
plot_scenario(scenarios[scenario], scenario, min_sm, max_sm) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-d", "--directory", | |
help="Directory containing result JSON files.", default='./results') | |
parser.add_argument("--min-sm", type=int, | |
help="The lowest-numbered SM to include on the plot.", default=0) | |
parser.add_argument("--max-sm", type=int, | |
help="The highest-numbered SM to include on the plot.", default=9999) | |
base_directory = "./results" | |
args = parser.parse_args() | |
filenames = glob.glob(args.directory + "/*.json") | |
show_plots(filenames, args.min_sm, args.max_sm) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment