Source file
src/math/big/calibrate_graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package main
18
19 import (
20 "bytes"
21 "encoding/csv"
22 "flag"
23 "fmt"
24 "log"
25 "math"
26 "os"
27 "strconv"
28 )
29
30 func usage() {
31 fmt.Fprintf(os.Stderr, "usage: go run calibrate_graph.go file.csv >file.svg\n")
32 os.Exit(2)
33 }
34
35
36 type Point struct {
37 X, Y float64
38 }
39
40
41 type Graph struct {
42 Title string
43 Geomean []Point
44 Lines [][]Point
45 XAxis string
46 YAxis string
47 Min Point
48 Max Point
49 }
50
51 var yMax = flag.Float64("ymax", 1.2, "maximum y axis value")
52 var alphaNorm = flag.Float64("alphanorm", 0.1, "alpha for a single norm line")
53
54 func main() {
55 flag.Usage = usage
56 flag.Parse()
57 if flag.NArg() != 1 {
58 usage()
59 }
60
61
62
63
64
65
66 fdata, err := os.ReadFile(flag.Arg(0))
67 if err != nil {
68 log.Fatal(err)
69 }
70 if _, after, ok := bytes.Cut(fdata, []byte(".csv --\n")); ok {
71 fdata = after
72 }
73 if before, _, ok := bytes.Cut(fdata, []byte("-- eof --\n")); ok {
74 fdata = before
75 }
76 rd := csv.NewReader(bytes.NewReader(fdata))
77 rd.FieldsPerRecord = -1
78 records, err := rd.ReadAll()
79 if err != nil {
80 log.Fatal(err)
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 g := &Graph{
108 YAxis: "Relative Slowdown",
109 Min: Point{0, 1},
110 Max: Point{1, 1.2},
111 }
112 meta := make(map[string]string)
113 table := 0
114 var thresholds []float64
115 maxNorm := 0.0
116 for _, rec := range records {
117 if len(rec) == 0 {
118 continue
119 }
120 if len(rec) == 2 {
121 meta[rec[0]] = rec[1]
122 continue
123 }
124 if rec[0] == `size \ threshold` {
125 table++
126 if table == 2 {
127 thresholds = parseFloats(rec)
128 g.Min.X = thresholds[0]
129 g.Max.X = thresholds[len(thresholds)-1]
130 }
131 continue
132 }
133 if rec[0] == "geomean" {
134 table = 3
135 geomeans := parseFloats(rec)
136 g.Geomean = floatsToLine(thresholds, geomeans)
137 continue
138 }
139 if table == 2 {
140 if _, err := strconv.Atoi(rec[0]); err != nil {
141 log.Fatalf("invalid table line: %q", rec)
142 }
143 norms := parseFloats(rec)
144 if len(norms) > len(thresholds) {
145 log.Fatalf("too many timings (%d > %d): %q", len(norms), len(thresholds), rec)
146 }
147 g.Lines = append(g.Lines, floatsToLine(thresholds, norms))
148 for _, y := range norms {
149 maxNorm = max(maxNorm, y)
150 }
151 continue
152 }
153 }
154
155 g.Max.Y = min(*yMax, math.Ceil(maxNorm*100)/100)
156 g.XAxis = meta["calibrate"] + "Threshold"
157 g.Title = meta["goos"] + "/" + meta["goarch"] + " " + meta["cpu"]
158
159 os.Stdout.Write(g.SVG())
160 }
161
162
163
164 func parseFloats(rec []string) []float64 {
165 floats := make([]float64, 0, len(rec)-1)
166 for _, v := range rec[1:] {
167 if v == "" {
168 floats = append(floats, math.Inf(+1))
169 continue
170 }
171 f, err := strconv.ParseFloat(v, 64)
172 if err != nil {
173 log.Fatalf("invalid record: %q (%v)", rec, err)
174 }
175 floats = append(floats, f)
176 }
177 return floats
178 }
179
180
181 func floatsToLine(x, y []float64) []Point {
182 var line []Point
183 for i, yi := range y {
184 if !math.IsInf(yi, 0) {
185 line = append(line, Point{x[i], yi})
186 }
187 }
188 return line
189 }
190
191 const svgHeader = `<svg width="%d" height="%d" version="1.1" xmlns="http://www.w3.org/2000/svg">
192 <defs>
193 <style type="text/css"><![CDATA[
194 text { stroke-width: 0; white-space: pre; }
195 text.hjc { text-anchor: middle; }
196 text.hjl { text-anchor: start; }
197 text.hjr { text-anchor: end; }
198 .def { stroke-linecap: round; stroke-linejoin: round; fill: none; stroke: #000000; stroke-width: 1px; }
199 .tick { stroke: #000000; fill: #000000; font: %dpx Times; }
200 .title { stroke: #000000; fill: #000000; font: %dpx Times; font-weight: bold; }
201 .axis { stroke-width: 2px; }
202 .norm { stroke: rgba(0,0,0,%f); }
203 .geomean { stroke: #6666ff; stroke-width: 2px; }
204 ]]></style>
205 </defs>
206 <g class="def">
207 `
208
209
210 const (
211 DX = 600
212 DY = 150
213 ML = 80
214 MT = 30
215 MR = 10
216 MB = 50
217 PS = 14
218 W = ML + DX + MR
219 H = MT + DY + MB
220 Tick = 5
221 )
222
223
224
225 type SVGPoint struct {
226 X, Y int
227 }
228
229 func (p SVGPoint) String() string {
230 return fmt.Sprintf("%d,%d", p.X, p.Y)
231 }
232
233
234 func (g *Graph) pt(x, y float64) SVGPoint {
235 return SVGPoint{
236 X: ML + int((x-g.Min.X)/(g.Max.X-g.Min.X)*DX),
237 Y: H - MB - int((y-g.Min.Y)/(g.Max.Y-g.Min.Y)*DY),
238 }
239 }
240
241
242 func (g *Graph) SVG() []byte {
243
244 var svg bytes.Buffer
245 fmt.Fprintf(&svg, svgHeader, W, H, PS, PS, *alphaNorm)
246
247
248 fmt.Fprintf(&svg, "<clipPath id=\"cp\"><path d=\"M %v L %v L %v L %v Z\" /></clipPath>\n",
249 g.pt(g.Min.X, g.Min.Y), g.pt(g.Max.X, g.Min.Y), g.pt(g.Max.X, g.Max.Y), g.pt(g.Min.X, g.Max.Y))
250 fmt.Fprintf(&svg, "<g clip-path=\"url(#cp)\">\n")
251 for _, line := range g.Lines {
252 if len(line) == 0 {
253 continue
254 }
255 fmt.Fprintf(&svg, "<path class=\"norm\" d=\"M %v", g.pt(line[0].X, line[0].Y))
256 for _, v := range line[1:] {
257 fmt.Fprintf(&svg, " L %v", g.pt(v.X, v.Y))
258 }
259 fmt.Fprintf(&svg, "\"/>\n")
260 }
261
262 if len(g.Geomean) > 0 {
263 line := g.Geomean
264 fmt.Fprintf(&svg, "<path class=\"geomean\" d=\"M %v", g.pt(line[0].X, line[0].Y))
265 for _, v := range line[1:] {
266 fmt.Fprintf(&svg, " L %v", g.pt(v.X, v.Y))
267 }
268 fmt.Fprintf(&svg, "\"/>\n")
269 }
270 fmt.Fprintf(&svg, "</g>\n")
271
272
273 fmt.Fprintf(&svg, "<path class=\"axis\" d=\"")
274 fmt.Fprintf(&svg, " M %v L %v", g.pt(g.Min.X, g.Min.Y), g.pt(g.Max.X, g.Min.Y))
275 fmt.Fprintf(&svg, " M %v L %v", g.pt(g.Min.X, g.Min.Y), g.pt(g.Min.X, g.Max.Y))
276 xscale := 10.0
277 if g.Max.X-g.Min.X < 100 {
278 xscale = 1.0
279 }
280 for x := int(math.Ceil(g.Min.X / xscale)); float64(x)*xscale <= g.Max.X; x++ {
281 if x%5 != 0 {
282 fmt.Fprintf(&svg, " M %v l 0,%d", g.pt(float64(x)*xscale, g.Min.Y), Tick)
283 } else {
284 fmt.Fprintf(&svg, " M %v l 0,%d", g.pt(float64(x)*xscale, g.Min.Y), 2*Tick)
285 }
286 }
287 yscale := 100.0
288 if g.Max.Y-g.Min.Y > 0.5 {
289 yscale = 10
290 }
291 for y := int(math.Ceil(g.Min.Y * yscale)); float64(y) <= g.Max.Y*yscale; y++ {
292 if y%5 != 0 {
293 fmt.Fprintf(&svg, " M %v l -%d,0", g.pt(g.Min.X, float64(y)/yscale), Tick)
294 } else {
295 fmt.Fprintf(&svg, " M %v l -%d,0", g.pt(g.Min.X, float64(y)/yscale), 2*Tick)
296 }
297 }
298 fmt.Fprintf(&svg, "\"/>\n")
299
300
301 for x := int(math.Ceil(g.Min.X / xscale)); float64(x)*xscale <= g.Max.X; x++ {
302 if x%5 == 0 {
303 p := g.pt(float64(x)*xscale, g.Min.Y)
304 fmt.Fprintf(&svg, "<text x=\"%d\" y=\"%d\" class=\"tick hjc\">%d</text>\n", p.X, p.Y+2*Tick+PS, x*int(xscale))
305 }
306 }
307 for y := int(math.Ceil(g.Min.Y * yscale)); float64(y) <= g.Max.Y*yscale; y++ {
308 if y%5 == 0 {
309 p := g.pt(g.Min.X, float64(y)/yscale)
310 fmt.Fprintf(&svg, "<text x=\"%d\" y=\"%d\" class=\"tick hjr\">%.2f</text>\n", p.X-2*Tick-Tick, p.Y+PS/3, float64(y)/yscale)
311 }
312 }
313
314
315 fmt.Fprintf(&svg, "<text x=\"%d\" y=\"%d\" class=\"title hjc\">%s</text>\n", ML+DX/2, MT-PS/3, g.Title)
316 fmt.Fprintf(&svg, "<text x=\"%d\" y=\"%d\" class=\"title hjc\">%s</text>\n", ML+DX/2, MT+DY+2*Tick+2*PS+PS/2, g.XAxis)
317 fmt.Fprintf(&svg, "<g transform=\"translate(%d,%d) rotate(-90)\"><text x=\"0\" y=\"0\" class=\"title hjc\">%s</text></g>\n", ML-Tick-Tick-3*PS, MT+DY/2, g.YAxis)
318
319 fmt.Fprintf(&svg, "</g></svg>\n")
320 return svg.Bytes()
321 }
322
View as plain text