diff options
Diffstat (limited to 'gprofng/src/gp-collect-app.cc')
-rw-r--r-- | gprofng/src/gp-collect-app.cc | 1598 |
1 files changed, 1598 insertions, 0 deletions
diff --git a/gprofng/src/gp-collect-app.cc b/gprofng/src/gp-collect-app.cc new file mode 100644 index 0000000..afaae70 --- /dev/null +++ b/gprofng/src/gp-collect-app.cc @@ -0,0 +1,1598 @@ +/* Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by Oracle. + + This file is part of GNU Binutils. + + This program 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. + + This program 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 51 Franklin Street - Fifth Floor, Boston, + MA 02110-1301, USA. */ + +#include "config.h" +#include <ctype.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <strings.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <sys/ptrace.h> + +#include "gp-defs.h" +#include "cpu_frequency.h" +#include "util.h" +#include "collctrl.h" +#include "hwcdrv.h" +#include "gp-experiment.h" +#include "collect.h" +#include "StringBuilder.h" + +#define SP_COLLECTOR_FOUNDER "SP_COLLECTOR_FOUNDER" + +extern char **environ; + +static volatile int interrupt = 0; +static int saved_stdout = -1; +static int saved_stderr = -1; +static int no_short_usage = 0; +static int usage_fd = 2; +static collect *collect_obj = NULL; +extern "C" void sigint_handler (int sig, siginfo_t *info, void *context); +static char *outredirect = NULL; +static int precheck; +static int nprocesses; +static Process **processes; + +int +main (int argc, char *argv[]) +{ + // disable any alarm that might be pending + int r = alarm (0); + if (r != 0) + dbe_write (2, GTXT ("collect has alarm(%d) pending\n"), r); + collect_obj = new collect (argc, argv, environ); + collect_obj->start (argc, argv); + delete collect_obj; + return 0; +} + +extern "C" void +sigint_handler (int, siginfo_t *, void *) +{ + interrupt = 1; + if (collect_obj->cc != NULL) + collect_obj->cc->interrupt (); + return; +} + +extern "C" void +sigalrm_handler (int, siginfo_t *, void *) +{ + dbe_write (2, GTXT ("collect: unexpected alarm clock signal received\n")); + return; +} + +extern "C" void +sigterm_handler (int, siginfo_t *, void *) +{ + for (int i = 0; i < nprocesses; i++) + { + Process *proc = processes[i]; + if (proc != NULL) + kill (proc->pid, SIGTERM); + } +} + +collect::collect (int argc, char *argv[], char **envp) +: Application (argc, argv) +{ + verbose = 0; + disabled = 0; + cc = NULL; + collect_warnings = NULL; + collect_warnings_idx = 0; + int ii; + for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++) + sp_preload_list[ii] = NULL; + for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++) + sp_libpath_list[ii] = NULL; + java_path = NULL; + java_how = NULL; + jseen_global = 0; + nlabels = 0; + origargc = argc; + origargv = argv; + origenvp = envp; + mem_so_me = false; +} + +collect::~collect () +{ + delete cc; +} + +struct sigaction old_sigint_handler; +struct sigaction old_sigalrm_handler; + +void +collect::start (int argc, char *argv[]) +{ + char *ccret; + char *extype; + /* create a collector control structure, disabling aggressive warning */ + cc = new Coll_Ctrl (0, false, false); + if (prog_name) + { + char *s = strrchr (prog_name, '/'); + if (s && (s - prog_name) > 5) // Remove /bin/ + { + s = dbe_sprintf (NTXT ("%.*s"), (int) (s - prog_name - 4), prog_name); + cc->set_project_home (s); + free (s); + } + } + char * errenable = cc->enable_expt (); + if (errenable) + { + writeStr (2, errenable); + free (errenable); + } + + /* install a handler for SIGALRM */ + struct sigaction act; + memset (&act, 0, sizeof (struct sigaction)); + sigemptyset (&act.sa_mask); + act.sa_handler = (SignalHandler) sigalrm_handler; + act.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigaction (SIGALRM, &act, &old_sigalrm_handler) == -1) + { + writeStr (2, GTXT ("Unable to install SIGALRM handler\n")); + exit (-1); + } + + /* install a handler for SIGINT */ + sigemptyset (&act.sa_mask); + act.sa_handler = (SignalHandler) sigint_handler; + act.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigaction (SIGINT, &act, &old_sigint_handler) == -1) + { + writeStr (2, GTXT ("Unable to install SIGINT handler\n")); + exit (-1); + } + + /* install a handler for SIGTERM */ + sigemptyset (&act.sa_mask); + act.sa_sigaction = sigterm_handler; + act.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigaction (SIGTERM, &act, NULL) == -1) + { + writeStr (2, GTXT ("Unable to install SIGTERM handler\n")); + exit (-1); + } + if (argc > 1 && strncmp (argv[1], NTXT ("--whoami="), 9) == 0) + { + whoami = argv[1] + 9; + argc--; + argv++; + } + + /* check for no arguments -- usage message */ + if (argc == 1) + { + verbose = 1; + usage_fd = 1; + validate_config (0); + usage (); + exit (0); + } + else if (argc == 2 && strcmp (argv[1], NTXT ("-h")) == 0) + { + /* only one argument, -h */ + verbose = 1; + validate_config (0); + /* now print the HWC usage message */ + show_hwc_usage (); + exit (0); + } + else if (argc == 2 && (strcmp (argv[1], NTXT ("-help")) == 0 || + strcmp (argv[1], NTXT ("--help")) == 0)) + { + /* only one argument, -help or --help */ + verbose = 1; + usage_fd = 1; + validate_config (0); + usage (); + exit (0); + } +// Ruud + else if ((argc == 2) && + (strcmp (argv[1], NTXT ("--version")) == 0)) + { + /* only one argument, --version */ + + /* print the version info */ + Application::print_version_info (); + exit (0); + } + + /* precheck the arguments -- scan for -O, -M flagS */ + precheck = 1; + targ_index = check_args (argc, argv); + if (targ_index < 0) + { + /* message has already been written */ + usage_fd = 2; + short_usage (); + exit (1); + } + /* crack the arguments */ + precheck = 0; + targ_index = check_args (argc, argv); + if (targ_index <= 0) + { + /* message has already been written */ + usage_fd = 2; + short_usage (); + exit (1); + } + if (targ_index != 0) + check_target (argc, argv); + if (disabled != 0 && cc->get_count () == 0) + { + // show collection parameters; count data + ccret = cc->show (0); + writeStr (1, ccret); + } + + // see if Java version should be checked + if (cc->get_java_default () == 0 && java_path != NULL) + validate_java (java_path, java_how, verbose); + + /* if count data is requested, exec bit to do the real work */ + /* even for a dryrun */ + if (cc->get_count () != 0) + get_count_data (); + + /* if a dry run, just exit */ + if (disabled != 0) + { + writeStr (1, cc->show_expt ()); + StringBuilder sb; + sb.append (GTXT ("Exec argv[] = ")); + for (int i = 0; i < nargs; i++) + sb.appendf (NTXT ("%s "), arglist[i]); + sb.append (NTXT ("\n")); + char *s = sb.toString (); + writeStr (1, s); + free (s); + exit (0); + } + + // If the mem_so_me flag is set, preload mem.so + // and launch the process + if (mem_so_me) + { + /* set env vars for mem.so */ + if (putenv_memso () != 0) + exit (1); /* message has already been written */ + /* ensure original outputs restored for target */ + reset_output (); + + /* now exec the target ... */ + if (cc->get_debug_mode () == 1) + { + traceme (arglist[0], arglist); + extype = NTXT ("traceme"); + } + else + { + execvp (arglist[0], arglist); + extype = NTXT ("exevcp"); + } + /* oops, exec of the target failed */ + char *em = strerror (errno); + set_output (); /* restore output for collector */ + if (em == NULL) + dbe_write (2, GTXT ("memso %s of %s failed: errno = %d\n"), extype, argv[targ_index], errno); + else + dbe_write (2, GTXT ("memso %s of %s failed: %s\n"), extype, argv[targ_index], em); + exit (1); + } + + /* normal path, setting up an experiment and launching the target */ + /* set up the experiment */ + ccret = cc->setup_experiment (); + if (ccret != NULL) + { + dbe_write (2, NTXT ("%s\n"), ccret); + free (ccret); + exit (1); + } + /* Beyond this point, the experiment is created */ + if (collect_warnings != NULL) + { + warn_open (); + for (int i = 0; i < collect_warnings_idx; i++) + warn_comment (SP_JCMD_CWARN, COL_WARN_APP_NOT_READY, collect_warnings[i], (int) strlen (collect_warnings[i])); + warn_close (); + } + /* check cpu frequency variation for intel*/ + unsigned char mode = COL_CPUFREQ_NONE; + int max_freq = get_cpu_frequency (&mode); + char freq_scaling[256]; + char turbo_mode[256]; + *freq_scaling = 0; + *turbo_mode = 0; + if (mode & COL_CPUFREQ_SCALING) + snprintf (freq_scaling, sizeof (freq_scaling), NTXT (" frequency_scaling=\"enabled\"")); + if (mode & COL_CPUFREQ_TURBO) + snprintf (turbo_mode, sizeof (turbo_mode), NTXT (" turbo_mode=\"enabled\"")); + if (mode != COL_CPUFREQ_NONE) + { + warn_open (); + if (warn_file != NULL) + { + warn_write ("<powerm>\n<frequency clk=\"%d\"%s%s/>\n</powerm>\n", + max_freq, freq_scaling, turbo_mode); + warn_close (); + } + } + + /* check for labels to write to notes file */ + if (nlabels != 0) + { + char *nbuf; + char nbuf2[MAXPATHLEN]; + // fetch the experiment name and CWD + char *exp = cc->get_experiment (); + char *ev = getcwd (nbuf2, sizeof (nbuf2)); + + // format the environment variable for the experiment directory name + if (ev != NULL && exp[0] != '/') + // cwd succeeded, and experiment is a relative path + nbuf = dbe_sprintf (NTXT ("%s/%s/%s"), nbuf2, exp, SP_NOTES_FILE); + else + // getcwd failed or experiment is a fullpath + nbuf = dbe_sprintf (NTXT ("%s/%s"), exp, SP_NOTES_FILE); + + FILE *f = fopen (nbuf, NTXT ("w")); + free (nbuf); + if (f != NULL) + { + for (int i = 0; i < nlabels; i++) + fprintf (f, NTXT ("%s\n"), label[i]); + fclose (f); + } + } + /* check for user interrupt */ + if (interrupt == 1) + { + cc->delete_expt (); + writeStr (2, GTXT ("User interrupt\n")); + exit (0); + } + + /* print data-collection parameters */ + if (verbose) + { + ccret = cc->show (0); + if (ccret != NULL) + writeStr (2, ccret); + } + ccret = cc->show_expt (); + if (ccret != NULL) + writeStr (1, ccret); /* write this to stdout */ + + pid_t pid = (pid_t) cc->get_attach_pid (); + if (pid == (pid_t) 0) + { + /* No attach */ + /* Set the environment for libcollector */ + if (putenv_libcollector () != 0) + { + /* message has already been written */ + cc->delete_expt (); + exit (1); + } + /* ensure original output fds restored for target */ + reset_output (); + + /* now exec the target ... */ + if (cc->get_debug_mode () == 1) + { + traceme (arglist[0], arglist); + extype = NTXT ("traceme"); + } + else + { + execvp (arglist[0], arglist); + extype = NTXT ("execvp"); + } + + /* we reach this point only if the target launch failed */ + char *em = strerror (errno); + + /* restore output for collector */ + set_output (); + + /* exec failed; delete experiment */ + cc->delete_expt (); + + /* print a message and exit */ + if (em == NULL) + dbe_write (2, GTXT ("%s of %s failed: errno = %d\n"), extype, argv[targ_index], errno); + else + dbe_write (2, GTXT ("%s of %s failed: %s\n"), extype, argv[targ_index], em); + exit (1); + } + else + abort (); +} + +/** + * Prepare a warning message and pass it to warn_write() + * @Parameters: + * kind Type of comment + * num ID + * s Comment sting + * len Length of the string + * @Return: none. + */ +void +collect::warn_comment (const char *kind, int num, char *s, int len) +{ + if (len != 0) + warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%.*s</event>\n"), + kind, num, len, s); + else if (s == NULL) + warn_write (NTXT ("<event kind=\"%s\" id=\"%d\"/>\n"), kind, num); + else + warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%s</event>\n"), kind, num, s); +} + +/** + * Open the warnings file in Append mode ("aw") + */ +void +collect::warn_open () +{ + // open the warnings file + warnfilename = dbe_sprintf (NTXT ("%s/%s"), cc->get_experiment (), SP_WARN_FILE); + int fd = open (warnfilename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + warn_file = fdopen (fd, NTXT ("aw")); +} + +/** + * Close the warnings file + */ +void +collect::warn_close () +{ + (void) fclose (warn_file); +} + +/** + * Format the warning message and write it to the warnings file + */ +void +collect::warn_write (const char *format, ...) +{ + char buf[4096]; + // format the input arguments into a string + va_list va; + va_start (va, format); + vsnprintf (buf, sizeof (buf), format, va); + va_end (va); + // write it to the warnings file (warnings.xml) + fwrite (buf, 1, strlen (buf), warn_file); + fflush (warn_file); +} + +/* process the args, setting expt. params, + * and finding offset for a.out name + */ +int +collect::check_args (int argc, char *argv[]) +{ + int hseen = 0; + int hoffseen = 0; + int lseen = 0; + int tseen = 0; + int pseen = 0; + int sseen = 0; + int yseen = 0; + int Fseen = 0; + int Aseen = 0; + int Sseen = 0; + int Hseen = 0; + int iseen = 0; + int Jseen = 0; + int ofseen = 0; + char *expName = NULL; + bool overwriteExp = false; + char *ccret; + char *ccwarn; + for (targ_index = 1; targ_index < argc; targ_index++) + { + if (argv[targ_index] == NULL) + break; + if (dbe_strcmp (argv[targ_index], "--") == 0) + { + targ_index++; + break; + } + if (argv[targ_index][0] != '-') + break; + int param; + switch (argv[targ_index][1]) + { + case 'y': + { + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + char *ptr; + int resume = 1; + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (yseen != 0) + { + dupflagseen ('y'); + return -1; + } + yseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-y requires a signal argument\n")); + return -1; + } + if ((ptr = strrchr (argv[targ_index], ',')) != NULL) + { + if ((*(ptr + 1) != 'r') || (*(ptr + 2) != 0)) + { + /* not the right trailer */ + dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]); + return -1; + } + resume = 0; + *ptr = 0; + } + param = cc->find_sig (argv[targ_index]); + if (param < 0) + { + /* invalid signal */ + dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]); + return -1; + } + ccret = cc->set_pauseresume_signal (param, resume); + if (ccret != NULL) + { + /* invalid signal; write message */ + writeStr (2, ccret); + return -1; + } + break; + } + case 'l': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (lseen != 0) + { + dupflagseen ('l'); + return -1; + } + lseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-l requires a signal argument\n")); + return -1; + } + param = cc->find_sig (argv[targ_index]); + if (param < 0) + { + /* invalid signal */ + dbe_write (2, GTXT ("Invalid sample signal %s\n"), argv[targ_index]); + return -1; + } + ccret = cc->set_sample_signal (param); + if (ccret != NULL) + { + /* invalid signal; write message */ + writeStr (2, ccret); + free (ccret); + return -1; + } + break; + +#ifdef GPROFNG_DOES_NOT_SUPPORT + case 'P': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (Pseen != 0) + { + dupflagseen ('P'); + return -1; + } + Pseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-P requires a process pid argument\n")); + return -1; + } + ccret = cc->set_attach_pid (argv[targ_index]); + if (ccret != NULL) + { + /* error; write message */ + writeStr (2, ccret); + free (ccret); + return -1; + } + break; +#endif + case 't': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (tseen != 0) + { + dupflagseen ('t'); + return -1; + } + tseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-t requires a run-duration argument\n")); + return -1; + } + ccret = cc->set_time_run (argv[targ_index]); + if (ccret != NULL) + { + /* error; write message */ + writeStr (2, ccret); + free (ccret); + return -1; + } + break; + case 'p': + { + char *warnmsg; + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (pseen != 0) + { + dupflagseen ('p'); + return -1; + } + pseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-p requires a clock-profiling argument\n")); + return -1; + } + ccret = cc->set_clkprof (argv[targ_index], &warnmsg); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + if (warnmsg != NULL) + { + writeStr (2, warnmsg); + free (warnmsg); + } + break; + } + case 's': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (sseen != 0) + { + dupflagseen ('s'); + return -1; + } + sseen++; + targ_index++; + if (argv[targ_index] == NULL) + { + writeStr (2, GTXT ("-s requires a synchronization-tracing argument\n")); + return -1; + } + ccret = cc->set_synctrace (argv[targ_index]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + break; + case 'h': + { + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + targ_index++; + if ((argv[targ_index] == NULL) || (strlen (argv[targ_index]) == 0)) + { + writeStr (2, GTXT ("-h requires a HW-counter-profiling argument\n")); + return -1; + } + // Check for some special cases + char * string = argv[targ_index]; + if (strcmp (argv[targ_index], NTXT ("off")) == 0) + { + if (hseen != 0) + { + no_short_usage = 1; + writeStr (2, GTXT ("-h off cannot be used with any other -h arguments\n")); + return -1; + } + hoffseen = 1; + hseen = 1; + cc->disable_hwc (); + break; + } + // Check to see if we can use HWC + unsigned hwc_maxregs = hwc_get_max_concurrent (false); + if (hwc_maxregs == 0) + { + char buf[1024]; + char *pch = hwcfuncs_errmsg_get (buf, sizeof (buf), 0); + if (*pch) + dbe_write (2, GTXT ("HW counter profiling is not supported on this system: %s%s"), + pch, pch[strlen (pch) - 1] == '\n' ? "" : "\n"); + else + dbe_write (2, GTXT ("HW counter profiling is not supported on this system\n")); + no_short_usage = 1; + return -1; + } + // Make sure there's no other -h after -h off + if (hoffseen != 0) + { + no_short_usage = 1; + writeStr (2, GTXT ("No -h arguments can be used after -h off\n")); + return -1; + } + // set up to process HW counters (to know about default counters) + cc->setup_hwc (); + hseen++; + char *warnmsg; + if (strcmp (argv[targ_index], NTXT ("on")) == 0) + ccret = cc->add_default_hwcstring ("on", &warnmsg, true); + else if (strcmp (argv[targ_index], NTXT ("hi")) == 0 || + strcmp (argv[targ_index], NTXT ("high")) == 0) + ccret = cc->add_default_hwcstring ("hi", &warnmsg, true); + else if (strcmp (argv[targ_index], NTXT ("lo")) == 0 || + strcmp (argv[targ_index], NTXT ("low")) == 0) + ccret = cc->add_default_hwcstring ("lo", &warnmsg, true); + else if (strcmp (argv[targ_index], NTXT ("auto")) == 0) + ccret = cc->add_default_hwcstring ("auto", &warnmsg, true); + else + ccret = cc->add_hwcstring (string, &warnmsg); + if (ccret != NULL) + { + /* set global flag to suppress the short_usage message for any subsequent HWC errors */ + no_short_usage = 1; + writeStr (2, ccret); + free (ccret); + return -1; + } + if (warnmsg != NULL) + { + writeStr (2, warnmsg); + free (warnmsg); + } + break; + } + case 'O': + overwriteExp = true; + __attribute__ ((fallthrough)); + case 'o': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s must be followed by a file name\n"), + argv[targ_index]); + return -1; + } + if (expName != NULL) + { + dbe_write (2, GTXT ("Only one -o or -O argument may be used\n")); + dupflagseen ('o'); + return -1; + } + expName = argv[targ_index + 1]; + targ_index++; + break; + case 'S': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s must be followed by a sample interval name\n"), + argv[targ_index]); + return -1; + } + if (Sseen != 0) + { + dupflagseen ('S'); + return -1; + } + Sseen++; + ccret = cc->set_sample_period (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'H': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a heap-tracing argument\n"), + argv[targ_index]); + return -1; + } + if (Hseen != 0) + { + dupflagseen ('H'); + return -1; + } + Hseen++; + ccret = cc->set_heaptrace (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + if (cc->get_java_default () == 1) + cc->set_java_mode (NTXT ("off")); + targ_index++; + break; + case 'i': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + fprintf (stderr, GTXT ("Argument %s requires an I/O-tracing argument\n"), + argv[targ_index]); + return -1; + } + if (iseen != 0) + { + dupflagseen ('i'); + return -1; + } + iseen++; + ccret = cc->set_iotrace (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'j': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a java-profiling argument\n"), + argv[targ_index]); + return -1; + } + if (jseen_global != 0) + { + dupflagseen ('j'); + return -1; + } + jseen_global++; + ccret = cc->set_java_mode (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'J': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a java argument\n"), + argv[targ_index]); + return -1; + } + if (Jseen != 0) + { + dupflagseen ('J'); + return -1; + } + Jseen++; + ccret = cc->set_java_args (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'F': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a descendant-following argument\n"), + argv[targ_index]); + return -1; + } + if (Fseen != 0) + { + dupflagseen ('F'); + return -1; + } + Fseen++; + ccret = cc->set_follow_mode (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'a': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a load-object archiving argument\n"), + argv[targ_index]); + return -1; + } + if (Aseen != 0) + { + dupflagseen ('a'); + return -1; + } + Aseen++; + ccret = cc->set_archive_mode (argv[targ_index + 1]); + if (ccret != NULL) + { + writeStr (2, ccret); + free (ccret); + return -1; + } + targ_index++; + break; + case 'C': + if (precheck == 1) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + if (checkflagterm (argv[targ_index]) == -1) + return -1; + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s must be followed by a comment\n"), + argv[targ_index]); + return -1; + } + if (nlabels == MAXLABELS) + { + dbe_write (2, GTXT ("No more than %d comments may be specified\n"), + MAXLABELS); + return -1; + } + label[nlabels] = argv[targ_index + 1]; + nlabels++; + targ_index++; + break; + case 'n': + case 'v': + case 'V': + if (precheck == 1) + break; + do_flag (&argv[targ_index][1]); + break; + case 'Z': + // special undocumented argument for debug builds only to allow analyzer to + // LD_PRELOAD mem.so for the target it spawns + mem_so_me = true; + break; + case '-': + if (strcmp (argv[targ_index], NTXT ("--verbose")) == 0) + do_flag ("v"); + else if (strcmp (argv[targ_index], "--outfile") == 0) + { + if (precheck == 0) + { + targ_index++; + if (argv[targ_index] == NULL) + return 0; + break; + } + // process this argument now + if (argv[targ_index + 1] == NULL) + { + dbe_write (2, GTXT ("Argument %s requires a file argument\n"), + argv[targ_index]); + return -1; + } + if (ofseen != 0) + { + dupflagseen (argv[targ_index]); + return -1; + } + ofseen++; + if (outredirect == NULL) + { + outredirect = argv[targ_index + 1]; + set_output (); + } // else already redirected; ignore with no message + targ_index++; + } + else + { + dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]); + return -1; + } + break; + default: + dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]); + return -1; + } + } + if (targ_index >= argc) + return -1; + if (argv[targ_index] == NULL) + { + if (precheck == 1) + return 0; + if (cc->get_attach_pid () != 0) /* no target is OK, if we're attaching */ + return 0; + writeStr (2, GTXT ("Name of target must be specified\n")); + return -1; + } + if (expName) + { + ccwarn = NULL; + ccret = cc->set_expt (expName, &ccwarn, overwriteExp); + if (ccwarn) + { + writeStr (2, ccwarn); + free (ccwarn); + } + if (ccret) + { + writeStr (2, ccret); + return -1; + } + } + if (cc->get_attach_pid () != 0) + { + writeStr (2, GTXT ("Name of target must not be specified when -P is used\n")); + return -1; + } + return targ_index; +} + +int +collect::checkflagterm (const char *c) +{ + if (c[2] != 0) + { + dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), c); + return -1; + } + return 0; +} + +int +collect::do_flag (const char *flags) +{ + char *s; + for (int i = 0;; i++) + { + switch (flags[i]) + { + case 0: // end of string + return 0; + case 'n': + disabled = 1; + if (verbose != 1) + { +// Ruud + Application::print_version_info (); +/* + dbe_write (2, NTXT ("GNU %s version %s\n"), + get_basename (prog_name), VERSION); +*/ + verbose = 1; + } + break; + case 'x': + s = cc->set_debug_mode (1); + if (s) + { + writeStr (2, s); + free (s); + } + break; + case 'v': + if (verbose != 1) + { +// Ruud + Application::print_version_info (); +/* + dbe_write (2, NTXT ("GNU %s version %s\n"), + get_basename (prog_name), VERSION); +*/ + verbose = 1; + } + break; + case 'V': +// Ruud + Application::print_version_info (); +/* + dbe_write (2, NTXT ("GNU %s version %s\n"), + get_basename (prog_name), VERSION); +*/ + /* no further processing.... */ + exit (0); + } + } +} + +/* + * traceme - cause the caller to stop at the end of the next exec() + * so that a debugger can attach to the new program + * + * Takes same arguments as execvp() + */ +int +collect::traceme (const char *execvp_file, char *const execvp_argv[]) +{ + int ret = -1; + pid_t pid = fork (); + if (pid == 0) + { // child + // child will set up itself to be PTRACE'd, and then exec the target executable + /* reset the SP_COLLECTOR_FOUNDER value to the new pid */ + pid_t mypid = getpid (); + char *ev = dbe_sprintf (NTXT ("%s=%d"), SP_COLLECTOR_FOUNDER, mypid); + if (putenv (ev) != 0) + { + dbe_write (2, GTXT ("fork-child: Can't putenv of \"%s\": run aborted\n"), ev); + return 1; + } + ptrace (PTRACE_TRACEME, 0, NULL, NULL); // initiate trace + ret = execvp (execvp_file, execvp_argv); // execvp user command + return ret; // execvp failed + } + else if (pid > 0) + { // parent + int status; + if (waitpid (pid, &status, 0) != pid) + { // wait for execvp to cause signal + writeStr (2, GTXT ("parent waitpid() failed\n")); + return -2; + } + if (!WIFSTOPPED (status)) + writeStr (2, GTXT ("WIFSTOPPED(status) failed\n")); + + // originally, PTRACE_DETACH would send SIGTSTP, but now we do it here: + if (kill (pid, SIGTSTP) != 0) + writeStr (2, GTXT ("kill(pid, SIGTSTP) failed\n")); + if (ptrace (PTRACE_DETACH, pid, NULL, 0) != 0) + { // detach trace + writeStr (2, GTXT ("ptrace(PTRACE_DETACH) failed\n")); + return -4; + } + dbe_write (2, GTXT ("Waiting for attach from debugger: pid=%d\n"), (int) pid); + + // wait for an external debugger to attach + if (waitpid (pid, &status, 0) != pid) + { // keep parent alive until child quits + writeStr (2, GTXT ("parent final waitpid() failed\n")); + return -5; + } + } + else + return -1; // fork failed + exit (0); +} + +void +collect::dupflagseen (char c) +{ + dbe_write (2, GTXT ("Only one -%c argument may be used\n"), c); +} + +void +collect::dupflagseen (const char *s) +{ + dbe_write (2, GTXT ("Only one %s argument may be used\n"), s); +} + +int +collect::set_output () +{ + static int initial = 1; + if (outredirect) + { + int fd = open (outredirect, O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd == -1) + { + dbe_write (2, GTXT ("Warning: Can't open collector output `%s': %s\n"), + outredirect, strerror (errno)); + } + else + { + if ((saved_stdout = dup (1)) == -1 || dup2 (fd, 1) == -1) + dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"), + NTXT ("stdout"), strerror (errno)); + if ((saved_stderr = dup (2)) == -1 || dup2 (fd, 2) == -1) + dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"), + NTXT ("stderr"), strerror (errno)); + close (fd); + if ((saved_stdout != -1) && (saved_stderr != -1)) + { + if (initial) + { + struct timeval tp; + gettimeofday (&tp, NULL); + writeStr (2, ctime (&tp.tv_sec)); + initial = 0; + } + return 1; // diversion in place + } + } + } + return 0; // no diversion +} + +void +collect::reset_output () +{ + if (saved_stdout != -1 && + (dup2 (saved_stdout, 1) == -1 || close (saved_stdout))) + dbe_write (2, GTXT ("Warning: Can't restore collector stdout: %s\n"), + strerror (errno)); + if (saved_stderr != -1 && + (dup2 (saved_stderr, 2) == -1 || close (saved_stderr))) + dbe_write (2, GTXT ("Warning: Can't restore collector stderr: %s\n"), + strerror (errno)); +} + +void +collect::usage () +{ + +/* + Ruud - Isolate this line because it has an argument. Otherwise it would be at the + end of this long list. +*/ + printf ( GTXT ( + "Usage: gprofng collect app [OPTION(S)] TARGET [TARGET_ARGUMENTS]\n")), + +/* +------------------------------------------------------------------------------- + For a reason I don't understand, the continuation line(s) need to start at + column 26 in order for help2man to do the righ thing. Ruud +------------------------------------------------------------------------------- +*/ + printf ( GTXT ( + "\n" + "Collect performance data on the target program. In addition to Program\n" + "Counter PC) sampling, hardware event counters and various tracing options\n" + "are supported.\n" + "\n" + "Options:\n" + "\n" + " --version print the version number and exit.\n" + " --help print usage information and exit.\n" + " --verbose {on|off} enable (on) or disable (off) verbose mode; the default is \"off\".\n" + "\n" + " -p {off|on|lo|hi|<value>} disable (off) or enable (on) clock-profiling using a default\n" + " sampling granularity, or enable clock-profiling implicitly by\n" + " setting the sampling granularity (lo, hi, or a specific value\n" + " in ms); by default clock profiling is enabled.\n" + "\n" + " -h {<ctr_def>...,<ctr_n_def>} enable hardware event counter profiling and select\n" + " the counter(s); to see the supported counters on this system use\n" + " the -h option without other arguments.\n" + "\n" + " -o <exp_name> specify the name for (and path to) the experiment directory; the\n" + " the default path is the current directory.\n" + "\n" + " -O <exp_name> the same as -o, but unlike the -o option, silently overwrite an\n" + " existing experiment directory with the same name.\n" + "\n" + " -C <label> add up to 10 comment labels to the experiment; comments appear in\n" + " the notes section of the header.\n" + "\n" + " -j {on|off|<path>} enable (on), or disable (off) Java profiling when the target\n" + " program is a JVM; optionally set the <path> to a non-default JVM;\n" + " the default is \"-j on\".\n" + "\n" + " -J <java-args> specify arguments to the JVM.\n" + "\n" + " -t <duration>[m|s] specify the duration over which to record data; the default unit\n" + " is seconds (s), but can be set to minutes (m).\n" + "\n" + " -n dry run; display several run-time settings, but do not run the\n" + " target, or collect performance data.\n" + "\n" + " -y <signal>[,r] specify delayed initialization and a pause/resume signal; by default\n" + " the target starts in paused mode; if the optional r keyword is\n" + " provided, start in resumed mode.\n" + "\n" + " -F {off|on|=<regex>} control to follow descendant processes; disable (off), enable (on),\n" + " or collect data on all descendant processes whose name matches the\n" + " specified regular expression; the default is \"-F on\".\n" + "\n" + " -a {off|on|ldobjects|src|usedldobjects|usedsrc} specify archiving of binaries and other files;\n" + " in addition to disable this feature (off), or enable archiving off all\n" + " loadobjects and sources (on), the other options support a more\n" + " refined selection. All of these options enable archiving, but the\n" + " keyword controls what exactly is selected: all load objects (ldobjects),\n" + " all source files (src), the loadobjects asscoiated with a program counter\n" + " (usedldobjects), or the source files associated with a program counter\n" + " (usedsrc); the default is \"-a ldobjects\".\n" + "\n" + " -S {off|on|<seconds>} disable (off) or enable (on) periodic sampling of process-wide resource\n" + " utilization; by default sampling occurs every second; use the <seconds>\n" + " option to change this; the default is \"-S on\".\n" + "\n" + " -l <signal> specify a signal that will trigger a sample of process-wide resource utilization.\n" + "\n" + " -s <option>[,<API>] enable synchronization wait tracing; <option> is used to define the specifics\n" + " of the tracing (on, off, <threshold>, or all); <API> is used to select the API:\n" + " \"n\" selects native/Pthreads, \"j\" selects Java, and \"nj\" selects both;\n" + " the default is \"-s off\".\n" + "\n" + " -H {off|on} disable (off), or enable (on) heap tracing; the default is \"-H off\".\n" + "\n" + " -i {off|on} disable (off), or enable (on) I/O tracing; the default is \"-i off\".\n" + "\n" + "Documentation:\n" + "\n" + "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n" + "gprofng programs are properly installed at your site, the command \"info gprofng\"\n" + "should give you access to this document.\n" + "\n" + "See also:\n" + "\n" + "gprofng(1), gp-archive(1), gp-display-html(1), gp-display-src(1), gp-display-text(1)\n")); +/* + char *s = dbe_sprintf (GTXT ("Usage: %s <args> target <target-args>\n"), + whoami); + writeStr (usage_fd, s); + free (s); + writeStr (usage_fd, GTXT (" -p {lo|on|hi|off|<value>}\tspecify clock-profiling\n")); + writeStr (usage_fd, GTXT ("\t`lo' per-thread rate of ~10 samples/second\n")); + writeStr (usage_fd, GTXT ("\t`on' per-thread rate of ~100 samples/second (default)\n")); + writeStr (usage_fd, GTXT ("\t`hi' per-thread rate of ~1000 samples/second\n")); + writeStr (usage_fd, GTXT ("\t`off' disable clock profiling\n")); + writeStr (usage_fd, GTXT ("\t<value> specify profile timer period in millisec.\n")); + s = dbe_sprintf (GTXT ("\t\t\tRange on this system is from %.3f to %.3f millisec.\n\t\t\tResolution is %.3f millisec.\n"), + (double) cc->get_clk_min () / 1000., + (double) cc->get_clk_max () / 1000., + (double) cc->get_clk_res () / 1000.); + writeStr (usage_fd, s); + free (s); + writeStr (usage_fd, GTXT (" -h <ctr_def>...[,<ctr_n_def>]\tspecify HW counter profiling\n")); + s = dbe_sprintf (GTXT ("\tto see the supported HW counters on this machine, run \"%s -h\" with no other arguments\n"), + whoami); + writeStr (usage_fd, s); + free (s); + writeStr (usage_fd, GTXT (" -s <threshold>[,<scope>]\tspecify synchronization wait tracing\n")); + writeStr (usage_fd, GTXT ("\t<scope> is \"j\" for tracing Java-APIs, \"n\" for tracing native-APIs, or \"nj\" for tracing both\n")); + writeStr (usage_fd, GTXT (" -H {on|off}\tspecify heap tracing\n")); + writeStr (usage_fd, GTXT (" -i {on|off}\tspecify I/O tracing\n")); + writeStr (usage_fd, GTXT (" -N <lib>\tspecify library to exclude count from instrumentation (requires -c also)\n")); + writeStr (usage_fd, GTXT (" \tmultiple -N arguments can be provided\n")); + writeStr (usage_fd, GTXT (" -j {on|off|path}\tspecify Java profiling\n")); + writeStr (usage_fd, GTXT (" -J <java-args>\tspecify arguments to Java for Java profiling\n")); + writeStr (usage_fd, GTXT (" -t <duration>\tspecify time over which to record data\n")); + writeStr (usage_fd, GTXT (" -n\tdry run -- don't run target or collect performance data\n")); + writeStr (usage_fd, GTXT (" -y <signal>[,r]\tspecify delayed initialization and pause/resume signal\n")); + writeStr (usage_fd, GTXT ("\tWhen set, the target starts in paused mode;\n\t if the optional r is provided, it starts in resumed mode\n")); + writeStr (usage_fd, GTXT (" -F {on|off|=<regex>}\tspecify following descendant processes\n")); + writeStr (usage_fd, GTXT (" -a {on|ldobjects|src|usedldobjects|usedsrc|off}\tspecify archiving of binaries and other files;\n")); + writeStr (usage_fd, GTXT (" -S {on|off|<seconds>}\t Set the interval for periodic sampling of process-wide resource utilization\n")); + writeStr (usage_fd, GTXT (" -l <signal>\tspecify signal that will trigger a sample of process-wide resource utilization\n")); + writeStr (usage_fd, GTXT (" -o <expt>\tspecify experiment name\n")); + writeStr (usage_fd, GTXT (" --verbose\tprint expanded log of processing\n")); + writeStr (usage_fd, GTXT (" -C <label>\tspecify comment label (up to 10 may appear)\n")); + writeStr (usage_fd, GTXT (" -V|--version\tprint version number and exit\n")); +*/ + /* don't document this feature */ + // writeStr (usage_fd, GTXT(" -Z\tPreload mem.so, and launch target [no experiment]\n") ); +/* + writeStr (usage_fd, GTXT ("\n See the gp-collect(1) man page for more information\n")); +*/ + +#if 0 + /* print an extended usage message */ + /* find a Java for Java profiling, set Java on to check Java */ + find_java (); + cc->set_java_mode (NTXT ("on")); + + /* check for variable-clock rate */ + unsigned char mode = COL_CPUFREQ_NONE; + get_cpu_frequency (&mode); + if (mode != COL_CPUFREQ_NONE) + writeStr (usage_fd, GTXT ("NOTE: system has variable clock frequency, which may cause variable program run times.\n")); + + /* show the experiment that would be run */ + writeStr (usage_fd, GTXT ("\n Default experiment:\n")); + char *ccret = cc->setup_experiment (); + if (ccret != NULL) + { + writeStr (usage_fd, ccret); + free (ccret); + exit (1); + } + cc->delete_expt (); + ccret = cc->show (1); + if (ccret != NULL) + { + writeStr (usage_fd, ccret); + free (ccret); + } +#endif +} + +void +collect::short_usage () +{ + if (no_short_usage == 0) + dbe_write (usage_fd, GTXT ("Run \"%s --help\" for a usage message.\n"), whoami); +} + +void +collect::show_hwc_usage () +{ + usage_fd = 1; + short_usage (); + cc->setup_hwc (); + hwc_usage (false, whoami, NULL); +} + +void +collect::writeStr (int f, const char *buf) +{ + if (buf != NULL) + write (f, buf, strlen (buf)); +} |