1 /* Gcc offline profile processing tool support. */
2 /* Copyright (C) 2014-2018 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 
429 static const struct option overlap_options[] =
430 {
431   { "verbose",                no_argument,       NULL, 'v' },
432   { "function",               no_argument,       NULL, 'f' },
433   { "fullname",               no_argument,       NULL, 'F' },
434   { "object",                 no_argument,       NULL, 'o' },
435   { "hotonly",                no_argument,       NULL, 'h' },
436   { "hot_threshold",          required_argument, NULL, 't' },
437   { 0, 0, 0, 0 }
438 };
439 
440 /* Print overlap usage and exit.  */
441 
442 static void
overlap_usage(void)443 overlap_usage (void)
444 {
445   fnotice (stderr, "Overlap subcomand usage:");
446   print_overlap_usage_message (true);
447   exit (FATAL_EXIT_CODE);
448 }
449 
450 int overlap_func_level;
451 int overlap_obj_level;
452 int overlap_hot_only;
453 int overlap_use_fullname;
454 double overlap_hot_threshold = 0.005;
455 
456 /* Driver for profile overlap sub-command.  */
457 
458 static int
do_overlap(int argc,char ** argv)459 do_overlap (int argc, char **argv)
460 {
461   int opt;
462   int ret;
463 
464   optind = 0;
465   while ((opt = getopt_long (argc, argv, "vfFoht:", overlap_options, NULL)) != -1)
466     {
467       switch (opt)
468         {
469         case 'v':
470           verbose = true;
471           gcov_set_verbose ();
472           break;
473         case 'f':
474           overlap_func_level = 1;
475           break;
476         case 'F':
477           overlap_use_fullname = 1;
478           break;
479         case 'o':
480           overlap_obj_level = 1;
481           break;
482         case 'h':
483           overlap_hot_only = 1;
484           break;
485         case 't':
486           overlap_hot_threshold = atof (optarg);
487           break;
488         default:
489           overlap_usage ();
490         }
491     }
492 
493   if (argc - optind == 2)
494     ret = profile_overlap (argv[optind], argv[optind+1]);
495   else
496     overlap_usage ();
497 
498   return ret;
499 }
500 
501 
502 /* Print a usage message and exit.  If ERROR_P is nonzero, this is an error,
503    otherwise the output of --help.  */
504 
505 static void
print_usage(int error_p)506 print_usage (int error_p)
507 {
508   FILE *file = error_p ? stderr : stdout;
509   int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
510 
511   fnotice (file, "Usage: %s [OPTION]... SUB_COMMAND [OPTION]...\n\n", progname);
512   fnotice (file, "Offline tool to handle gcda counts\n\n");
513   fnotice (file, "  -h, --help                            Print this help, then exit\n");
514   fnotice (file, "  -v, --version                         Print version number, then exit\n");
515   print_merge_usage_message (error_p);
516   print_rewrite_usage_message (error_p);
517   print_overlap_usage_message (error_p);
518   fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
519            bug_report_url);
520   exit (status);
521 }
522 
523 /* Print version information and exit.  */
524 
525 static void
print_version(void)526 print_version (void)
527 {
528   fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
529   fnotice (stdout, "Copyright %s 2018 Free Software Foundation, Inc.\n",
530            _("(C)"));
531   fnotice (stdout,
532            _("This is free software; see the source for copying conditions.\n"
533              "There is NO warranty; not even for MERCHANTABILITY or \n"
534              "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
535   exit (SUCCESS_EXIT_CODE);
536 }
537 
538 static const struct option options[] =
539 {
540   { "help",                 no_argument,       NULL, 'h' },
541   { "version",              no_argument,       NULL, 'v' },
542   { 0, 0, 0, 0 }
543 };
544 
545 /* Process args, return index to first non-arg.  */
546 
547 static int
process_args(int argc,char ** argv)548 process_args (int argc, char **argv)
549 {
550   int opt;
551 
552   while ((opt = getopt_long (argc, argv, "+hv", options, NULL)) != -1)
553     {
554       switch (opt)
555         {
556         case 'h':
557           print_usage (false);
558           /* Print_usage will exit.  */
559 	  /* FALLTHRU */
560         case 'v':
561           print_version ();
562           /* Print_version will exit.  */
563 	  /* FALLTHRU */
564         default:
565           print_usage (true);
566           /* Print_usage will exit.  */
567         }
568     }
569 
570   return optind;
571 }
572 
573 /* Main function for gcov-tool.  */
574 
575 int
main(int argc,char ** argv)576 main (int argc, char **argv)
577 {
578   const char *p;
579   const char *sub_command;
580 
581   p = argv[0] + strlen (argv[0]);
582   while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
583     --p;
584   progname = p;
585 
586   xmalloc_set_program_name (progname);
587 
588   /* Unlock the stdio streams.  */
589   unlock_std_streams ();
590 
591   gcc_init_libintl ();
592 
593   diagnostic_initialize (global_dc, 0);
594 
595   /* Handle response files.  */
596   expandargv (&argc, &argv);
597 
598   process_args (argc, argv);
599   if (optind >= argc)
600     print_usage (true);
601 
602   sub_command = argv[optind];
603 
604   if (!strcmp (sub_command, "merge"))
605     return do_merge (argc - optind, argv + optind);
606   else if (!strcmp (sub_command, "rewrite"))
607     return do_rewrite (argc - optind, argv + optind);
608   else if (!strcmp (sub_command, "overlap"))
609     return do_overlap (argc - optind, argv + optind);
610 
611   print_usage (true);
612 }
613