blob: 3fec12315ac0fbfc728bbe15245b0c9c20526d4d [file] [log] [blame]
/**
* @file operf.cpp
* Front-end (containing main) for handling a user request to run a profile
* using the new Linux Performance Events Subsystem.
*
* @remark Copyright 2011 OProfile authors
* @remark Read the file COPYING
*
* Created on: Dec 7, 2011
* @author Maynard Johnson
* (C) Copyright IBM Corp. 2011
*
* Modified by Maynard Johnson <maynardj@us.ibm.com>
* (C) Copyright IBM Corporation 2012, 2013
*
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <dirent.h>
#include <exception>
#include <pwd.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <ftw.h>
#include <getopt.h>
#include <iostream>
#include "operf_utils.h"
#include "op_libiberty.h"
#include "string_manip.h"
#include "cverb.h"
#include "operf_counter.h"
#include "op_cpu_type.h"
#include "op_cpufreq.h"
#include "op_events.h"
#include "op_string.h"
#include "operf_kernel.h"
#include "child_reader.h"
#include "op_get_time.h"
#include "operf_stats.h"
#include "op_netburst.h"
using namespace std;
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;
// Globals
char * app_name = NULL;
bool use_cpu_minus_one = false;
pid_t app_PID = -1;
uint64_t kernel_start, kernel_end;
operf_read operfRead;
op_cpu cpu_type;
double cpu_speed;
uint op_nr_events;
verbose vmisc("misc");
uid_t my_uid;
bool no_vmlinux;
int kptr_restrict;
char * start_time_human_readable;
#define DEFAULT_OPERF_OUTFILE "operf.data"
#define CALLGRAPH_MIN_COUNT_SCALE 15
static char full_pathname[PATH_MAX];
static char * app_name_SAVE = NULL;
static char ** app_args = NULL;
static pid_t jitconv_pid = -1;
static bool app_started;
static pid_t operf_record_pid;
static pid_t operf_read_pid;
static string samples_dir;
static bool startApp;
static string outputfile;
static char start_time_str[32];
static vector<operf_event_t> events;
static bool jit_conversion_running;
static void convert_sample_data(void);
static int sample_data_pipe[2];
bool ctl_c = false;
bool pipe_closed = false;
namespace operf_options {
bool system_wide;
bool append;
int pid;
bool callgraph;
int mmap_pages_mult;
string session_dir;
string vmlinux;
bool separate_cpu;
bool separate_thread;
bool post_conversion;
vector<string> evts;
}
static const char * valid_verbose_vals[] = { "debug", "record", "convert", "misc", "sfile", "arcs", "all"};
#define NUM_VERBOSE_OPTIONS (sizeof(valid_verbose_vals)/sizeof(char *))
struct option long_options [] =
{
{"verbose", required_argument, NULL, 'V'},
{"session-dir", required_argument, NULL, 'd'},
{"vmlinux", required_argument, NULL, 'k'},
{"callgraph", no_argument, NULL, 'g'},
{"system-wide", no_argument, NULL, 's'},
{"append", no_argument, NULL, 'a'},
{"pid", required_argument, NULL, 'p'},
{"events", required_argument, NULL, 'e'},
{"separate-cpu", no_argument, NULL, 'c'},
{"separate-thread", no_argument, NULL, 't'},
{"lazy-conversion", no_argument, NULL, 'l'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{"usage", no_argument, NULL, 'u'},
{NULL, 9, NULL, 0}
};
const char * short_options = "V:d:k:gsap:e:ctlhuv";
vector<string> verbose_string;
void __set_event_throttled(int index)
{
if (index < 0) {
cerr << "Unable to determine if throttling occurred for ";
cerr << "event " << events[index].name << endl;
} else {
events[index].throttled = true;
}
}
static void __print_usage_and_exit(const char * extra_msg)
{
if (extra_msg)
cerr << extra_msg << endl;
cerr << "usage: operf [ options ] [ --system-wide | --pid <pid> | [ command [ args ] ] ]" << endl;
cerr << "See operf man page for details." << endl;
exit(EXIT_FAILURE);
}
// Signal handler for main (parent) process.
static void op_sig_stop(int val __attribute__((unused)))
{
// Received a signal to quit, so we need to stop the
// app being profiled.
size_t dummy __attribute__ ((__unused__));
ctl_c = true;
if (cverb << vdebug)
dummy = write(1, "in op_sig_stop\n", 15);
if (startApp)
kill(app_PID, SIGKILL);
}
// For child processes to manage a controlled stop after Ctl-C is done
static void _handle_sigint(int val __attribute__((unused)))
{
size_t dummy __attribute__ ((__unused__));
/* Each process (parent and each forked child) will have their own copy of
* the ctl_c variable, so this can be used by each process in managing their
* shutdown procedure.
*/
ctl_c = true;
if (cverb << vdebug)
dummy = write(1, "in _handle_sigint\n", 19);
return;
}
void _set_basic_SIGINT_handler_for_child(void)
{
struct sigaction act;
sigset_t ss;
sigfillset(&ss);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
act.sa_handler = _handle_sigint;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
if (sigaction(SIGINT, &act, NULL)) {
perror("operf: install of SIGINT handler failed: ");
exit(EXIT_FAILURE);
}
}
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("operf: install of SIGINT handler failed: ");
exit(EXIT_FAILURE);
}
}
static int app_ready_pipe[2], start_app_pipe[2], operf_record_ready_pipe[2];
static string args_to_string(void)
{
string ret;
char * const * ptr = app_args + 1;
while (*ptr != NULL) {
ret.append(*ptr);
ret += ' ';
ptr++;
}
return ret;
}
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;
app_started = true;
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);
}
int start_profiling(void)
{
// The only process that should return from this function is the process
// which invoked it. Any forked process must do _exit() rather than return().
struct timeval tv;
unsigned long long start_time = 0ULL;
gettimeofday(&tv, NULL);
start_time = 0ULL;
start_time = tv.tv_sec;
sprintf(start_time_str, "%llu", start_time);
start_time_human_readable = op_get_time();
startApp = ((app_PID != operf_options::pid) && (operf_options::system_wide == false));
if (startApp) {
if (pipe(app_ready_pipe) < 0 || pipe(start_app_pipe) < 0) {
perror("Internal error: operf-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
if (!operf_options::post_conversion) {
close(sample_data_pipe[0]);
close(sample_data_pipe[1]);
}
run_app();
}
}
// parent
if (pipe(operf_record_ready_pipe) < 0) {
perror("Internal error: could not create pipe");
return -1;
}
operf_record_pid = fork();
if (operf_record_pid < 0) {
return -1;
} else if (operf_record_pid == 0) { // operf-record process
int ready = 0;
int exit_code = EXIT_SUCCESS;
_set_basic_SIGINT_handler_for_child();
close(operf_record_ready_pipe[0]);
if (!operf_options::post_conversion)
close(sample_data_pipe[0]);
/*
* Since an informative message will be displayed to the user if
* an error occurs, we don't want to blow chunks here; instead, we'll
* exit gracefully. Clear out the operf.data file as an indication
* to the parent process that the profile data isn't valid.
*/
try {
OP_perf_utils::vmlinux_info_t vi;
int outfd;
int flags = O_WRONLY | O_CREAT | O_TRUNC;
vi.image_name = operf_options::vmlinux;
vi.start = kernel_start;
vi.end = kernel_end;
if (operf_options::post_conversion) {
outfd = open(outputfile.c_str(), flags, S_IRUSR|S_IWUSR);
if (outfd < 0) {
string errmsg = "Internal error: Could not create temporary output file. errno is ";
errmsg += strerror(errno);
throw runtime_error(errmsg);
}
} else {
outfd = sample_data_pipe[1];
}
operf_record operfRecord(outfd, operf_options::system_wide, app_PID,
(operf_options::pid == app_PID), events, vi,
operf_options::callgraph,
operf_options::separate_cpu, operf_options::post_conversion);
if (operfRecord.get_valid() == false) {
/* If valid is false, it means that one of the "known" errors has
* occurred:
* - profiled process has already ended
* - passed PID was invalid
* - device or resource busy
* - failure to mmap kernel profile data
*/
cerr << "operf record init failed" << endl;
cerr << "usage: operf [ options ] [ --system-wide | --pid <pid> | [ command [ args ] ] ]" << endl;
// Exit with SUCCESS to avoid the unnecessary "operf-record process ended
// abnormally" message
goto fail_out;
}
ready = 1;
if (write(operf_record_ready_pipe[1], &ready, sizeof(ready)) < 0) {
perror("Internal error on operf_record_ready_pipe");
exit_code = EXIT_FAILURE;
goto fail_out;
}
// start recording
operfRecord.recordPerfData();
cverb << vdebug << "Total bytes recorded from perf events: " << dec
<< operfRecord.get_total_bytes_recorded() << endl;
} catch (runtime_error re) {
/* If the user does ctl-c, the operf-record process may get interrupted
* in a system call, causing problems with writes to the sample data pipe.
* So we'll ignore such errors unless the user requests debug info.
*/
if (!ctl_c || (cverb << vmisc)) {
cerr << "Caught runtime_error: " << re.what() << endl;
exit_code = EXIT_FAILURE;
}
goto fail_out;
}
// done
_exit(exit_code);
fail_out:
if (!ready){
/* ready==0 means we've not yet told parent we're ready,
* but the parent is reading our pipe. So we tell the
* parent we're not ready so it can continue.
*/
if (write(operf_record_ready_pipe[1], &ready, sizeof(ready)) < 0) {
perror("Internal error on operf_record_ready_pipe");
}
}
_exit(exit_code);
} else { // parent
int recorder_ready = 0;
int startup;
close(operf_record_ready_pipe[1]);
if (startApp) {
if (read(app_ready_pipe[0], &startup, sizeof(startup)) == -1) {
perror("Internal error on app_ready_pipe");
return -1;
} else if (startup != 1) {
cerr << "app is not ready to start; exiting" << endl;
return -1;
}
}
if (read(operf_record_ready_pipe[0], &recorder_ready, sizeof(recorder_ready)) == -1) {
perror("Internal error on operf_record_ready_pipe");
return -1;
} else if (recorder_ready != 1) {
cverb << vdebug << "operf record process failure; exiting" << endl;
if (startApp) {
cverb << vdebug << "telling child to abort starting of app" << endl;
startup = 0;
if (write(start_app_pipe[1], &startup, sizeof(startup)) < 0) {
perror("Internal error on start_app_pipe");
}
}
return -1;
}
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;
}
}
}
if (!operf_options::system_wide)
app_started = true;
// parent returns
return 0;
}
static end_code_t _kill_operf_read_pid(end_code_t rc)
{
// Now stop the operf-read process
int waitpid_status;
struct timeval tv;
long long start_time_sec;
long long usec_timer;
bool keep_trying = true;
waitpid_status = 0;
gettimeofday(&tv, NULL);
start_time_sec = tv.tv_sec;
usec_timer = tv.tv_usec;
/* We'll initially try the waitpid with WNOHANG once every 100,000 usecs.
* If it hasn't ended within 5 seconds, we'll kill it and do one
* final wait.
*/
while (keep_trying) {
int option = WNOHANG;
int wait_rc;
gettimeofday(&tv, NULL);
if (tv.tv_sec > start_time_sec + 5) {
keep_trying = false;
option = 0;
cerr << "now trying to kill convert pid..." << endl;
if (kill(operf_read_pid, SIGUSR1) < 0) {
perror("Attempt to stop operf-read process failed");
rc = rc ? PERF_BOTH_ERROR : PERF_READ_ERROR;
break;
}
} else {
/* If we exceed the 100000 usec interval or if the tv_usec
* value has rolled over to restart at 0, then we reset
* the usec_timer to current tv_usec and try waitpid.
*/
if ((tv.tv_usec % 1000000) > (usec_timer + 100000)
|| (tv.tv_usec < usec_timer))
usec_timer = tv.tv_usec;
else
continue;
}
if ((wait_rc = waitpid(operf_read_pid, &waitpid_status, option)) < 0) {
keep_trying = false;
if (errno != ECHILD) {
perror("waitpid for operf-read process failed");
rc = rc ? PERF_BOTH_ERROR : PERF_READ_ERROR;
}
} else if (wait_rc) {
if (WIFEXITED(waitpid_status)) {
keep_trying = false;
if (!WEXITSTATUS(waitpid_status)) {
cverb << vdebug << "operf-read process returned OK" << endl;
} else if (WIFEXITED(waitpid_status)) {
/* If user did ctl-c, operf-read may get spurious errors, like
* broken pipe, etc. We ignore these unless the user asks for
* debug output.
*/
if (!ctl_c || cverb << vdebug) {
cerr << "operf-read process ended abnormally. Status = "
<< WEXITSTATUS(waitpid_status) << endl;
rc = rc ? PERF_BOTH_ERROR : PERF_READ_ERROR;
}
}
} else if (WIFSIGNALED(waitpid_status)) {
keep_trying = false;
/* If user did ctl-c, operf-read may get spurious errors, like
* broken pipe, etc. We ignore these unless the user asks for
* debug output.
*/
if (!ctl_c || cverb << vdebug) {
cerr << "operf-read process killed by signal "
<< WTERMSIG(waitpid_status) << endl;
rc = PERF_RECORD_ERROR;
}
}
}
}
return rc;
}
static end_code_t _kill_operf_record_pid(void)
{
int waitpid_status = 0;
end_code_t rc = ALL_OK;
// stop operf-record process
errno = 0;
if (kill(operf_record_pid, SIGUSR1) < 0) {
// If operf-record process is already ended, don't consider this an error.
if (errno != ESRCH) {
perror("Attempt to stop operf-record process failed");
rc = PERF_RECORD_ERROR;
}
} else {
if (waitpid(operf_record_pid, &waitpid_status, 0) < 0) {
perror("waitpid for operf-record process failed");
rc = PERF_RECORD_ERROR;
} else {
if (WIFEXITED(waitpid_status) && (!WEXITSTATUS(waitpid_status))) {
cverb << vdebug << "operf-record process returned OK" << endl;
} else if (WIFEXITED(waitpid_status)) {
/* If user did ctl-c, operf-record may get spurious errors, like
* broken pipe, etc. We ignore these unless the user asks for
* debug output.
*/
if (!ctl_c || cverb << vdebug) {
cerr << "operf-record process ended abnormally: "
<< WEXITSTATUS(waitpid_status) << endl;
rc = PERF_RECORD_ERROR;
}
} else if (WIFSIGNALED(waitpid_status)) {
if (!ctl_c || cverb << vdebug) {
cerr << "operf-record process killed by signal "
<< WTERMSIG(waitpid_status) << endl;
rc = PERF_RECORD_ERROR;
}
}
}
}
return rc;
}
static end_code_t _run(void)
{
int waitpid_status = 0;
end_code_t rc = ALL_OK;
bool kill_record = true;
// Fork processes with signals blocked.
sigset_t ss;
sigfillset(&ss);
sigprocmask(SIG_BLOCK, &ss, NULL);
/* By default (unless the user specifies --lazy-conversion), the operf-record process
* writes the sample data to a pipe, from which the operf-read process reads.
*/
if (!operf_options::post_conversion && pipe(sample_data_pipe) < 0) {
perror("Internal error: operf-record could not create pipe");
_exit(EXIT_FAILURE);
}
if (start_profiling() < 0) {
return PERF_RECORD_ERROR;
}
// parent continues here
if (startApp)
cverb << vdebug << "app " << app_PID << " is running" << endl;
/* If we're not doing system wide profiling and no app is started, then
* there's no profile data to convert. So if this condition is NOT true,
* then we'll do the convert.
* Note that if --lazy-conversion is passed, then operf_options::post_conversion
* will be set, and we will defer conversion until after the operf-record
* process is done.
*/
if (!operf_options::post_conversion) {
if (!(!app_started && !operf_options::system_wide)) {
cverb << vdebug << "Forking read pid" << endl;
operf_read_pid = fork();
if (operf_read_pid < 0) {
perror("Internal error: fork failed");
_exit(EXIT_FAILURE);
} else if (operf_read_pid == 0) { // child process
close(sample_data_pipe[1]);
_set_basic_SIGINT_handler_for_child();
convert_sample_data();
_exit(EXIT_SUCCESS);
}
// parent
close(sample_data_pipe[0]);
close(sample_data_pipe[1]);
}
}
set_signals_for_parent();
if (startApp) {
/* The user passed in a command or program name to start, so we'll need to do waitpid on that
* process. However, while that user-requested process is running, it's possible we
* may get an error in the operf-record process. If that happens, we want to know it right
* away so we can stop profiling and kill the user app. Therefore, we must use WNOHANG
* on the waitpid call and bounce back and forth between the user app and the operf-record
* process, checking their status. The profiled app may end normally, abnormally, or by way
* of ctrl-C. The operf-record process should not end here, except abnormally. The normal
* flow is:
* 1. profiled app ends or is stopped via ctrl-C
* 2. keep_trying is set to false, so we drop out of while loop and proceed to end of function
* 3. call _kill_operf_record_pid and _kill_operf_read_pid
*/
struct timeval tv;
long long usec_timer;
bool keep_trying = true;
const char * app_process = "profiled app";
const char * record_process = "operf-record process";
waitpid_status = 0;
gettimeofday(&tv, NULL);
usec_timer = tv.tv_usec;
cverb << vdebug << "going into waitpid on profiled app " << app_PID << endl;
// We'll try the waitpid with WNOHANG once every 100,000 usecs.
while (keep_trying) {
pid_t the_pid = app_PID;
int wait_rc;
const char * the_process = app_process;
gettimeofday(&tv, NULL);
/* If we exceed the 100000 usec interval or if the tv_usec
* value has rolled over to restart at 0, then we reset
* the usec_timer to current tv_usec and try waitpid.
*/
if ((tv.tv_usec % 1000000) > (usec_timer + 100000)
|| (tv.tv_usec < usec_timer))
usec_timer = tv.tv_usec;
else
continue;
bool trying_user_app = true;
again:
if ((wait_rc = waitpid(the_pid, &waitpid_status, WNOHANG)) < 0) {
keep_trying = false;
if (errno == EINTR) {
// Ctrl-C will only kill the profiled app. See the op_sig_stop signal handler.
cverb << vdebug << "Caught ctrl-C. Killed " << the_process << "." << endl;
} else {
cerr << "waitpid for " << the_process << " failed: " << strerror(errno) << endl;
rc = trying_user_app ? APP_ABNORMAL_END : PERF_RECORD_ERROR;
}
} else if (wait_rc) {
keep_trying = false;
if (WIFEXITED(waitpid_status) && (!WEXITSTATUS(waitpid_status))) {
cverb << vdebug << the_process << " ended normally." << endl;
} else if (WIFEXITED(waitpid_status)) {
cerr << the_process << " exited with the following status: "
<< WEXITSTATUS(waitpid_status) << endl;
rc = trying_user_app ? APP_ABNORMAL_END : PERF_RECORD_ERROR;
} else if (WIFSIGNALED(waitpid_status)) {
if (WTERMSIG(waitpid_status) != SIGKILL) {
cerr << the_process << " killed by signal "
<< WTERMSIG(waitpid_status) << endl;
rc = trying_user_app ? APP_ABNORMAL_END : PERF_RECORD_ERROR;
}
} else {
keep_trying = true;
}
}
if (trying_user_app && (rc == ALL_OK)) {
trying_user_app = false;
the_pid = operf_record_pid;
the_process = record_process;
goto again;
} else if (rc != ALL_OK) {
// If trying_user_app == true, implies profiled app ended; otherwise, operf-record process abended.
if (!trying_user_app)
kill_record = false;
}
}
} else {
// User passed in --pid or --system-wide
cout << "operf: Press Ctl-c or 'kill -SIGINT " << getpid() << "' to stop profiling" << endl;
cverb << vdebug << "going into waitpid on operf record process " << operf_record_pid << endl;
if (waitpid(operf_record_pid, &waitpid_status, 0) < 0) {
if (errno == EINTR) {
cverb << vdebug << "Caught ctrl-C. Killing operf-record process . . ." << endl;
} else {
cerr << "waitpid errno is " << errno << endl;
perror("waitpid for operf-record process failed");
kill_record = false;
rc = PERF_RECORD_ERROR;
}
} else {
if (WIFEXITED(waitpid_status) && (!WEXITSTATUS(waitpid_status))) {
cverb << vdebug << "waitpid for operf-record process returned OK" << endl;
} else if (WIFEXITED(waitpid_status)) {
kill_record = false;
cerr << "operf-record process ended abnormally: "
<< WEXITSTATUS(waitpid_status) << endl;
rc = PERF_RECORD_ERROR;
} else if (WIFSIGNALED(waitpid_status)) {
kill_record = false;
cerr << "operf-record process killed by signal "
<< WTERMSIG(waitpid_status) << endl;
rc = PERF_RECORD_ERROR;
}
}
}
if (kill_record) {
if (operf_options::post_conversion)
rc = _kill_operf_record_pid();
else
rc = _kill_operf_read_pid(_kill_operf_record_pid());
} else {
if (!operf_options::post_conversion)
rc = _kill_operf_read_pid(rc);
}
return rc;
}
static void cleanup(void)
{
free(app_name_SAVE);
free(app_args);
events.clear();
verbose_string.clear();
if (operf_options::post_conversion) {
string cmd = "rm -f " + outputfile;
if (system(cmd.c_str()) != 0)
cerr << "Unable to remove " << outputfile << endl;
}
}
static void _jitconv_complete(int val __attribute__((unused)))
{
int child_status;
pid_t the_pid = wait(&child_status);
if (the_pid != jitconv_pid) {
return;
}
jit_conversion_running = false;
if (WIFEXITED(child_status) && (!WEXITSTATUS(child_status))) {
cverb << vdebug << "JIT dump processing complete." << endl;
} else {
if (WIFSIGNALED(child_status))
cerr << "child received signal " << WTERMSIG(child_status) << endl;
else
cerr << "JIT dump processing exited abnormally: "
<< WEXITSTATUS(child_status) << endl;
}
}
static void _set_signals_for_convert(void)
{
struct sigaction act;
sigset_t ss;
sigfillset(&ss);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
act.sa_handler = _jitconv_complete;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGCHLD);
if (sigaction(SIGCHLD, &act, NULL)) {
perror("operf: install of SIGCHLD handler failed: ");
exit(EXIT_FAILURE);
}
}
static void _do_jitdump_convert()
{
int arg_num;
unsigned long long end_time = 0ULL;
struct timeval tv;
char end_time_str[32];
char opjitconv_path[PATH_MAX + 1];
char * exec_args[8];
jitconv_pid = fork();
switch (jitconv_pid) {
case -1:
perror("Error forking JIT dump process!");
break;
case 0: {
const char * jitconv_pgm = "opjitconv";
const char * debug_option = "-d";
const char * non_root_user = "--non-root";
const char * delete_jitdumps = "--delete-jitdumps";
gettimeofday(&tv, NULL);
end_time = tv.tv_sec;
sprintf(end_time_str, "%llu", end_time);
sprintf(opjitconv_path, "%s/%s", OP_BINDIR, jitconv_pgm);
arg_num = 0;
exec_args[arg_num++] = (char *)jitconv_pgm;
if (cverb << vdebug)
exec_args[arg_num++] = (char *)debug_option;
if (my_uid != 0)
exec_args[arg_num++] = (char *)non_root_user;
exec_args[arg_num++] = (char *)delete_jitdumps;
exec_args[arg_num++] = (char *)operf_options::session_dir.c_str();
exec_args[arg_num++] = start_time_str;
exec_args[arg_num++] = end_time_str;
exec_args[arg_num] = (char *) NULL;
execvp(opjitconv_path, exec_args);
fprintf(stderr, "Failed to exec %s: %s\n",
exec_args[0], strerror(errno));
/* We don't want any cleanup in the child */
_exit(EXIT_FAILURE);
break;
}
default: // parent
jit_conversion_running = true;
break;
}
}
static int __delete_old_previous_sample_data(const char *fpath,
const struct stat *sb __attribute__((unused)),
int tflag __attribute__((unused)),
struct FTW *ftwbuf __attribute__((unused)))
{
if (remove(fpath)) {
perror("sample data removal error");
return FTW_STOP;
} else {
return FTW_CONTINUE;
}
}
/* Read perf_events sample data written by the operf-record process through
* the sample_data_pipe or file (dependent on 'lazy-conversion' option)
* and convert the perf format sample data to to oprofile format sample files.
*
* If not invoked with --lazy-conversion option, this function is executed by
* the "operf-read" child process. If user does a ctrl-C, the parent will
* execute _kill_operf_read_pid which will try to allow the conversion process
* to complete, waiting 5 seconds before it forcefully kills the operf-read
* process via 'kill SIGUSR1'.
*
* But if --lazy-conversion option is used, then it's the parent process that's
* running convert_sample_data. If the user does a ctrl-C during this procedure,
* the ctrl-C is handled via op_sig_stop which essentially does nothing to stop
* the conversion procedure, which in general is fine. On the very rare chance
* that the procedure gets stuck (hung) somehow, the user will have to do a
* 'kill -KILL'.
*/
static void convert_sample_data(void)
{
int inputfd;
string inputfname;
int rc = EXIT_SUCCESS;
int keep_waiting = 0;
string current_sampledir = samples_dir + "/current/";
string previous_sampledir = samples_dir + "/previous";
string stats_dir = "";
current_sampledir.copy(op_samples_current_dir, current_sampledir.length(), 0);
if (!app_started && !operf_options::system_wide)
return;
if (!operf_options::append) {
int flags = FTW_DEPTH | FTW_ACTIONRETVAL;
errno = 0;
if (nftw(previous_sampledir.c_str(), __delete_old_previous_sample_data, 32, flags) !=0 &&
errno != ENOENT) {
cerr << "Unable to remove old sample data at " << previous_sampledir << "." << endl;
if (errno)
cerr << strerror(errno) << endl;
rc = EXIT_FAILURE;
goto out;
}
if (rename(current_sampledir.c_str(), previous_sampledir.c_str()) < 0) {
if (errno && (errno != ENOENT)) {
cerr << "Unable to move old profile data to " << previous_sampledir << endl;
cerr << strerror(errno) << endl;
rc = EXIT_FAILURE;
goto out;
}
}
}
rc = mkdir(current_sampledir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (rc && (errno != EEXIST)) {
cerr << "Error trying to create " << current_sampledir << " dir." << endl;
perror("mkdir failed with");
rc = EXIT_FAILURE;
goto out;
}
if (operf_options::post_conversion) {
inputfd = -1;
inputfname = outputfile;
} else {
inputfd = sample_data_pipe[0];
inputfname = "";
}
operfRead.init(inputfd, inputfname, current_sampledir, cpu_type, events, operf_options::system_wide);
if ((rc = operfRead.readPerfHeader()) < 0) {
if (rc != OP_PERF_HANDLED_ERROR)
cerr << "Error: Cannot create read header info for sample data " << endl;
rc = EXIT_FAILURE;
goto out;
}
cverb << vdebug << "Successfully read header info for sample data " << endl;
if (operfRead.is_valid()) {
try {
unsigned int num = operfRead.convertPerfData();
cverb << vdebug << "operf_read: Total bytes received from operf_record process: " << dec << num << endl;
} catch (runtime_error e) {
cerr << "Caught runtime error from operf_read::convertPerfData" << endl;
cerr << e.what() << endl;
rc = EXIT_FAILURE;
goto out;
}
}
_set_signals_for_convert();
cverb << vdebug << "Calling _do_jitdump_convert" << endl;
_do_jitdump_convert();
while (jit_conversion_running && (keep_waiting < 2)) {
sleep(1);
keep_waiting++;
}
if (jit_conversion_running) {
kill(jitconv_pid, SIGKILL);
}
out:
if (!operf_options::post_conversion)
_exit(rc);
}
static int find_app_file_in_dir(const struct dirent * d)
{
if (!strcmp(d->d_name, app_name))
return 1;
else
return 0;
}
static int get_PATH_based_pathname(char * path_holder, size_t n)
{
int retval = -1;
char * real_path = getenv("PATH");
char * path = (char *) xstrdup(real_path);
char * segment = strtok(path, ":");
while (segment) {
struct dirent ** namelist;
int rc = scandir(segment, &namelist, find_app_file_in_dir, NULL);
if (rc < 0) {
if (errno != ENOENT) {
cerr << strerror(errno) << endl;
cerr << app_name << " cannot be found in your PATH." << endl;
break;
}
} else if (rc == 1) {
size_t applen = strlen(app_name);
size_t dirlen = strlen(segment);
if (applen + dirlen + 2 > n) {
cerr << "Path segment " << segment
<< " prepended to the passed app name is too long"
<< endl;
retval = -1;
break;
}
if (!strcmp(segment, ".")) {
if (getcwd(path_holder, PATH_MAX) == NULL) {
retval = -1;
cerr << "getcwd [3] failed when processing <cur-dir>/" << app_name << " found via PATH. Aborting."
<< endl;
break;
}
} else {
strncpy(path_holder, segment, dirlen);
}
strcat(path_holder, "/");
strncat(path_holder, app_name, applen);
retval = 0;
free(namelist[0]);
free(namelist);
break;
}
segment = strtok(NULL, ":");
}
free(path);
return retval;
}
int validate_app_name(void)
{
int rc = 0;
struct stat filestat;
size_t len = strlen(app_name);
if (len > (size_t) (OP_APPNAME_LEN - 1)) {
cerr << "app name longer than max allowed (" << OP_APPNAME_LEN
<< " chars)\n";
cerr << app_name << endl;
rc = -1;
goto out;
}
if (index(app_name, '/') == app_name) {
// Full pathname of app was specified, starting with "/".
strncpy(full_pathname, app_name, len);
} else if ((app_name[0] == '.') && (app_name[1] == '/')) {
// Passed app is in current directory; e.g., "./myApp"
if (getcwd(full_pathname, PATH_MAX) == NULL) {
rc = -1;
cerr << "getcwd [1] failed when trying to find app name " << app_name << ". Aborting."
<< endl;
goto out;
}
strcat(full_pathname, "/");
if ((strlen(full_pathname) + strlen(app_name + 2) + 1) > PATH_MAX) {
rc = -1;
cerr << "Length of current dir (" << full_pathname << ") and app name ("
<< (app_name + 2) << ") exceeds max allowed (" << PATH_MAX << "). Aborting."
<< endl;
goto out;
}
strcat(full_pathname, (app_name + 2));
} else if (index(app_name, '/')) {
// Passed app is in a subdirectory of cur dir; e.g., "test-stuff/myApp"
if (getcwd(full_pathname, PATH_MAX) == NULL) {
rc = -1;
cerr << "getcwd [2] failed when trying to find app name " << app_name << ". Aborting."
<< endl;
goto out;
}
strcat(full_pathname, "/");
strcat(full_pathname, app_name);
} else {
// Passed app name, at this point, MUST be found in PATH
rc = get_PATH_based_pathname(full_pathname, PATH_MAX);
}
if (rc) {
cerr << "Problem finding app name " << app_name << ". Aborting."
<< endl;
goto out;
}
app_name_SAVE = app_name;
app_name = full_pathname;
if (stat(app_name, &filestat)) {
char msg[OP_APPNAME_LEN + 50];
snprintf(msg, OP_APPNAME_LEN + 50, "Non-existent app name \"%s\"",
app_name);
perror(msg);
rc = -1;
}
out: return rc;
}
static void _get_event_code(operf_event_t * event)
{
FILE * fp;
char oprof_event_code[9];
string command;
u64 base_code, config;
char buf[20];
if ((snprintf(buf, 20, "%lu", event->count)) < 0) {
cerr << "Error parsing event count of " << event->count << endl;
exit(EXIT_FAILURE);
}
base_code = config = 0ULL;
command = OP_BINDIR;
command += "ophelp ";
command += event->name;
fp = popen(command.c_str(), "r");
if (fp == NULL) {
cerr << "Unable to execute ophelp to get info for event "
<< event->name << endl;
exit(EXIT_FAILURE);
}
if (fgets(oprof_event_code, sizeof(oprof_event_code), fp) == NULL) {
pclose(fp);
cerr << "Unable to find info for event "
<< event->name << endl;
exit(EXIT_FAILURE);
}
pclose(fp);
base_code = strtoull(oprof_event_code, (char **) NULL, 10);
#if defined(__i386__) || defined(__x86_64__)
// Setup EventSelct[11:8] field for AMD
char mask[12];
const char * vendor_AMD = "AuthenticAMD";
if (op_is_cpu_vendor((char *)vendor_AMD)) {
config = base_code & 0xF00ULL;
config = config << 32;
}
// Setup EventSelct[7:0] field
config |= base_code & 0xFFULL;
// Setup unitmask field
handle_named_um:
if (event->um_name[0]) {
command = OP_BINDIR;
command += "ophelp ";
command += "--extra-mask ";
command += event->name;
command += ":";
command += buf;
command += ":";
command += event->um_name;
fp = popen(command.c_str(), "r");
if (fp == NULL) {
cerr << "Unable to execute ophelp to get info for event "
<< event->name << endl;
exit(EXIT_FAILURE);
}
if (fgets(mask, sizeof(mask), fp) == NULL) {
pclose(fp);
cerr << "Unable to find unit mask info for " << event->um_name << " for event "
<< event->name << endl;
exit(EXIT_FAILURE);
}
pclose(fp);
// FIXME: The mask value here is the extra bits from the named unit mask. It's not
// ideal to put that value into the UM's mask, since that's what will show up in
// opreport. It would be better if we could somehow have the unit mask name that the
// user passed to us show up in opreort.
event->evt_um = strtoull(mask, (char **) NULL, 10);
/* A value >= EXTRA_MIN_VAL returned by 'ophelp --extra-mask' is interpreted as a
* valid extra value; otherwise we interpret it as a simple unit mask value
* for a named unit mask with EXTRA_NONE.
*/
if (event->evt_um >= EXTRA_MIN_VAL)
config |= event->evt_um;
else
config |= ((event->evt_um & 0xFFULL) << 8);
} else if (!event->evt_um) {
char * endptr;
command.clear();
command = OP_BINDIR;
command += "ophelp ";
command += "--unit-mask ";
command += event->name;
command += ":";
command += buf;
fp = popen(command.c_str(), "r");
if (fp == NULL) {
cerr << "Unable to execute ophelp to get unit mask for event "
<< event->name << endl;
exit(EXIT_FAILURE);
}
if (fgets(mask, sizeof(mask), fp) == NULL) {
pclose(fp);
cerr << "Unable to find unit mask info for event " << event->name << endl;
exit(EXIT_FAILURE);
}
pclose(fp);
event->evt_um = strtoull(mask, &endptr, 10);
if ((endptr >= mask) &&
(endptr <= (mask + strlen(mask) - 1))) {
// Must be a default named unit mask
strncpy(event->um_name, mask, OP_MAX_UM_NAME_LEN);
goto handle_named_um;
}
config |= ((event->evt_um & 0xFFULL) << 8);
} else {
config |= ((event->evt_um & 0xFFULL) << 8);
}
#else
config = base_code;
#endif
event->op_evt_code = base_code;
if (cpu_type == CPU_P4 || cpu_type == CPU_P4_HT2) {
if (op_netburst_get_perf_encoding(event->name, event->evt_um, 1, 1, &config)) {
cerr << "Unable to get event encoding for " << event->name << endl;
exit(EXIT_FAILURE);
}
}
event->evt_code = config;
}
#if PPC64_ARCH
/* All ppc64 events (except CYCLES) have a _GRP<n> suffix. This is
* because the legacy opcontrol profiler can only profile events in
* the same group (i.e., having the same _GRP<n> suffix). But operf
* can multiplex events, so we should allow the user to pass event
* names without the _GRP<n> suffix.
*
* If event name is not CYCLES or does not have a _GRP<n> suffix,
* we'll call ophelp and scan the list of events, searching for one
* that matches up to the _GRP<n> suffix. If we don't find a match,
* then we'll exit with the expected error message for invalid event name.
*/
static string _handle_powerpc_event_spec(string event_spec)
{
FILE * fp;
char line[MAX_INPUT];
size_t grp_pos;
string evt, retval, err_msg;
size_t evt_name_len;
bool first_non_cyc_evt_found = false;
bool event_found = false;
char event_name[OP_MAX_EVT_NAME_LEN], event_spec_str[OP_MAX_EVT_NAME_LEN + 20], * count_str;
string cmd = OP_BINDIR;
cmd += "/ophelp";
strncpy(event_spec_str, event_spec.c_str(), event_spec.length() + 1);
strncpy(event_name, strtok(event_spec_str, ":"), OP_MAX_EVT_NAME_LEN);
count_str = strtok(NULL, ":");
if (!count_str) {
err_msg = "Invalid count for event ";
goto out;
}
if (!strcmp("CYCLES", event_name)) {
event_found = true;
goto out;
}
evt = event_name;
// Need to make sure the event name truly has a _GRP<n> suffix.
grp_pos = evt.rfind("_GRP");
if ((grp_pos != string::npos) && ((evt = evt.substr(grp_pos, string::npos))).length() > 4) {
char * end;
strtoul(evt.substr(4, string::npos).c_str(), &end, 0);
if (end && (*end == '\0')) {
// Valid group number found after _GRP, so we can skip to the end.
event_found = true;
goto out;
}
}
// If we get here, it implies the user passed a non-CYCLES event without a GRP suffix.
// Lets try to find a valid suffix for it.
fp = popen(cmd.c_str(), "r");
if (fp == NULL) {
cerr << "Unable to execute ophelp to get info for event "
<< event_spec << endl;
exit(EXIT_FAILURE);
}
evt_name_len = strlen(event_name);
err_msg = "Cannot find event ";
while (fgets(line, MAX_INPUT, fp)) {
if (!first_non_cyc_evt_found) {
if (!strncmp(line, "PM_", 3))
first_non_cyc_evt_found = true;
else
continue;
}
if (line[0] == ' ' || line[0] == '\t')
continue;
if (!strncmp(line, event_name, evt_name_len)) {
// Found a potential match. Check if it's a perfect match.
string save_event_name = event_name;
size_t full_evt_len = index(line, ':') - line;
memset(event_name, '\0', OP_MAX_EVT_NAME_LEN);
strncpy(event_name, line, full_evt_len);
string candidate = event_name;
if (candidate.rfind("_GRP") == evt_name_len) {
event_found = true;
break;
} else {
memset(event_name, '\0', OP_MAX_EVT_NAME_LEN);
strncpy(event_name, save_event_name.c_str(), evt_name_len);
}
}
}
pclose(fp);
out:
if (!event_found) {
cerr << err_msg << event_name << endl;
cerr << "Error retrieving info for event "
<< event_spec << endl;
exit(EXIT_FAILURE);
}
retval = event_name;
return retval + ":" + count_str;
}
#endif
static void _process_events_list(void)
{
string cmd = OP_BINDIR;
if (operf_options::evts.size() > OP_MAX_EVENTS) {
cerr << "Number of events specified is greater than allowed maximum of "
<< OP_MAX_EVENTS << "." << endl;
exit(EXIT_FAILURE);
}
cmd += "/ophelp --check-events ";
for (unsigned int i = 0; i < operf_options::evts.size(); i++) {
FILE * fp;
string full_cmd = cmd;
string event_spec = operf_options::evts[i];
#if PPC64_ARCH
// Starting with CPU_PPC64_ARCH_V1, ppc64 events files are formatted like
// other architectures, so no special handling is needed.
if (cpu_type < CPU_PPC64_ARCH_V1)
event_spec = _handle_powerpc_event_spec(event_spec);
#endif
if (operf_options::callgraph) {
full_cmd += " --callgraph=1 ";
}
full_cmd += event_spec;
fp = popen(full_cmd.c_str(), "r");
if (fp == NULL) {
cerr << "Unable to execute ophelp to get info for event "
<< event_spec << endl;
exit(EXIT_FAILURE);
}
if (fgetc(fp) == EOF) {
pclose(fp);
cerr << "Error retrieving info for event "
<< event_spec << endl;
if (operf_options::callgraph)
cerr << "Note: When doing callgraph profiling, the sample count must be"
<< endl << "15 times the minimum count value for the event." << endl;
exit(EXIT_FAILURE);
}
pclose(fp);
char * event_str = op_xstrndup(event_spec.c_str(), event_spec.length());
operf_event_t event;
strncpy(event.name, strtok(event_str, ":"), OP_MAX_EVT_NAME_LEN - 1);
event.count = atoi(strtok(NULL, ":"));
/* Name and count are required in the event spec in order for
* 'ophelp --check-events' to pass. But since unit mask and domain
* control bits are optional, we need to ensure the result of strtok
* is valid.
*/
char * info;
#define _OP_UM 1
#define _OP_KERNEL 2
#define _OP_USER 3
int place = _OP_UM;
char * endptr = NULL;
event.evt_um = 0ULL;
event.no_kernel = 0;
event.no_user = 0;
event.throttled = false;
memset(event.um_name, '\0', OP_MAX_UM_NAME_LEN);
while ((info = strtok(NULL, ":"))) {
switch (place) {
case _OP_UM:
event.evt_um = strtoul(info, &endptr, 0);
// If any of the UM part is not a number, then we
// consider the entire part a string.
if (*endptr) {
event.evt_um = 0;
strncpy(event.um_name, info, OP_MAX_UM_NAME_LEN - 1);
}
break;
case _OP_KERNEL:
if (atoi(info) == 0)
event.no_kernel = 1;
break;
case _OP_USER:
if (atoi(info) == 0)
event.no_user = 1;
break;
}
place++;
}
free(event_str);
_get_event_code(&event);
events.push_back(event);
}
#if PPC64_ARCH
{
/* For ppc64 architecture processors prior to the introduction of
* architected_events_v1, the oprofile event code needs to be converted
* to the appropriate event code to pass to the perf_event_open syscall.
* But as of the introduction of architected_events_v1, the events
* file contains the necessary event code information, so this conversion
* step is no longer needed.
*/
using namespace OP_perf_utils;
if ((cpu_type < CPU_PPC64_ARCH_V1) && !op_convert_event_vals(&events)) {
cerr << "Unable to convert all oprofile event values to perf_event values" << endl;
exit(EXIT_FAILURE);
}
}
#endif
}
static void get_default_event(void)
{
operf_event_t dft_evt;
struct op_default_event_descr descr;
vector<operf_event_t> tmp_events;
op_default_event(cpu_type, &descr);
if (descr.name[0] == '\0') {
cerr << "Unable to find default event" << endl;
exit(EXIT_FAILURE);
}
memset(&dft_evt, 0, sizeof(dft_evt));
if (operf_options::callgraph) {
struct op_event * _event;
op_events(cpu_type);
if ((_event = find_event_by_name(descr.name, 0, 0))) {
dft_evt.count = _event->min_count * CALLGRAPH_MIN_COUNT_SCALE;
} else {
cerr << "Error getting event info for " << descr.name << endl;
exit(EXIT_FAILURE);
}
} else {
dft_evt.count = descr.count;
}
dft_evt.evt_um = descr.um;
strncpy(dft_evt.name, descr.name, OP_MAX_EVT_NAME_LEN - 1);
_get_event_code(&dft_evt);
events.push_back(dft_evt);
#if PPC64_ARCH
{
/* This section of code is for architectures such as ppc[64] for which
* the oprofile event code needs to be converted to the appropriate event
* code to pass to the perf_event_open syscall.
*/
using namespace OP_perf_utils;
if ((cpu_type < CPU_PPC64_ARCH_V1) && !op_convert_event_vals(&events)) {
cerr << "Unable to convert all oprofile event values to perf_event values" << endl;
exit(EXIT_FAILURE);
}
}
#endif
}
static void _process_session_dir(void)
{
if (operf_options::session_dir.empty()) {
char * cwd = NULL;
int rc;
cwd = (char *) xmalloc(PATH_MAX);
// set default session dir
cwd = getcwd(cwd, PATH_MAX);
if (cwd == NULL) {
perror("Error calling getcwd");
exit(EXIT_FAILURE);
}
operf_options::session_dir = cwd;
operf_options::session_dir +="/oprofile_data";
samples_dir = operf_options::session_dir + "/samples";
free(cwd);
rc = mkdir(operf_options::session_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (rc && (errno != EEXIST)) {
cerr << "Error trying to create " << operf_options::session_dir << " dir." << endl;
perror("mkdir failed with");
exit(EXIT_FAILURE);
}
rc = mkdir(samples_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (rc && (errno != EEXIST)) {
cerr << "Error trying to create " << samples_dir << " dir." << endl;
perror("mkdir failed with");
exit(EXIT_FAILURE);
}
} else {
struct stat filestat;
int rc;
if (stat(operf_options::session_dir.c_str(), &filestat)) {
perror("stat operation on passed session-dir failed");
exit(EXIT_FAILURE);
}
if (!S_ISDIR(filestat.st_mode)) {
cerr << "Passed session-dir " << operf_options::session_dir
<< " is not a directory" << endl;
exit(EXIT_FAILURE);
}
samples_dir = operf_options::session_dir + "/samples";
rc = mkdir(samples_dir.c_str(), S_IRWXU);
if (rc && (errno != EEXIST)) {
cerr << "Error trying to create " << samples_dir << " dir." << endl;
perror("mkdir failed with");
exit(EXIT_FAILURE);
}
}
cverb << vdebug << "Using samples dir " << samples_dir << endl;
}
bool _get_vmlinux_address_info(vector<string> args, string cmp_val, string &str)
{
bool found = false;
child_reader reader("objdump", args);
if (reader.error()) {
cerr << "An error occurred while trying to get vmlinux address info:\n\n";
cerr << reader.error_str() << endl;
exit(EXIT_FAILURE);
}
while (reader.getline(str)) {
if (str.find(cmp_val.c_str()) != string::npos) {
found = true;
break;
}
}
// objdump always returns SUCCESS so we must rely on the stderr state
// of objdump. If objdump error message is cryptic our own error
// message will be probably also cryptic
ostringstream std_err;
ostringstream std_out;
reader.get_data(std_out, std_err);
if (std_err.str().length()) {
cerr << "An error occurred while getting vmlinux address info:\n\n";
cerr << std_err.str() << endl;
// If we found the string we were looking for in objdump output,
// treat this as non-fatal error.
if (!found)
exit(EXIT_FAILURE);
}
// force error code to be acquired
reader.terminate_process();
// required because if objdump stop by signal all above things suceeed
// (signal error message are not output through stdout/stderr)
if (reader.error()) {
cerr << "An error occur during the execution of objdump to get vmlinux address info:\n\n";
cerr << reader.error_str() << endl;
if (!found)
exit(EXIT_FAILURE);
}
return found;
}
string _process_vmlinux(string vmlinux_file)
{
vector<string> args;
char start[17], end[17];
string str, start_end;
bool found;
int ret;
no_vmlinux = false;
args.push_back("-h");
args.push_back(vmlinux_file);
if ((found = _get_vmlinux_address_info(args, " .text", str))) {
cverb << vmisc << str << endl;
ret = sscanf(str.c_str(), " %*s %*s %*s %s", start);
}
if (!found || ret != 1){
cerr << "Unable to obtain vmlinux start address." << endl;
cerr << "The specified vmlinux file (" << vmlinux_file << ") "
<< "does not seem to be valid." << endl;
cerr << "Make sure you are using a non-compressed image file "
<< "(e.g. vmlinux not vmlinuz)" << endl;
exit(EXIT_FAILURE);
}
args.clear();
args.push_back("-t");
args.push_back(vmlinux_file);
if ((found = _get_vmlinux_address_info(args, " _etext", str))) {
cverb << vmisc << str << endl;
ret = sscanf(str.c_str(), "%s", end);
}
if (!found || ret != 1){
cerr << "Unable to obtain vmlinux end address." << endl;
cerr << "The specified vmlinux file (" << vmlinux_file << ") "
<< "does not seem to be valid." << endl;
cerr << "Make sure you are using a non-compressed image file "
<< "(e.g. vmlinux not vmlinuz)" << endl;
exit(EXIT_FAILURE);
}
errno = 0;
kernel_start = strtoull(start, NULL, 16);
if (errno) {
cerr << "Unable to convert vmlinux start address " << start
<< " to a valid hex value. errno is " << strerror(errno) << endl;
exit(EXIT_FAILURE);
}
errno = 0;
kernel_end = strtoull(end, NULL, 16);
if (errno) {
cerr << "Unable to convert vmlinux end address " << start
<< " to a valid hex value. errno is " << strerror(errno) << endl;
exit(EXIT_FAILURE);
}
start_end = start;
start_end.append(",");
start_end.append(end);
return start_end;
}
static void _print_valid_verbose_options(void)
{
cerr << "Valid verbosity options are: ";
for (unsigned i = 0; i < (NUM_VERBOSE_OPTIONS - 1); i++)
cerr << valid_verbose_vals[i] << ",";
cerr << valid_verbose_vals[NUM_VERBOSE_OPTIONS - 1] << endl;
}
static bool _validate_verbose_args(char * verbosity)
{
bool valid_verbosity = true;
char * verbose_cand = strtok(verbosity, ",");
do {
unsigned i;
for (i = 0; i < (NUM_VERBOSE_OPTIONS); i++) {
if (!strcmp(verbose_cand, valid_verbose_vals[i])) {
verbose_string.push_back(verbose_cand);
break;
}
}
if (i == NUM_VERBOSE_OPTIONS) {
valid_verbosity = false;
cerr << "Verbosity argument " << verbose_cand << " is not valid." << endl;
_print_valid_verbose_options();
}
} while ((verbose_cand = strtok(NULL, ",")) && valid_verbosity);
return valid_verbosity;
}
static int _process_operf_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 << "non-option detected at optind " << optind << endl;
keep_trying = false;
idx_of_non_options = -1;
break;
case 'V':
if (!_validate_verbose_args(optarg))
__print_usage_and_exit("NULL");
break;
case 'd':
operf_options::session_dir = optarg;
break;
case 'k':
operf_options::vmlinux = optarg;
break;
case 'g':
operf_options::callgraph = true;
break;
case 's':
operf_options::system_wide = true;
break;
case 'a':
operf_options::append = true;
break;
case 'p':
operf_options::pid = strtol(optarg, &endptr, 10);
if ((endptr >= optarg) && (endptr <= (optarg + strlen(optarg) - 1)))
__print_usage_and_exit("operf: Invalid numeric value for --pid option.");
break;
case 'e':
event = strtok(optarg, ",");
do {
operf_options::evts.push_back(event);
} while ((event = strtok(NULL, ",")));
break;
case 'c':
operf_options::separate_cpu = true;
break;
case 't':
operf_options::separate_thread = true;
break;
case 'l':
operf_options::post_conversion = true;
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("unexpected end of arg parsing");
}
}
return idx_of_non_options;
}
static void process_args(int argc, char * const argv[])
{
int non_options_idx = _process_operf_and_app_args(argc, argv);
if (non_options_idx < 0) {
__print_usage_and_exit(NULL);
} else if ((non_options_idx) > 0) {
if (operf_options::pid || operf_options::system_wide)
__print_usage_and_exit(NULL);
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 (validate_app_name() < 0) {
__print_usage_and_exit(NULL);
}
} else { // non_options_idx == 0
if (operf_options::pid) {
if (operf_options::system_wide)
__print_usage_and_exit(NULL);
app_PID = operf_options::pid;
} else if (operf_options::system_wide) {
app_PID = -1;
} else {
__print_usage_and_exit(NULL);
}
}
/* At this point, we know which of the three kinds of profiles the user requested:
* - profile app by name
* - profile app by PID
* - profile whole system
*/
if (!verbose::setup(verbose_string)) {
cerr << "unknown --verbose= options\n";
exit(EXIT_FAILURE);
}
_process_session_dir();
if (operf_options::post_conversion)
outputfile = samples_dir + "/" + DEFAULT_OPERF_OUTFILE;
if (operf_options::evts.empty()) {
// Use default event
get_default_event();
} else {
_process_events_list();
}
op_nr_events = events.size();
if (operf_options::vmlinux.empty()) {
no_vmlinux = true;
operf_create_vmlinux(NULL, NULL);
} else {
string startEnd = _process_vmlinux(operf_options::vmlinux);
operf_create_vmlinux(operf_options::vmlinux.c_str(), startEnd.c_str());
}
return;
}
static int _get_cpu_for_perf_events_cap(void)
{
int retval;
string err_msg;
char cpus_online[257];
FILE * online_cpus;
DIR *dir = NULL;
int total_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (!total_cpus) {
err_msg = "Internal Error (1): Number of online cpus cannot be determined.";
retval = -1;
goto error;
}
online_cpus = fopen("/sys/devices/system/cpu/online", "r");
if (!online_cpus) {
err_msg = "Internal Error (2): Number of online cpus cannot be determined.";
retval = -1;
goto error;
}
memset(cpus_online, 0, sizeof(cpus_online));
if ( fgets(cpus_online, sizeof(cpus_online), online_cpus) == NULL) {
fclose(online_cpus);
err_msg = "Internal Error (3): Number of online cpus cannot be determined.";
retval = -1;
goto error;
}
if (!cpus_online[0]) {
fclose(online_cpus);
err_msg = "Internal Error (4): Number of online cpus cannot be determined.";
retval = -1;
goto error;
}
if (index(cpus_online, ',') || cpus_online[0] != '0') {
// A comma in cpus_online implies a gap, which in turn implies that not all
// CPUs are online.
if ((dir = opendir("/sys/devices/system/cpu")) == NULL) {
fclose(online_cpus);
err_msg = "Internal Error (5): Number of online cpus cannot be determined.";
retval = -1;
goto error;
} else {
struct dirent *entry = NULL;
retval = OP_perf_utils::op_get_next_online_cpu(dir, entry);
closedir(dir);
}
} else {
// All CPUs are available, so we just arbitrarily choose CPU 0.
retval = 0;
}
fclose(online_cpus);
error:
return retval;
}
static int _check_perf_events_cap(bool use_cpu_minus_one)
{
/* If perf_events syscall is not implemented, the syscall below will fail
* with ENOSYS (38). If implemented, but the processor type on which this
* program is running is not supported by perf_events, the syscall returns
* ENOENT (2).
*/
struct perf_event_attr attr;
pid_t pid ;
int cpu_to_try = use_cpu_minus_one ? -1 : _get_cpu_for_perf_events_cap();
errno = 0;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sample_type = PERF_SAMPLE_IP;
pid = getpid();
syscall(__NR_perf_event_open, &attr, pid, cpu_to_try, -1, 0);
return errno;
}
static void _precheck_permissions_to_samplesdir(string sampledir, bool for_current)
{
/* Pre-check to make sure we have permission to remove old sample data
* or to create new sample data in the specified sample data directory.
* If the user wants us to remove old data, we don't actually do it now,
* since the profile session may fail for some reason or the user may do ctl-c.
* We should exit without unnecessarily removing the old sample data as
* the user may expect it to still be there after an aborted run.
*/
string sampledir_testfile = sampledir + "/.xxxTeStFiLe";
ofstream afile;
errno = 0;
afile.open(sampledir_testfile.c_str());
if (!afile.is_open() && (errno != ENOENT)) {
if (operf_options::append && for_current)
cerr << "Unable to write to sample data directory at "
<< sampledir << "." << endl;
else
cerr << "Unable to remove old sample data at "
<< sampledir << "." << endl;
if (errno)
cerr << strerror(errno) << endl;
cerr << "Try a manual removal of " << sampledir << endl;
cleanup();
exit(1);
}
afile.close();
}
static int _get_sys_value(const char * filename)
{
char str[10];
int _val = -999;
FILE * fp = fopen(filename, "r");
if (fp == NULL)
return _val;
if (fgets(str, 9, fp))
sscanf(str, "%d", &_val);
fclose(fp);
return _val;
}
int main(int argc, char * const argv[])
{
int rc;
int perf_event_paranoid = _get_sys_value("/proc/sys/kernel/perf_event_paranoid");
my_uid = geteuid();
throttled = false;
rc = _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 profile 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 = _check_perf_events_cap(use_cpu_minus_one);
}
}
if (rc == EBUSY) {
cerr << "Performance monitor unit is busy. Do 'opcontrol --deinit' and try again." << endl;
exit(1);
}
if (rc == ENOSYS) {
cerr << "Your kernel does not implement a required syscall"
<< " for the operf 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 operf: " << strerror(rc) << endl;
}
if (rc) {
cerr << "Please use the opcontrol command instead of operf." << endl;
exit(1);
}
cpu_type = op_get_cpu_type();
cpu_speed = op_cpu_frequency();
process_args(argc, argv);
if (operf_options::system_wide && ((my_uid != 0) && (perf_event_paranoid > 0))) {
cerr << "To do system-wide profiling, 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 (my_uid != 0) {
bool for_current = true;
string current_sampledir = samples_dir + "/current";
_precheck_permissions_to_samplesdir(current_sampledir, for_current);
if (!operf_options::append) {
string previous_sampledir = samples_dir + "/previous";
for_current = false;
_precheck_permissions_to_samplesdir(previous_sampledir, for_current);
}
}
kptr_restrict = _get_sys_value("/proc/sys/kernel/kptr_restrict");
end_code_t run_result;
if ((run_result = _run())) {
if (startApp && app_started && (run_result != APP_ABNORMAL_END)) {
int rc;
cverb << vdebug << "Killing profiled app . . ." << endl;
rc = kill(app_PID, SIGKILL);
if (rc) {
if (errno == ESRCH)
cverb << vdebug
<< "Unable to kill profiled app because it has already ended"
<< endl;
else
perror("Attempt to kill profiled app failed.");
}
}
if ((run_result == PERF_RECORD_ERROR) || (run_result == PERF_BOTH_ERROR)) {
cerr << "Error running profiler" << endl;
} else if (run_result == PERF_READ_ERROR) {
cerr << "Error converting operf sample data to oprofile sample format" << endl;
} else {
cerr << "WARNING: Profile results may be incomplete due to to abend of profiled app." << endl;
}
} else {
cerr << endl << "Profiling done." << endl;
}
if (operf_options::post_conversion) {
if (!(!app_started && !operf_options::system_wide))
convert_sample_data();
}
cleanup();
return run_result;;
}