111
|
1 /* Gcc offline profile processing tool support. */
|
131
|
2 /* Copyright (C) 2014-2018 Free Software Foundation, Inc.
|
111
|
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 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
|
131
|
441 static void ATTRIBUTE_NORETURN
|
111
|
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
|
|
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
|
|
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
|
|
525 print_version (void)
|
|
526 {
|
|
527 fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
|
131
|
528 fnotice (stdout, "Copyright %s 2018 Free Software Foundation, Inc.\n",
|
111
|
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
|
|
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
|
|
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 }
|