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