111
|
1 /* Gcc offline profile processing tool support. */
|
|
2 /* Copyright (C) 2014-2017 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\n", 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
|
|
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\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
|
|
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
|
|
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\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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
526 print_version (void)
|
|
527 {
|
|
528 fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
|
|
529 fnotice (stdout, "Copyright %s 2014-2017 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
|
|
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
|
|
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 }
|