trace-cmd: Add trace-cmd report --event option

Add an --event option for trace-cmd report that lets the user pass in
a regular expression that will only list the events in the file that
match the regex given.

 trace-cmd report --event ftrace

Will list all ftrace events.

 trace-cmd report --event sys:read

Will list all the events where the system matches "sys" and the event
name matches "read".

Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
diff --git a/Documentation/trace-cmd-report.1.txt b/Documentation/trace-cmd-report.1.txt
index 428ae04..f88a7ab 100644
--- a/Documentation/trace-cmd-report.1.txt
+++ b/Documentation/trace-cmd-report.1.txt
@@ -41,6 +41,21 @@
 *--events*::
     This will list the event formats that are stored in the trace.dat file.
 
+*--event* regex::
+    This will print events that match the given regex. If a colon is specified,
+    then the characters before the colon will be used to match the system and
+    the characters after the colon will match the event.
+
+     trace-cmd report --event sys:read
+
+    The above will only match events where the system name contains "sys"
+    and the event name contains "read".
+
+     trace-cmd report --event read
+
+    The above will match all events that contain "read" in its name. Also it
+    may list all events of a system that contains "read" as well.
+
 *--check-events*::
     This will parse the event format strings that are stored in the trace.dat
     file and return whether the formats can be parsed correctly. It will load
diff --git a/trace-cmd.h b/trace-cmd.h
index 03f9abd..92b4ff2 100644
--- a/trace-cmd.h
+++ b/trace-cmd.h
@@ -116,7 +116,7 @@
 struct tracecmd_input *tracecmd_buffer_instance_handle(struct tracecmd_input *handle, int indx);
 int tracecmd_is_buffer_instance(struct tracecmd_input *handle);
 
-void tracecmd_print_events(struct tracecmd_input *handle);
+void tracecmd_print_events(struct tracecmd_input *handle, const char *regex);
 
 int tracecmd_init_data(struct tracecmd_input *handle);
 
diff --git a/trace-input.c b/trace-input.c
index 7954752..f7ac3d1 100644
--- a/trace-input.c
+++ b/trace-input.c
@@ -30,6 +30,7 @@
 #include <sys/wait.h>
 #include <sys/mman.h>
 #include <pthread.h>
+#include <regex.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <ctype.h>
@@ -348,8 +349,39 @@
 	return -1;
 }
 
+static int regex_event_buf(const char *file, int size, regex_t *epreg)
+{
+	char *buf;
+	char *line;
+	int ret;
+
+	buf = malloc(size + 1);
+	if (!buf)
+		die("malloc");
+
+	strncpy(buf, file, size);
+	buf[size] = 0;
+
+	/* get the name from the first line */
+	line = strtok(buf, "\n");
+	if (!line) {
+		warning("No newline found in '%s'", buf);
+		return 0;
+	}
+	/* skip name if it is there */
+	if (strncmp(line, "name: ", 6) == 0)
+		line += 6;
+
+	ret = regexec(epreg, line, 0, NULL, 0) == 0;
+
+	free(buf);
+
+	return ret;
+}
+
 static int read_ftrace_file(struct tracecmd_input *handle,
-			    unsigned long long size, int print)
+			    unsigned long long size,
+			    int print, regex_t *epreg)
 {
 	struct pevent *pevent = handle->pevent;
 	char *buf;
@@ -362,8 +394,9 @@
 		return -1;
 	}
 
