1 | /* Generate graphic from memory profiling data. |
2 | Copyright (C) 1998-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | This program is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published |
7 | by the Free Software Foundation; version 2 of the License, or |
8 | (at your option) any later version. |
9 | |
10 | This program is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | GNU General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU General Public License |
16 | along with this program; if not, see <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #define _FILE_OFFSET_BITS 64 |
19 | |
20 | #include <argp.h> |
21 | #include <assert.h> |
22 | #include <errno.h> |
23 | #include <error.h> |
24 | #include <fcntl.h> |
25 | #include <getopt.h> |
26 | #include <inttypes.h> |
27 | #include <libintl.h> |
28 | #include <stdio.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | #include <unistd.h> |
32 | #include <unistd_ext.h> |
33 | #include <stdint.h> |
34 | #include <sys/param.h> |
35 | #include <sys/stat.h> |
36 | |
37 | #include <gd.h> |
38 | #include <gdfontl.h> |
39 | #include <gdfonts.h> |
40 | |
41 | #include "../version.h" |
42 | #define PACKAGE _libc_intl_domainname |
43 | |
44 | /* Default size of the generated image. */ |
45 | #define XSIZE 800 |
46 | #define YSIZE 600 |
47 | |
48 | #ifndef N_ |
49 | # define N_(Arg) Arg |
50 | #endif |
51 | |
52 | |
53 | /* Definitions of arguments for argp functions. */ |
54 | static const struct argp_option options[] = |
55 | { |
56 | { "output" , 'o', N_ ("FILE" ), 0, N_ ("Name output file" ) }, |
57 | { "string" , 's', N_ ("STRING" ), 0, N_ ("Title string used in output graphic" ) }, |
58 | { "time" , 't', NULL, 0, N_ ("\ |
59 | Generate output linear to time (default is linear to number of function calls)\ |
60 | " ) }, |
61 | { "total" , 'T', NULL, 0, |
62 | N_ ("Also draw graph for total memory consumption" ) }, |
63 | { "x-size" , 'x', N_ ("VALUE" ), 0, |
64 | N_ ("Make output graphic VALUE pixels wide" ) }, |
65 | { "y-size" , 'y', "VALUE" , 0, N_ ("Make output graphic VALUE pixels high" ) }, |
66 | { NULL, 0, NULL, 0, NULL } |
67 | }; |
68 | |
69 | /* Short description of program. */ |
70 | static const char doc[] = N_ ("Generate graphic from memory profiling data" ); |
71 | |
72 | /* Strings for arguments in help texts. */ |
73 | static const char args_doc[] = N_ ("DATAFILE [OUTFILE]" ); |
74 | |
75 | /* Prototype for option handler. */ |
76 | static error_t parse_opt (int key, char *arg, struct argp_state *state); |
77 | |
78 | /* Function to print some extra text in the help message. */ |
79 | static char *more_help (int key, const char *text, void *input); |
80 | |
81 | /* Name and version of program. */ |
82 | static void print_version (FILE *stream, struct argp_state *state); |
83 | void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; |
84 | |
85 | /* Data structure to communicate with argp functions. */ |
86 | static struct argp argp = |
87 | { |
88 | options, parse_opt, args_doc, doc, NULL, more_help |
89 | }; |
90 | |
91 | |
92 | struct entry |
93 | { |
94 | uint64_t heap; |
95 | uint64_t stack; |
96 | uint32_t time_low; |
97 | uint32_t time_high; |
98 | }; |
99 | |
100 | |
101 | /* Size of the image. */ |
102 | static size_t xsize; |
103 | static size_t ysize; |
104 | |
105 | /* Name of the output file. */ |
106 | static char *outname; |
107 | |
108 | /* Title string for the graphic. */ |
109 | static const char *string; |
110 | |
111 | /* Nonzero if graph should be generated linear in time. */ |
112 | static int time_based; |
113 | |
114 | /* Nonzero if graph to display total use of memory should be drawn as well. */ |
115 | static int also_total = 0; |
116 | |
117 | |
118 | int |
119 | main (int argc, char *argv[]) |
120 | { |
121 | int remaining; |
122 | const char *inname; |
123 | gdImagePtr im_out; |
124 | int grey, blue, red, green, yellow, black; |
125 | int fd; |
126 | struct stat st; |
127 | size_t maxsize_heap; |
128 | size_t maxsize_stack; |
129 | size_t maxsize_total; |
130 | uint64_t total; |
131 | uint64_t cnt, cnt2; |
132 | FILE *outfile; |
133 | char buf[30]; |
134 | size_t last_heap; |
135 | size_t last_stack; |
136 | size_t last_total; |
137 | struct entry headent[2]; |
138 | uint64_t start_time; |
139 | uint64_t end_time; |
140 | uint64_t total_time; |
141 | const char *heap_format, *stack_format; |
142 | int heap_scale, stack_scale, line; |
143 | |
144 | outname = NULL; |
145 | xsize = XSIZE; |
146 | ysize = YSIZE; |
147 | string = NULL; |
148 | |
149 | /* Parse and process arguments. */ |
150 | argp_parse (argp: &argp, argc: argc, argv: argv, flags: 0, arg_index: &remaining, NULL); |
151 | |
152 | if (remaining >= argc || remaining + 2 < argc) |
153 | { |
154 | argp_help (argp: &argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR, |
155 | name: program_invocation_short_name); |
156 | exit (status: 1); |
157 | } |
158 | |
159 | inname = argv[remaining++]; |
160 | |
161 | if (remaining < argc) |
162 | outname = argv[remaining]; |
163 | else if (outname == NULL) |
164 | { |
165 | size_t len = strlen (s: inname); |
166 | outname = alloca (len + 5); |
167 | stpcpy (stpcpy (outname, inname), ".png" ); |
168 | } |
169 | |
170 | /* Open for read/write since we try to repair the file in case the |
171 | application hasn't terminated cleanly. */ |
172 | fd = open (file: inname, O_RDWR); |
173 | if (fd == -1) |
174 | error (EXIT_FAILURE, errno, format: "cannot open input file" ); |
175 | if (fstat (fd: fd, buf: &st) != 0) |
176 | { |
177 | close (fd: fd); |
178 | error (EXIT_FAILURE, errno, format: "cannot get size of input file" ); |
179 | } |
180 | /* Test whether the file contains only full records. */ |
181 | if ((st.st_size % sizeof (struct entry)) != 0 |
182 | /* The file must at least contain the two administrative records. */ |
183 | || st.st_size < 2 * sizeof (struct entry)) |
184 | { |
185 | close (fd: fd); |
186 | error (EXIT_FAILURE, errnum: 0, format: "input file has incorrect size" ); |
187 | } |
188 | /* Compute number of data entries. */ |
189 | total = st.st_size / sizeof (struct entry) - 2; |
190 | |
191 | /* Read the administrative information. */ |
192 | read_all (fd, buffer: headent, length: sizeof (headent)); |
193 | maxsize_heap = headent[1].heap; |
194 | maxsize_stack = headent[1].stack; |
195 | maxsize_total = headent[0].stack; |
196 | |
197 | if (maxsize_heap == 0 && maxsize_stack == 0) |
198 | { |
199 | /* The program aborted before memusage was able to write the |
200 | information about the maximum heap and stack use. Repair |
201 | the file now. */ |
202 | struct entry next; |
203 | |
204 | while (1) |
205 | { |
206 | if (read (fd: fd, buf: &next, nbytes: sizeof (next)) == 0) |
207 | break; |
208 | if (next.heap > maxsize_heap) |
209 | maxsize_heap = next.heap; |
210 | if (next.stack > maxsize_stack) |
211 | maxsize_stack = next.stack; |
212 | if (maxsize_heap + maxsize_stack > maxsize_total) |
213 | maxsize_total = maxsize_heap + maxsize_stack; |
214 | } |
215 | |
216 | headent[0].stack = maxsize_total; |
217 | headent[1].heap = maxsize_heap; |
218 | headent[1].stack = maxsize_stack; |
219 | headent[1].time_low = next.time_low; |
220 | headent[1].time_high = next.time_high; |
221 | |
222 | /* Write the computed values in the file. */ |
223 | lseek (fd: fd, offset: 0, SEEK_SET); |
224 | write_all (fd, buffer: headent, length: sizeof (headent)); |
225 | |
226 | } |
227 | |
228 | if (also_total) |
229 | { |
230 | /* We use one scale and since we also draw the total amount of |
231 | memory used we have to adapt the maximum. */ |
232 | maxsize_heap = maxsize_total; |
233 | maxsize_stack = maxsize_total; |
234 | } |
235 | |
236 | start_time = ((uint64_t) headent[0].time_high) << 32 | headent[0].time_low; |
237 | end_time = ((uint64_t) headent[1].time_high) << 32 | headent[1].time_low; |
238 | total_time = end_time - start_time; |
239 | |
240 | if (xsize < 100) |
241 | xsize = 100; |
242 | if (ysize < 80) |
243 | ysize = 80; |
244 | |
245 | /* Create output image with the specified size. */ |
246 | im_out = gdImageCreate (xsize, ysize); |
247 | |
248 | /* First color allocated is background. */ |
249 | grey = gdImageColorAllocate (im_out, 224, 224, 224); |
250 | |
251 | /* Set transparent color. */ |
252 | gdImageColorTransparent (im_out, grey); |
253 | |
254 | /* These are all the other colors we need (in the moment). */ |
255 | red = gdImageColorAllocate (im_out, 255, 0, 0); |
256 | green = gdImageColorAllocate (im_out, 0, 130, 0); |
257 | blue = gdImageColorAllocate (im_out, 0, 0, 255); |
258 | yellow = gdImageColorAllocate (im_out, 154, 205, 50); |
259 | black = gdImageColorAllocate (im_out, 0, 0, 0); |
260 | |
261 | gdImageRectangle (im_out, 40, 20, xsize - 40, ysize - 20, blue); |
262 | |
263 | if (maxsize_heap < 1024) |
264 | { |
265 | heap_format = "%Zu" ; |
266 | heap_scale = 1; |
267 | } |
268 | else if (maxsize_heap < 1024 * 1024 * 100) |
269 | { |
270 | heap_format = "%Zuk" ; |
271 | heap_scale = 1024; |
272 | } |
273 | else |
274 | { |
275 | heap_format = "%ZuM" ; |
276 | heap_scale = 1024 * 1024; |
277 | } |
278 | |
279 | if (maxsize_stack < 1024) |
280 | { |
281 | stack_format = "%Zu" ; |
282 | stack_scale = 1; |
283 | } |
284 | else if (maxsize_stack < 1024 * 1024 * 100) |
285 | { |
286 | stack_format = "%Zuk" ; |
287 | stack_scale = 1024; |
288 | } |
289 | else |
290 | { |
291 | stack_format = "%ZuM" ; |
292 | stack_scale = 1024 * 1024; |
293 | } |
294 | |
295 | gdImageString (im_out, gdFontSmall, 38, ysize - 14, (unsigned char *) "0" , |
296 | blue); |
297 | snprintf (s: buf, maxlen: sizeof (buf), format: heap_format, 0); |
298 | gdImageString (im_out, gdFontSmall, maxsize_heap < 1024 ? 32 : 26, |
299 | ysize - 26, (unsigned char *) buf, red); |
300 | snprintf (s: buf, maxlen: sizeof (buf), format: stack_format, 0); |
301 | gdImageString (im_out, gdFontSmall, xsize - 37, ysize - 26, |
302 | (unsigned char *) buf, green); |
303 | |
304 | if (string != NULL) |
305 | gdImageString (im_out, gdFontLarge, (xsize - strlen (s: string) * 8) / 2, |
306 | 2, (unsigned char *) string, green); |
307 | |
308 | gdImageStringUp (im_out, gdFontSmall, 1, ysize / 2 - 10, |
309 | (unsigned char *) "allocated" , red); |
310 | gdImageStringUp (im_out, gdFontSmall, 11, ysize / 2 - 10, |
311 | (unsigned char *) "memory" , red); |
312 | |
313 | gdImageStringUp (im_out, gdFontSmall, xsize - 39, ysize / 2 - 10, |
314 | (unsigned char *) "used" , green); |
315 | gdImageStringUp (im_out, gdFontSmall, xsize - 27, ysize / 2 - 10, |
316 | (unsigned char *) "stack" , green); |
317 | |
318 | snprintf (s: buf, maxlen: sizeof (buf), format: heap_format, maxsize_heap / heap_scale); |
319 | gdImageString (im_out, gdFontSmall, 39 - strlen (s: buf) * 6, 14, |
320 | (unsigned char *) buf, red); |
321 | snprintf (s: buf, maxlen: sizeof (buf), format: stack_format, maxsize_stack / stack_scale); |
322 | gdImageString (im_out, gdFontSmall, xsize - 37, 14, |
323 | (unsigned char *) buf, green); |
324 | |
325 | for (line = 1; line <= 3; ++line) |
326 | { |
327 | if (maxsize_heap > 0) |
328 | { |
329 | cnt = (((ysize - 40) * (maxsize_heap / 4 * line / heap_scale)) |
330 | / (maxsize_heap / heap_scale)); |
331 | gdImageDashedLine (im_out, 40, ysize - 20 - cnt, xsize - 40, |
332 | ysize - 20 - cnt, red); |
333 | snprintf (s: buf, maxlen: sizeof (buf), format: heap_format, |
334 | maxsize_heap / 4 * line / heap_scale); |
335 | gdImageString (im_out, gdFontSmall, 39 - strlen (s: buf) * 6, |
336 | ysize - 26 - cnt, (unsigned char *) buf, red); |
337 | } |
338 | else |
339 | cnt = 0; |
340 | |
341 | if (maxsize_stack > 0) |
342 | cnt2 = (((ysize - 40) * (maxsize_stack / 4 * line / stack_scale)) |
343 | / (maxsize_stack / stack_scale)); |
344 | else |
345 | cnt2 = 0; |
346 | |
347 | if (cnt != cnt2) |
348 | gdImageDashedLine (im_out, 40, ysize - 20 - cnt2, xsize - 40, |
349 | ysize - 20 - cnt2, green); |
350 | snprintf (s: buf, maxlen: sizeof (buf), format: stack_format, |
351 | maxsize_stack / 4 * line / stack_scale); |
352 | gdImageString (im_out, gdFontSmall, xsize - 37, ysize - 26 - cnt2, |
353 | (unsigned char *) buf, green); |
354 | } |
355 | |
356 | snprintf (s: buf, maxlen: sizeof (buf), format: "%llu" , (unsigned long long) total); |
357 | gdImageString (im_out, gdFontSmall, xsize - 50, ysize - 14, |
358 | (unsigned char *) buf, blue); |
359 | |
360 | if (!time_based) |
361 | { |
362 | uint64_t previously = start_time; |
363 | |
364 | gdImageString (im_out, gdFontSmall, 40 + (xsize - 32 * 6 - 80) / 2, |
365 | ysize - 12, |
366 | (unsigned char *) "# memory handling function calls" , |
367 | blue); |
368 | |
369 | |
370 | last_stack = last_heap = last_total = ysize - 20; |
371 | for (cnt = 1; cnt <= total; ++cnt) |
372 | { |
373 | struct entry entry; |
374 | size_t new[2]; |
375 | uint64_t now; |
376 | |
377 | read_all (fd, buffer: &entry, length: sizeof (entry)); |
378 | |
379 | now = ((uint64_t) entry.time_high) << 32 | entry.time_low; |
380 | |
381 | if ((((previously - start_time) * 100) / total_time) % 10 < 5) |
382 | gdImageFilledRectangle (im_out, |
383 | 40 + ((cnt - 1) * (xsize - 80)) / total, |
384 | ysize - 19, |
385 | 39 + (cnt * (xsize - 80)) / total, |
386 | ysize - 14, yellow); |
387 | previously = now; |
388 | |
389 | if (also_total && maxsize_heap > 0) |
390 | { |
391 | size_t new3; |
392 | |
393 | new3 = (ysize - 20) - ((((unsigned long long int) (ysize - 40)) |
394 | * (entry.heap + entry.stack)) |
395 | / maxsize_heap); |
396 | gdImageLine (im_out, 40 + ((xsize - 80) * (cnt - 1)) / total, |
397 | last_total, |
398 | 40 + ((xsize - 80) * cnt) / total, new3, |
399 | black); |
400 | last_total = new3; |
401 | } |
402 | |
403 | if (maxsize_heap > 0) |
404 | { |
405 | new[0] = ((ysize - 20) |
406 | - ((((unsigned long long int) (ysize - 40)) |
407 | * entry.heap) / maxsize_heap)); |
408 | gdImageLine (im_out, 40 + ((xsize - 80) * (cnt - 1)) / total, |
409 | last_heap, 40 + ((xsize - 80) * cnt) / total, |
410 | new[0], red); |
411 | last_heap = new[0]; |
412 | } |
413 | |
414 | if (maxsize_stack > 0) |
415 | { |
416 | new[1] = ((ysize - 20) |
417 | - ((((unsigned long long int) (ysize - 40)) |
418 | * entry.stack) / maxsize_stack)); |
419 | gdImageLine (im_out, 40 + ((xsize - 80) * (cnt - 1)) / total, |
420 | last_stack, 40 + ((xsize - 80) * cnt) / total, |
421 | new[1], green); |
422 | last_stack = new[1]; |
423 | } |
424 | } |
425 | |
426 | cnt = 0; |
427 | while (cnt < total) |
428 | { |
429 | gdImageLine (im_out, 40 + ((xsize - 80) * cnt) / total, ysize - 20, |
430 | 40 + ((xsize - 80) * cnt) / total, ysize - 15, blue); |
431 | cnt += MAX (1, total / 20); |
432 | } |
433 | gdImageLine (im_out, xsize - 40, ysize - 20, xsize - 40, ysize - 15, |
434 | blue); |
435 | } |
436 | else |
437 | { |
438 | uint64_t next_tick = MAX (1, total / 20); |
439 | size_t last_xpos = 40; |
440 | |
441 | gdImageString (im_out, gdFontSmall, 40 + (xsize - 39 * 6 - 80) / 2, |
442 | ysize - 12, |
443 | (unsigned char *) " \ |
444 | # memory handling function calls / time" , blue); |
445 | |
446 | for (cnt = 0; cnt < 20; cnt += 2) |
447 | gdImageFilledRectangle (im_out, |
448 | 40 + (cnt * (xsize - 80)) / 20, ysize - 19, |
449 | 39 + ((cnt + 1) * (xsize - 80)) / 20, |
450 | ysize - 14, yellow); |
451 | |
452 | last_stack = last_heap = last_total = ysize - 20; |
453 | for (cnt = 1; cnt <= total; ++cnt) |
454 | { |
455 | struct entry entry; |
456 | size_t new[2]; |
457 | size_t xpos; |
458 | uint64_t now; |
459 | |
460 | read_all (fd, buffer: &entry, length: sizeof (entry)); |
461 | |
462 | now = ((uint64_t) entry.time_high) << 32 | entry.time_low; |
463 | xpos = 40 + ((xsize - 80) * (now - start_time)) / total_time; |
464 | |
465 | if (cnt == next_tick) |
466 | { |
467 | gdImageLine (im_out, xpos, ysize - 20, xpos, ysize - 15, blue); |
468 | next_tick += MAX (1, total / 20); |
469 | } |
470 | |
471 | if (also_total && maxsize_heap > 0) |
472 | { |
473 | size_t new3; |
474 | |
475 | new3 = (ysize - 20) - ((((unsigned long long int) (ysize - 40)) |
476 | * (entry.heap + entry.stack)) |
477 | / maxsize_heap); |
478 | gdImageLine (im_out, last_xpos, last_total, xpos, new3, black); |
479 | last_total = new3; |
480 | } |
481 | |
482 | if (maxsize_heap > 0) |
483 | { |
484 | new[0] = ((ysize - 20) |
485 | - ((((unsigned long long int) (ysize - 40)) |
486 | * entry.heap) / maxsize_heap)); |
487 | gdImageLine (im_out, last_xpos, last_heap, xpos, new[0], red); |
488 | last_heap = new[0]; |
489 | } |
490 | |
491 | if (maxsize_stack > 0) |
492 | { |
493 | new[1] = ((ysize - 20) |
494 | - ((((unsigned long long int) (ysize - 40)) |
495 | * entry.stack) / maxsize_stack)); |
496 | gdImageLine (im_out, last_xpos, last_stack, xpos, new[1], |
497 | green); |
498 | last_stack = new[1]; |
499 | } |
500 | |
501 | last_xpos = xpos; |
502 | } |
503 | } |
504 | |
505 | /* Write out the result. */ |
506 | outfile = fopen (filename: outname, modes: "w" ); |
507 | if (outfile == NULL) |
508 | error (EXIT_FAILURE, errno, format: "cannot open output file" ); |
509 | |
510 | gdImagePng (im_out, outfile); |
511 | |
512 | fclose (stream: outfile); |
513 | |
514 | gdImageDestroy (im_out); |
515 | |
516 | return 0; |
517 | } |
518 | |
519 | |
520 | /* Handle program arguments. */ |
521 | static error_t |
522 | parse_opt (int key, char *arg, struct argp_state *state) |
523 | { |
524 | switch (key) |
525 | { |
526 | case 'o': |
527 | outname = arg; |
528 | break; |
529 | case 's': |
530 | string = arg; |
531 | break; |
532 | case 't': |
533 | time_based = 1; |
534 | break; |
535 | case 'T': |
536 | also_total = 1; |
537 | break; |
538 | case 'x': |
539 | xsize = atoi (arg); |
540 | if (xsize == 0) |
541 | xsize = XSIZE; |
542 | break; |
543 | case 'y': |
544 | ysize = atoi (arg); |
545 | if (ysize == 0) |
546 | ysize = XSIZE; |
547 | break; |
548 | default: |
549 | return ARGP_ERR_UNKNOWN; |
550 | } |
551 | return 0; |
552 | } |
553 | |
554 | |
555 | static char * |
556 | more_help (int key, const char *text, void *input) |
557 | { |
558 | char *tp; |
559 | |
560 | switch (key) |
561 | { |
562 | case ARGP_KEY_HELP_EXTRA: |
563 | /* We print some extra information. */ |
564 | if (asprintf (ptr: &tp, gettext ("\ |
565 | For bug reporting instructions, please see:\n\ |
566 | %s.\n" ), REPORT_BUGS_TO) < 0) |
567 | return NULL; |
568 | |
569 | return tp; |
570 | |
571 | default: |
572 | break; |
573 | } |
574 | return (char *) text; |
575 | } |
576 | |
577 | /* Print the version information. */ |
578 | static void |
579 | print_version (FILE *stream, struct argp_state *state) |
580 | { |
581 | fprintf (stream: stream, format: "memusagestat %s%s\n" , PKGVERSION, VERSION); |
582 | fprintf (stream: stream, gettext ("\ |
583 | Copyright (C) %s Free Software Foundation, Inc.\n\ |
584 | This is free software; see the source for copying conditions. There is NO\n\ |
585 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ |
586 | " ), "2024" ); |
587 | fprintf (stream: stream, gettext ("Written by %s.\n" ), "Ulrich Drepper" ); |
588 | } |
589 | |