1/* Gcc offline profile processing tool support. */
2/* Copyright (C) 2014-2015 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 int gcov_profile_merge (struct gcov_info*, struct gcov_info*, int, int);
44extern int gcov_profile_overlap (struct gcov_info*, struct gcov_info*);
45extern int gcov_profile_normalize (struct gcov_info*, gcov_type);
46extern int gcov_profile_scale (struct gcov_info*, float, int, int);
47extern struct gcov_info* gcov_read_profile_dir (const char*, int);
48extern void gcov_do_dump (struct gcov_info *, int);
49extern void gcov_set_verbose (void);
50
51/* Set to verbose output mode.  */
52static bool verbose;
53
54#if HAVE_FTW_H
55
56/* Remove file NAME if it has a gcda suffix. */
57
58static int
59unlink_gcda_file (const char *name,
60                  const struct stat *status ATTRIBUTE_UNUSED,
61                  int type ATTRIBUTE_UNUSED,
62                  struct FTW *ftwbuf ATTRIBUTE_UNUSED)
63{
64  int ret = 0;
65  int len = strlen (name);
66  int len1 = strlen (GCOV_DATA_SUFFIX);
67
68  if (len > len1 && !strncmp (len -len1 + name, GCOV_DATA_SUFFIX, len1))
69    ret = remove (name);
70
71  if (ret)
72    fatal_error (input_location, "error in removing %s\n", name);
73
74  return ret;
75}
76#endif
77
78/* Remove the gcda files in PATH recursively.  */
79
80static int
81unlink_profile_dir (const char *path ATTRIBUTE_UNUSED)
82{
83#if HAVE_FTW_H
84    return nftw(path, unlink_gcda_file, 64, FTW_DEPTH | FTW_PHYS);
85#else
86    return -1;
87#endif
88}
89
90/* Output GCOV_INFO lists PROFILE to directory OUT. Note that
91   we will remove all the gcda files in OUT.  */
92
93static void
94gcov_output_files (const char *out, struct gcov_info *profile)
95{
96  char *pwd;
97  int ret;
98
99  /* Try to make directory if it doesn't already exist.  */
100  if (access (out, F_OK) == -1)
101    {
102      if (mkdir (out, S_IRWXU | S_IRWXG | S_IRWXO) == -1 && errno != EEXIST)
103        fatal_error (input_location, "Cannot make directory %s", out);
104    } else
105      unlink_profile_dir (out);
106
107  /* Output new profile.  */
108  pwd = getcwd (NULL, 0);
109
110  if (pwd == NULL)
111    fatal_error (input_location, "Cannot get current directory name");
112
113  ret = chdir (out);
114  if (ret)
115    fatal_error (input_location, "Cannot change directory to %s", out);
116
117  gcov_do_dump (profile, 0);
118
119  ret = chdir (pwd);
120  if (ret)
121    fatal_error (input_location, "Cannot change directory to %s", pwd);
122
123  free (pwd);
124}
125
126/* Merging profile D1 and D2 with weight as W1 and W2, respectively.
127   The result profile is written to directory OUT.
128   Return 0 on success.  */
129
130static int
131profile_merge (const char *d1, const char *d2, const char *out, int w1, int w2)
132{
133  struct gcov_info *d1_profile;
134  struct gcov_info *d2_profile;
135  int ret;
136
137  d1_profile = gcov_read_profile_dir (d1, 0);
138  if (!d1_profile)
139    return 1;
140
141  if (d2)
142    {
143      d2_profile = gcov_read_profile_dir (d2, 0);
144      if (!d2_profile)
145        return 1;
146
147      /* The actual merge: we overwrite to d1_profile.  */
148      ret = gcov_profile_merge (d1_profile, d2_profile, w1, w2);
149
150      if (ret)
151        return ret;
152    }
153
154  gcov_output_files (out, d1_profile);
155
156  return 0;
157}
158
159/* Usage message for profile merge.  */
160
161static void
162print_merge_usage_message (int error_p)
163{
164  FILE *file = error_p ? stderr : stdout;
165
166  fnotice (file, "  merge [options] <dir1> <dir2>         Merge coverage file contents\n");
167  fnotice (file, "    -v, --verbose                       Verbose mode\n");
168  fnotice (file, "    -o, --output <dir>                  Output directory\n");
169  fnotice (file, "    -w, --weight <w1,w2>                Set weights (float point values)\n");
170}
171
172static const struct option merge_options[] =
173{
174  { "verbose",                no_argument,       NULL, 'v' },
175  { "output",                 required_argument, NULL, 'o' },
176  { "weight",                 required_argument, NULL, 'w' },
177  { 0, 0, 0, 0 }
178};
179
180/* Print merge usage and exit.  */
181
182static void
183merge_usage (void)
184{
185  fnotice (stderr, "Merge subcomand usage:");
186  print_merge_usage_message (true);
187  exit (FATAL_EXIT_CODE);
188}
189
190/* Driver for profile merge sub-command.  */
191
192static int
193do_merge (int argc, char **argv)
194{
195  int opt;
196  int ret;
197  const char *output_dir = 0;
198  int w1 = 1, w2 = 1;
199
200  optind = 0;
201  while ((opt = getopt_long (argc, argv, "vo:w:", merge_options, NULL)) != -1)
202    {
203      switch (opt)
204        {
205        case 'v':
206          verbose = true;
207          gcov_set_verbose ();
208          break;
209        case 'o':
210          output_dir = optarg;
211          break;
212        case 'w':
213          sscanf (optarg, "%d,%d", &w1, &w2);
214          if (w1 < 0 || w2 < 0)
215            fatal_error (input_location, "weights need to be non-negative\n");
216          break;
217        default:
218          merge_usage ();
219        }
220    }
221
222  if (output_dir == NULL)
223    output_dir = "merged_profile";
224
225  if (argc - optind == 2)
226    ret = profile_merge (argv[optind], argv[optind+1], output_dir, w1, w2);
227  else
228    merge_usage ();
229
230  return ret;
231}
232
233/* If N_VAL is no-zero, normalize the profile by setting the largest counter
234   counter value to N_VAL and scale others counters proportionally.
235   Otherwise, multiply the all counters by SCALE.  */
236
237static int
238profile_rewrite (const char *d1, const char *out, long long n_val,
239                 float scale, int n, int d)
240{
241  struct gcov_info * d1_profile;
242
243  d1_profile = gcov_read_profile_dir (d1, 0);
244  if (!d1_profile)
245    return 1;
246
247  if (n_val)
248    gcov_profile_normalize (d1_profile, (gcov_type) n_val);
249  else
250    gcov_profile_scale (d1_profile, scale, n, d);
251
252  gcov_output_files (out, d1_profile);
253  return 0;
254}
255
256/* Usage function for profile rewrite.  */
257
258static void
259print_rewrite_usage_message (int error_p)
260{
261  FILE *file = error_p ? stderr : stdout;
262
263  fnotice (file, "  rewrite [options] <dir>               Rewrite coverage file contents\n");
264  fnotice (file, "    -v, --verbose                       Verbose mode\n");
265  fnotice (file, "    -o, --output <dir>                  Output directory\n");
266  fnotice (file, "    -s, --scale <float or simple-frac>  Scale the profile counters\n");
267  fnotice (file, "    -n, --normalize <long long>         Normalize the profile\n");
268}
269
270static const struct option rewrite_options[] =
271{
272  { "verbose",                no_argument,       NULL, 'v' },
273  { "output",                 required_argument, NULL, 'o' },
274  { "scale",                  required_argument, NULL, 's' },
275  { "normalize",              required_argument, NULL, 'n' },
276  { 0, 0, 0, 0 }
277};
278
279/* Print profile rewrite usage and exit.  */
280
281static void
282rewrite_usage (void)
283{
284  fnotice (stderr, "Rewrite subcommand usage:");
285  print_rewrite_usage_message (true);
286  exit (FATAL_EXIT_CODE);
287}
288
289/* Driver for profile rewrite sub-command. */
290
291static int
292do_rewrite (int argc, char **argv)
293{
294  int opt;
295  int ret;
296  const char *output_dir = 0;
297#ifdef HAVE_LONG_LONG
298  long long normalize_val = 0;
299#else
300  int64_t normalize_val = 0;
301#endif
302  float scale = 0.0;
303  int numerator = 1;
304  int denominator = 1;
305  int do_scaling = 0;
306
307  optind = 0;
308  while ((opt = getopt_long (argc, argv, "vo:s:n:", rewrite_options, NULL)) != -1)
309    {
310      switch (opt)
311        {
312        case 'v':
313          verbose = true;
314          gcov_set_verbose ();
315          break;
316        case 'o':
317          output_dir = optarg;
318          break;
319        case 'n':
320          if (!do_scaling)
321#if defined(HAVE_LONG_LONG)
322	    normalize_val = strtoll (optarg, (char **)NULL, 10);
323#elif defined(INT64_T_IS_LONG)
324	    normalize_val = strtol (optarg, (char **)NULL, 10);
325#else
326	    sscanf (optarg, "%" SCNd64, &normalize_val);
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
390static int
391profile_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
414static void
415print_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, "    -v, --verbose                       Verbose mode\n");
421  fnotice (file, "    -h, --hotonly                       Only print info for hot objects/functions\n");
422  fnotice (file, "    -f, --function                      Print function level info\n");
423  fnotice (file, "    -F, --fullname                      Print full filename\n");
424  fnotice (file, "    -o, --object                        Print object level info\n");
425  fnotice (file, "    -t <float>, --hot_threshold <float> Set the threshold for hotness\n");
426
427}
428
429static 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
442static void
443overlap_usage (void)
444{
445  fnotice (stderr, "Overlap subcomand usage:");
446  print_overlap_usage_message (true);
447  exit (FATAL_EXIT_CODE);
448}
449
450int overlap_func_level;
451int overlap_obj_level;
452int overlap_hot_only;
453int overlap_use_fullname;
454double overlap_hot_threshold = 0.005;
455
456/* Driver for profile overlap sub-command.  */
457
458static int
459do_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
505static void
506print_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
525static void
526print_version (void)
527{
528  fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
529  fnotice (stdout, "Copyright %s 2014-2015 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
538static 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
547static int
548process_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        case 'v':
560          print_version ();
561          /* Print_version will exit.  */
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
573int
574main (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