1 /* Gcc offline profile processing tool support. */
2 /* Copyright (C) 2014-2019 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
unlink_gcda_file(const char * name,const struct stat * status ATTRIBUTE_UNUSED,int type ATTRIBUTE_UNUSED,struct FTW * ftwbuf ATTRIBUTE_UNUSED)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\n", name);
74 
75   return ret;
76 }
77 #endif
78 
79 /* Remove the gcda files in PATH recursively.  */
80 
81 static int
unlink_profile_dir(const char * path ATTRIBUTE_UNUSED)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
gcov_output_files(const char * out,struct gcov_info * profile)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
profile_merge(const char * d1,const char * d2,const char * out,int w1,int w2)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
print_merge_usage_message(int error_p)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
merge_usage(void)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
do_merge(int argc,char ** argv)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\n");
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
profile_rewrite(const char * d1,const char * out,int64_t n_val,float scale,int n,int d)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
print_rewrite_usage_message(int error_p)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
rewrite_usage(void)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
do_rewrite(int argc,char ** argv)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\n");
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
profile_overlap(const char * d1,const char * d2)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
print_overlap_usage_message(int error_p)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
overlap_usage(void)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
do_overlap(int argc,char ** argv)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
print_usage(int error_p)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
print_version(void)525 print_version (void)
526 {
527   fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
528   fnotice (stdout, "Copyright %s 2019 Free Software Foundation, Inc.\n",
529            _("(C)"));
530   fnotice (stdout,
531            _("This is free software; see the source for copying conditions.\n"
532              "There is NO warranty; not even for MERCHANTABILITY or \n"
533              "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
534   exit (SUCCESS_EXIT_CODE);
535 }
536 
537 static const struct option options[] =
538 {
539   { "help",                 no_argument,       NULL, 'h' },
540   { "version",              no_argument,       NULL, 'v' },
541   { 0, 0, 0, 0 }
542 };
543 
544 /* Process args, return index to first non-arg.  */
545 
546 static int
process_args(int argc,char ** argv)547 process_args (int argc, char **argv)
548 {
549   int opt;
550 
551   while ((opt = getopt_long (argc, argv, "+hv", options, NULL)) != -1)
552     {
553       switch (opt)
554         {
555         case 'h':
556           print_usage (false);
557           /* Print_usage will exit.  */
558 	  /* FALLTHRU */
559         case 'v':
560           print_version ();
561           /* Print_version will exit.  */
562 	  /* FALLTHRU */
563         default:
564           print_usage (true);
565           /* Print_usage will exit.  */
566         }
567     }
568 
569   return optind;
570 }
571 
572 /* Main function for gcov-tool.  */
573 
574 int
main(int argc,char ** argv)575 main (int argc, char **argv)
576 {
577   const char *p;
578   const char *sub_command;
579 
580   p = argv[0] + strlen (argv[0]);
581   while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
582     --p;
583   progname = p;
584 
585   xmalloc_set_program_name (progname);
586 
587   /* Unlock the stdio streams.  */
588   unlock_std_streams ();
589 
590   gcc_init_libintl ();
591 
592   diagnostic_initialize (global_dc, 0);
593 
594   /* Handle response files.  */
595   expandargv (&argc, &argv);
596 
597   process_args (argc, argv);
598   if (optind >= argc)
599     print_usage (true);
600 
601   sub_command = argv[optind];
602 
603   if (!strcmp (sub_command, "merge"))
604     return do_merge (argc - optind, argv + optind);
605   else if (!strcmp (sub_command, "rewrite"))
606     return do_rewrite (argc - optind, argv + optind);
607   else if (!strcmp (sub_command, "overlap"))
608     return do_overlap (argc - optind, argv + optind);
609 
610   print_usage (true);
611 }
612