Rename LeapFrog to Leapfrog
[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('408040')),
15     ('stdmap',          colorTuple('b0b090')),
16     ('nbds',            colorTuple('9090b0')),
17     ('linear',          colorTuple('ff4040')),
18     ('michael',         colorTuple('202020')),
19     ('tbb',             colorTuple('0090b0')),
20     ('cuckoo',          colorTuple('d040d0')),
21     ('grampa',          colorTuple('ff6040')),
22     ('leapfrog',        colorTuple('ff8040')),
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, 330 + int(xAttribs.size + 0.5), 55 + 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(80, 8 + yAttribs.size)
139
140         # Draw axes
141         labelFont = createScaledFont('Arial', 13)
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             cr.stroke()
150             for pos, label in xAttribs.iterLabels():    # Labels
151                 x = math.floor(pos + 0.5)
152                 with Saved(cr):
153                     cr.translate(x, 9)
154                     fillAlignedText(cr, 0, 6, labelFont, label, 0.5)
155
156             # Vertical axis
157             cr.set_source_rgb(*colorTuple('f0f0f0'))                
158             for pos, label in yAttribs.iterLabels():    # Background lines
159                 if label == '0':
160                     continue
161                 y = -math.floor(pos + 0.5) - 0.5
162                 cr.move_to(1, y)
163                 cr.rel_line_to(xAttribs.size + 1, 0)
164             cr.stroke()
165             cr.set_source_rgb(.4, .4, .4)
166             cr.move_to(0.5, 0)
167             cr.rel_line_to(0, -yAttribs.size - 0.5)
168             if False:
169                 for pos, label in yAttribs.iterLabels():    # Tick marks
170                     if label == '0':
171                         continue
172                     y = -math.floor(pos + 0.5) - 0.5
173                     cr.move_to(1, y)
174                     cr.rel_line_to(-4, 0)
175             cr.stroke()
176             for pos, label in yAttribs.iterLabels():    # Labels
177                 if label == '0':
178                     continue
179                 fillAlignedText(cr, -4, -pos + 4, labelFont, label, 1)
180
181         """
182         with Saved(cr):
183             x = xAttribs.size - 70.5 + 80
184             y = -234.5
185             cr.rectangle(x, y, 120, 82)
186             cr.set_source_rgb(*colorTuple('ffffff'))                
187             cr.fill()
188             cr.set_source_rgb(*colorTuple('f0f0f0'))                
189             cr.rectangle(x, y, 120, 82)
190             cr.set_line_width(1)
191             cr.stroke()
192         """
193
194         # Draw curves
195         for cn, curve in enumerate(self.curves):
196             points = curve.points
197             color = curve.color
198             width = 1.75
199             #if color == colorTuple('ff4040'):
200                 #width = 2
201             with Saved(cr):
202                 cr.set_line_width(width)
203                 cr.set_source_rgba(*color)
204                 #if color == colorTuple('9090b0'):
205                 #    cr.set_dash([9, 2])
206                 with Saved(cr):
207                     cr.rectangle(0, 5, xAttribs.size, -yAttribs.size - 15)
208                     cr.clip()
209                     x, y = points[0]
210                     cr.move_to(xAttribs.mapAxisValue(x), -yAttribs.mapAxisValue(y))
211                     for x, y in points[1:]:
212                         cr.line_to(xAttribs.mapAxisValue(x) + 0.5, -yAttribs.mapAxisValue(y) - 0.5)
213                     cr.stroke()
214                 for x, y in points:
215                     cr.rectangle(xAttribs.mapAxisValue(x) - 2.5, -yAttribs.mapAxisValue(y) - 2.5, 5, 5)
216                 cr.fill()
217
218                 x = xAttribs.size + 40
219                 y = -120 + (5 - cn) * 14
220                 cr.move_to(x - 4.5, y - 4.5)
221                 cr.rel_line_to(-21, 0)
222                 cr.stroke()
223
224                 # Label
225                 weight = cairo.FONT_WEIGHT_NORMAL
226                 if color == colorTuple('ff4040'):
227                     weight = cairo.FONT_WEIGHT_BOLD
228                 labelFont = createScaledFont('Arial', 13, weight=weight)
229                 label = curve.name
230                 #x, y = points[-1]
231                 #fillAlignedText(cr, xAttribs.mapAxisValue(x) + 3, -yAttribs.mapAxisValue(y) + 4, labelFont, label, 0)
232                 x = xAttribs.size + 40
233                 y = -120 + (5 - cn) * 14
234                 fillAlignedText(cr, x, y, labelFont, label, 0)
235
236         # Draw axis names
237         cr.set_source_rgb(0, 0, 0)
238         axisFont = createScaledFont('Helvetica', 16, weight=cairo.FONT_WEIGHT_BOLD)
239         with Saved(cr):
240             cr.translate(-66, -yAttribs.size / 2.0)
241             cr.rotate(-math.pi / 2)
242             fillAlignedText(cr, 0, 0, axisFont, 'Map Operations / Sec', 0.5)
243         with Saved(cr):
244             axisFont2 = createScaledFont('Helvetica', 13)
245             cr.translate(-50, -yAttribs.size / 2.0)
246             cr.rotate(-math.pi / 2)
247             cr.set_source_rgba(*colorTuple('808080'))
248             fillAlignedText(cr, 0, 0, axisFont2, '(Total Across All Threads)', 0.5)
249         fillAlignedText(cr, xAttribs.size / 2.0, 42, axisFont, 'Threads', 0.5)
250
251         # Save PNG file
252         surface.write_to_png(fileName)
253
254
255 #---------------------------------------------------
256 #  main
257 #---------------------------------------------------
258 def formatTime(x):
259     if x < 0.9e-6:
260         return '%d ns' % (x * 1e9 + 0.5)
261     elif x < 0.9e-3:
262         return '%d us' % (x * 1e6 + 0.5)  # FIXME: Micro symbol
263     if x < 0.9:
264         return '%d ms' % (x * 1e3 + 0.5)
265     else:
266         return '%d s' % (x + 0.5)
267
268 graph = Graph(AxisAttribs(250, 1, 6, 1),
269               AxisAttribs(240, 0, 150, 10, False, lambda x: '%dM' % x if x % 50 == 0 else ''))
270
271 for suffix, color in ALL_MAPS:
272     resultsPath = 'build-%s/results.txt' % suffix
273     if os.path.exists(resultsPath):
274         with open(resultsPath, 'r') as f:
275             results = eval(f.read())
276             dataPoints = makeNamedTuples(results)
277             def makeGraphPoint(pt):
278                 mapOpsPerSec = pt.mapOpsDone / pt.totalTime
279                 return (pt.numThreads, mapOpsPerSec * pt.numThreads / 1000000)
280             graphPoints = [makeGraphPoint(pt) for pt in dataPoints]
281             graph.addCurve(Curve(results['mapType'], graphPoints, color))
282
283 graph.renderTo('out.png')