-	if (print) {
-		printf("%.*s\n", (int)size, buf);
+	if (epreg) {
+		if (print || regex_event_buf(buf, size, epreg))
+			printf("%.*s\n", (int)size, buf);
 	} else {
 		if (pevent_parse_event(pevent, buf, size, "ftrace"))
 			pevent->parsing_failures = 1;
@@ -375,7 +408,8 @@
 
 static int read_event_file(struct tracecmd_input *handle,
 			   char *system, unsigned long long size,
-			   int print)
+			   int print, int *sys_printed,
+			   regex_t *epreg)
 {
 	struct pevent *pevent = handle->pevent;
 	char *buf;
@@ -389,8 +423,14 @@
 		return -1;
 	}
 
-	if (print) {
-		printf("%.*s\n", (int)size, buf);
+	if (epreg) {
+		if (print || regex_event_buf(buf, size, epreg)) {
+			if (!*sys_printed) {
+				printf("\nsystem: %s\n", system);
+				*sys_printed = 1;
+			}
+			printf("%.*s\n", (int)size, buf);
+		}
 	} else {
 		if (pevent_parse_event(pevent, buf, size, system))
 			pevent->parsing_failures = 1;
@@ -400,13 +440,89 @@
 	return 0;
 }
 
-static int read_ftrace_files(struct tracecmd_input *handle, int print)
+static int make_preg_files(const char *regex, regex_t *system,
+			   regex_t *event, int *unique)
+{
+	char *buf;
+	char *sstr;
+	char *estr;
+	int ret;
+
+	/* unique is set if a colon is found */
+	*unique = 0;
+
+	/* split "system:event" into "system" and "event" */
+
+	buf = strdup(regex);
+	if (!buf)
+		die("malloc");
+
+	sstr = strtok(buf, ":");
+	estr = strtok(NULL, ":");
+
+	/* If no colon is found, set event == system */
+	if (!estr)
+		estr = sstr;
+	else
+		*unique = 1;
+
+	ret = regcomp(system, sstr, REG_ICASE|REG_NOSUB);
+	if (ret) {
+		warning("Bad regular expression '%s'", sstr);
+		goto out;
+	}
+
+	ret = regcomp(event, estr, REG_ICASE|REG_NOSUB);
+	if (ret) {
+		warning("Bad regular expression '%s'", estr);
+		goto out;
+	}
+
+ out:
+	free(buf);
+	return ret;
+}
+
+static int read_ftrace_files(struct tracecmd_input *handle, const char *regex)
 {
 	unsigned long long size;
+	regex_t spreg;
+	regex_t epreg;
+	regex_t *sreg = NULL;
+	regex_t *ereg = NULL;
+	int print_all = 0;
+	int unique;
 	int count;
 	int ret;
 	int i;
 
+	if (regex) {
+		sreg = &spreg;
+		ereg = &epreg;
+		ret = make_preg_files(regex, sreg, ereg, &unique);
+		if (ret)
+			return -1;
+
+		if (regexec(sreg, "ftrace", 0, NULL, 0) == 0) {
+			/*
+			 * If the system matches a regex that did
+			 * not contain a colon, then print all events.
+			 */
+			if (!unique)
+				print_all = 1;
+		} else if (unique) {
+			/*
+			 * The user specified a unique event that did
+			 * not match the ftrace system. Don't print any
+			 * events here.
+			 */
+			regfree(sreg);
+			regfree(ereg);
+			sreg = NULL;
+			ereg = NULL;
+		}
+	}
+
 	count = read4(handle);
 	if (count < 0)
 		return -1;
@@ -415,7 +531,7 @@
 		size = read8(handle);
 		if (size < 0)
 			return -1;
-		ret = read_ftrace_file(handle, size, print);
+		ret = read_ftrace_file(handle, size, print_all, ereg);
 		if (ret < 0)
 			return -1;
 	}
@@ -423,18 +539,39 @@
 	handle->event_files_start =
 		lseek64(handle->fd, 0, SEEK_CUR);
 
+	if (sreg) {
+		regfree(sreg);
+		regfree(ereg);
+	}
+
 	return 0;
 }
 
-static int read_event_files(struct tracecmd_input *handle, int print)
+static int read_event_files(struct tracecmd_input *handle, const char *regex)
 {
 	unsigned long long size;
 	char *system;
+	regex_t spreg;
+	regex_t epreg;
+	regex_t *sreg = NULL;
+	regex_t *ereg = NULL;
+	regex_t *reg;
 	int systems;
+	int print_all;
+	int sys_printed;
 	int count;
+	int unique;
 	int ret;
 	int i,x;
 
+	if (regex) {
+		sreg = &spreg;
+		ereg = &epreg;
+		ret = make_preg_files(regex, sreg, ereg, &unique);
+		if (ret)
+			return -1;
+	}
+
 	systems = read4(handle);
 	if (systems < 0)
 		return -1;
@@ -444,8 +581,30 @@
 		if (!system)
 			return -1;
 
-		if (print)
-			printf("\nsystem: %s\n", system);
+		sys_printed = 0;
+		print_all = 0;
+		reg = ereg;
+
+		if (sreg) {
+			if (regexec(sreg, system, 0, NULL, 0) == 0) {
+				/*
+				 * If the user passed in a regex that
+				 * did not contain a colon, then we can
+				 * print all the events of this system.
+				 */
+				if (!unique)
+					print_all = 1;
+			} else if (unique) {
+				/*
+				 * The user passed in a unique event that
+				 * specified a specific system and event.
+				 * Since this system doesn't match this
+				 * event, then we don't print any events
+				 * for this system.
+				 */
+				reg = NULL;
+			}
+		}
 
 		count = read4(handle);
 		if (count < 0)
@@ -456,16 +615,28 @@
 			if (size < 0)
 				goto failed;
 
-			ret = read_event_file(handle, system, size, print);
+			ret = read_event_file(handle, system, size,
+					      print_all, &sys_printed,
+					      reg);
 			if (ret < 0)
 				goto failed;
 		}
 		free(system);
 	}
 
