From: Maynard J. <may...@us...> - 2013-06-25 14:01:12
|
New ocount tool and associated ocount_counter classes This patch implements the executable 'ocount' tool, as well as some classes used for opening and managing the "perf_events" file descriptors (opened via perf_event_open). Signed-off-by: Maynard Johnson <may...@us...> --- Makefile.am | 1 + configure.ac | 1 + pe_counting/Makefile.am | 28 ++ pe_counting/ocount.cpp | 817 ++++++++++++++++++++++++++++++++++++++++ pe_counting/ocount_counter.cpp | 614 ++++++++++++++++++++++++++++++ pe_counting/ocount_counter.h | 113 ++++++ 6 files changed, 1574 insertions(+), 0 deletions(-) create mode 100644 pe_counting/Makefile.am create mode 100644 pe_counting/ocount.cpp create mode 100644 pe_counting/ocount_counter.cpp create mode 100644 pe_counting/ocount_counter.h diff --git a/Makefile.am b/Makefile.am index 5d1117a..293114b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,6 +22,7 @@ SUBDIRS = \ libperf_events \ pe_profiling \ libpe_utils \ + pe_counting \ agents #### ATTENTION #### # The agents directory must be kept as the last subdir diff --git a/configure.ac b/configure.ac index fce24f6..4681d57 100644 --- a/configure.ac +++ b/configure.ac @@ -399,6 +399,7 @@ OP_DOCDIR=`eval echo "${my_op_prefix}/share/doc/$PACKAGE/"` AC_SUBST(OP_DOCDIR) AC_OUTPUT(Makefile \ + pe_counting/Makefile \ libpe_utils/Makefile \ pe_profiling/Makefile \ libperf_events/Makefile \ diff --git a/pe_counting/Makefile.am b/pe_counting/Makefile.am new file mode 100644 index 0000000..6e7c17f --- /dev/null +++ b/pe_counting/Makefile.am @@ -0,0 +1,28 @@ +LIBS=@LIBERTY_LIBS@ @PFM_LIB@ +if BUILD_FOR_PERF_EVENT + +AM_CPPFLAGS = \ + -I ${top_srcdir}/libutil \ + -I ${top_srcdir}/libutil++ \ + -I ${top_srcdir}/libop \ + -I ${top_srcdir}/libperf_events \ + -I ${top_srcdir}/libpe_utils \ + @PERF_EVENT_FLAGS@ \ + @OP_CPPFLAGS@ + +ocount_SOURCES = ocount.cpp \ + ocount_counter.h \ + ocount_counter.cpp + + +AM_CXXFLAGS = @OP_CXXFLAGS@ +AM_LDFLAGS = @OP_LDFLAGS@ + +bin_PROGRAMS = ocount +ocount_LDADD = ../libpe_utils/libpe_utils.a \ + ../libpe_utils/libpe_utils.a \ + ../libop/libop.a \ + ../libutil/libutil.a \ + ../libutil++/libutil++.a + +endif diff --git a/pe_counting/ocount.cpp b/pe_counting/ocount.cpp new file mode 100644 index 0000000..7301a01 --- /dev/null +++ b/pe_counting/ocount.cpp @@ -0,0 +1,817 @@ +/** + * @file ocount.cpp + * Tool for event counting using the new Linux Performance Events Subsystem. + * + * @remark Copyright 2013 OProfile authors + * @remark Read the file COPYING + * + * Created on: May 21, 2013 + * @author Maynard Johnson + * (C) Copyright IBM Corp. 2013 + * + */ + +#include "config.h" + +#include <iostream> +#include <fstream> +#include <vector> +#include <set> + +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <sys/time.h> + +#include "op_pe_utils.h" +#include "ocount_counter.h" +#include "op_cpu_type.h" +#include "op_cpufreq.h" +#include "operf_event.h" +#include "cverb.h" +#include "op_libiberty.h" + +// Globals +char * app_name = NULL; +bool use_cpu_minus_one = false; +std::vector<operf_event_t> events; +op_cpu cpu_type; + + +static char * app_name_SAVE = NULL; +static char ** app_args = NULL; +static bool app_started; +static bool startApp; +static bool stop = false; +static std::ofstream outfile; +static pid_t my_uid; +static double cpu_speed; +static ocount_record * orecord; +static pid_t app_PID = -1; + +using namespace std; +using namespace op_pe_utils; + + +typedef enum END_CODE { + ALL_OK = 0, + APP_ABNORMAL_END = 1, + PERF_RECORD_ERROR = 2, + PERF_READ_ERROR = 4, + PERF_BOTH_ERROR = 8 +} end_code_t; + +namespace ocount_options { +bool verbose; +bool system_wide; +vector<pid_t> processes; +vector<pid_t> threads; +vector<int> cpus; +string outfile; +bool separate_cpu; +bool separate_thread; +vector<string> evts; +bool csv_output; +long display_interval; +long num_intervals; +} + + +static enum op_runmode runmode = OP_MAX_RUNMODE; +static string runmode_options[] = { "<command> [command-args]", "--system-wide", "--cpu-list", + "--process-list", "--thread-list" +}; + + +struct option long_options [] = +{ + {"verbose", no_argument, NULL, 'V'}, + {"system-wide", no_argument, NULL, 's'}, + {"cpu-list", required_argument, NULL, 'C'}, + {"process-list", required_argument, NULL, 'p'}, + {"thread-list", required_argument, NULL, 'r'}, + {"events", required_argument, NULL, 'e'}, + {"output-file", required_argument, NULL, 'f'}, + {"separate-cpu", no_argument, NULL, 'c'}, + {"separate-thread", no_argument, NULL, 't'}, + {"brief-format", no_argument, NULL, 'b'}, + {"time-interval", required_argument, NULL, 'i'}, + {"help", no_argument, NULL, 'h'}, + {"usage", no_argument, NULL, 'u'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 9, NULL, 0} +}; + +const char * short_options = "VsC:p:r:e:f:ctbi:huv"; + +static void cleanup(void) +{ + free(app_name_SAVE); + free(app_args); + events.clear(); + if (!ocount_options::outfile.empty()) + outfile.close(); +} + + +// Signal handler for main (parent) process. +static void op_sig_stop(int sigval __attribute__((unused))) +{ + // Received a signal to quit, so we need to stop the + // app being counted. + size_t dummy __attribute__ ((__unused__)); + stop = true; + if (cverb << vdebug) + dummy = write(1, "in op_sig_stop\n", 15); + if (startApp) + kill(app_PID, SIGKILL); +} + +void set_signals_for_parent(void) +{ + struct sigaction act; + sigset_t ss; + + sigfillset(&ss); + sigprocmask(SIG_UNBLOCK, &ss, NULL); + + act.sa_handler = op_sig_stop; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGINT); + + if (sigaction(SIGINT, &act, NULL)) { + perror("ocount: install of SIGINT handler failed: "); + exit(EXIT_FAILURE); + } +} + + +static void __print_usage_and_exit(const char * extra_msg) +{ + if (extra_msg) + cerr << extra_msg << endl; + cerr << "usage: ocount [ options ] [ --system-wide | --pid <pid> | [ command [ args ] ] ]" << endl; + cerr << "See ocount man page for details." << endl; + exit(EXIT_FAILURE); +} + +static string args_to_string(void) +{ + string ret; + char * const * ptr = app_args + 1; + while (*ptr != NULL) { + ret.append(*ptr); + ret += ' '; + ptr++; + } + return ret; +} + +static int app_ready_pipe[2], start_app_pipe[2]; + +void run_app(void) +{ + // ASSUMPTION: app_name is a fully-qualified pathname + char * app_fname = rindex(app_name, '/') + 1; + app_args[0] = app_fname; + + string arg_str = args_to_string(); + cverb << vdebug << "Exec args are: " << app_fname << " " << arg_str << endl; + // Fake an exec to warm-up the resolver + execvp("", app_args); + // signal to the parent that we're ready to exec + int startup = 1; + if (write(app_ready_pipe[1], &startup, sizeof(startup)) < 0) { + perror("Internal error on app_ready_pipe"); + _exit(EXIT_FAILURE); + } + + // wait for parent to tell us to start + int startme = 0; + if (read(start_app_pipe[0], &startme, sizeof(startme)) == -1) { + perror("Internal error in run_app on start_app_pipe"); + _exit(EXIT_FAILURE); + } + if (startme != 1) + _exit(EXIT_SUCCESS); + + cverb << vdebug << "parent says start app " << app_name << endl; + execvp(app_name, app_args); + cerr << "Failed to exec " << app_fname << " " << arg_str << ": " << strerror(errno) << endl; + /* We don't want any cleanup in the child */ + _exit(EXIT_FAILURE); + +} + +bool start_counting(void) +{ + vector<pid_t> proc_list; // May contain processes or threads + bool procs_are_threads; + + // The only process that should return from this function is the process + // which invoked it. Any forked process must do _exit() rather than return(). + + startApp = runmode == OP_START_APP; + + if (startApp) { + if (pipe(app_ready_pipe) < 0 || pipe(start_app_pipe) < 0) { + perror("Internal error: ocount-record could not create pipe"); + _exit(EXIT_FAILURE); + } + app_PID = fork(); + if (app_PID < 0) { + perror("Internal error: fork failed"); + _exit(EXIT_FAILURE); + } else if (app_PID == 0) { // child process for exec'ing app + run_app(); + } + } + + // parent + int startup; + if (startApp) { + if (read(app_ready_pipe[0], &startup, sizeof(startup)) == -1) { + perror("Internal error on app_ready_pipe"); + return false; + } else if (startup != 1) { + cerr << "app is not ready to start; exiting" << endl; + return false; + } + proc_list.push_back(app_PID); + procs_are_threads = false; + } else if (!ocount_options::threads.empty()) { + procs_are_threads = true; + proc_list = ocount_options::threads; + } else if (!ocount_options::processes.empty()) { + procs_are_threads = false; + proc_list = ocount_options::processes; + } + + if (startApp) { + // Tell app_PID to start the app + cverb << vdebug << "telling child to start app" << endl; + if (write(start_app_pipe[1], &startup, sizeof(startup)) < 0) { + perror("Internal error on start_app_pipe"); + return -1; + } + app_started = true; + } + + orecord = new ocount_record(runmode, events); + bool ret; + switch (runmode) { + case OP_START_APP: + ret = orecord->start_counting_app_process(app_PID); + break; + case OP_SYSWIDE: + ret = orecord->start_counting_syswide(); + break; + case OP_CPULIST: + ret = orecord->start_counting_cpulist(ocount_options::cpus); + break; + case OP_PROCLIST: + ret = orecord->start_counting_tasklist(ocount_options::processes, false); + break; + case OP_THREADLIST: + ret = orecord->start_counting_tasklist(ocount_options::threads, true); + break; + default: + ret = false; + break; // impossible to get here, since we validate runmode prior to this point + } + if (!orecord->get_valid()) { + /* If valid is false, it means that one of the "known" errors has + * occurred: + * - monitored process has already ended + * - passed PID was invalid + * - device or resource busy + */ + cverb << vdebug << "ocount record init failed" << endl; + ret = false; + } + + return ret; +} + +static void do_results(ostream & out) +{ + orecord->output_results(out, ocount_options::separate_cpu | ocount_options::separate_thread, + ocount_options::csv_output); +} + +end_code_t _get_waitpid_status(int waitpid_status, int wait_rc) +{ + end_code_t rc = ALL_OK; + if (wait_rc < 0) { + if (errno == EINTR) { + // Ctrl-C will only kill the monitored app. See the op_sig_stop signal handler. + cverb << vdebug << "Caught ctrl-C. Killed app process." << endl; + } else { + cerr << "waitpid for app process failed: " << strerror(errno) << endl; + rc = APP_ABNORMAL_END; + } + } else if (wait_rc) { + if (WIFEXITED(waitpid_status) && (!WEXITSTATUS(waitpid_status))) { + cverb << vdebug << "app process ended normally." << endl; + } else if (WIFEXITED(waitpid_status)) { + cerr << "app process exited with the following status: " + << WEXITSTATUS(waitpid_status) << endl; + rc = APP_ABNORMAL_END; + } else if (WIFSIGNALED(waitpid_status)) { + if (WTERMSIG(waitpid_status) != SIGKILL) { + cerr << "app process killed by signal " + << WTERMSIG(waitpid_status) << endl; + rc = APP_ABNORMAL_END; + } + } + } + return rc; +} + +end_code_t _wait_for_app(ostream & out) +{ + int wait_rc; + end_code_t rc = ALL_OK; + int waitpid_status = 0; + + bool done = false; + cverb << vdebug << "going into waitpid on monitored app " << app_PID << endl; + if (ocount_options::display_interval) { + long number_intervals = ocount_options::num_intervals; + do { + struct timeval mytime; + int countdown = ocount_options::display_interval; + while (countdown) { + sleep(1); + if (--countdown == 0) { + if (gettimeofday(&mytime, NULL) < 0) { + cleanup(); + perror("gettimeofday"); + exit(EXIT_FAILURE); + } + if (!ocount_options::csv_output) + out << endl << "Current time (seconds since epoch): "; + else + out << endl << "t:"; + out << dec << mytime.tv_sec; + do_results(out); + } + wait_rc = waitpid(app_PID, &waitpid_status, WNOHANG); + if (wait_rc) { + rc = _get_waitpid_status(waitpid_status, wait_rc); + done = true; + countdown = 0; + } + } + if (--number_intervals == 0) { + done = true; + kill(app_PID, SIGKILL); + } + } while (!done); + } else { + wait_rc = waitpid(app_PID, &waitpid_status, 0); + rc = _get_waitpid_status(waitpid_status, wait_rc); + } + return rc; +} + +static end_code_t _run(ostream & out) +{ + end_code_t rc = ALL_OK; + + // Fork processes with signals blocked. + sigset_t ss; + sigfillset(&ss); + sigprocmask(SIG_BLOCK, &ss, NULL); + + if (!start_counting()) { + return PERF_RECORD_ERROR; + } + // parent continues here + if (startApp) + cverb << vdebug << "app " << app_PID << " is running" << endl; + + set_signals_for_parent(); + if (startApp) { + rc = _wait_for_app(out); + } else { + cout << "ocount: Press Ctl-c or 'kill -SIGINT " << getpid() << "' to stop counting" << endl; + if (ocount_options::display_interval) { + long number_intervals = ocount_options::num_intervals; + struct timeval mytime; + while (!stop) { + sleep(ocount_options::display_interval); + if (gettimeofday(&mytime, NULL) < 0) { + cleanup(); + perror("gettimeofday"); + exit(EXIT_FAILURE); + } + if (!ocount_options::csv_output) + out << endl << "Current time (seconds since epoch): "; + else + out << endl << "t:"; + out << dec << mytime.tv_sec; + do_results(out); + if (--number_intervals == 0) + stop = true; + } + } else { + while (!stop) + sleep(1); + } + } + return rc; +} + +static void _parse_cpu_list(void) +{ + char * comma_sep; + char * endptr; + char * aCpu = strtok_r(optarg, ",", &comma_sep); + do { + int tmp = strtol(aCpu, &endptr, 10); + if ((endptr >= aCpu) && (endptr <= (aCpu + strlen(aCpu) - 1))) { + // Check if user has passed a range of cpu numbers: e.g., '3-8' + char * dash_sep; + char * ending_cpu_str, * starting_cpu_str = strtok_r(aCpu, "-", &dash_sep); + int starting_cpu, ending_cpu; + if (starting_cpu_str) { + ending_cpu_str = strtok_r(NULL, "-", &dash_sep); + if (!ending_cpu_str) { + __print_usage_and_exit("ocount: Invalid cpu range."); + } + starting_cpu = strtol(starting_cpu_str, &endptr, 10); + if ((endptr >= starting_cpu_str) && + (endptr <= (starting_cpu_str + strlen(starting_cpu_str) - 1))) { + __print_usage_and_exit("ocount: Invalid numeric value for --cpu-list option."); + } + ending_cpu = strtol(ending_cpu_str, &endptr, 10); + if ((endptr >= ending_cpu_str) && + (endptr <= (ending_cpu_str + strlen(ending_cpu_str) - 1))) { + __print_usage_and_exit("ocount: Invalid numeric value for --cpu-list option."); + } + for (int i = starting_cpu; i < ending_cpu + 1; i++) + ocount_options::cpus.push_back(i); + } else { + __print_usage_and_exit("ocount: Invalid numeric value for --cpu-list option."); + } + } else { + ocount_options::cpus.push_back(tmp); + } + } while ((aCpu = strtok_r(NULL, ",", &comma_sep))); +} + +static void _parse_time_interval(void) +{ + char * endptr; + char * num_intervals, * interval = strtok(optarg, ":"); + ocount_options::display_interval = strtol(interval, &endptr, 10); + if ((endptr >= interval) && (endptr <= (interval + strlen(interval) - 1))) { + __print_usage_and_exit("ocount: Invalid numeric value for num_seconds."); + } + // User has specified num_intervals: e.g., '-i 5:10' + num_intervals = strtok(NULL, ":"); + if (num_intervals) { + ocount_options::num_intervals = strtol(num_intervals, &endptr, 10); + if ((endptr >= num_intervals) && (endptr <= (num_intervals + strlen(num_intervals) - 1))) + __print_usage_and_exit("ocount: Invalid numeric value for num_intervals."); + } +} + +static int _process_ocount_and_app_args(int argc, char * const argv[]) +{ + bool keep_trying = true; + int idx_of_non_options = 0; + setenv("POSIXLY_CORRECT", "1", 0); + while (keep_trying) { + int option_idx = 0; + int c = getopt_long(argc, argv, short_options, long_options, &option_idx); + switch (c) { + char * endptr; + char * event; + + case -1: + if (optind != argc) { + idx_of_non_options = optind; + } + keep_trying = false; + break; + case '?': + cerr << "ocount: non-option detected at optind " << optind << endl; + keep_trying = false; + idx_of_non_options = -1; + break; + case 'V': + ocount_options::verbose = true; + break; + case 's': + ocount_options::system_wide = true; + break; + case 'C': + _parse_cpu_list(); + break; + case 'p': + { + char * aPid = strtok(optarg, ","); + do { + ocount_options::processes.push_back(strtol(aPid, &endptr, 10)); + if ((endptr >= aPid) && (endptr <= (aPid + strlen(aPid) - 1))) + __print_usage_and_exit("ocount: Invalid numeric value for --process-list option."); + } while ((aPid = strtok(NULL, ","))); + break; + } + case 'r': + { + char * aTid = strtok(optarg, ","); + do { + ocount_options::threads.push_back(strtol(aTid, &endptr, 10)); + if ((endptr >= aTid) && (endptr <= (aTid + strlen(aTid) - 1))) + __print_usage_and_exit("ocount: Invalid numeric value for --thread-list option."); + } while ((aTid = strtok(NULL, ","))); + break; + } + case 'e': + event = strtok(optarg, ","); + do { + ocount_options::evts.push_back(event); + } while ((event = strtok(NULL, ","))); + break; + case 'f': + ocount_options::outfile = optarg; + break; + case 'c': + ocount_options::separate_cpu = true; + break; + case 't': + ocount_options::separate_thread = true; + break; + case 'b': + ocount_options::csv_output = true; + break; + case 'i': + _parse_time_interval(); + break; + case 'h': + __print_usage_and_exit(NULL); + break; + case 'u': + __print_usage_and_exit(NULL); + break; + case 'v': + cout << argv[0] << ": " << PACKAGE << " " << VERSION << " compiled on " << __DATE__ + << " " << __TIME__ << endl; + exit(EXIT_SUCCESS); + break; + default: + __print_usage_and_exit("ocount: unexpected end of arg parsing"); + } + } + return idx_of_non_options; +} + + +static enum op_runmode _get_runmode(int starting_point) +{ + enum op_runmode ret_rm = OP_MAX_RUNMODE; + for (int i = starting_point; i < OP_MAX_RUNMODE && ret_rm == OP_MAX_RUNMODE; i++) { + switch (i) { + // There is no option to check for OP_START_APP; we include a case + // statement here just to silence Coverity. + case OP_START_APP: + break; + case OP_SYSWIDE: + if (ocount_options::system_wide) + ret_rm = OP_SYSWIDE; + break; + case OP_CPULIST: + if (!ocount_options::cpus.empty()) + ret_rm = OP_CPULIST; + break; + case OP_PROCLIST: + if (!ocount_options::processes.empty()) + ret_rm = OP_PROCLIST; + break; + case OP_THREADLIST: + if (!ocount_options::threads.empty()) + ret_rm = OP_THREADLIST; + break; + default: + break; + } + } + return ret_rm; +} + +static void _validate_args(void) +{ + if (ocount_options::verbose && !verbose::setup("debug")) { + cerr << "unknown --verbose= options\n"; + __print_usage_and_exit(NULL); + } + if (runmode == OP_START_APP) { + enum op_runmode conflicting_mode = OP_MAX_RUNMODE; + if (ocount_options::system_wide) + conflicting_mode = OP_SYSWIDE; + else if (!ocount_options::cpus.empty()) + conflicting_mode = OP_CPULIST; + else if (!ocount_options::processes.empty()) + conflicting_mode = OP_PROCLIST; + else if (!ocount_options::threads.empty()) + conflicting_mode = OP_THREADLIST; + + if (conflicting_mode != OP_MAX_RUNMODE) { + cerr << "Run mode " << runmode_options[OP_START_APP] << " is incompatible with " + << runmode_options[conflicting_mode] << endl; + __print_usage_and_exit(NULL); + } + } else { + enum op_runmode rm2; + runmode = _get_runmode(OP_SYSWIDE); + if (runmode == OP_MAX_RUNMODE) { + __print_usage_and_exit("You must either pass in the name of a command or app to run or specify a run mode"); + } + rm2 = _get_runmode(runmode + 1); + if (rm2 != OP_MAX_RUNMODE) { + cerr << "Run mode " << runmode_options[rm2] << " is incompatible with " + << runmode_options[runmode] << endl; + __print_usage_and_exit(NULL); + } + + } + + if (ocount_options::separate_cpu && !(ocount_options::system_wide || !ocount_options::cpus.empty())) { + cerr << "The --separate-cpu option is only valid with --system-wide or --cpu-list." << endl; + __print_usage_and_exit(NULL); + } + + if (ocount_options::separate_thread && !(!ocount_options::threads.empty() || !ocount_options::processes.empty())) { + cerr << "The --separate-thread option is only valid with --process_list or --thread_list." << endl; + __print_usage_and_exit(NULL); + } + + if (runmode == OP_CPULIST) { + int num_cpus = use_cpu_minus_one ? 1 : sysconf(_SC_NPROCESSORS_ONLN); + if (num_cpus < 1) { + cerr << "System config says number of online CPUs is " << num_cpus << "; cannot continue" << endl; + exit(EXIT_FAILURE); + } + + set<int> available_cpus = op_pe_utils::op_get_available_cpus(num_cpus); + size_t k; + for (k = 0; k < ocount_options::cpus.size(); k++) { + if (available_cpus.find(ocount_options::cpus[k]) == available_cpus.end()) { + cerr << "Specified CPU " << ocount_options::cpus[k] << " is not valid" << endl; + __print_usage_and_exit(NULL); + } + } + } +} + +static void process_args(int argc, char * const argv[]) +{ + int non_options_idx = _process_ocount_and_app_args(argc, argv); + + if (non_options_idx < 0) { + __print_usage_and_exit(NULL); + } else if ((non_options_idx) > 0) { + runmode = OP_START_APP; + app_name = (char *) xmalloc(strlen(argv[non_options_idx]) + 1); + strcpy(app_name, argv[non_options_idx]); + // Note 1: app_args[0] is placeholder for app_fname (filled in later). + // Note 2: app_args[<end>] is set to NULL (required by execvp) + if (non_options_idx < (argc -1)) { + app_args = (char **) xmalloc((sizeof *app_args) * + (argc - non_options_idx + 1)); + for(int i = non_options_idx + 1; i < argc; i++) { + app_args[i - non_options_idx] = argv[i]; + } + app_args[argc - non_options_idx] = NULL; + } else { + app_args = (char **) xmalloc((sizeof *app_args) * 2); + app_args[1] = NULL; + } + if (op_validate_app_name(&app_name, &app_name_SAVE) < 0) { + __print_usage_and_exit(NULL); + } + } + _validate_args(); + + /* At this point, we know which of the three counting modes the user requested: + * - count events in named app + * - count events in app by PID + * - count events in whole system + */ + + if (ocount_options::evts.empty()) { + // Use default event + op_pe_utils::op_get_default_event(); + } else { + op_pe_utils::op_process_events_list(ocount_options::evts); + } + cverb << vdebug << "Number of events passed is " << events.size() << endl; + return; +} + +int main(int argc, char * const argv[]) +{ + int rc; + bool get_results = true; + int perf_event_paranoid = op_get_sys_value("/proc/sys/kernel/perf_event_paranoid"); + + my_uid = geteuid(); + rc = op_check_perf_events_cap(use_cpu_minus_one); + if (rc == EACCES) { + /* Early perf_events kernels required the cpu argument to perf_event_open + * to be '-1' when setting up to monitor a single process if 1) the user is + * not root; and 2) perf_event_paranoid is > 0. An EACCES error would be + * returned if passing '0' or greater for the cpu arg and the above criteria + * was not met. Unfortunately, later kernels turned this requirement around + * such that the passed cpu arg must be '0' or greater when the user is not + * root. + * + * We don't really have a good way to check whether we're running on such an + * early kernel except to try the perf_event_open with different values to see + * what works. + */ + if (my_uid != 0 && perf_event_paranoid > 0) { + use_cpu_minus_one = true; + rc = op_check_perf_events_cap(use_cpu_minus_one); + } + } + if (rc == EBUSY) + cerr << "Performance monitor unit is busy. Do 'opcontrol --deinit' and try again." << endl; + else if (rc == ENOSYS) + cerr << "Your kernel does not implement a required syscall" + << " for the ocount program." << endl; + else if (rc == ENOENT) + cerr << "Your kernel's Performance Events Subsystem does not support" + << " your processor type." << endl; + else if (rc) + cerr << "Unexpected error running ocount: " << strerror(rc) << endl; + + if (rc) + exit(1); + + cpu_type = op_get_cpu_type(); + cpu_speed = op_cpu_frequency(); + process_args(argc, argv); + + if ((runmode == OP_SYSWIDE || runmode == OP_CPULIST) && ((my_uid != 0) && (perf_event_paranoid > 0))) { + cerr << "To do "; + if (runmode == OP_SYSWIDE) + cerr << "system-wide "; + else + cerr << "cpu-list "; + cerr << "event counting, either you must be root or" << endl; + cerr << "/proc/sys/kernel/perf_event_paranoid must be set to 0 or -1." << endl; + cleanup(); + exit(1); + } + + if (cpu_type == CPU_NO_GOOD) { + cerr << "Unable to ascertain cpu type. Exiting." << endl; + cleanup(); + exit(1); + } + + if (!ocount_options::outfile.empty()) { + outfile.open(ocount_options::outfile.c_str()); + } + ostream & out = !ocount_options::outfile.empty() ? outfile : cout; + + end_code_t run_result; + if ((run_result = _run(out))) { + get_results = false; + if (startApp && app_started && (run_result != APP_ABNORMAL_END)) { + int rc; + cverb << vdebug << "Killing monitored app . . ." << endl; + rc = kill(app_PID, SIGKILL); + if (rc) { + if (errno == ESRCH) + cverb << vdebug + << "Unable to kill monitored app because it has already ended" + << endl; + else + perror("Attempt to kill monitored app failed."); + } + } + if ((run_result == PERF_RECORD_ERROR) || (run_result == PERF_BOTH_ERROR)) { + cerr << "Error running ocount" << endl; + } else { + get_results = true; + cverb << vdebug << "WARNING: Results may be incomplete due to to abend of monitored app." << endl; + } + } + if (get_results) + // We don't do a final display of results if we've been doing it on an interval already. + if (!ocount_options::display_interval) + do_results(out); + + cleanup(); + return 0; +} diff --git a/pe_counting/ocount_counter.cpp b/pe_counting/ocount_counter.cpp new file mode 100644 index 0000000..abc60e7 --- /dev/null +++ b/pe_counting/ocount_counter.cpp @@ -0,0 +1,614 @@ +/** + * @file ocount_counter.cpp + * Functions and classes for ocount tool. + * + * @remark Copyright 2013 OProfile authors + * @remark Read the file COPYING + * + * Created on: May 22, 2013 + * @author Maynard Johnson + * (C) Copyright IBM Corp. 2013 + * + */ +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <dirent.h> +#include <stdlib.h> +#include <sys/types.h> +#include <signal.h> + + +#include <iostream> +#include <sstream> +#include <stdexcept> + +#include "ocount_counter.h" +#include "op_pe_utils.h" +#include "operf_event.h" +#include "cverb.h" + +extern verbose vdebug; +extern bool use_cpu_minus_one; +extern char * app_name; + +using namespace std; + + +ocount_counter::ocount_counter(operf_event_t & evt, bool enable_on_exec, + bool inherit) +{ + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.type = PERF_TYPE_RAW; + attr.config = evt.evt_code; + attr.inherit = inherit ? 1 : 0; + attr.enable_on_exec = enable_on_exec ? 1 : 0; + attr.disabled = attr.enable_on_exec; + attr.exclude_idle = 0; + attr.exclude_kernel = evt.no_kernel; + attr.exclude_user = evt.no_user; + attr.exclude_hv = evt.no_hv; + // This format allows us to tell user percent of time an event was scheduled + // when multiplexing has been done by the kernel. + attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | + PERF_FORMAT_TOTAL_TIME_RUNNING; + event_name = evt.name; + fd = -1; +} + +ocount_counter::~ocount_counter() { +} + +#include <stdio.h> +int ocount_counter::perf_event_open(pid_t _pid, int _cpu) +{ + fd = op_perf_event_open(&attr, _pid, _cpu, -1, 0); + if (fd < 0) { + int ret = -1; + cverb << vdebug << "perf_event_open failed: " << strerror(errno) << endl; + if (errno == EBUSY) { + cerr << "The performance monitoring hardware reports EBUSY. Is another profiling tool in use?" << endl + << "On some architectures, tools such as oprofile and perf being used in system-wide " + << "mode can cause this problem." << endl; + ret = OP_PERF_HANDLED_ERROR; + } else if (errno == ESRCH) { + cerr << "!!!! No samples collected !!!" << endl; + cerr << "The target program/command ended before profiling was started." << endl; + ret = OP_PERF_HANDLED_ERROR; + } else { + cerr << "perf_event_open failed with " << strerror(errno) << endl; + } + return ret; + } + pid = _pid; + cpu = _cpu; + + cverb << vdebug << "perf_event_open returning fd " << fd << endl; + return fd; +} + +int ocount_counter::read_count_data(ocount_accum_t * count_data) +{ + size_t len = 3 * sizeof(u64); + char * buf = (char *)count_data; + + while (len) { + int ret = read(fd, buf, len); + + if (ret <= 0) + return ret; + + len -= ret; + buf += ret; + } + + return 0; +} + +ocount_record::ocount_record(enum op_runmode _runmode, std::vector<operf_event_t> & _evts) +{ + runmode = _runmode; + evts = _evts; + valid = false; + system_wide = false; +} + +bool ocount_record::start_counting_app_process(pid_t _pid) +{ + if (valid) { + cerr << "ocount internal error: ocount_record already initialized" << endl; + return false; + } + if (runmode != OP_START_APP) { + cerr << "ocount internal error: Current run mode " << runmode << " is incompatible with " + "starting app." << endl; + return false; + } + app_pid = _pid; + setup(); + return true; +} + +/* + * There are separate ocount options for counting events for a set of processes ("--process-list") + * or a set of threads ("--thread-list"). This function is used for passing the set of either + * processes or threads to ocount_record, along with a boolean argument to indicate whether or not + * the set of passed tasks are threads. If they are threads, we set up perf_event_open to NOT + * do "inherit". + */ +bool ocount_record::start_counting_tasklist(std::vector<pid_t> _tasks, bool _are_threads) +{ + if (valid) { + cerr << "ocount internal error: ocount_record already initialized" << endl; + return false; + } + tasks_are_threads = _are_threads; + specified_tasks = _tasks; + if (tasks_are_threads) { + if (runmode != OP_THREADLIST) { + cerr << "ocount internal error: Current run mode " << runmode << " is incompatible with " + "--thread-list option." << endl; + return false; + } + } else { + if (runmode != OP_PROCLIST) { + cerr << "ocount internal error: Current run mode " << runmode << " is incompatible with " + "--process-list option." << endl; + return false; + } + } + setup(); + if (tasks_to_count.empty()) { + cerr << "No valid tasks to monitor -- quitting." << endl; + return false; + } + return true; +} + +bool ocount_record::start_counting_cpulist(std::vector<int> _cpus) +{ + if (valid) { + cerr << "ocount internal error: ocount_record already initialized" << endl; + return false; + } + if (runmode != OP_CPULIST) { + cerr << "ocount internal error: Current run mode " << runmode << " is incompatible with " + "--cpu-list option." << endl; + return false; + } + specified_cpus = _cpus; + setup(); + return true; +} + +bool ocount_record::start_counting_syswide(void) +{ + if (valid) { + cerr << "ocount internal error: ocount_record already initialized" << endl; + return false; + } + if (runmode != OP_SYSWIDE) { + cerr << "ocount internal error: Current run mode " << runmode << " is incompatible with " + "--system-wide option." << endl; + return false; + } + system_wide = true; + setup(); + return true; +} + +int ocount_record::do_counting_per_task(void) +{ + string err_msg; + int rc = 0; + + for (set<pid_t>::iterator it = tasks_to_count.begin(); it != tasks_to_count.end(); it++) { + pid_t the_pid = *it; + bool inherit = are_tasks_processes(); + cverb << vdebug << "calling perf_event_open for task " << the_pid << endl; + for (unsigned event = 0; event < evts.size(); event++) { + ocount_accum_t count_data = {0ULL, 0ULL, 0ULL}; + accum_counts.push_back(count_data); + ocount_counter op_ctr(ocount_counter(evts[event], false, inherit)); + if ((rc = op_ctr.perf_event_open(the_pid, -1)) < 0) { + err_msg = "Internal Error. Perf event setup failed."; + goto out; + } else { + rc = 0; + } + perfCounters.push_back(op_ctr); + } + } +out: + if (rc && rc != OP_PERF_HANDLED_ERROR) + throw runtime_error(err_msg); + return rc; +} + +int ocount_record::do_counting_per_cpu(void) +{ + string err_msg; + int rc = 0; + + /* We'll do this sanity check here, but we also do it at the front-end where user + * args are being validated. If we wait until we get here, the invalid CPU argument + * becomes an ugly thrown exception. + */ + set<int> available_cpus = op_pe_utils::op_get_available_cpus(num_cpus); + if (runmode == OP_CPULIST) { + size_t k; + for (k = 0; k < specified_cpus.size(); k++) { + if (available_cpus.find(specified_cpus[k]) == available_cpus.end()) { + ostringstream err_msg_ostr; + err_msg_ostr << "Specified CPU " << specified_cpus[k] << " is not valid"; + err_msg = err_msg_ostr.str(); + rc = -1; + goto out; + } else { + cpus_to_count.insert(specified_cpus[k]); + } + } + } else { + cpus_to_count = available_cpus; + } + + for (set<pid_t>::iterator it = cpus_to_count.begin(); it != cpus_to_count.end(); it++) { + int the_cpu = *it; + cverb << vdebug << "calling perf_event_open for cpu " << the_cpu << endl; + for (unsigned event = 0; event < evts.size(); event++) { + ocount_accum_t count_data = {0ULL, 0ULL, 0ULL}; + accum_counts.push_back(count_data); + ocount_counter op_ctr(ocount_counter(evts[event], false, true)); + if ((rc = op_ctr.perf_event_open(-1, the_cpu)) < 0) { + err_msg = "Internal Error. Perf event setup failed."; + goto out; + } else { + rc = 0; + } + perfCounters.push_back(op_ctr); + } + } +out: + if (rc && rc != OP_PERF_HANDLED_ERROR) + throw runtime_error(err_msg); + return rc; +} + +void ocount_record::setup() +{ + int rc = 0; + string err_msg; + + if (!specified_tasks.empty()) { + if ((rc = get_process_info(specified_tasks)) < 0) { + if (rc == OP_PERF_HANDLED_ERROR) + return; + else + throw runtime_error("Unexpected error in ocount_record setup"); + } + } + + + /* To set up to count events for an existing thread group, we need call perf_event_open + * for each thread, and we need to pass cpu=-1 on the syscall. + */ + use_cpu_minus_one = use_cpu_minus_one ? true : ((system_wide || (runmode == OP_CPULIST)) ? false : true); + num_cpus = use_cpu_minus_one ? 1 : sysconf(_SC_NPROCESSORS_ONLN); + if (num_cpus < 1) { + char int_str[256]; + sprintf(int_str, "Number of online CPUs is %d; cannot continue", num_cpus); + throw runtime_error(int_str); + } + if (system_wide || (runmode == OP_CPULIST)) { + rc = do_counting_per_cpu(); + } else if (!specified_tasks.empty()) { + rc = do_counting_per_task(); + } else { + cverb << vdebug << "calling perf_event_open for pid " << app_pid << endl; + for (unsigned event = 0; event < evts.size(); event++) { + ocount_accum_t count_data = {0ULL, 0ULL, 0ULL}; + accum_counts.push_back(count_data); + ocount_counter op_ctr(ocount_counter(evts[event], true, true)); + if ((rc = op_ctr.perf_event_open(app_pid, -1)) < 0) { + err_msg = "Internal Error. Perf event setup failed."; + goto error; + } else { + rc = 0; + } + perfCounters.push_back(op_ctr); + } + } + if (!rc) { + cverb << vdebug << "perf counter setup complete" << endl; + // Set bit to indicate we're set to go. + valid = true; + return; + } + +error: + if (rc != OP_PERF_HANDLED_ERROR) + throw runtime_error(err_msg); +} + +void ocount_record::output_short_results(ostream & out, bool use_separation) +{ + int num_elements_of_separation = perfCounters.size()/evts.size(); + int stride_length = use_separation ? 1 : num_elements_of_separation; + + out << endl; + for (size_t num = 0; num < perfCounters.size(); num+=stride_length) { + int evt_num = num/num_elements_of_separation; + ostringstream count_str; + ocount_accum_t tmp_accum; + if (use_separation) { + if (cpus_to_count.size()) { + out << perfCounters[num].get_cpu(); + } else { + out << perfCounters[num].get_pid(); + } + out << "," << perfCounters[num].get_event_name() << ","; + + errno = 0; + cverb << vdebug << "Reading counter data for event " << perfCounters[num].get_event_name() << endl; + if (perfCounters[num].read_count_data(&tmp_accum) < 0) { + string err_msg = "Internal error: read of perfCounter fd failed with "; + err_msg += errno ? strerror(errno) : "unknown error"; + throw runtime_error(err_msg); + } + double percent_time_enabled = (double)tmp_accum.running_time/tmp_accum.enabled_time; + u64 scaled_count = tmp_accum.aggregated_count ? tmp_accum.aggregated_count/percent_time_enabled : 0; + out << dec << scaled_count << ","; + } else { + double percent_time_enabled = (double)accum_counts[evt_num].running_time/accum_counts[evt_num].enabled_time; + u64 scaled_count = accum_counts[evt_num].aggregated_count ? accum_counts[evt_num].aggregated_count/percent_time_enabled : 0; + out << perfCounters[num].get_event_name() << "," << dec << scaled_count << ","; + } + if (use_separation) { + if (!tmp_accum.enabled_time) { + out << 0 << endl; + } else { + out.precision(2); + out << fixed << ((double)tmp_accum.running_time/tmp_accum.enabled_time) * 100 + << endl; + } + } else { + if (!accum_counts[evt_num].enabled_time) { + out << "Event not counted" << endl; + } else { + out.precision(2); + out << fixed << ((double)accum_counts[evt_num].running_time/accum_counts[evt_num].enabled_time) * 100 + << endl; + } + } + } +} + +void ocount_record::output_long_results(ostream & out, bool use_separation, + int longest_event_name, bool scaled) +{ +#define COUNT_COLUMN_WIDTH 25 +#define SEPARATION_ELEMENT_COLUMN_WIDTH 10 + char space_padding[64], temp[64]; + char const * cpu, * task, * scaling; + cpu = "CPU"; + task = "Task ID"; + scaling = scaled ? "(scaled) " : "(actual) "; + + // We put 8 spaces between the end of the event name and beginning of the second column + unsigned int begin_second_col = longest_event_name + 8; + unsigned int num_pads = begin_second_col - strlen("Event"); + memset(space_padding, ' ', 64); + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + + out << "\nEvent counts " << scaling; + if (app_name) + out << "for " << app_name << ":"; + else if (system_wide) + out << "for the whole system:"; + else if (!cpus_to_count.empty()) + out << "for the specified CPU(s):"; + else if (tasks_are_threads) + out << "for the specified thread(s):"; + else + out << "for the specified process(es):"; + out << endl; + + out << "\tEvent" << temp; + if (use_separation) { + if (cpus_to_count.size()) { + out << cpu; + num_pads = SEPARATION_ELEMENT_COLUMN_WIDTH - strlen(cpu); + } else { + out << task; + num_pads = SEPARATION_ELEMENT_COLUMN_WIDTH - strlen(task); + + } + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + out << temp; + } + out << "Count"; + num_pads = COUNT_COLUMN_WIDTH - strlen("Count"); + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + out << temp << "% time enabled" << endl; + + /* If counting per-cpu or per-thread, I refer generically to cpu or thread values + * as "elements of separation". We will have one ocount_counter object per element of + * separation per event. So if we're counting 2 events for 4 processes (or threads), + * we'll have 2x4 (8) ocount_counter objects. + * + * If 'use_separation' is true, then we need to print individual counts for + * each element of separation for each event; otherwise, we print aggregated counts + * for each event. + */ + int num_elements_of_separation = perfCounters.size()/evts.size(); + int stride_length = use_separation ? 1 : num_elements_of_separation; + + for (size_t num = 0; num < perfCounters.size(); num+=stride_length) { + out << "\t" << perfCounters[num].get_event_name(); + num_pads = begin_second_col - perfCounters[num].get_event_name().length(); + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + out << temp; + + int evt_num = num/num_elements_of_separation; + ostringstream count_str; + ocount_accum_t tmp_accum; + if (use_separation) { + ostringstream separation_element_str; + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + if (cpus_to_count.size()) { + separation_element_str << dec << perfCounters[num].get_cpu(); + out << perfCounters[num].get_cpu(); + } else { + separation_element_str << dec << perfCounters[num].get_pid(); + out << perfCounters[num].get_pid(); + } + num_pads = SEPARATION_ELEMENT_COLUMN_WIDTH - separation_element_str.str().length(); + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + out << temp; + + errno = 0; + cverb << vdebug << "Reading counter data for event " << perfCounters[num].get_event_name() << endl; + if (perfCounters[num].read_count_data(&tmp_accum) < 0) { + string err_msg = "Internal error: read of perfCounter fd failed with "; + err_msg += errno ? strerror(errno) : "unknown error"; + throw runtime_error(err_msg); + } + double percent_time_enabled = (double)tmp_accum.running_time/tmp_accum.enabled_time; + u64 scaled_count = tmp_accum.aggregated_count ? tmp_accum.aggregated_count/percent_time_enabled : 0; + // Don't be fooled by the name, this is not really aggregated; it's the value read from one counter + count_str << dec << scaled_count; + } else { + double percent_time_enabled = (double)accum_counts[evt_num].running_time/accum_counts[evt_num].enabled_time; + u64 scaled_count = accum_counts[evt_num].aggregated_count ? accum_counts[evt_num].aggregated_count/percent_time_enabled : 0; + count_str << dec << scaled_count; + } + string count = count_str.str(); + for (int i = count.size() - 3; i > 0; i-=3) { + count.insert(i, 1, ','); + } + out << count; + num_pads = COUNT_COLUMN_WIDTH - count.size(); + strncpy(temp, space_padding, num_pads); + temp[num_pads] = '\0'; + out << temp; + if (use_separation) { + if (!tmp_accum.enabled_time) { + out << "Event not counted" << endl; + } else { + out.precision(2); + out << fixed << ((double)tmp_accum.running_time/tmp_accum.enabled_time) * 100 + << endl; + } + } else { + if (!accum_counts[evt_num].enabled_time) { + out << "Event not counted" << endl; + } else { + out.precision(2); + out << fixed << ((double)accum_counts[evt_num].running_time/accum_counts[evt_num].enabled_time) * 100 + << endl; + } + } + } +} + +void ocount_record::output_results(ostream & out, bool use_separation, bool short_format) +{ + size_t longest_event_name = 0; + bool scaled = false; + + for (unsigned long evt_num = 0; evt_num < evts.size(); evt_num++) { + if (strlen(evts[evt_num].name) > longest_event_name) + longest_event_name = strlen(evts[evt_num].name); + } + + for (unsigned long ocounter = 0; ocounter < perfCounters.size(); ocounter++) { + ocount_accum_t tmp_accum; + int evt_key = ocounter % evts.size(); + errno = 0; + cverb << vdebug << "Reading counter data for event " << evts[evt_key].name << endl; + if (perfCounters[ocounter].read_count_data(&tmp_accum) < 0) { + string err_msg = "Internal error: read of perfCounter fd failed with "; + err_msg += errno ? strerror(errno) : "unknown error"; + throw runtime_error(err_msg); + } + if (!use_separation) { + ocount_accum_t real_accum = accum_counts[evt_key]; + real_accum.aggregated_count += tmp_accum.aggregated_count; + real_accum.enabled_time += tmp_accum.enabled_time; + real_accum.running_time += tmp_accum.running_time; + accum_counts[evt_key] = real_accum; + } + if (tmp_accum.enabled_time != tmp_accum.running_time) + scaled = true; + } + if (short_format) + output_short_results(out, use_separation); + else + output_long_results(out, use_separation, longest_event_name, scaled); +} + +int ocount_record::_get_one_process_info(pid_t pid) +{ + char fname[PATH_MAX]; + DIR *tids; + struct dirent dirent, *next; + int ret = 0; + + add_process(pid); + if (are_tasks_processes()) { + snprintf(fname, sizeof(fname), "/proc/%d/task", pid); + tids = opendir(fname); + if (tids == NULL) { + // process must have exited + ret = -1; + cverb << vdebug << "Process " << pid << " apparently exited while " + << "process info was being collected"<< endl; + goto out; + } + + while (!readdir_r(tids, &dirent, &next) && next) { + char *end; + pid = strtol(dirent.d_name, &end, 10); + if (*end) + continue; + add_process(pid); + } + closedir(tids); + } + +out: + return ret; +} + +/* Obtain process information for one or more active process, where the user has + * either passed in a set of processes via the --process-list option or has specified + * --system_wide. + */ +int ocount_record::get_process_info(const vector<pid_t> & _procs) +{ + int ret = 0; + if (cverb << vdebug) + cout << "op_get_process_info" << endl; + for (size_t i = 0; i < _procs.size(); i++) { + errno = 0; + if (kill(_procs[i], 0) < 0) { + if (errno == EPERM) { + string errmsg = "You do not have permission to monitor "; + errmsg += are_tasks_processes() ? "process " : "thread "; + cerr << errmsg << _procs[i] << endl; + ret = OP_PERF_HANDLED_ERROR; + } + break; + } + if ((ret = _get_one_process_info(_procs[i])) < 0) + break; + } + return ret; +} diff --git a/pe_counting/ocount_counter.h b/pe_counting/ocount_counter.h new file mode 100644 index 0000000..7397d4c --- /dev/null +++ b/pe_counting/ocount_counter.h @@ -0,0 +1,113 @@ +/** + * @file ocount_counter.h + * Definitions and prototypes for ocount tool. + * + * @remark Copyright 2013 OProfile authors + * @remark Read the file COPYING + * + * Created on: May 22, 2013 + * @author Maynard Johnson + * (C) Copyright IBM Corp. 2013 + * + */ + +#ifndef OCOUNT_COUNTER_H_ +#define OCOUNT_COUNTER_H_ + +#include <linux/perf_event.h> +#include <asm/unistd.h> + +#include <vector> +#include <set> +#include <string> + +#include "operf_event.h" + +#define OP_PERF_HANDLED_ERROR -101 + +enum op_runmode { + OP_START_APP, + OP_SYSWIDE, + OP_CPULIST, + OP_PROCLIST, + OP_THREADLIST, + OP_MAX_RUNMODE +}; + +typedef struct ocount_accum { + u64 aggregated_count; + u64 enabled_time; + u64 running_time; +} ocount_accum_t; + +static inline int +op_perf_event_open(struct perf_event_attr * attr, + pid_t pid, int cpu, int group_fd, + unsigned long flags) +{ + return syscall(__NR_perf_event_open, attr, pid, cpu, + group_fd, flags); +} + + +class ocount_record; +class ocount_counter { +public: + ocount_counter(operf_event_t & evt, bool enable_on_exec, + bool inherit); + ~ocount_counter(); + int perf_event_open(pid_t pid, int cpu); + int get_cpu(void) { return cpu; } + pid_t get_pid(void) { return pid; } + const std::string get_event_name(void) const { return event_name; } + int read_count_data(ocount_accum_t * accum); + +private: + struct perf_event_attr attr; + int fd; + int cpu; + pid_t pid; + std::string event_name; +}; + +class ocount_record { +public: + ocount_record(enum op_runmode _runmode, std::vector<operf_event_t> & _evts); + ~ocount_record(); + bool start_counting_app_process(pid_t _pid); + bool start_counting_tasklist(std::vector<pid_t> _tasks, bool _are_threads); + bool start_counting_cpulist(std::vector<int> _cpus); + bool start_counting_syswide(void); + void add_process(pid_t proc) { tasks_to_count.insert(proc); } + void output_results(std::ostream & out, bool use_separation, bool short_format); + bool get_valid(void) { return valid; } + bool are_tasks_processes(void) { return !tasks_are_threads; } + +private: + void setup(void); + int get_process_info(const std::vector<pid_t> & _procs); + int _get_one_process_info(pid_t pid); + int do_counting_per_cpu(void); + int do_counting_per_task(void); + void output_short_results(std::ostream & out, bool use_separation); + void output_long_results(std::ostream & out, bool use_separation, + int longest_event_name, bool scaled); + + enum op_runmode runmode; + bool tasks_are_threads; + int num_cpus; + pid_t app_pid; + std::set<pid_t> tasks_to_count; + std::set<int> cpus_to_count; + bool system_wide; + std::vector<ocount_counter> perfCounters; + unsigned int total_bytes_recorded; + std::vector<operf_event_t> evts; + std::vector<pid_t> specified_tasks; + std::vector<int> specified_cpus; + std::vector<ocount_accum_t> accum_counts; // same size as evts; one object for each event + bool valid; +}; + + +#endif /* OCOUNT_COUNTER_H_ */ -- 1.7.1 |
From: Wainer M. <wai...@li...> - 2013-06-25 21:11:08
|
On 06/25/2013 11:00 AM, Maynard Johnson wrote: > New ocount tool and associated ocount_counter classes > > This patch implements the executable 'ocount' tool, as > well as some classes used for opening and managing the > "perf_events" file descriptors (opened via perf_event_open). Hi Maynard, I've built oprofile with ocount support on an PowerPC machine, made some pretty simple tests and, as far as I can tell you, it all seems to work very well. However, I've found some inconsistencies between the manpage and actual ocount options. Please, see comments below. > > Signed-off-by: Maynard Johnson <may...@us...> > --- > Makefile.am | 1 + > configure.ac | 1 + > pe_counting/Makefile.am | 28 ++ > pe_counting/ocount.cpp | 817 ++++++++++++++++++++++++++++++++++++++++ > pe_counting/ocount_counter.cpp | 614 ++++++++++++++++++++++++++++++ > pe_counting/ocount_counter.h | 113 ++++++ > 6 files changed, 1574 insertions(+), 0 deletions(-) > create mode 100644 pe_counting/Makefile.am > create mode 100644 pe_counting/ocount.cpp > create mode 100644 pe_counting/ocount_counter.cpp > create mode 100644 pe_counting/ocount_counter.h > <snip> > + > +static enum op_runmode runmode = OP_MAX_RUNMODE; > +static string runmode_options[] = { "<command> [command-args]", "--system-wide", "--cpu-list", > + "--process-list", "--thread-list" > +}; > + > + > +struct option long_options [] = > +{ > + {"verbose", no_argument, NULL, 'V'}, > + {"system-wide", no_argument, NULL, 's'}, > + {"cpu-list", required_argument, NULL, 'C'}, > + {"process-list", required_argument, NULL, 'p'}, > + {"thread-list", required_argument, NULL, 'r'}, > + {"events", required_argument, NULL, 'e'}, > + {"output-file", required_argument, NULL, 'f'}, > + {"separate-cpu", no_argument, NULL, 'c'}, > + {"separate-thread", no_argument, NULL, 't'}, > + {"brief-format", no_argument, NULL, 'b'}, > + {"time-interval", required_argument, NULL, 'i'}, The ocount's man says "--time-intervals" but actually "--time-interval" is the valid option (as defined above). Need to either adjust the option name or change the manpage. <snip> > + > + > +static void __print_usage_and_exit(const char * extra_msg) > +{ > + if (extra_msg) > + cerr << extra_msg << endl; > + cerr << "usage: ocount [ options ] [ --system-wide | --pid <pid> | [ command [ args ] ] ]" << endl; The "--pid" flag doesn't exit and should be "--process-list". It is also missing "--thread-list" and "--cpu-list" on usage message as well. > + cerr << "See ocount man page for details." << endl; > + exit(EXIT_FAILURE); > +} > + > + <snip> Hey, nice job! I'm sure it will be lot of useful to performance engineers using Oprofile. Thanks, Wainer dos Santos Moschetta |
From: Maynard J. <may...@us...> - 2013-06-25 22:17:45
|
On 06/25/2013 04:10 PM, Wainer Moschetta wrote: > On 06/25/2013 11:00 AM, Maynard Johnson wrote: >> New ocount tool and associated ocount_counter classes >> >> This patch implements the executable 'ocount' tool, as >> well as some classes used for opening and managing the >> "perf_events" file descriptors (opened via perf_event_open). > Hi Maynard, > > I've built oprofile with ocount support on an PowerPC machine, made some > pretty simple tests and, as far as I can tell you, it all seems to work > very well. > > However, I've found some inconsistencies between the manpage and actual > ocount options. Please, see comments below. Wainer, Thanks very much for taking the time to test and review. You have a keen eye to catch the inconsistencies below. Unless I get more review comments requiring some more extensive rework of the patch set, I won't bother submitting a version 2 of the set. I'll fix the issues you raised here and in your other review posting when I commit. -Maynard > >> >> Signed-off-by: Maynard Johnson <may...@us...> >> --- >> Makefile.am | 1 + >> configure.ac | 1 + >> pe_counting/Makefile.am | 28 ++ >> pe_counting/ocount.cpp | 817 ++++++++++++++++++++++++++++++++++++++++ >> pe_counting/ocount_counter.cpp | 614 ++++++++++++++++++++++++++++++ >> pe_counting/ocount_counter.h | 113 ++++++ >> 6 files changed, 1574 insertions(+), 0 deletions(-) >> create mode 100644 pe_counting/Makefile.am >> create mode 100644 pe_counting/ocount.cpp >> create mode 100644 pe_counting/ocount_counter.cpp >> create mode 100644 pe_counting/ocount_counter.h >> > <snip> >> + >> +static enum op_runmode runmode = OP_MAX_RUNMODE; >> +static string runmode_options[] = { "<command> [command-args]", "--system-wide", "--cpu-list", >> + "--process-list", "--thread-list" >> +}; >> + >> + >> +struct option long_options [] = >> +{ >> + {"verbose", no_argument, NULL, 'V'}, >> + {"system-wide", no_argument, NULL, 's'}, >> + {"cpu-list", required_argument, NULL, 'C'}, >> + {"process-list", required_argument, NULL, 'p'}, >> + {"thread-list", required_argument, NULL, 'r'}, >> + {"events", required_argument, NULL, 'e'}, >> + {"output-file", required_argument, NULL, 'f'}, >> + {"separate-cpu", no_argument, NULL, 'c'}, >> + {"separate-thread", no_argument, NULL, 't'}, >> + {"brief-format", no_argument, NULL, 'b'}, >> + {"time-interval", required_argument, NULL, 'i'}, > > The ocount's man says "--time-intervals" but actually "--time-interval" > is the valid option (as defined above). Need to either adjust the option > name or change the manpage. > > <snip> >> + >> + >> +static void __print_usage_and_exit(const char * extra_msg) >> +{ >> + if (extra_msg) >> + cerr << extra_msg << endl; >> + cerr << "usage: ocount [ options ] [ --system-wide | --pid <pid> | [ command [ args ] ] ]" << endl; > > The "--pid" flag doesn't exit and should be "--process-list". It is > also missing "--thread-list" and "--cpu-list" on usage message as well. > >> + cerr << "See ocount man page for details." << endl; >> + exit(EXIT_FAILURE); >> +} >> + >> + > <snip> > > Hey, nice job! I'm sure it will be lot of useful to performance > engineers using Oprofile. > > Thanks, > Wainer dos Santos Moschetta > > > ------------------------------------------------------------------------------ > This SF.net email is sponsored by Windows: > > Build for Windows Store. > > http://p.sf.net/sfu/windows-dev2dev > _______________________________________________ > oprofile-list mailing list > opr...@li... > https://lists.sourceforge.net/lists/listinfo/oprofile-list > |