1/* Gcc offline profile processing tool support. */
2/* Copyright (C) 2014-2023 Free Software Foundation, Inc.
3 Contributed by Rong Xu <xur@google.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17Under Section 7 of GPL version 3, you are granted additional
18permissions described in the GCC Runtime Library Exception, version
193.1, as published by the Free Software Foundation.
20
21You should have received a copy of the GNU General Public License and
22a copy of the GCC Runtime Library Exception along with this program;
23see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
24<http://www.gnu.org/licenses/>. */
25
26#include "config.h"
27#include "system.h"
28#include "coretypes.h"
29#include "tm.h"
30#include "intl.h"
31#include "diagnostic.h"
32#include "version.h"
33#include "gcov-io.h"
34#include <stdlib.h>
35#include <stdio.h>
36#include <sys/stat.h>
37#include <unistd.h>
38#if HAVE_FTW_H
39#include <ftw.h>
40#endif
41#include <getopt.h>
42
43extern struct gcov_info *gcov_profile_merge (struct gcov_info*,
44 struct gcov_info*, int, int);
45extern struct gcov_info *gcov_profile_merge_stream (const char *, int, int);
46extern int gcov_profile_overlap (struct gcov_info*, struct gcov_info*);
47extern int gcov_profile_normalize (struct gcov_info*, gcov_type);
48extern int gcov_profile_scale (struct gcov_info*, float, int, int);
49extern struct gcov_info* gcov_read_profile_dir (const char*, int);
50extern void gcov_do_dump (struct gcov_info *, int, int);
51extern const char *gcov_get_filename (struct gcov_info *list);
52extern void gcov_set_verbose (void);
53
54/* Set to verbose output mode. */
55static bool verbose;
56
57#if HAVE_FTW_H
58
59/* Remove file NAME if it has a gcda suffix. */
60
61static int
62unlink_gcda_file (const char *name,
63 const struct stat *status ATTRIBUTE_UNUSED,
64 int type ATTRIBUTE_UNUSED,
65 struct FTW *ftwbuf ATTRIBUTE_UNUSED)
66{
67 int ret = 0;
68 int len = strlen (s: name);
69 int len1 = strlen (GCOV_DATA_SUFFIX);
70
71 if (len > len1 && !strncmp (s1: len -len1 + name, GCOV_DATA_SUFFIX, n: len1))
72 ret = remove (filename: name);
73
74 if (ret)
75 fatal_error (input_location, "error in removing %s", name);
76
77 return ret;
78}
79#endif
80
81/* Remove the gcda files in PATH recursively. */
82
83static int
84unlink_profile_dir (const char *path ATTRIBUTE_UNUSED)
85{
86#if HAVE_FTW_H
87 return nftw(dir: path, func: unlink_gcda_file, descriptors: 64, FTW_DEPTH | FTW_PHYS);
88#else
89 return -1;
90#endif
91}
92
93/* Output GCOV_INFO lists PROFILE to directory OUT. Note that
94 we will remove all the gcda files in OUT. */
95
96static void
97gcov_output_files (const char *out, struct gcov_info *profile)
98{
99 char *pwd;
100 int ret;
101
102 /* Try to make directory if it doesn't already exist. */
103 if (access (name: out, F_OK) == -1)
104 {
105 if (mkdir (path: out, S_IRWXU | S_IRWXG | S_IRWXO) == -1 && errno != EEXIST)
106 fatal_error (input_location, "Cannot make directory %s", out);
107 } else
108 unlink_profile_dir (path: out);
109
110 /* Output new profile. */
111 pwd = getcwd (NULL, size: 0);
112
113 if (pwd == NULL)
114 fatal_error (input_location, "Cannot get current directory name");
115
116 ret = chdir (path: out);
117 if (ret)
118 fatal_error (input_location, "Cannot change directory to %s", out);
119
120 /* Verify that output file does not exist (either was removed by
121 unlink_profile_data or removed by user). */
122 const char *filename = gcov_get_filename (list: profile);
123
124 if (access (name: filename, F_OK) != -1)
125 fatal_error (input_location, "output file %s already exists in folder %s",
126 filename, out);
127
128 gcov_do_dump (profile, 0, 0);
129
130 ret = chdir (path: pwd);
131 if (ret)
132 fatal_error (input_location, "Cannot change directory to %s", pwd);
133
134 free (ptr: pwd);
135}
136
137/* Merging profile D1 and D2 with weight as W1 and W2, respectively.
138 The result profile is written to directory OUT.
139 Return 0 on success. */
140
141static int
142profile_merge (const char *d1, const char *d2, const char *out, int w1, int w2)
143{
144 struct gcov_info *d1_profile;
145 struct gcov_info *d2_profile;
146 struct gcov_info *merged_profile;
147
148 d1_profile = gcov_read_profile_dir (d1, 0);
149 d2_profile = gcov_read_profile_dir (d2, 0);
150
151 /* The actual merge: we overwrite to d1_profile. */
152 merged_profile = gcov_profile_merge (d1_profile, d2_profile, w1, w2);
153
154 if (merged_profile)
155 gcov_output_files (out, profile: merged_profile);
156 else if (verbose)
157 fnotice (stdout, "no profile files were merged\n");
158
159 return 0;
160}
161
162/* Usage message for profile merge. */
163
164static void
165print_merge_usage_message (int error_p)
166{
167 FILE *file = error_p ? stderr : stdout;
168
169 fnotice (file, " merge [options] <dir1> <dir2> Merge coverage file contents\n");
170 fnotice (file, " -o, --output <dir> Output directory\n");
171 fnotice (file, " -v, --verbose Verbose mode\n");
172 fnotice (file, " -w, --weight <w1,w2> Set weights (float point values)\n");
173}
174
175static const struct option merge_options[] =
176{
177 { .name: "verbose", no_argument, NULL, .val: 'v' },
178 { .name: "output", required_argument, NULL, .val: 'o' },
179 { .name: "weight", required_argument, NULL, .val: 'w' },
180 { .name: 0, .has_arg: 0, .flag: 0, .val: 0 }
181};
182
183/* Print merge usage and exit. */
184
185static void ATTRIBUTE_NORETURN
186merge_usage (void)
187{
188 fnotice (stderr, "Merge subcommand usage:");
189 print_merge_usage_message (error_p: true);
190 exit (FATAL_EXIT_CODE);
191}
192
193/* Driver for profile merge subcommand. */
194
195static int
196do_merge (int argc, char **argv)
197{
198 int opt;
199 const char *output_dir = 0;
200 int w1 = 1, w2 = 1;
201
202 optind = 0;
203 while ((opt = getopt_long (argc, argv, shortopts: "vo:w:", longopts: merge_options, NULL)) != -1)
204 {
205 switch (opt)
206 {
207 case 'v':
208 verbose = true;
209 gcov_set_verbose ();
210 break;
211 case 'o':
212 output_dir = optarg;
213 break;
214 case 'w':
215 sscanf (s: optarg, format: "%d,%d", &w1, &w2);
216 if (w1 < 0 || w2 < 0)
217 fatal_error (input_location, "weights need to be non-negative");
218 break;
219 default:
220 merge_usage ();
221 }
222 }
223
224 if (output_dir == NULL)
225 output_dir = "merged_profile";
226
227 if (argc - optind != 2)
228 merge_usage ();
229
230 return profile_merge (d1: argv[optind], d2: argv[optind+1], out: output_dir, w1, w2);
231}
232
233/* Usage message for profile merge-stream. */
234
235static void
236print_merge_stream_usage_message (int error_p)
237{
238 FILE *file = error_p ? stderr : stdout;
239
240 fnotice (file, " merge-stream [options] [<file>] Merge coverage stream file (or stdin)\n"
241 " and coverage file contents\n");
242 fnotice (file, " -v, --verbose Verbose mode\n");
243 fnotice (file, " -w, --weight <w1,w2> Set weights (float point values)\n");
244}
245
246static const struct option merge_stream_options[] =
247{
248 { .name: "verbose", no_argument, NULL, .val: 'v' },
249 { .name: "weight", required_argument, NULL, .val: 'w' },
250 { .name: 0, .has_arg: 0, .flag: 0, .val: 0 }
251};
252
253/* Print merge-stream usage and exit. */
254
255static void ATTRIBUTE_NORETURN
256merge_stream_usage (void)
257{
258 fnotice (stderr, "Merge-stream subcommand usage:");
259 print_merge_stream_usage_message (error_p: true);
260 exit (FATAL_EXIT_CODE);
261}
262
263/* Driver for profile merge-stream subcommand. */
264
265static int
266do_merge_stream (int argc, char **argv)
267{
268 int opt;
269 int w1 = 1, w2 = 1;
270 struct gcov_info *merged_profile;
271
272 optind = 0;
273 while ((opt = getopt_long (argc, argv, shortopts: "vw:",
274 longopts: merge_stream_options, NULL)) != -1)
275 {
276 switch (opt)
277 {
278 case 'v':
279 verbose = true;
280 gcov_set_verbose ();
281 break;
282 case 'w':
283 sscanf (s: optarg, format: "%d,%d", &w1, &w2);
284 if (w1 < 0 || w2 < 0)
285 fatal_error (input_location, "weights need to be non-negative");
286 break;
287 default:
288 merge_stream_usage ();
289 }
290 }
291
292 if (argc - optind > 1)
293 merge_stream_usage ();
294
295 merged_profile = gcov_profile_merge_stream (argv[optind], w1, w2);
296
297 if (merged_profile)
298 gcov_do_dump (merged_profile, 0, -1);
299 else if (verbose)
300 fnotice (stdout, "no profile files were merged\n");
301
302 return 0;
303}
304
305/* If N_VAL is no-zero, normalize the profile by setting the largest counter
306 counter value to N_VAL and scale others counters proportionally.
307 Otherwise, multiply the all counters by SCALE. */
308
309static int
310profile_rewrite (const char *d1, const char *out, int64_t n_val,
311 float scale, int n, int d)
312{
313 struct gcov_info * d1_profile;
314
315 d1_profile = gcov_read_profile_dir (d1, 0);
316 if (!d1_profile)
317 return 1;
318
319 if (n_val)
320 gcov_profile_normalize (d1_profile, (gcov_type) n_val);
321 else
322 gcov_profile_scale (d1_profile, scale, n, d);
323
324 gcov_output_files (out, profile: d1_profile);
325 return 0;
326}
327
328/* Usage function for profile rewrite. */
329
330static void
331print_rewrite_usage_message (int error_p)
332{
333 FILE *file = error_p ? stderr : stdout;
334
335 fnotice (file, " rewrite [options] <dir> Rewrite coverage file contents\n");
336 fnotice (file, " -n, --normalize <int64_t> Normalize the profile\n");
337 fnotice (file, " -o, --output <dir> Output directory\n");
338 fnotice (file, " -s, --scale <float or simple-frac> Scale the profile counters\n");
339 fnotice (file, " -v, --verbose Verbose mode\n");
340}
341
342static const struct option rewrite_options[] =
343{
344 { .name: "verbose", no_argument, NULL, .val: 'v' },
345 { .name: "output", required_argument, NULL, .val: 'o' },
346 { .name: "scale", required_argument, NULL, .val: 's' },
347 { .name: "normalize", required_argument, NULL, .val: 'n' },
348 { .name: 0, .has_arg: 0, .flag: 0, .val: 0 }
349};
350
351/* Print profile rewrite usage and exit. */
352
353static void ATTRIBUTE_NORETURN
354rewrite_usage (void)
355{
356 fnotice (stderr, "Rewrite subcommand usage:");
357 print_rewrite_usage_message (error_p: true);
358 exit (FATAL_EXIT_CODE);
359}
360
361/* Driver for profile rewrite subcommand. */
362
363static int
364do_rewrite (int argc, char **argv)
365{
366 int opt;
367 int ret;
368 const char *output_dir = 0;
369 int64_t normalize_val = 0;
370 float scale = 0.0;
371 int numerator = 1;
372 int denominator = 1;
373 int do_scaling = 0;
374
375 optind = 0;
376 while ((opt = getopt_long (argc, argv, shortopts: "vo:s:n:", longopts: rewrite_options, NULL)) != -1)
377 {
378 switch (opt)
379 {
380 case 'v':
381 verbose = true;
382 gcov_set_verbose ();
383 break;
384 case 'o':
385 output_dir = optarg;
386 break;
387 case 'n':
388 if (!do_scaling)
389#if defined(INT64_T_IS_LONG)
390 normalize_val = strtol (nptr: optarg, endptr: (char **)NULL, base: 10);
391#else
392 normalize_val = strtoll (optarg, (char **)NULL, 10);
393#endif
394 else
395 fnotice (stderr, "scaling cannot co-exist with normalization,"
396 " skipping\n");
397 break;
398 case 's':
399 ret = 0;
400 do_scaling = 1;
401 if (strstr (haystack: optarg, needle: "/"))
402 {
403 ret = sscanf (s: optarg, format: "%d/%d", &numerator, &denominator);
404 if (ret == 2)
405 {
406 if (numerator < 0 || denominator <= 0)
407 {
408 fnotice (stderr, "incorrect format in scaling, using 1/1\n");
409 denominator = 1;
410 numerator = 1;
411 }
412 }
413 }
414 if (ret != 2)
415 {
416 ret = sscanf (s: optarg, format: "%f", &scale);
417 if (ret != 1)
418 fnotice (stderr, "incorrect format in scaling, using 1/1\n");
419 else
420 denominator = 0;
421 }
422
423 if (scale < 0.0)
424 fatal_error (input_location, "scale needs to be non-negative");
425
426 if (normalize_val != 0)
427 {
428 fnotice (stderr, "normalization cannot co-exist with scaling\n");
429 normalize_val = 0;
430 }
431 break;
432 default:
433 rewrite_usage ();
434 }
435 }
436
437 if (output_dir == NULL)
438 output_dir = "rewrite_profile";
439
440 if (argc - optind == 1)
441 {
442 if (denominator > 0)
443 ret = profile_rewrite (d1: argv[optind], out: output_dir, n_val: 0, scale: 0.0, n: numerator, d: denominator);
444 else
445 ret = profile_rewrite (d1: argv[optind], out: output_dir, n_val: normalize_val, scale, n: 0, d: 0);
446 }
447 else
448 rewrite_usage ();
449
450 return ret;
451}
452
453/* Driver function to computer the overlap score b/w profile D1 and D2.
454 Return 1 on error and 0 if OK. */
455
456static int
457profile_overlap (const char *d1, const char *d2)
458{
459 struct gcov_info *d1_profile;
460 struct gcov_info *d2_profile;
461
462 d1_profile = gcov_read_profile_dir (d1, 0);
463 if (!d1_profile)
464 return 1;
465
466 if (d2)
467 {
468 d2_profile = gcov_read_profile_dir (d2, 0);
469 if (!d2_profile)
470 return 1;
471
472 return gcov_profile_overlap (d1_profile, d2_profile);
473 }
474
475 return 1;
476}
477
478/* Usage message for profile overlap. */
479
480static void
481print_overlap_usage_message (int error_p)
482{
483 FILE *file = error_p ? stderr : stdout;
484
485 fnotice (file, " overlap [options] <dir1> <dir2> Compute the overlap of two profiles\n");
486 fnotice (file, " -f, --function Print function level info\n");
487 fnotice (file, " -F, --fullname Print full filename\n");
488 fnotice (file, " -h, --hotonly Only print info for hot objects/functions\n");
489 fnotice (file, " -o, --object Print object level info\n");
490 fnotice (file, " -t <float>, --hot_threshold <float> Set the threshold for hotness\n");
491 fnotice (file, " -v, --verbose Verbose mode\n");
492}
493
494static const struct option overlap_options[] =
495{
496 { .name: "verbose", no_argument, NULL, .val: 'v' },
497 { .name: "function", no_argument, NULL, .val: 'f' },
498 { .name: "fullname", no_argument, NULL, .val: 'F' },
499 { .name: "object", no_argument, NULL, .val: 'o' },
500 { .name: "hotonly", no_argument, NULL, .val: 'h' },
501 { .name: "hot_threshold", required_argument, NULL, .val: 't' },
502 { .name: 0, .has_arg: 0, .flag: 0, .val: 0 }
503};
504
505/* Print overlap usage and exit. */
506
507static void ATTRIBUTE_NORETURN
508overlap_usage (void)
509{
510 fnotice (stderr, "Overlap subcommand usage:");
511 print_overlap_usage_message (error_p: true);
512 exit (FATAL_EXIT_CODE);
513}
514
515int overlap_func_level;
516int overlap_obj_level;
517int overlap_hot_only;
518int overlap_use_fullname;
519double overlap_hot_threshold = 0.005;
520
521/* Driver for profile overlap subcommand. */
522
523static int
524do_overlap (int argc, char **argv)
525{
526 int opt;
527 int ret;
528
529 optind = 0;
530 while ((opt = getopt_long (argc, argv, shortopts: "vfFoht:", longopts: overlap_options, NULL)) != -1)
531 {
532 switch (opt)
533 {
534 case 'v':
535 verbose = true;
536 gcov_set_verbose ();
537 break;
538 case 'f':
539 overlap_func_level = 1;
540 break;
541 case 'F':
542 overlap_use_fullname = 1;
543 break;
544 case 'o':
545 overlap_obj_level = 1;
546 break;
547 case 'h':
548 overlap_hot_only = 1;
549 break;
550 case 't':
551 overlap_hot_threshold = atof (nptr: optarg);
552 break;
553 default:
554 overlap_usage ();
555 }
556 }
557
558 if (argc - optind == 2)
559 ret = profile_overlap (d1: argv[optind], d2: argv[optind+1]);
560 else
561 overlap_usage ();
562
563 return ret;
564}
565
566
567/* Print a usage message and exit. If ERROR_P is nonzero, this is an error,
568 otherwise the output of --help. */
569
570static void
571print_usage (int error_p)
572{
573 FILE *file = error_p ? stderr : stdout;
574 int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
575
576 fnotice (file, "Usage: %s [OPTION]... SUB_COMMAND [OPTION]...\n\n", progname);
577 fnotice (file, "Offline tool to handle gcda counts\n\n");
578 fnotice (file, " -h, --help Print this help, then exit\n");
579 fnotice (file, " -v, --version Print version number, then exit\n");
580 print_merge_usage_message (error_p);
581 print_merge_stream_usage_message (error_p);
582 print_rewrite_usage_message (error_p);
583 print_overlap_usage_message (error_p);
584 fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
585 bug_report_url);
586 exit (status: status);
587}
588
589/* Print version information and exit. */
590
591static void
592print_version (void)
593{
594 fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
595 fnotice (stdout, "Copyright %s 2023 Free Software Foundation, Inc.\n",
596 _("(C)"));
597 fnotice (stdout,
598 _("This is free software; see the source for copying conditions. There is NO\n\
599warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
600 exit (SUCCESS_EXIT_CODE);
601}
602
603static const struct option options[] =
604{
605 { .name: "help", no_argument, NULL, .val: 'h' },
606 { .name: "version", no_argument, NULL, .val: 'v' },
607 { .name: 0, .has_arg: 0, .flag: 0, .val: 0 }
608};
609
610/* Process args, return index to first non-arg. */
611
612static int
613process_args (int argc, char **argv)
614{
615 int opt;
616
617 while ((opt = getopt_long (argc, argv, shortopts: "+hv", longopts: options, NULL)) != -1)
618 {
619 switch (opt)
620 {
621 case 'h':
622 print_usage (error_p: false);
623 /* Print_usage will exit. */
624 /* FALLTHRU */
625 case 'v':
626 print_version ();
627 /* Print_version will exit. */
628 /* FALLTHRU */
629 default:
630 print_usage (error_p: true);
631 /* Print_usage will exit. */
632 }
633 }
634
635 return optind;
636}
637
638/* Main function for gcov-tool. */
639
640int
641main (int argc, char **argv)
642{
643 const char *p;
644 const char *sub_command;
645
646 p = argv[0] + strlen (s: argv[0]);
647 while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
648 --p;
649 progname = p;
650
651 xmalloc_set_program_name (progname);
652
653 /* Unlock the stdio streams. */
654 unlock_std_streams ();
655
656 gcc_init_libintl ();
657
658 diagnostic_initialize (context: global_dc, n_opts: 0);
659
660 /* Handle response files. */
661 expandargv (&argc, &argv);
662
663 process_args (argc, argv);
664 if (optind >= argc)
665 print_usage (error_p: true);
666
667 sub_command = argv[optind];
668
669 if (!strcmp (s1: sub_command, s2: "merge"))
670 return do_merge (argc: argc - optind, argv: argv + optind);
671 else if (!strcmp (s1: sub_command, s2: "merge-stream"))
672 return do_merge_stream (argc: argc - optind, argv: argv + optind);
673 else if (!strcmp (s1: sub_command, s2: "rewrite"))
674 return do_rewrite (argc: argc - optind, argv: argv + optind);
675 else if (!strcmp (s1: sub_command, s2: "overlap"))
676 return do_overlap (argc: argc - optind, argv: argv + optind);
677
678 print_usage (error_p: true);
679}
680

source code of gcc/gcov-tool.cc