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