/*
 * 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;
}
