diff --git a/tools/perf/Makefile b/tools/perf/Makefile index c16836611d2..225b292caae 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -366,6 +366,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-list.o BUILTIN_OBJS += $(OUTPUT)builtin-record.o BUILTIN_OBJS += $(OUTPUT)builtin-report.o BUILTIN_OBJS += $(OUTPUT)builtin-stat.o +BUILTIN_OBJS += $(OUTPUT)builtin-periodic.o BUILTIN_OBJS += $(OUTPUT)builtin-timechart.o BUILTIN_OBJS += $(OUTPUT)builtin-top.o BUILTIN_OBJS += $(OUTPUT)builtin-script.o diff --git a/tools/perf/builtin-periodic.c b/tools/perf/builtin-periodic.c new file mode 100644 index 00000000000..70a0e2beb3e --- /dev/null +++ b/tools/perf/builtin-periodic.c @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 +#include + +#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++) { + 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 []", + NULL +}; + +static const struct option options[] = { + OPT_CALLBACK('e', "event", &evsel_list, "event", + "event selector. use 'perf list' to list available events", + parse_events), + 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; +} diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index 4702e2443a8..889ff68a826 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -26,6 +26,7 @@ extern int cmd_list(int argc, const char **argv, const char *prefix); extern int cmd_record(int argc, const char **argv, const char *prefix); extern int cmd_report(int argc, const char **argv, const char *prefix); extern int cmd_stat(int argc, const char **argv, const char *prefix); +extern int cmd_periodic(int argc, const char **argv, const char *prefix); extern int cmd_timechart(int argc, const char **argv, const char *prefix); extern int cmd_top(int argc, const char **argv, const char *prefix); extern int cmd_script(int argc, const char **argv, const char *prefix); diff --git a/tools/perf/perf.c b/tools/perf/perf.c index ec635b7cc8e..32f66733ac9 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -320,6 +320,7 @@ static void handle_internal_command(int argc, const char **argv) { "report", cmd_report, 0 }, { "bench", cmd_bench, 0 }, { "stat", cmd_stat, 0 }, + { "periodic", cmd_periodic, 0 }, { "timechart", cmd_timechart, 0 }, { "top", cmd_top, 0 }, { "annotate", cmd_annotate, 0 },