1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * svghelper.c - helper functions for outputting svg |
4 | * |
5 | * (C) Copyright 2009 Intel Corporation |
6 | * |
7 | * Authors: |
8 | * Arjan van de Ven <arjan@linux.intel.com> |
9 | */ |
10 | |
11 | #include <inttypes.h> |
12 | #include <stdio.h> |
13 | #include <stdlib.h> |
14 | #include <unistd.h> |
15 | #include <string.h> |
16 | #include <linux/bitmap.h> |
17 | #include <linux/string.h> |
18 | #include <linux/time64.h> |
19 | #include <linux/zalloc.h> |
20 | #include <internal/cpumap.h> |
21 | #include <perf/cpumap.h> |
22 | |
23 | #include "env.h" |
24 | #include "svghelper.h" |
25 | |
26 | static u64 first_time, last_time; |
27 | static u64 turbo_frequency, max_freq; |
28 | |
29 | |
30 | #define SLOT_MULT 30.0 |
31 | #define SLOT_HEIGHT 25.0 |
32 | #define SLOT_HALF (SLOT_HEIGHT / 2) |
33 | |
34 | int svg_page_width = 1000; |
35 | u64 svg_highlight; |
36 | const char *svg_highlight_name; |
37 | |
38 | #define MIN_TEXT_SIZE 0.01 |
39 | |
40 | static u64 total_height; |
41 | static FILE *svgfile; |
42 | |
43 | static double cpu2slot(int cpu) |
44 | { |
45 | return 2 * cpu + 1; |
46 | } |
47 | |
48 | static int *topology_map; |
49 | |
50 | static double cpu2y(int cpu) |
51 | { |
52 | if (topology_map) |
53 | return cpu2slot(cpu: topology_map[cpu]) * SLOT_MULT; |
54 | else |
55 | return cpu2slot(cpu) * SLOT_MULT; |
56 | } |
57 | |
58 | static double time2pixels(u64 __time) |
59 | { |
60 | double X; |
61 | |
62 | X = 1.0 * svg_page_width * (__time - first_time) / (last_time - first_time); |
63 | return X; |
64 | } |
65 | |
66 | /* |
67 | * Round text sizes so that the svg viewer only needs a discrete |
68 | * number of renderings of the font |
69 | */ |
70 | static double round_text_size(double size) |
71 | { |
72 | int loop = 100; |
73 | double target = 10.0; |
74 | |
75 | if (size >= 10.0) |
76 | return size; |
77 | while (loop--) { |
78 | if (size >= target) |
79 | return target; |
80 | target = target / 2.0; |
81 | } |
82 | return size; |
83 | } |
84 | |
85 | void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end) |
86 | { |
87 | int new_width; |
88 | |
89 | svgfile = fopen(filename, "w" ); |
90 | if (!svgfile) { |
91 | fprintf(stderr, "Cannot open %s for output\n" , filename); |
92 | return; |
93 | } |
94 | first_time = start; |
95 | first_time = first_time / 100000000 * 100000000; |
96 | last_time = end; |
97 | |
98 | /* |
99 | * if the recording is short, we default to a width of 1000, but |
100 | * for longer recordings we want at least 200 units of width per second |
101 | */ |
102 | new_width = (last_time - first_time) / 5000000; |
103 | |
104 | if (new_width > svg_page_width) |
105 | svg_page_width = new_width; |
106 | |
107 | total_height = (1 + rows + cpu2slot(cpu: cpus)) * SLOT_MULT; |
108 | fprintf(svgfile, "<?xml version=\"1.0\" standalone=\"no\"?> \n" ); |
109 | fprintf(svgfile, "<!DOCTYPE svg SYSTEM \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" ); |
110 | fprintf(svgfile, "<svg width=\"%i\" height=\"%" PRIu64 "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n" , svg_page_width, total_height); |
111 | |
112 | fprintf(svgfile, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n" ); |
113 | |
114 | fprintf(svgfile, " rect { stroke-width: 1; }\n" ); |
115 | fprintf(svgfile, " rect.process { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:1; stroke:rgb( 0, 0, 0); } \n" ); |
116 | fprintf(svgfile, " rect.process2 { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
117 | fprintf(svgfile, " rect.process3 { fill:rgb(180,180,180); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
118 | fprintf(svgfile, " rect.sample { fill:rgb( 0, 0,255); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
119 | fprintf(svgfile, " rect.sample_hi{ fill:rgb(255,128, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
120 | fprintf(svgfile, " rect.error { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
121 | fprintf(svgfile, " rect.net { fill:rgb( 0,128, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
122 | fprintf(svgfile, " rect.disk { fill:rgb( 0, 0,255); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
123 | fprintf(svgfile, " rect.sync { fill:rgb(128,128, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
124 | fprintf(svgfile, " rect.poll { fill:rgb( 0,128,128); fill-opacity:0.2; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
125 | fprintf(svgfile, " rect.blocked { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
126 | fprintf(svgfile, " rect.waiting { fill:rgb(224,214, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
127 | fprintf(svgfile, " rect.WAITING { fill:rgb(255,214, 48); fill-opacity:0.6; stroke-width:0; stroke:rgb( 0, 0, 0); } \n" ); |
128 | fprintf(svgfile, " rect.cpu { fill:rgb(192,192,192); fill-opacity:0.2; stroke-width:0.5; stroke:rgb(128,128,128); } \n" ); |
129 | fprintf(svgfile, " rect.pstate { fill:rgb(128,128,128); fill-opacity:0.8; stroke-width:0; } \n" ); |
130 | fprintf(svgfile, " rect.c1 { fill:rgb(255,214,214); fill-opacity:0.5; stroke-width:0; } \n" ); |
131 | fprintf(svgfile, " rect.c2 { fill:rgb(255,172,172); fill-opacity:0.5; stroke-width:0; } \n" ); |
132 | fprintf(svgfile, " rect.c3 { fill:rgb(255,130,130); fill-opacity:0.5; stroke-width:0; } \n" ); |
133 | fprintf(svgfile, " rect.c4 { fill:rgb(255, 88, 88); fill-opacity:0.5; stroke-width:0; } \n" ); |
134 | fprintf(svgfile, " rect.c5 { fill:rgb(255, 44, 44); fill-opacity:0.5; stroke-width:0; } \n" ); |
135 | fprintf(svgfile, " rect.c6 { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; } \n" ); |
136 | fprintf(svgfile, " line.pstate { stroke:rgb(255,255, 0); stroke-opacity:0.8; stroke-width:2; } \n" ); |
137 | |
138 | fprintf(svgfile, " ]]>\n </style>\n</defs>\n" ); |
139 | } |
140 | |
141 | static double normalize_height(double height) |
142 | { |
143 | if (height < 0.25) |
144 | return 0.25; |
145 | else if (height < 0.50) |
146 | return 0.50; |
147 | else if (height < 0.75) |
148 | return 0.75; |
149 | else |
150 | return 0.100; |
151 | } |
152 | |
153 | void svg_ubox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) |
154 | { |
155 | double w = time2pixels(time: end) - time2pixels(time: start); |
156 | height = normalize_height(height); |
157 | |
158 | if (!svgfile) |
159 | return; |
160 | |
161 | fprintf(svgfile, "<g>\n" ); |
162 | fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n" , fd, err, merges); |
163 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n" , |
164 | time2pixels(time: start), |
165 | w, |
166 | Yslot * SLOT_MULT, |
167 | SLOT_HALF * height, |
168 | type); |
169 | fprintf(svgfile, "</g>\n" ); |
170 | } |
171 | |
172 | void svg_lbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) |
173 | { |
174 | double w = time2pixels(time: end) - time2pixels(time: start); |
175 | height = normalize_height(height); |
176 | |
177 | if (!svgfile) |
178 | return; |
179 | |
180 | fprintf(svgfile, "<g>\n" ); |
181 | fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n" , fd, err, merges); |
182 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n" , |
183 | time2pixels(time: start), |
184 | w, |
185 | Yslot * SLOT_MULT + SLOT_HEIGHT - SLOT_HALF * height, |
186 | SLOT_HALF * height, |
187 | type); |
188 | fprintf(svgfile, "</g>\n" ); |
189 | } |
190 | |
191 | void svg_fbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) |
192 | { |
193 | double w = time2pixels(time: end) - time2pixels(time: start); |
194 | height = normalize_height(height); |
195 | |
196 | if (!svgfile) |
197 | return; |
198 | |
199 | fprintf(svgfile, "<g>\n" ); |
200 | fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n" , fd, err, merges); |
201 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n" , |
202 | time2pixels(time: start), |
203 | w, |
204 | Yslot * SLOT_MULT + SLOT_HEIGHT - SLOT_HEIGHT * height, |
205 | SLOT_HEIGHT * height, |
206 | type); |
207 | fprintf(svgfile, "</g>\n" ); |
208 | } |
209 | |
210 | void svg_box(int Yslot, u64 start, u64 end, const char *type) |
211 | { |
212 | if (!svgfile) |
213 | return; |
214 | |
215 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n" , |
216 | time2pixels(time: start), time2pixels(time: end)-time2pixels(time: start), Yslot * SLOT_MULT, SLOT_HEIGHT, type); |
217 | } |
218 | |
219 | static char *time_to_string(u64 duration); |
220 | void svg_blocked(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) |
221 | { |
222 | if (!svgfile) |
223 | return; |
224 | |
225 | fprintf(svgfile, "<g>\n" ); |
226 | fprintf(svgfile, "<title>#%d blocked %s</title>\n" , cpu, |
227 | time_to_string(duration: end - start)); |
228 | if (backtrace) |
229 | fprintf(svgfile, "<desc>Blocked on:\n%s</desc>\n" , backtrace); |
230 | svg_box(Yslot, start, end, type: "blocked" ); |
231 | fprintf(svgfile, "</g>\n" ); |
232 | } |
233 | |
234 | void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) |
235 | { |
236 | double text_size; |
237 | const char *type; |
238 | |
239 | if (!svgfile) |
240 | return; |
241 | |
242 | if (svg_highlight && end - start > svg_highlight) |
243 | type = "sample_hi" ; |
244 | else |
245 | type = "sample" ; |
246 | fprintf(svgfile, "<g>\n" ); |
247 | |
248 | fprintf(svgfile, "<title>#%d running %s</title>\n" , |
249 | cpu, time_to_string(duration: end - start)); |
250 | if (backtrace) |
251 | fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n" , backtrace); |
252 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n" , |
253 | time2pixels(time: start), time2pixels(time: end)-time2pixels(time: start), Yslot * SLOT_MULT, SLOT_HEIGHT, |
254 | type); |
255 | |
256 | text_size = (time2pixels(time: end)-time2pixels(time: start)); |
257 | if (cpu > 9) |
258 | text_size = text_size/2; |
259 | if (text_size > 1.25) |
260 | text_size = 1.25; |
261 | text_size = round_text_size(size: text_size); |
262 | |
263 | if (text_size > MIN_TEXT_SIZE) |
264 | fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"%.8fpt\">%i</text>\n" , |
265 | time2pixels(time: start), Yslot * SLOT_MULT + SLOT_HEIGHT - 1, text_size, cpu + 1); |
266 | |
267 | fprintf(svgfile, "</g>\n" ); |
268 | } |
269 | |
270 | static char *time_to_string(u64 duration) |
271 | { |
272 | static char text[80]; |
273 | |
274 | text[0] = 0; |
275 | |
276 | if (duration < NSEC_PER_USEC) /* less than 1 usec */ |
277 | return text; |
278 | |
279 | if (duration < NSEC_PER_MSEC) { /* less than 1 msec */ |
280 | sprintf(buf: text, fmt: "%.1f us" , duration / (double)NSEC_PER_USEC); |
281 | return text; |
282 | } |
283 | sprintf(buf: text, fmt: "%.1f ms" , duration / (double)NSEC_PER_MSEC); |
284 | |
285 | return text; |
286 | } |
287 | |
288 | void svg_waiting(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) |
289 | { |
290 | char *text; |
291 | const char *style; |
292 | double font_size; |
293 | |
294 | if (!svgfile) |
295 | return; |
296 | |
297 | style = "waiting" ; |
298 | |
299 | if (end-start > 10 * NSEC_PER_MSEC) /* 10 msec */ |
300 | style = "WAITING" ; |
301 | |
302 | text = time_to_string(duration: end-start); |
303 | |
304 | font_size = 1.0 * (time2pixels(time: end)-time2pixels(time: start)); |
305 | |
306 | if (font_size > 3) |
307 | font_size = 3; |
308 | |
309 | font_size = round_text_size(size: font_size); |
310 | |
311 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\">\n" , time2pixels(time: start), Yslot * SLOT_MULT); |
312 | fprintf(svgfile, "<title>#%d waiting %s</title>\n" , cpu, time_to_string(duration: end - start)); |
313 | if (backtrace) |
314 | fprintf(svgfile, "<desc>Waiting on:\n%s</desc>\n" , backtrace); |
315 | fprintf(svgfile, "<rect x=\"0\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n" , |
316 | time2pixels(time: end)-time2pixels(time: start), SLOT_HEIGHT, style); |
317 | if (font_size > MIN_TEXT_SIZE) |
318 | fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%.8fpt\"> %s</text>\n" , |
319 | font_size, text); |
320 | fprintf(svgfile, "</g>\n" ); |
321 | } |
322 | |
323 | static char *cpu_model(void) |
324 | { |
325 | static char cpu_m[255]; |
326 | char buf[256]; |
327 | FILE *file; |
328 | |
329 | cpu_m[0] = 0; |
330 | /* CPU type */ |
331 | file = fopen("/proc/cpuinfo" , "r" ); |
332 | if (file) { |
333 | while (fgets(buf, 255, file)) { |
334 | if (strcasestr(buf, "model name" )) { |
335 | strlcpy(cpu_m, &buf[13], 255); |
336 | break; |
337 | } |
338 | } |
339 | fclose(file); |
340 | } |
341 | |
342 | /* CPU type */ |
343 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies" , "r" ); |
344 | if (file) { |
345 | while (fgets(buf, 255, file)) { |
346 | unsigned int freq; |
347 | freq = strtoull(buf, NULL, 10); |
348 | if (freq > max_freq) |
349 | max_freq = freq; |
350 | } |
351 | fclose(file); |
352 | } |
353 | return cpu_m; |
354 | } |
355 | |
356 | void svg_cpu_box(int cpu, u64 __max_freq, u64 __turbo_freq) |
357 | { |
358 | char cpu_string[80]; |
359 | if (!svgfile) |
360 | return; |
361 | |
362 | max_freq = __max_freq; |
363 | turbo_frequency = __turbo_freq; |
364 | |
365 | fprintf(svgfile, "<g>\n" ); |
366 | |
367 | fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"cpu\"/>\n" , |
368 | time2pixels(time: first_time), |
369 | time2pixels(time: last_time)-time2pixels(time: first_time), |
370 | cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT); |
371 | |
372 | sprintf(buf: cpu_string, fmt: "CPU %i" , (int)cpu); |
373 | fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\">%s</text>\n" , |
374 | 10+time2pixels(time: first_time), cpu2y(cpu) + SLOT_HEIGHT/2, cpu_string); |
375 | |
376 | fprintf(svgfile, "<text transform=\"translate(%.8f,%.8f)\" font-size=\"1.25pt\">%s</text>\n" , |
377 | 10+time2pixels(time: first_time), cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - 4, cpu_model()); |
378 | |
379 | fprintf(svgfile, "</g>\n" ); |
380 | } |
381 | |
382 | void svg_process(int cpu, u64 start, u64 end, int pid, const char *name, const char *backtrace) |
383 | { |
384 | double width; |
385 | const char *type; |
386 | |
387 | if (!svgfile) |
388 | return; |
389 | |
390 | if (svg_highlight && end - start >= svg_highlight) |
391 | type = "sample_hi" ; |
392 | else if (svg_highlight_name && strstr(name, svg_highlight_name)) |
393 | type = "sample_hi" ; |
394 | else |
395 | type = "sample" ; |
396 | |
397 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\">\n" , time2pixels(time: start), cpu2y(cpu)); |
398 | fprintf(svgfile, "<title>%d %s running %s</title>\n" , pid, name, time_to_string(duration: end - start)); |
399 | if (backtrace) |
400 | fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n" , backtrace); |
401 | fprintf(svgfile, "<rect x=\"0\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n" , |
402 | time2pixels(time: end)-time2pixels(time: start), SLOT_MULT+SLOT_HEIGHT, type); |
403 | width = time2pixels(time: end)-time2pixels(time: start); |
404 | if (width > 6) |
405 | width = 6; |
406 | |
407 | width = round_text_size(size: width); |
408 | |
409 | if (width > MIN_TEXT_SIZE) |
410 | fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%.8fpt\">%s</text>\n" , |
411 | width, name); |
412 | |
413 | fprintf(svgfile, "</g>\n" ); |
414 | } |
415 | |
416 | void svg_cstate(int cpu, u64 start, u64 end, int type) |
417 | { |
418 | double width; |
419 | char style[128]; |
420 | |
421 | if (!svgfile) |
422 | return; |
423 | |
424 | |
425 | fprintf(svgfile, "<g>\n" ); |
426 | |
427 | if (type > 6) |
428 | type = 6; |
429 | sprintf(buf: style, fmt: "c%i" , type); |
430 | |
431 | fprintf(svgfile, "<rect class=\"%s\" x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\"/>\n" , |
432 | style, |
433 | time2pixels(time: start), time2pixels(time: end)-time2pixels(time: start), |
434 | cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT); |
435 | |
436 | width = (time2pixels(time: end)-time2pixels(time: start))/2.0; |
437 | if (width > 6) |
438 | width = 6; |
439 | |
440 | width = round_text_size(size: width); |
441 | |
442 | if (width > MIN_TEXT_SIZE) |
443 | fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"%.8fpt\">C%i</text>\n" , |
444 | time2pixels(time: start), cpu2y(cpu)+width, width, type); |
445 | |
446 | fprintf(svgfile, "</g>\n" ); |
447 | } |
448 | |
449 | static char *HzToHuman(unsigned long hz) |
450 | { |
451 | static char buffer[1024]; |
452 | unsigned long long Hz; |
453 | |
454 | memset(buffer, 0, 1024); |
455 | |
456 | Hz = hz; |
457 | |
458 | /* default: just put the Number in */ |
459 | sprintf(buf: buffer, fmt: "%9lli" , Hz); |
460 | |
461 | if (Hz > 1000) |
462 | sprintf(buf: buffer, fmt: " %6lli Mhz" , (Hz+500)/1000); |
463 | |
464 | if (Hz > 1500000) |
465 | sprintf(buf: buffer, fmt: " %6.2f Ghz" , (Hz+5000.0)/1000000); |
466 | |
467 | if (Hz == turbo_frequency) |
468 | sprintf(buf: buffer, fmt: "Turbo" ); |
469 | |
470 | return buffer; |
471 | } |
472 | |
473 | void svg_pstate(int cpu, u64 start, u64 end, u64 freq) |
474 | { |
475 | double height = 0; |
476 | |
477 | if (!svgfile) |
478 | return; |
479 | |
480 | fprintf(svgfile, "<g>\n" ); |
481 | |
482 | if (max_freq) |
483 | height = freq * 1.0 / max_freq * (SLOT_HEIGHT + SLOT_MULT); |
484 | height = 1 + cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - height; |
485 | fprintf(svgfile, "<line x1=\"%.8f\" x2=\"%.8f\" y1=\"%.1f\" y2=\"%.1f\" class=\"pstate\"/>\n" , |
486 | time2pixels(time: start), time2pixels(time: end), height, height); |
487 | fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"0.25pt\">%s</text>\n" , |
488 | time2pixels(time: start), height+0.9, HzToHuman(hz: freq)); |
489 | |
490 | fprintf(svgfile, "</g>\n" ); |
491 | } |
492 | |
493 | |
494 | void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2, const char *backtrace) |
495 | { |
496 | double height; |
497 | |
498 | if (!svgfile) |
499 | return; |
500 | |
501 | |
502 | fprintf(svgfile, "<g>\n" ); |
503 | |
504 | fprintf(svgfile, "<title>%s wakes up %s</title>\n" , |
505 | desc1 ? desc1 : "?" , |
506 | desc2 ? desc2 : "?" ); |
507 | |
508 | if (backtrace) |
509 | fprintf(svgfile, "<desc>%s</desc>\n" , backtrace); |
510 | |
511 | if (row1 < row2) { |
512 | if (row1) { |
513 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
514 | time2pixels(time: start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(time: start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32); |
515 | if (desc2) |
516 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n" , |
517 | time2pixels(time: start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_HEIGHT/48, desc2); |
518 | } |
519 | if (row2) { |
520 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
521 | time2pixels(time: start), row2 * SLOT_MULT - SLOT_MULT/32, time2pixels(time: start), row2 * SLOT_MULT); |
522 | if (desc1) |
523 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n" , |
524 | time2pixels(time: start), row2 * SLOT_MULT - SLOT_MULT/32, desc1); |
525 | } |
526 | } else { |
527 | if (row2) { |
528 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
529 | time2pixels(time: start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(time: start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32); |
530 | if (desc1) |
531 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n" , |
532 | time2pixels(time: start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/48, desc1); |
533 | } |
534 | if (row1) { |
535 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
536 | time2pixels(time: start), row1 * SLOT_MULT - SLOT_MULT/32, time2pixels(time: start), row1 * SLOT_MULT); |
537 | if (desc2) |
538 | fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n" , |
539 | time2pixels(time: start), row1 * SLOT_MULT - SLOT_HEIGHT/32, desc2); |
540 | } |
541 | } |
542 | height = row1 * SLOT_MULT; |
543 | if (row2 > row1) |
544 | height += SLOT_HEIGHT; |
545 | if (row1) |
546 | fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n" , |
547 | time2pixels(time: start), height); |
548 | |
549 | fprintf(svgfile, "</g>\n" ); |
550 | } |
551 | |
552 | void svg_wakeline(u64 start, int row1, int row2, const char *backtrace) |
553 | { |
554 | double height; |
555 | |
556 | if (!svgfile) |
557 | return; |
558 | |
559 | |
560 | fprintf(svgfile, "<g>\n" ); |
561 | |
562 | if (backtrace) |
563 | fprintf(svgfile, "<desc>%s</desc>\n" , backtrace); |
564 | |
565 | if (row1 < row2) |
566 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
567 | time2pixels(time: start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(time: start), row2 * SLOT_MULT); |
568 | else |
569 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n" , |
570 | time2pixels(time: start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(time: start), row1 * SLOT_MULT); |
571 | |
572 | height = row1 * SLOT_MULT; |
573 | if (row2 > row1) |
574 | height += SLOT_HEIGHT; |
575 | fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n" , |
576 | time2pixels(time: start), height); |
577 | |
578 | fprintf(svgfile, "</g>\n" ); |
579 | } |
580 | |
581 | void svg_interrupt(u64 start, int row, const char *backtrace) |
582 | { |
583 | if (!svgfile) |
584 | return; |
585 | |
586 | fprintf(svgfile, "<g>\n" ); |
587 | |
588 | fprintf(svgfile, "<title>Wakeup from interrupt</title>\n" ); |
589 | |
590 | if (backtrace) |
591 | fprintf(svgfile, "<desc>%s</desc>\n" , backtrace); |
592 | |
593 | fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n" , |
594 | time2pixels(time: start), row * SLOT_MULT); |
595 | fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n" , |
596 | time2pixels(time: start), row * SLOT_MULT + SLOT_HEIGHT); |
597 | |
598 | fprintf(svgfile, "</g>\n" ); |
599 | } |
600 | |
601 | void svg_text(int Yslot, u64 start, const char *text) |
602 | { |
603 | if (!svgfile) |
604 | return; |
605 | |
606 | fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\">%s</text>\n" , |
607 | time2pixels(time: start), Yslot * SLOT_MULT+SLOT_HEIGHT/2, text); |
608 | } |
609 | |
610 | static void svg_legenda_box(int X, const char *text, const char *style) |
611 | { |
612 | double boxsize; |
613 | boxsize = SLOT_HEIGHT / 2; |
614 | |
615 | fprintf(svgfile, "<rect x=\"%i\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n" , |
616 | X, boxsize, boxsize, style); |
617 | fprintf(svgfile, "<text transform=\"translate(%.8f, %.8f)\" font-size=\"%.8fpt\">%s</text>\n" , |
618 | X + boxsize + 5, boxsize, 0.8 * boxsize, text); |
619 | } |
620 | |
621 | void svg_io_legenda(void) |
622 | { |
623 | if (!svgfile) |
624 | return; |
625 | |
626 | fprintf(svgfile, "<g>\n" ); |
627 | svg_legenda_box(X: 0, text: "Disk" , style: "disk" ); |
628 | svg_legenda_box(X: 100, text: "Network" , style: "net" ); |
629 | svg_legenda_box(X: 200, text: "Sync" , style: "sync" ); |
630 | svg_legenda_box(X: 300, text: "Poll" , style: "poll" ); |
631 | svg_legenda_box(X: 400, text: "Error" , style: "error" ); |
632 | fprintf(svgfile, "</g>\n" ); |
633 | } |
634 | |
635 | void svg_legenda(void) |
636 | { |
637 | if (!svgfile) |
638 | return; |
639 | |
640 | fprintf(svgfile, "<g>\n" ); |
641 | svg_legenda_box(X: 0, text: "Running" , style: "sample" ); |
642 | svg_legenda_box(X: 100, text: "Idle" ,style: "c1" ); |
643 | svg_legenda_box(X: 200, text: "Deeper Idle" , style: "c3" ); |
644 | svg_legenda_box(X: 350, text: "Deepest Idle" , style: "c6" ); |
645 | svg_legenda_box(X: 550, text: "Sleeping" , style: "process2" ); |
646 | svg_legenda_box(X: 650, text: "Waiting for cpu" , style: "waiting" ); |
647 | svg_legenda_box(X: 800, text: "Blocked on IO" , style: "blocked" ); |
648 | fprintf(svgfile, "</g>\n" ); |
649 | } |
650 | |
651 | void svg_time_grid(double min_thickness) |
652 | { |
653 | u64 i; |
654 | |
655 | if (!svgfile) |
656 | return; |
657 | |
658 | i = first_time; |
659 | while (i < last_time) { |
660 | int color = 220; |
661 | double thickness = 0.075; |
662 | if ((i % 100000000) == 0) { |
663 | thickness = 0.5; |
664 | color = 192; |
665 | } |
666 | if ((i % 1000000000) == 0) { |
667 | thickness = 2.0; |
668 | color = 128; |
669 | } |
670 | |
671 | if (thickness >= min_thickness) |
672 | fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%" PRIu64 "\" style=\"stroke:rgb(%i,%i,%i);stroke-width:%.3f\"/>\n" , |
673 | time2pixels(i), SLOT_MULT/2, time2pixels(i), |
674 | total_height, color, color, color, thickness); |
675 | |
676 | i += 10000000; |
677 | } |
678 | } |
679 | |
680 | void svg_close(void) |
681 | { |
682 | if (svgfile) { |
683 | fprintf(svgfile, "</svg>\n" ); |
684 | fclose(svgfile); |
685 | svgfile = NULL; |
686 | } |
687 | } |
688 | |
689 | #define cpumask_bits(maskp) ((maskp)->bits) |
690 | typedef struct { DECLARE_BITMAP(bits, MAX_NR_CPUS); } cpumask_t; |
691 | |
692 | struct topology { |
693 | cpumask_t *sib_core; |
694 | int sib_core_nr; |
695 | cpumask_t *sib_thr; |
696 | int sib_thr_nr; |
697 | }; |
698 | |
699 | static void scan_thread_topology(int *map, struct topology *t, int cpu, |
700 | int *pos, int nr_cpus) |
701 | { |
702 | int i; |
703 | int thr; |
704 | |
705 | for (i = 0; i < t->sib_thr_nr; i++) { |
706 | if (!test_bit(cpu, cpumask_bits(&t->sib_thr[i]))) |
707 | continue; |
708 | |
709 | for_each_set_bit(thr, cpumask_bits(&t->sib_thr[i]), nr_cpus) |
710 | if (map[thr] == -1) |
711 | map[thr] = (*pos)++; |
712 | } |
713 | } |
714 | |
715 | static void scan_core_topology(int *map, struct topology *t, int nr_cpus) |
716 | { |
717 | int pos = 0; |
718 | int i; |
719 | int cpu; |
720 | |
721 | for (i = 0; i < t->sib_core_nr; i++) |
722 | for_each_set_bit(cpu, cpumask_bits(&t->sib_core[i]), nr_cpus) |
723 | scan_thread_topology(map, t, cpu, pos: &pos, nr_cpus); |
724 | } |
725 | |
726 | static int str_to_bitmap(char *s, cpumask_t *b, int nr_cpus) |
727 | { |
728 | int i; |
729 | int ret = 0; |
730 | struct perf_cpu_map *m; |
731 | struct perf_cpu c; |
732 | |
733 | m = perf_cpu_map__new(s); |
734 | if (!m) |
735 | return -1; |
736 | |
737 | for (i = 0; i < perf_cpu_map__nr(m); i++) { |
738 | c = perf_cpu_map__cpu(m, i); |
739 | if (c.cpu >= nr_cpus) { |
740 | ret = -1; |
741 | break; |
742 | } |
743 | |
744 | __set_bit(c.cpu, cpumask_bits(b)); |
745 | } |
746 | |
747 | perf_cpu_map__put(m); |
748 | |
749 | return ret; |
750 | } |
751 | |
752 | int svg_build_topology_map(struct perf_env *env) |
753 | { |
754 | int i, nr_cpus; |
755 | struct topology t; |
756 | char *sib_core, *sib_thr; |
757 | int ret = -1; |
758 | |
759 | nr_cpus = min(env->nr_cpus_online, MAX_NR_CPUS); |
760 | |
761 | t.sib_core_nr = env->nr_sibling_cores; |
762 | t.sib_thr_nr = env->nr_sibling_threads; |
763 | t.sib_core = calloc(env->nr_sibling_cores, sizeof(cpumask_t)); |
764 | t.sib_thr = calloc(env->nr_sibling_threads, sizeof(cpumask_t)); |
765 | |
766 | sib_core = env->sibling_cores; |
767 | sib_thr = env->sibling_threads; |
768 | |
769 | if (!t.sib_core || !t.sib_thr) { |
770 | fprintf(stderr, "topology: no memory\n" ); |
771 | goto exit; |
772 | } |
773 | |
774 | for (i = 0; i < env->nr_sibling_cores; i++) { |
775 | if (str_to_bitmap(s: sib_core, b: &t.sib_core[i], nr_cpus)) { |
776 | fprintf(stderr, "topology: can't parse siblings map\n" ); |
777 | goto exit; |
778 | } |
779 | |
780 | sib_core += strlen(sib_core) + 1; |
781 | } |
782 | |
783 | for (i = 0; i < env->nr_sibling_threads; i++) { |
784 | if (str_to_bitmap(s: sib_thr, b: &t.sib_thr[i], nr_cpus)) { |
785 | fprintf(stderr, "topology: can't parse siblings map\n" ); |
786 | goto exit; |
787 | } |
788 | |
789 | sib_thr += strlen(sib_thr) + 1; |
790 | } |
791 | |
792 | topology_map = malloc(sizeof(int) * nr_cpus); |
793 | if (!topology_map) { |
794 | fprintf(stderr, "topology: no memory\n" ); |
795 | goto exit; |
796 | } |
797 | |
798 | for (i = 0; i < nr_cpus; i++) |
799 | topology_map[i] = -1; |
800 | |
801 | scan_core_topology(map: topology_map, t: &t, nr_cpus); |
802 | |
803 | ret = 0; |
804 | |
805 | exit: |
806 | zfree(&t.sib_core); |
807 | zfree(&t.sib_thr); |
808 | |
809 | return ret; |
810 | } |
811 | |