Add Rules and Behavior section to README
[junction.git] / samples / MapPerformanceTests / RenderGraphs.py
1 #!/usr/bin/env python
2 import os
3 import cairo
4 import math
5 import glob
6 import collections
7
8
9 def colorTuple(h):
10     return (int(h[0:2], 16) / 255.0, int(h[2:4], 16) / 255.0, int(h[4:6], 16) / 255.0)
11
12 NULL_MAP = 'null'
13 ALL_MAPS = [
14     ('folly',           colorTuple('606080')),
15     ('tervel',          colorTuple('9090b0')),
16     ('stdmap',          colorTuple('606080')),
17     ('nbds',            colorTuple('9090b0')),
18     ('michael',         colorTuple('606080')),
19     ('tbb',             colorTuple('9090b0')),
20     ('linear',          colorTuple('ff4040')),
21     ('grampa',          colorTuple('ff4040')),
22     ('leapfrog',        colorTuple('ff4040')),
23 ]
24
25 #---------------------------------------------------
26 #  Cairo drawing helpers
27 #---------------------------------------------------
28 def createScaledFont(family, size, slant=cairo.FONT_SLANT_NORMAL, weight=cairo.FONT_WEIGHT_NORMAL):
29     """ Simple helper function to create a cairo ScaledFont. """
30     face = cairo.ToyFontFace(family, slant, weight)
31     DEFAULT_FONT_OPTIONS = cairo.FontOptions()
32     DEFAULT_FONT_OPTIONS.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
33     return cairo.ScaledFont(face, cairo.Matrix(xx=size, yy=size), cairo.Matrix(), DEFAULT_FONT_OPTIONS)
34
35 def fillAlignedText(cr, x, y, scaledFont, text, alignment = 0):
36     """ Draw some aligned text at the specified co-ordinates.
37     alignment = 0: left-justify
38     alignment = 0.5: center
39     alignment = 1: right-justify """
40     ascent, descent = scaledFont.extents()[:2]
41     x_bearing, y_bearing, width, height = scaledFont.text_extents(text)[:4]
42     with Saved(cr):
43         cr.set_scaled_font(scaledFont)
44         cr.move_to(math.floor(x + 0.5 - width * alignment), math.floor(y + 0.5))
45         cr.text_path(text)
46         cr.fill()
47
48 class Saved():
49     """ Preserve cairo state inside the scope of a with statement. """
50     def __init__(self, cr):
51         self.cr = cr
52     def __enter__(self):
53         self.cr.save()
54         return self.cr
55     def __exit__(self, type, value, traceback):
56         self.cr.restore()
57
58
59 #---------------------------------------------------
60 #  AxisAttribs
61 #---------------------------------------------------
62 class AxisAttribs:
63     """ Describes one axis on the graph. Can be linear or logarithmic. """
64     
65     def __init__(self, size, min, max, step, logarithmic = False, labeler = lambda x: str(int(x + 0.5))):
66         self.size = float(size)
67         self.logarithmic = logarithmic
68         self.labeler = labeler
69         self.toAxis = lambda x: math.log(x) if logarithmic else float(x)
70         self.fromAxis = lambda x: math.exp(x) if logarithmic else float(x)
71         self.min = self.toAxis(min)
72         self.max = self.toAxis(max)
73         self.step = self.toAxis(step)
74
75     def setMinMax(self, min, max):
76         self.min = self.toAxis(min)
77         self.max = self.toAxis(max)
78
79     def mapAxisValue(self, x):
80         """ Maps x to a point along the axis. """
81         return (self.toAxis(x) - self.min) / (self.max - self.min) * self.size
82     
83     def iterLabels(self):
84         """ Helper to iterate through all the tick marks along the axis. """
85         lo = int(math.floor(self.min / self.step + 1 - 1e-9))
86         hi = int(math.floor(self.max / self.step + 1e-9))
87         for i in xrange(lo, hi + 1):
88             value = i * self.step
89             if self.min == 0 and i == 0:
90                 continue
91             yield self.mapAxisValue(self.fromAxis(value)), self.labeler(self.fromAxis(value))
92
93
94 #---------------------------------------------------
95 #  Graph
96 #---------------------------------------------------
97 def makeNamedTuples(results, typeName = 'Point', labels = 'labels', points = 'points'):
98     namedTupleType = collections.namedtuple(typeName, results[labels])
99     numLabels = len(results[labels])
100     return [namedTupleType(*p[:numLabels]) for p in results[points]]
101
102 class Curve:
103     def __init__(self, name, points, color):
104         self.name = name
105         self.points = points
106         self.color = color
107         
108 class Graph:
109     """ Renders a graph. """
110     
111     def __init__(self, xAttribs, yAttribs):
112         self.xAttribs = xAttribs
113         self.yAttribs = yAttribs
114         self.curves = []
115         self.xMin = None
116         self.xMax = None
117
118     def addCurve(self, curve):
119         self.curves.append(curve)
120         xMin = min(x for x, y in curve.points)
121         if self.xMin is None or xMin < self.xMin:
122             self.xMin = xMin
123         xMax = max(x for x, y in curve.points)
124         if self.xMax is None or xMax > self.xMax:
125             self.xMax = xMax
126
127     def renderTo(self, fileName):
128         xAttribs = self.xAttribs
129         yAttribs = self.yAttribs
130         xAttribs.setMinMax(self.xMin, self.xMax)
131
132         # Create the image surface and cairo context
133         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 150 + int(xAttribs.size + 0.5), 65 + int(yAttribs.size + 0.5))
134         cr = cairo.Context(surface)
135         cr.set_source_rgb(1, 1, 1)
136         cr.paint()
137         cr.set_miter_limit(1.414)
138         cr.translate(58, 11 + yAttribs.size)
139
140         # Draw axes
141         labelFont = createScaledFont('Arial', 11)
142         with Saved(cr):
143             cr.set_line_width(1)
144             cr.set_source_rgb(.4, .4, .4)
145
146             # Horizontal axis
147             cr.move_to(0, -0.5)
148             cr.rel_line_to(xAttribs.size + 1, 0)
149             for pos, label in xAttribs.iterLabels():    # Tick marks
150                 x = math.floor(pos + 0.5) + 0.5
151                 cr.move_to(x, -1)
152                 cr.rel_line_to(0, 4)
153             cr.stroke()
154             for pos, label in xAttribs.iterLabels():    # Labels
155                 x = math.floor(pos + 0.5)
156                 with Saved(cr):
157                     cr.translate(x - 1, 5)
158                     cr.rotate(-math.pi / 4)
159                     fillAlignedText(cr, 0, 6, labelFont, label, 1)
160
161             # Vertical axis
162             cr.set_source_rgb(*colorTuple('f0f0f0'))                
163             for pos, label in yAttribs.iterLabels():    # Background lines
164                 if label == '0':
165                     continue
166                 y = -math.floor(pos + 0.5) - 0.5
167                 cr.move_to(1, y)
168                 cr.rel_line_to(xAttribs.size + 1, 0)
169             cr.stroke()
170             cr.set_source_rgb(.4, .4, .4)
171             cr.move_to(0.5, 0)
172             cr.rel_line_to(0, -yAttribs.size - 0.5)
173             if False:
174                 for pos, label in yAttribs.iterLabels():    # Tick marks
175                     if label == '0':
176                         continue
177                     y = -math.floor(pos + 0.5) - 0.5
178                     cr.move_to(1, y)
179                     cr.rel_line_to(-4, 0)
180             cr.stroke()
181             for pos, label in yAttribs.iterLabels():    # Labels
182                 if label == '0':
183                     continue
184                 fillAlignedText(cr, -4, -pos + 4, labelFont, label, 1)
185
186         with Saved(cr):
187             x = xAttribs.size - 70.5
188             y = -234.5
189             cr.rectangle(x, y, 120, 82)
190             cr.set_source_rgb(*colorTuple('ffffff'))                
191             cr.fill()
192             cr.set_source_rgb(*colorTuple('f0f0f0'))                
193             cr.rectangle(x, y, 120, 82)
194             cr.set_line_width(1)
195             cr.stroke()
196
197         # Draw curves
198         for cn, curve in enumerate(self.curves):
199             points = curve.points
200             width = 2.5
201             color = curve.color
202             with Saved(cr):
203                 cr.set_line_width(width)
204                 cr.set_source_rgba(*color)
205                 if cn in [1, 3, 5]:
206                     cr.set_dash([10, 1])
207                 with Saved(cr):
208                     cr.rectangle(0, 5, xAttribs.size, -yAttribs.size - 15)
209                     cr.clip()
210                     x, y = points[0]
211                     cr.move_to(xAttribs.mapAxisValue(x), -yAttribs.mapAxisValue(y))
212                     for x, y in points[1:]:
213                         cr.line_to(xAttribs.mapAxisValue(x) + 0.5, -yAttribs.mapAxisValue(y) - 0.5)
214                     x = xAttribs.size - 40
215                     y = -220 + cn * 12
216                     cr.move_to(x - 4.5, y - 4.5)
217                     cr.rel_line_to(-21, 0)
218                     cr.stroke()
219
220                 # Label
221                 labelFont = createScaledFont('Arial', 11)
222                 label = curve.name
223                 #x, y = points[-1]
224                 #fillAlignedText(cr, xAttribs.mapAxisValue(x) + 3, -yAttribs.mapAxisValue(y) + 4, labelFont, label, 0)
225                 x = xAttribs.size - 40
226                 y = -220 + cn * 12
227                 fillAlignedText(cr, x, y, labelFont, label, 0)
228
229         # Draw axis names
230         cr.set_source_rgb(0, 0, 0)
231         axisFont = createScaledFont('Helvetica', 16, weight=cairo.FONT_WEIGHT_BOLD)
232         with Saved(cr):
233             cr.translate(-44, -yAttribs.size / 2.0)
234             cr.rotate(-math.pi / 2)
235             fillAlignedText(cr, 0, 0, axisFont, 'CPU Time Spent in Map', 0.5)
236         fillAlignedText(cr, xAttribs.size / 2.0, 50, axisFont, 'Interval Between Map Operations', 0.5)
237
238         # Save PNG file
239         surface.write_to_png(fileName)
240
241
242 #---------------------------------------------------
243 #  main
244 #---------------------------------------------------
245 def formatTime(x):
246     if x < 0.9e-6:
247         return '%d ns' % (x * 1e9 + 0.5)
248     elif x < 0.9e-3:
249         return '%d us' % (x * 1e6 + 0.5)  # FIXME: Micro symbol
250     if x < 0.9:
251         return '%d ms' % (x * 1e3 + 0.5)
252     else:
253         return '%d s' % (x + 0.5)
254
255 graph = Graph(AxisAttribs(550, 1e-9, 10000e-9, 10, True, formatTime),
256               AxisAttribs(240, 0, 1, 0.1, False, lambda x: '%d%%' % int(x * 100 + 0.5)))
257
258 nullResultFn = 'build-%s/results.txt' % NULL_MAP
259 nullResults = eval(open(nullResultFn, 'r').read())
260 nullPoints = makeNamedTuples(nullResults)
261
262 for suffix, color in ALL_MAPS:
263     resultsPath = 'build-%s/results.txt' % suffix
264     if os.path.exists(resultsPath):
265         with open(resultsPath, 'r') as f:
266             results = eval(f.read())
267             dataPoints = makeNamedTuples(results)
268             def makeGraphPoint(nullPt, pt):
269                 wuPerOp = float(nullPt.workUnitsDone) / nullPt.mapOpsDone
270                 timePerWU = nullPt.totalTime / nullPt.workUnitsDone
271                 timeBetweenOps = wuPerOp * timePerWU
272                 mapTime = pt.totalTime - pt.workUnitsDone * timePerWU
273                 return (timeBetweenOps, mapTime / pt.totalTime)
274             graphPoints = [makeGraphPoint(*pair) for pair in zip(nullPoints, dataPoints)]
275             def smooth(pts):
276                 result = []
277                 for i in xrange(len(pts) - 1):
278                     x0, y0 = pts[i]
279                     x1, y1 = pts[i + 1]
280                     result += [(0.75*x0 + 0.25*x1, 0.75*y0 + 0.25*y1),
281                                (0.25*x0 + 0.75*x1, 0.25*y0 + 0.75*y1)]
282                 return result
283             def smooth2(pts):
284                 result = []
285                 for i in xrange(len(pts) - 1):
286                     x0, y0 = pts[i]
287                     x1, y1 = pts[i + 1]
288                     result += [(0.5*x0 + 0.5*x1, 0.5*y0 + 0.5*y1)]
289                 return result
290
291             graphPoints = smooth2(graphPoints)
292             graphPoints = smooth2(graphPoints)
293             graphPoints = smooth2(graphPoints)
294             graphPoints = smooth2(graphPoints)
295             graphPoints = smooth2(graphPoints)
296             graphPoints = smooth2(graphPoints)
297             graphPoints = smooth(graphPoints)
298             graphPoints = smooth2(graphPoints)
299             graph.addCurve(Curve(results['mapType'], graphPoints, color))
300
301 graph.renderTo('out.png')