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