diff options
Diffstat (limited to 'gcc/gcov-tool.cc')
-rw-r--r-- | gcc/gcov-tool.cc | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/gcc/gcov-tool.cc b/gcc/gcov-tool.cc new file mode 100644 index 0000000..f4e42ae --- /dev/null +++ b/gcc/gcov-tool.cc @@ -0,0 +1,610 @@ +/* Gcc offline profile processing tool support. */ +/* Copyright (C) 2014-2022 Free Software Foundation, Inc. + Contributed by Rong Xu <xur@google.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "intl.h" +#include "diagnostic.h" +#include "version.h" +#include "gcov-io.h" +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#if HAVE_FTW_H +#include <ftw.h> +#endif +#include <getopt.h> + +extern int gcov_profile_merge (struct gcov_info*, struct gcov_info*, int, int); +extern int gcov_profile_overlap (struct gcov_info*, struct gcov_info*); +extern int gcov_profile_normalize (struct gcov_info*, gcov_type); +extern int gcov_profile_scale (struct gcov_info*, float, int, int); +extern struct gcov_info* gcov_read_profile_dir (const char*, int); +extern void gcov_do_dump (struct gcov_info *, int); +extern const char *gcov_get_filename (struct gcov_info *list); +extern void gcov_set_verbose (void); + +/* Set to verbose output mode. */ +static bool verbose; + +#if HAVE_FTW_H + +/* Remove file NAME if it has a gcda suffix. */ + +static int +unlink_gcda_file (const char *name, + const struct stat *status ATTRIBUTE_UNUSED, + int type ATTRIBUTE_UNUSED, + struct FTW *ftwbuf ATTRIBUTE_UNUSED) +{ + int ret = 0; + int len = strlen (name); + int len1 = strlen (GCOV_DATA_SUFFIX); + + if (len > len1 && !strncmp (len -len1 + name, GCOV_DATA_SUFFIX, len1)) + ret = remove (name); + + if (ret) + fatal_error (input_location, "error in removing %s", name); + + return ret; +} +#endif + +/* Remove the gcda files in PATH recursively. */ + +static int +unlink_profile_dir (const char *path ATTRIBUTE_UNUSED) +{ +#if HAVE_FTW_H + return nftw(path, unlink_gcda_file, 64, FTW_DEPTH | FTW_PHYS); +#else + return -1; +#endif +} + +/* Output GCOV_INFO lists PROFILE to directory OUT. Note that + we will remove all the gcda files in OUT. */ + +static void +gcov_output_files (const char *out, struct gcov_info *profile) +{ + char *pwd; + int ret; + + /* Try to make directory if it doesn't already exist. */ + if (access (out, F_OK) == -1) + { + if (mkdir (out, S_IRWXU | S_IRWXG | S_IRWXO) == -1 && errno != EEXIST) + fatal_error (input_location, "Cannot make directory %s", out); + } else + unlink_profile_dir (out); + + /* Output new profile. */ + pwd = getcwd (NULL, 0); + + if (pwd == NULL) + fatal_error (input_location, "Cannot get current directory name"); + + ret = chdir (out); + if (ret) + fatal_error (input_location, "Cannot change directory to %s", out); + + /* Verify that output file does not exist (either was removed by + unlink_profile_data or removed by user). */ + const char *filename = gcov_get_filename (profile); + + if (access (filename, F_OK) != -1) + fatal_error (input_location, "output file %s already exists in folder %s", + filename, out); + + gcov_do_dump (profile, 0); + + ret = chdir (pwd); + if (ret) + fatal_error (input_location, "Cannot change directory to %s", pwd); + + free (pwd); +} + +/* Merging profile D1 and D2 with weight as W1 and W2, respectively. + The result profile is written to directory OUT. + Return 0 on success. */ + +static int +profile_merge (const char *d1, const char *d2, const char *out, int w1, int w2) +{ + struct gcov_info *d1_profile; + struct gcov_info *d2_profile; + int ret; + + d1_profile = gcov_read_profile_dir (d1, 0); + if (!d1_profile) + return 1; + + if (d2) + { + d2_profile = gcov_read_profile_dir (d2, 0); + if (!d2_profile) + return 1; + + /* The actual merge: we overwrite to d1_profile. */ + ret = gcov_profile_merge (d1_profile, d2_profile, w1, w2); + + if (ret) + return ret; + } + + gcov_output_files (out, d1_profile); + + return 0; +} + +/* Usage message for profile merge. */ + +static void +print_merge_usage_message (int error_p) +{ + FILE *file = error_p ? stderr : stdout; + + fnotice (file, " merge [options] <dir1> <dir2> Merge coverage file contents\n"); + fnotice (file, " -o, --output <dir> Output directory\n"); + fnotice (file, " -v, --verbose Verbose mode\n"); + fnotice (file, " -w, --weight <w1,w2> Set weights (float point values)\n"); +} + +static const struct option merge_options[] = +{ + { "verbose", no_argument, NULL, 'v' }, + { "output", required_argument, NULL, 'o' }, + { "weight", required_argument, NULL, 'w' }, + { 0, 0, 0, 0 } +}; + +/* Print merge usage and exit. */ + +static void ATTRIBUTE_NORETURN +merge_usage (void) +{ + fnotice (stderr, "Merge subcomand usage:"); + print_merge_usage_message (true); + exit (FATAL_EXIT_CODE); +} + +/* Driver for profile merge sub-command. */ + +static int +do_merge (int argc, char **argv) +{ + int opt; + const char *output_dir = 0; + int w1 = 1, w2 = 1; + + optind = 0; + while ((opt = getopt_long (argc, argv, "vo:w:", merge_options, NULL)) != -1) + { + switch (opt) + { + case 'v': + verbose = true; + gcov_set_verbose (); + break; + case 'o': + output_dir = optarg; + break; + case 'w': + sscanf (optarg, "%d,%d", &w1, &w2); + if (w1 < 0 || w2 < 0) + fatal_error (input_location, "weights need to be non-negative"); + break; + default: + merge_usage (); + } + } + + if (output_dir == NULL) + output_dir = "merged_profile"; + + if (argc - optind != 2) + merge_usage (); + + return profile_merge (argv[optind], argv[optind+1], output_dir, w1, w2); +} + +/* If N_VAL is no-zero, normalize the profile by setting the largest counter + counter value to N_VAL and scale others counters proportionally. + Otherwise, multiply the all counters by SCALE. */ + +static int +profile_rewrite (const char *d1, const char *out, int64_t n_val, + float scale, int n, int d) +{ + struct gcov_info * d1_profile; + + d1_profile = gcov_read_profile_dir (d1, 0); + if (!d1_profile) + return 1; + + if (n_val) + gcov_profile_normalize (d1_profile, (gcov_type) n_val); + else + gcov_profile_scale (d1_profile, scale, n, d); + + gcov_output_files (out, d1_profile); + return 0; +} + +/* Usage function for profile rewrite. */ + +static void +print_rewrite_usage_message (int error_p) +{ + FILE *file = error_p ? stderr : stdout; + + fnotice (file, " rewrite [options] <dir> Rewrite coverage file contents\n"); + fnotice (file, " -n, --normalize <int64_t> Normalize the profile\n"); + fnotice (file, " -o, --output <dir> Output directory\n"); + fnotice (file, " -s, --scale <float or simple-frac> Scale the profile counters\n"); + fnotice (file, " -v, --verbose Verbose mode\n"); +} + +static const struct option rewrite_options[] = +{ + { "verbose", no_argument, NULL, 'v' }, + { "output", required_argument, NULL, 'o' }, + { "scale", required_argument, NULL, 's' }, + { "normalize", required_argument, NULL, 'n' }, + { 0, 0, 0, 0 } +}; + +/* Print profile rewrite usage and exit. */ + +static void ATTRIBUTE_NORETURN +rewrite_usage (void) +{ + fnotice (stderr, "Rewrite subcommand usage:"); + print_rewrite_usage_message (true); + exit (FATAL_EXIT_CODE); +} + +/* Driver for profile rewrite sub-command. */ + +static int +do_rewrite (int argc, char **argv) +{ + int opt; + int ret; + const char *output_dir = 0; + int64_t normalize_val = 0; + float scale = 0.0; + int numerator = 1; + int denominator = 1; + int do_scaling = 0; + + optind = 0; + while ((opt = getopt_long (argc, argv, "vo:s:n:", rewrite_options, NULL)) != -1) + { + switch (opt) + { + case 'v': + verbose = true; + gcov_set_verbose (); + break; + case 'o': + output_dir = optarg; + break; + case 'n': + if (!do_scaling) +#if defined(INT64_T_IS_LONG) + normalize_val = strtol (optarg, (char **)NULL, 10); +#else + normalize_val = strtoll (optarg, (char **)NULL, 10); +#endif + else + fnotice (stderr, "scaling cannot co-exist with normalization," + " skipping\n"); + break; + case 's': + ret = 0; + do_scaling = 1; + if (strstr (optarg, "/")) + { + ret = sscanf (optarg, "%d/%d", &numerator, &denominator); + if (ret == 2) + { + if (numerator < 0 || denominator <= 0) + { + fnotice (stderr, "incorrect format in scaling, using 1/1\n"); + denominator = 1; + numerator = 1; + } + } + } + if (ret != 2) + { + ret = sscanf (optarg, "%f", &scale); + if (ret != 1) + fnotice (stderr, "incorrect format in scaling, using 1/1\n"); + else + denominator = 0; + } + + if (scale < 0.0) + fatal_error (input_location, "scale needs to be non-negative"); + + if (normalize_val != 0) + { + fnotice (stderr, "normalization cannot co-exist with scaling\n"); + normalize_val = 0; + } + break; + default: + rewrite_usage (); + } + } + + if (output_dir == NULL) + output_dir = "rewrite_profile"; + + if (argc - optind == 1) + { + if (denominator > 0) + ret = profile_rewrite (argv[optind], output_dir, 0, 0.0, numerator, denominator); + else + ret = profile_rewrite (argv[optind], output_dir, normalize_val, scale, 0, 0); + } + else + rewrite_usage (); + + return ret; +} + +/* Driver function to computer the overlap score b/w profile D1 and D2. + Return 1 on error and 0 if OK. */ + +static int +profile_overlap (const char *d1, const char *d2) +{ + struct gcov_info *d1_profile; + struct gcov_info *d2_profile; + + d1_profile = gcov_read_profile_dir (d1, 0); + if (!d1_profile) + return 1; + + if (d2) + { + d2_profile = gcov_read_profile_dir (d2, 0); + if (!d2_profile) + return 1; + + return gcov_profile_overlap (d1_profile, d2_profile); + } + + return 1; +} + +/* Usage message for profile overlap. */ + +static void +print_overlap_usage_message (int error_p) +{ + FILE *file = error_p ? stderr : stdout; + + fnotice (file, " overlap [options] <dir1> <dir2> Compute the overlap of two profiles\n"); + fnotice (file, " -f, --function Print function level info\n"); + fnotice (file, " -F, --fullname Print full filename\n"); + fnotice (file, " -h, --hotonly Only print info for hot objects/functions\n"); + fnotice (file, " -o, --object Print object level info\n"); + fnotice (file, " -t <float>, --hot_threshold <float> Set the threshold for hotness\n"); + fnotice (file, " -v, --verbose Verbose mode\n"); +} + +static const struct option overlap_options[] = +{ + { "verbose", no_argument, NULL, 'v' }, + { "function", no_argument, NULL, 'f' }, + { "fullname", no_argument, NULL, 'F' }, + { "object", no_argument, NULL, 'o' }, + { "hotonly", no_argument, NULL, 'h' }, + { "hot_threshold", required_argument, NULL, 't' }, + { 0, 0, 0, 0 } +}; + +/* Print overlap usage and exit. */ + +static void ATTRIBUTE_NORETURN +overlap_usage (void) +{ + fnotice (stderr, "Overlap subcomand usage:"); + print_overlap_usage_message (true); + exit (FATAL_EXIT_CODE); +} + +int overlap_func_level; +int overlap_obj_level; +int overlap_hot_only; +int overlap_use_fullname; +double overlap_hot_threshold = 0.005; + +/* Driver for profile overlap sub-command. */ + +static int +do_overlap (int argc, char **argv) +{ + int opt; + int ret; + + optind = 0; + while ((opt = getopt_long (argc, argv, "vfFoht:", overlap_options, NULL)) != -1) + { + switch (opt) + { + case 'v': + verbose = true; + gcov_set_verbose (); + break; + case 'f': + overlap_func_level = 1; + break; + case 'F': + overlap_use_fullname = 1; + break; + case 'o': + overlap_obj_level = 1; + break; + case 'h': + overlap_hot_only = 1; + break; + case 't': + overlap_hot_threshold = atof (optarg); + break; + default: + overlap_usage (); + } + } + + if (argc - optind == 2) + ret = profile_overlap (argv[optind], argv[optind+1]); + else + overlap_usage (); + + return ret; +} + + +/* Print a usage message and exit. If ERROR_P is nonzero, this is an error, + otherwise the output of --help. */ + +static void +print_usage (int error_p) +{ + FILE *file = error_p ? stderr : stdout; + int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE; + + fnotice (file, "Usage: %s [OPTION]... SUB_COMMAND [OPTION]...\n\n", progname); + fnotice (file, "Offline tool to handle gcda counts\n\n"); + fnotice (file, " -h, --help Print this help, then exit\n"); + fnotice (file, " -v, --version Print version number, then exit\n"); + print_merge_usage_message (error_p); + print_rewrite_usage_message (error_p); + print_overlap_usage_message (error_p); + fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", + bug_report_url); + exit (status); +} + +/* Print version information and exit. */ + +static void +print_version (void) +{ + fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string); + fnotice (stdout, "Copyright %s 2022 Free Software Foundation, Inc.\n", + _("(C)")); + fnotice (stdout, + _("This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n")); + exit (SUCCESS_EXIT_CODE); +} + +static const struct option options[] = +{ + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { 0, 0, 0, 0 } +}; + +/* Process args, return index to first non-arg. */ + +static int +process_args (int argc, char **argv) +{ + int opt; + + while ((opt = getopt_long (argc, argv, "+hv", options, NULL)) != -1) + { + switch (opt) + { + case 'h': + print_usage (false); + /* Print_usage will exit. */ + /* FALLTHRU */ + case 'v': + print_version (); + /* Print_version will exit. */ + /* FALLTHRU */ + default: + print_usage (true); + /* Print_usage will exit. */ + } + } + + return optind; +} + +/* Main function for gcov-tool. */ + +int +main (int argc, char **argv) +{ + const char *p; + const char *sub_command; + + p = argv[0] + strlen (argv[0]); + while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1])) + --p; + progname = p; + + xmalloc_set_program_name (progname); + + /* Unlock the stdio streams. */ + unlock_std_streams (); + + gcc_init_libintl (); + + diagnostic_initialize (global_dc, 0); + + /* Handle response files. */ + expandargv (&argc, &argv); + + process_args (argc, argv); + if (optind >= argc) + print_usage (true); + + sub_command = argv[optind]; + + if (!strcmp (sub_command, "merge")) + return do_merge (argc - optind, argv + optind); + else if (!strcmp (sub_command, "rewrite")) + return do_rewrite (argc - optind, argv + optind); + else if (!strcmp (sub_command, "overlap")) + return do_overlap (argc - optind, argv + optind); + + print_usage (true); +} |