486 lines
12 KiB
C
486 lines
12 KiB
C
|
/*
|
||
|
* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* A very simple perf program to periodically print the performance
|
||
|
* counter reqested on the command line to standard out at the rate
|
||
|
* specified.
|
||
|
*
|
||
|
* This is valuable for showing the output in a simple plot or
|
||
|
* exporting the counter data for post processing. No attempt
|
||
|
* to process the data is made.
|
||
|
*
|
||
|
* Scaling is not supported, use only as many counters as are
|
||
|
* provided by the hardware.
|
||
|
*
|
||
|
* Math functions are support to combine counter results by using
|
||
|
* the -m flag.
|
||
|
*
|
||
|
* The -r -w flags supports user signalling for input. This assumes
|
||
|
* that a pipe/fifo is needed so the -rw cmd line arg is a string
|
||
|
* that is the name of the named pipe to open for read/write. User
|
||
|
* sends data on the read pipe to the process to collect a sample.
|
||
|
* Commands are also supported on the pipe.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "perf.h"
|
||
|
#include "builtin.h"
|
||
|
#include "util/util.h"
|
||
|
#include "util/parse-options.h"
|
||
|
#include "util/parse-events.h"
|
||
|
#include "util/event.h"
|
||
|
#include "util/evsel.h"
|
||
|
#include "util/evlist.h"
|
||
|
#include "util/debug.h"
|
||
|
#include "util/header.h"
|
||
|
#include "util/cpumap.h"
|
||
|
#include "util/thread.h"
|
||
|
#include <signal.h>
|
||
|
#include <sys/types.h>
|
||
|
|
||
|
#define PERF_PERIODIC_ERROR -1
|
||
|
|
||
|
/* number of pieces of data on each read. */
|
||
|
#define DATA_SIZE 2
|
||
|
|
||
|
#define DEFAULT_FIFO_NAME "xxbadFiFo"
|
||
|
#define MAX_NAMELEN 50
|
||
|
|
||
|
struct perf_evlist *evsel_list;
|
||
|
|
||
|
/*
|
||
|
* command line variables and settings
|
||
|
* Default to current process, no_inherit, process
|
||
|
*/
|
||
|
static pid_t target_pid = -1; /* all */
|
||
|
static bool system_wide;
|
||
|
static int cpumask = -1; /* all */
|
||
|
static int ncounts;
|
||
|
static int ms_sleep = 1000; /* 1 second */
|
||
|
static char const *operations = "nnnnnnnnnnnnnnnn"; /* nop */
|
||
|
static bool math_enabled;
|
||
|
static bool calc_delta;
|
||
|
static double old_accum, accum;
|
||
|
static int math_op_index;
|
||
|
static char const *wfifo_name = DEFAULT_FIFO_NAME;
|
||
|
static char const *rfifo_name = DEFAULT_FIFO_NAME;
|
||
|
static bool use_fifo;
|
||
|
static bool is_ratio;
|
||
|
static FILE *fd_in, *fd_out;
|
||
|
|
||
|
static FILE *tReadFifo, *tWriteFifo;
|
||
|
|
||
|
/*
|
||
|
* Raw results from perf, we track the current value and
|
||
|
* the old value.
|
||
|
*/
|
||
|
struct perf_raw_results_s {
|
||
|
u64 values;
|
||
|
u64 old_value;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Everything we need to support a perf counter across multiple
|
||
|
* CPUs. We need to support multiple file descriptors (perf_fd)
|
||
|
* because perf requires a fd per counter, so 1 per core enabled.
|
||
|
*
|
||
|
* Raw results values are calculated across all the cores as they
|
||
|
* are read.
|
||
|
*/
|
||
|
struct perf_setup_s {
|
||
|
int event_index;
|
||
|
struct perf_event_attr *attr;
|
||
|
int perf_fd[MAX_NR_CPUS];
|
||
|
pid_t pid;
|
||
|
int cpu;
|
||
|
int flags;
|
||
|
int group;
|
||
|
struct perf_raw_results_s data;
|
||
|
struct perf_raw_results_s totals;
|
||
|
struct perf_raw_results_s output;
|
||
|
};
|
||
|
|
||
|
static void do_cleanup(void)
|
||
|
{
|
||
|
if (fd_in) {
|
||
|
if (0 != fclose(fd_in))
|
||
|
error("Error closing fd_in\n");
|
||
|
}
|
||
|
if (fd_out) {
|
||
|
if (0 != fclose(fd_out))
|
||
|
error("Error closing fd_out\n");
|
||
|
}
|
||
|
if (use_fifo) {
|
||
|
if (0 != unlink(rfifo_name))
|
||
|
error("Error unlinking rfifo\n");
|
||
|
if (0 != unlink(wfifo_name))
|
||
|
error("Error unlinking wfifo\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Unexpected signal for error indication, cleanup
|
||
|
*/
|
||
|
static int sig_dummy;
|
||
|
static void sig_do_cleanup(int sig)
|
||
|
{
|
||
|
sig_dummy = sig;
|
||
|
do_cleanup();
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
#define PERIODIC_MAX_STRLEN 100
|
||
|
/*
|
||
|
* Delay for either a timed period or the wait on the read_fifo
|
||
|
*/
|
||
|
static void delay(unsigned long milli)
|
||
|
{
|
||
|
char tmp_stg[PERIODIC_MAX_STRLEN];
|
||
|
int done;
|
||
|
int ret;
|
||
|
|
||
|
if (use_fifo) {
|
||
|
do {
|
||
|
done = true;
|
||
|
ret = fscanf(tReadFifo, "%s", tmp_stg);
|
||
|
if (ret == 0)
|
||
|
return;
|
||
|
/*
|
||
|
* Look for a command request, and if we get a command
|
||
|
* Need to process and then wait again w/o sending data.
|
||
|
*/
|
||
|
if (strncmp(tmp_stg, "PID", strnlen(tmp_stg,
|
||
|
PERIODIC_MAX_STRLEN)) == 0) {
|
||
|
fprintf(fd_out, " %u\n", getpid());
|
||
|
fflush(fd_out);
|
||
|
done = false;
|
||
|
} else if (strncmp(tmp_stg, "EXIT",
|
||
|
strnlen(tmp_stg, PERIODIC_MAX_STRLEN))
|
||
|
== 0) {
|
||
|
do_cleanup();
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
} while (done != true);
|
||
|
} else
|
||
|
usleep(milli*1000);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create a perf counter event.
|
||
|
* Some interesting behaviour that is not documented anywhere else:
|
||
|
* the CPU will not work if out of range.
|
||
|
* The CPU will only work for a single CPU, so to collect the counts
|
||
|
* on the system in SMP based systems a counter needs to be created
|
||
|
* for each CPU.
|
||
|
*/
|
||
|
static int create_perf_counter(struct perf_setup_s *p)
|
||
|
{
|
||
|
struct cpu_map *cpus;
|
||
|
int cpu;
|
||
|
|
||
|
cpus = cpu_map__new(NULL);
|
||
|
if (p == NULL)
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
for (cpu = 0; cpu < cpus->nr; cpu++) {
|
||
|
if (((1 << cpu) & cpumask) == 0)
|
||
|
continue;
|
||
|
p->perf_fd[cpu] = sys_perf_event_open(p->attr, target_pid, cpu,
|
||
|
-1, 0);
|
||
|
if (p->perf_fd[cpu] < 0)
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Perf init setup
|
||
|
*/
|
||
|
static int perf_setup_init(struct perf_setup_s *p)
|
||
|
{
|
||
|
if (p == NULL)
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
|
||
|
bzero(p, sizeof(struct perf_setup_s));
|
||
|
p->group = -1;
|
||
|
p->flags = 0;
|
||
|
|
||
|
p->output.values = 0;
|
||
|
p->output.old_value = 0;
|
||
|
p->data.values = 0;
|
||
|
p->data.old_value = 0;
|
||
|
p->totals.old_value = 0;
|
||
|
p->totals.values = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read in ALL the performance counters configured for the CPU,
|
||
|
* one performance monitor per core that was configured during
|
||
|
* "all" mode
|
||
|
*/
|
||
|
static int perf_setup_read(struct perf_setup_s *p)
|
||
|
{
|
||
|
u64 data[DATA_SIZE];
|
||
|
int i, status;
|
||
|
|
||
|
p->totals.values = 0;
|
||
|
p->data.values = 0;
|
||
|
for (i = 0; i < MAX_NR_CPUS; i++) {
|
||
|
if (p->perf_fd[i] == 0)
|
||
|
continue;
|
||
|
status = read(p->perf_fd[i], &data, sizeof(data));
|
||
|
p->data.values += data[0];
|
||
|
p->totals.values += data[0];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Normally we show totals, we want to support
|
||
|
* showing deltas from the previous value so external apps do not have
|
||
|
* to do this...
|
||
|
*/
|
||
|
if (calc_delta) {
|
||
|
p->output.values = p->data.values - p->data.old_value;
|
||
|
p->data.old_value = p->data.values;
|
||
|
} else
|
||
|
p->output.values = p->totals.values;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int perf_setup_show(struct perf_setup_s *p)
|
||
|
{
|
||
|
if (p == NULL)
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
fprintf(fd_out, " %llu", p->output.values);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static const char * const periodic_usage[] = {
|
||
|
"perf periodic [<options>]",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct option options[] = {
|
||
|
OPT_CALLBACK('e', "event", &evsel_list, "event",
|
||
|
"event selector. use 'perf list' to list available events",
|
||
|
parse_events_option),
|
||
|
OPT_STRING('m', "math-operations", &operations, "nnnnnn",
|
||
|
"math operation to perform on values collected asmd in order"),
|
||
|
OPT_STRING('r', "readpipe", &rfifo_name, "xxbadFiFo",
|
||
|
"wait for a user input fifo - will be created"),
|
||
|
OPT_STRING('w', "writepipe", &wfifo_name, "xxbadFifo",
|
||
|
"write data out on this pipe - pipe is created"),
|
||
|
OPT_INTEGER('i', "increment", &ncounts,
|
||
|
"number of times periods to count/iterate (default 0-forever)"),
|
||
|
OPT_INTEGER('p', "pid", &target_pid,
|
||
|
"stat events on existing process id"),
|
||
|
OPT_INTEGER('c', "cpumask", &cpumask,
|
||
|
"cpumask to enable counters, default all (-1)"),
|
||
|
OPT_INTEGER('s', "sleep", &ms_sleep,
|
||
|
"how long to sleep in ms between each sample (default 1000)"),
|
||
|
OPT_BOOLEAN('a', "all-cpus", &system_wide,
|
||
|
"system-wide collection from all CPUs overrides cpumask"),
|
||
|
OPT_BOOLEAN('d', "delta", &calc_delta,
|
||
|
"calculate and display the delta values math funcs will use delta"),
|
||
|
OPT_INCR('v', "verbose", &verbose,
|
||
|
"be more verbose (show counter open errors, etc)"),
|
||
|
OPT_END()
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* After every period we reset any math that was performed.
|
||
|
*/
|
||
|
static void reset_math(void)
|
||
|
{
|
||
|
math_op_index = 0;
|
||
|
old_accum = accum;
|
||
|
accum = 0;
|
||
|
}
|
||
|
|
||
|
static void do_math_op(struct perf_setup_s *p)
|
||
|
{
|
||
|
if (!math_enabled)
|
||
|
return;
|
||
|
switch (operations[math_op_index++]) {
|
||
|
case 'm':
|
||
|
accum *= (double)p->output.values; break;
|
||
|
case 'a':
|
||
|
accum += (double)p->output.values; break;
|
||
|
case 's':
|
||
|
accum -= (double)p->output.values; break;
|
||
|
case 'd':
|
||
|
accum /= (double)p->output.values; break;
|
||
|
case 'z':
|
||
|
accum = 0; break;
|
||
|
case 't':
|
||
|
accum = (double)p->output.values; break; /*transfer*/
|
||
|
case 'T':
|
||
|
accum += old_accum; break; /*total*/
|
||
|
case 'i': /* ignore */
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int cmd_periodic(int argc, const char **argv, const char *prefix __used)
|
||
|
{
|
||
|
int status = 0;
|
||
|
int c, i;
|
||
|
struct perf_setup_s *p[MAX_COUNTERS];
|
||
|
struct perf_evsel *counter;
|
||
|
FILE *fp;
|
||
|
int nr_counters = 0;
|
||
|
|
||
|
evsel_list = perf_evlist__new(NULL, NULL);
|
||
|
if (evsel_list == NULL)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
argc = parse_options(argc, argv, options, periodic_usage,
|
||
|
PARSE_OPT_STOP_AT_NON_OPTION);
|
||
|
|
||
|
if (system_wide)
|
||
|
cpumask = -1;
|
||
|
|
||
|
/*
|
||
|
* The r & w option redirects stdout to a newly created pipe and
|
||
|
* waits for input on the read pipe before continuing
|
||
|
*/
|
||
|
fd_in = stdin;
|
||
|
fd_out = stdout;
|
||
|
if (strncmp(rfifo_name, DEFAULT_FIFO_NAME,
|
||
|
strnlen(rfifo_name, MAX_NAMELEN))) {
|
||
|
fp = fopen(rfifo_name, "r");
|
||
|
if (fp != NULL) {
|
||
|
fclose(fp);
|
||
|
remove(rfifo_name);
|
||
|
}
|
||
|
if (mkfifo(rfifo_name, 0777) == -1) {
|
||
|
error("Could not open read fifo\n");
|
||
|
do_cleanup();
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
tReadFifo = fopen(rfifo_name, "r+");
|
||
|
if (tReadFifo == 0) {
|
||
|
do_cleanup();
|
||
|
error("Could not open read fifo file\n");
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
use_fifo = true;
|
||
|
}
|
||
|
if (strncmp(wfifo_name, DEFAULT_FIFO_NAME,
|
||
|
strnlen(wfifo_name, MAX_NAMELEN))) {
|
||
|
fp = fopen(wfifo_name, "r");
|
||
|
if (fp != NULL) {
|
||
|
fclose(fp);
|
||
|
remove(wfifo_name);
|
||
|
}
|
||
|
if (mkfifo(wfifo_name, 0777) == -1) {
|
||
|
do_cleanup();
|
||
|
error("Could not open write fifo\n");
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
fd_out = fopen(wfifo_name, "w+");
|
||
|
if (fd_out == 0) {
|
||
|
do_cleanup();
|
||
|
error("Could not open write fifo file\n");
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
tWriteFifo = fd_out;
|
||
|
}
|
||
|
|
||
|
math_enabled = (operations[0] != 'n');
|
||
|
|
||
|
/*
|
||
|
* If we don't ignore SIG_PIPE then when the other side
|
||
|
* of a pipe closes we shutdown too...
|
||
|
*/
|
||
|
signal(SIGPIPE, SIG_IGN);
|
||
|
signal(SIGINT, sig_do_cleanup);
|
||
|
signal(SIGQUIT, sig_do_cleanup);
|
||
|
signal(SIGKILL, sig_do_cleanup);
|
||
|
signal(SIGTERM, sig_do_cleanup);
|
||
|
|
||
|
i = 0;
|
||
|
list_for_each_entry(counter, &evsel_list->entries, node) {
|
||
|
p[i] = malloc(sizeof(struct perf_setup_s));
|
||
|
if (p[i] == NULL) {
|
||
|
error("Error allocating perf_setup_s\n");
|
||
|
do_cleanup();
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
bzero(p[i], sizeof(struct perf_setup_s));
|
||
|
perf_setup_init(p[i]);
|
||
|
p[i]->attr = &(counter->attr);
|
||
|
p[i]->event_index = counter->idx;
|
||
|
if (create_perf_counter(p[i]) < 0) {
|
||
|
do_cleanup();
|
||
|
die("Not all events could be opened.\n");
|
||
|
return PERF_PERIODIC_ERROR;
|
||
|
}
|
||
|
i++;
|
||
|
nr_counters++;
|
||
|
}
|
||
|
i = 0;
|
||
|
while (1) {
|
||
|
|
||
|
/*
|
||
|
* Wait first otherwise single sample will print w/o signal
|
||
|
* when using the -u (user signal) flag
|
||
|
*/
|
||
|
delay(ms_sleep);
|
||
|
|
||
|
/*
|
||
|
* Do the collection, read and then perform any math operations
|
||
|
*/
|
||
|
for (c = 0; c < nr_counters; c++) {
|
||
|
status = perf_setup_read(p[c]);
|
||
|
do_math_op(p[c]);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* After all collection and math, we perform one last math
|
||
|
* to allow totaling, if enabled etc, then either printout
|
||
|
* a single float value when the math is enabled or ...
|
||
|
*/
|
||
|
if (math_enabled) {
|
||
|
do_math_op(p[c]);
|
||
|
if (is_ratio)
|
||
|
fprintf(fd_out, "%#f\n", accum*100);
|
||
|
else
|
||
|
fprintf(fd_out, "%#f\n", accum);
|
||
|
} else {
|
||
|
/*
|
||
|
* ... print out one integer value for each counter
|
||
|
*/
|
||
|
for (c = 0; c < nr_counters; c++)
|
||
|
status = perf_setup_show(p[c]);
|
||
|
fprintf(fd_out, "\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Did the user give us an iteration count?
|
||
|
*/
|
||
|
if ((ncounts != 0) && (++i >= ncounts))
|
||
|
break;
|
||
|
reset_math();
|
||
|
fflush(fd_out); /* make sure data is flushed out the pipe*/
|
||
|
}
|
||
|
|
||
|
do_cleanup();
|
||
|
|
||
|
return status;
|
||
|
}
|