+	if (sreg) {
+		regfree(sreg);
+		regfree(ereg);
+	}
+
 	return 0;
 
  failed:
+	if (sreg) {
+		regfree(sreg);
+		regfree(ereg);
+	}
+
 	free(system);
 	return -1;
 }
@@ -543,11 +714,11 @@
 	if (ret < 0)
 		return -1;
 
-	ret = read_ftrace_files(handle, 0);
+	ret = read_ftrace_files(handle, NULL);
 	if (ret < 0)
 		return -1;
 
-	ret = read_event_files(handle, 0);
+	ret = read_event_files(handle, NULL);
 	if (ret < 0)
 		return -1;
 
@@ -1968,23 +2139,27 @@
 /**
  * tracecmd_print_events - print the events that are stored in trace.dat
  * @handle: input handle for the trace.dat file
+ * @regex: regex of events to print (NULL is all events)
  *
  * This is a debugging routine to print out the events that
  * are stored in a given trace.dat file.
  */
-void tracecmd_print_events(struct tracecmd_input *handle)
+void tracecmd_print_events(struct tracecmd_input *handle, const char *regex)
 {
 	int ret;
 
+	if (!regex)
+		regex = ".*";
+
 	if (!handle->ftrace_files_start) {
 		lseek64(handle->fd, handle->header_files_start, SEEK_SET);
 		read_header_files(handle);
 	}
-	ret = read_ftrace_files(handle, 1);
+	ret = read_ftrace_files(handle, regex);
 	if (ret < 0)
 		return;
 
-	read_event_files(handle, 1);
+	read_event_files(handle, regex);
 	return;
 }
 
diff --git a/trace-read.c b/trace-read.c
index 67e6e9a..41ff69d 100644
--- a/trace-read.c
+++ b/trace-read.c
@@ -1156,6 +1156,7 @@
 }
 
 enum {
+	OPT_event	= 246,
 	OPT_comm	= 247,
 	OPT_boundary	= 248,
 	OPT_stat	= 249,
@@ -1176,6 +1177,7 @@
 	struct event_str **raw_ptr = &raw_events;
 	struct event_str **nohandler_ptr = &nohandler_events;
 	const char *functions = NULL;
+	const char *print_event = NULL;
 	struct input_files *inputs;
 	struct handle_list *handles;
 	int show_stat = 0;
@@ -1211,6 +1213,7 @@
 		static struct option long_options[] = {
 			{"cpu", required_argument, NULL, OPT_cpu},
 			{"events", no_argument, NULL, OPT_events},
+			{"event", required_argument, NULL, OPT_event},
 			{"filter-test", no_argument, NULL, 'T'},
 			{"kallsyms", required_argument, NULL, OPT_kallsyms},
 			{"pid", required_argument, NULL, OPT_pid},
@@ -1312,6 +1315,9 @@
 		case OPT_events:
 			print_events = 1;
 			break;
+		case OPT_event:
+			print_event = optarg;
+			break;
 		case OPT_kallsyms:
 			functions = optarg;
 			break;
@@ -1395,7 +1401,12 @@
 		}
 
 		if (print_events) {
-			tracecmd_print_events(handle);
+			tracecmd_print_events(handle, NULL);
+			return;
+		}
+
+		if (print_event) {
+			tracecmd_print_events(handle, print_event);
 			return;
 		}