blob: 5a85c3fa5102746b70a0e1fd154ac52d8ed8218d [file] [log] [blame]
/**
* @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 <stdexcept>
#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 | -p <pids> | -r <tids> | -C <cpus> [ 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
// 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);
} else if (!ocount_options::threads.empty()) {
proc_list = ocount_options::threads;
} else if (!ocount_options::processes.empty()) {
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, ocount_options::display_interval ? true : false);
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)
{
try {
orecord->output_results(out, ocount_options::separate_cpu | ocount_options::separate_thread,
ocount_options::csv_output);
} catch (runtime_error e) {
cerr << "Caught runtime error from ocount_record::output_results" << endl;
cerr << e.what() << endl;
cleanup();
exit(EXIT_FAILURE);
}
}
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 << "timestamp,";
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);
try {
if (!start_counting()) {
return PERF_RECORD_ERROR;
}
} catch (runtime_error e) {
cerr << "Caught runtime error while setting up counters" << endl;
cerr << e.what() << endl;
cleanup();
exit(EXIT_FAILURE);
}
// 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();
try {
process_args(argc, argv);
} catch (runtime_error e) {
cerr << "Caught runtime error while processing args" << endl;
cerr << e.what() << endl;
cleanup();
exit(EXIT_FAILURE);
}
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;
}