ktrace: Add code to create a synthetic event

Add the "create synthetic" command and the completion to create a
synthetic event.

Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
diff --git a/src/create.c b/src/create.c
index d1ba688..38cb0b6 100644
--- a/src/create.c
+++ b/src/create.c
@@ -264,6 +264,181 @@
 	return 0;
 }
 
+static int usage_synthetic(struct ccli *ccli)
+{
+	ccli_printf(ccli, "# usage: create synthetic name system/event.field1[,field2,..] system2/event2.field1[,field2,..] field1=systemX/eventX.field[ field2=systemX/eventX.field ...]\n"
+		    "#  Where the synthetic event is created when the fields of system/event\n"
+		    "#  match system/event2 fields. Then add the fields of the synthetic event\n"
+		    "#  on how they will map to the other fields.\n#\n"
+		    "#  A synthetic event field may also equal timestamps:\n"
+		    "#    start=system/event.TIMESTAMP\n#\n"
+		    "#  Or even a delta:\n"
+		    "#    delta=system/event.TIMESTAMP-system2/event2.TIMESTAMP\n"
+		);
+	return 0;
+}
+
+int add_synth_field(struct ccli *ccli, struct tracefs_synth *synth,
+		    char *start_system, char *start_event,
+		    char *end_system, char *end_event, char *field)
+{
+	char *system;
+	char *event;
+	char *name;
+	char *val;
+	char *sav;
+
+	name = strtok_r(field, "=", &sav);
+	system = strtok_r(NULL, "/", &sav);
+	if (!system) {
+		ccli_printf(ccli, "# Missing '=system/event.field' in '%s'\n", name);
+		return -1;
+	}
+
+	event = strtok_r(NULL, ".", &sav);
+	if (!event) {
+		ccli_printf(ccli, "# Missing /event.field in '%s'\n", system);
+		return -1;
+	}
+
+	val = strtok_r(NULL, ".", &sav);
+	if (!system) {
+		ccli_printf(ccli, "# Missing .field in '%s' \n", event);
+		return -1;
+	}
+
+	if ((strcmp(system, start_system) == 0) &&
+	    (strcmp(event, start_event) == 0)) {
+		return tracefs_synth_add_start_field(synth, val, name);
+	}
+
+	if ((strcmp(system, end_system) == 0) &&
+	    (strcmp(event, end_event) == 0)) {
+		return tracefs_synth_add_end_field(synth, val, name);
+	}
+
+	ccli_printf(ccli, "# %s/%s does not match either start or end events\n",
+		    system, event);
+	return -1;
+}
+
+static int create_synthetic(struct ccli *ccli, void *data,
+			    int argc, char **argv)
+{
+	struct tep_handle *tep = data;
+	struct tracefs_synth *synth;
+	struct trace_seq seq;
+	char *system1;
+	char *event1;
+	char *system2;
+	char *event2;
+	char **fields1 = NULL;
+	char **fields2 = NULL;
+	char **svals = NULL;
+	char **tmp;
+	char *field;
+	char *name;
+	char *sav;
+	int ret;
+	int f = 0;
+	int i;
+
+	if (argc < 4)
+		return usage_synthetic(ccli);
+
+	name = argv[0];
+
+	system1 = strtok_r(argv[1], "/", &sav);
+	event1 = strtok_r(NULL, ".", &sav);
+	if (!system1 || !event1) {
+		ccli_printf(ccli, "# No slash found in first event %s\n", system1);
+		return 0;
+	}
+
+	field = strtok_r(NULL, ",", &sav);
+	if (!field) {
+		ccli_printf(ccli, "# No field found for %s/%s\n",
+			    system1, event1);
+		return 0;
+	}
+
+	do {
+		tmp = realloc(fields1, sizeof(*tmp) * (f + 1));
+		if (!tmp)
+			return 0;
+		tmp[f++] = field;
+		fields1 = tmp;
+		field = strtok_r(NULL, ",", &sav);
+	} while (field);
+
+	/* Get the second event */
+	system2 = strtok_r(argv[2], "/", &sav);
+	event2 = strtok_r(NULL, ".", &sav);
+	if (!system2 || !event2) {
+		ccli_printf(ccli, "# No slash found in second event %s\n", system2);
+		goto free;
+	}
+
+	fields2 = calloc(f, sizeof(*fields2));
+	if (!fields2)
+		goto free;
+
+	for (i = 0; i < f; i++) {
+		field = strtok_r(NULL, ",", &sav);
+		if (!field)
+			goto free_fields;
+		fields2[i] = field;
+	}
+	field = strtok_r(NULL, ",", &sav);
+	if (field)
+		goto free_fields;
+
+	svals = calloc(argc - 3, sizeof(*svals));
+	if (!svals)
+		goto free;
+
+	synth = tracefs_synth_alloc(tep, name, system1, event1,
+				    system2, event2, fields1[0],
+				    fields2[0], NULL);
+	if (!synth) {
+		ccli_printf(ccli, "# Failed to create synthetic event\n");
+		goto free;
+	}
+
+	for (i = 1; i < f; i++) {
+		ret = tracefs_synth_add_match_field(synth, fields1[i],
+						    fields2[i], NULL);
+		if (ret < 0) {
+			ccli_printf(ccli, "# Failed to match %s with %s\n",
+				    fields1[i], fields2[i]);
+			goto free_synth;
+		}
+	}
+
+	for (i = 3; i < argc; i++) {
+		ret = add_synth_field(ccli, synth, system1, event1,
+				      system2, event2, argv[i]);
+		if (ret < 0)
+			goto free_synth;
+	}
+
+	trace_seq_init(&seq);
+	tracefs_synth_echo_cmd(&seq, synth);
+	trace_seq_terminate(&seq);
+	ccli_printf(ccli, "%s\n", seq.buffer);
+	trace_seq_destroy(&seq);
+free_synth:
+	tracefs_synth_free(synth);
+free:
+	free(fields1);
+	free(fields2);
+	free(svals);
+	return 0;
+free_fields:
+	ccli_printf(ccli, "# End event must have same number of fields as start event\n");
+	goto free;
+}
+
 int cmd_create(struct ccli *ccli, const char *command, const char *line,
 	       void *data, int argc, char **argv)
 {
@@ -278,6 +453,9 @@
 	if (strcmp(argv[1], "eprobe") == 0)
 		return create_eprobe(ccli, data, argc - 2, argv + 2);
 
+	if (strcmp(argv[1], "synthetic") == 0)
+		return create_synthetic(ccli, data, argc - 2, argv + 2);
+
 	return 0;
 }
 
@@ -478,24 +656,126 @@
 	return 0;
 }
 
+static int event_completion(struct ccli *ccli, struct tep_handle *tep,
+			    char ***list, char *match, char append)
+{
+	char **systems;
+	char **events;
+	char **words;
+	char *system;
+	char *event;
+	char *p;
+	int i;
+
+	p = strchr(match, '/');
+	if (p) {
+		system = strdup(match);
+		if (!system)
+			return 0;
+		system[p - match] = '\0';
+		events = tracefs_system_events(NULL, system);
+		if (!events) {
+			free(system);
+			return 0;
+		}
+		words = calloc(tracefs_list_size(events), sizeof(char *));
+		i = 0;
+		if (words) {
+			for (; events[i]; i++) {
+				asprintf(&event, "%s/%s", system, events[i]);
+				if (append && event && !strcmp(event, match)) {
+					asprintf(words + i, "%s.", event);
+					free(event);
+				} else
+					words[i] = event;
+			}
+		}
+		tracefs_list_free(events);
+	} else {
+		systems = tracefs_event_systems(NULL);
+		if (!systems)
+			return 0;
+		words = calloc(tracefs_list_size(systems), sizeof(char *));
+		i = 0;
+		if (words) {
+			for (; systems[i]; i++)
+				words[i] = strdup(systems[i]);
+		}
+		tracefs_list_free(systems);
+		/* Use '/' as a delim */
+		match[strlen(match)] = '/';
+	}
+	*list = words;
+	return i;
+}
+
+static struct tep_event *find_event(struct tep_handle *tep, char *ename)
+{
+	struct tep_event *event;
+	char *system;
+	char *name;
+	char *sav;
+
+	system = strtok_r(ename, "/", &sav);
+	name = strtok_r(NULL, "/", &sav);
+	if (!system || !name)
+		return NULL;
+
+	event = tep_find_event_by_name(tep, system, name);
+	/* put back ename to how we found it */
+	sav = system + strlen(system);
+	*sav = '/';
+	return event;
+}
+
+static int field_completion(struct tep_event *event, char ***list, char *prefix)
+{
+	struct tep_format_field **common_fields;
+	struct tep_format_field **fields;
+	char **words;
+	char **tmp;
+	int i, x;
+
+	common_fields = tep_event_common_fields(event);
+	fields = tep_event_fields(event);
+	words = NULL;
+	x = 0;
+	for (i = 0; common_fields && common_fields[i]; i++) {
+		tmp = realloc(words, sizeof(char *) * (x + 2));
+		if (!tmp) {
+			ccli_argv_free(words);
+			return 0;
+		}
+		words = tmp;
+		asprintf(&words[x++], "%s%s", prefix, common_fields[i]->name);
+		words[x] = NULL;
+	}
+	for (i = 0; fields && fields[i]; i++) {
+		tmp = realloc(words, sizeof(char *) * (x + 2));
+		if (!tmp) {
+			ccli_argv_free(words);
+			return 0;
+		}
+		words = tmp;
+		asprintf(&words[x++], "%s%s", prefix, fields[i]->name);
+		words[x] = NULL;
+	}
+	free(common_fields);
+	free(fields);
+	*list = words;
+	return x;
+}
+
 static int eprobe_completion(struct ccli *ccli, void *data,
 			     int argc, char **argv,
 			     char ***list, int word, char *match)
 {
 	struct tep_handle *tep = data;
-	struct tep_format_field **common_fields;
-	struct tep_format_field **fields;
 	struct tep_event *event;
-	char **systems;
-	char **events;
-	char **words;
-	char **tmp;
-	char *system;
-	char *name;
-	char *sav;
+	char *prefix;
 	char *p, *m;
 	int len;
-	int i, x;
+	int ret;
 
 	switch (word) {
 	case 0:
@@ -503,50 +783,11 @@
 		ccli_line_refresh(ccli);
 		return 0;
 	case 1:
-		p = strchr(match, '/');
-		if (p) {
-			system = strdup(match);
-			if (!system)
-				return 0;
-			system[p - match] = '\0';
-			events = tracefs_system_events(NULL, system);
-			if (!events) {
-				free(system);
-				return 0;
-			}
-			words = calloc(tracefs_list_size(events), sizeof(char *));
-			i = 0;
-			if (words) {
-				for (; events[i]; i++)
-					asprintf(words + i, "%s/%s",
-						 system, events[i]);
-			}
-			tracefs_list_free(events);
-		} else {
-			systems = tracefs_event_systems(NULL);
-			if (!systems)
-				return 0;
-			words = calloc(tracefs_list_size(systems), sizeof(char *));
-			i = 0;
-			if (words) {
-				for (; systems[i]; i++)
-					words[i] = strdup(systems[i]);
-			}
-			tracefs_list_free(systems);
-			/* Use '/' as a delim */
-			match[strlen(match)] = '/';
-		}
-		*list = words;
-		return i;
+		return event_completion(ccli, tep, list, match, 0);
 	default:
-		system = strtok_r(argv[1], "/", &sav);
-		name = strtok_r(NULL, "/", &sav);
-		if (!system || !name)
-			return 0;
-		event = tep_find_event_by_name(tep, system, name);
+		event = find_event(tep, argv[1]);
 		if (!event) {
-			ccli_printf(ccli, "\n# Event %s/%s not found\n",
-				    system, name);
+			ccli_printf(ccli, "\n# Event %s not found\n", argv[1]);
 			return 0;
 		}
 		p = strchr(match, '=');
@@ -566,39 +807,14 @@
 
 		switch (*m) {
 		default:
-			common_fields = tep_event_common_fields(event);
-			fields = tep_event_fields(event);
-			words = NULL;
-			x = 0;
-			for (i = 0; common_fields && common_fields[i]; i++) {
-				tmp = realloc(words, sizeof(char *) * (x + 2));
-				if (!tmp) {
-					ccli_argv_free(words);
-					return 0;
-				}
-				words = tmp;
-				asprintf(&words[x++], "%.*s=%s",
-					 len, match,
-					 common_fields[i]->name);
-				words[x] = NULL;
-			}
-			for (i = 0; fields && fields[i]; i++) {
-				tmp = realloc(words, sizeof(char *) * (x + 2));
-				if (!tmp) {
-					ccli_argv_free(words);
-					return 0;
-				}
-				words = tmp;
-				asprintf(&words[x++], "%.*s=%s",
-					 len, match,
-					 fields[i]->name);
-				words[x] = NULL;
-			}
-			free(common_fields);
-			free(fields);
-			*list = words;
-			match[strlen(match)] = CCLI_NOSPACE;
-			return x;
+			asprintf(&prefix, "%.*s=", len, match);
+			if (!prefix)
+				return 0;
+			ret = field_completion(event, list, prefix);
+			free(prefix);
+			if (ret > 0)
+				match[strlen(match)] = CCLI_NOSPACE;
+			return ret;
 		case '.':
 			return type_completion(list, match, len);
 		case '-':
@@ -609,6 +825,164 @@
 	return 0;
 }
 
+static int append_event_field(struct ccli *ccli, struct tep_handle *tep, char ***list,
+			      char *ename, char *match)
+{
+	struct tep_event *event;
+	char *p;
+
+	/* Find the end of the event */
+	for (p = ename; *p && *p != '.'; p++)
+		;
+	if (!*p) // Should not happen!
+		return 0;
+	*p = '\0';
+
+	event = find_event(tep, ename);
+	if (!event) {
+		ccli_printf(ccli, "\n# Event %s not found\n", ename);
+		return 0;
+	}
+	*p = '.';
+	/* Find the start of the last field in the comma separated list */
+	for (p += strlen(p) - 1; *p != '.' && *p != ',' ; p--)
+		;
+	p++;
+	*p = '\0';
+
+	return field_completion(event, list, ename);
+}
+
+static int append_field_ts(struct ccli *ccli, struct tep_handle *tep, char ***list,
+			      char *ename)
+{
+	struct tep_event *event;
+	char **words;
+	char *e;
+	char *p;
+	int ret;
+
+	/* Find start of the event */
+	e = strchr(ename, '=');
+	if (!e) // should not happen
+		return 0;
+	e++;
+
+	/* Find the end of the event */
+	for (p = e; *p && *p != '.'; p++)
+		;
+	if (!*p) // Should not happen!
+		return 0;
+	*p = '\0';
+
+	event = find_event(tep, e);
+	if (!event) {
+		ccli_printf(ccli, "\n# Event %s not found\n", e);
+		ccli_line_refresh(ccli);
+		return 0;
+	}
+
+	*p = '.';
+	p[1] = '\0';
+
+	ret = field_completion(event, list, ename);
+
+	if (ret > 0) {
+		words = realloc(*list, sizeof(*words) * (ret + 1));
+		if (words) {
+			asprintf(words + ret, "%sTIMESTAMP", ename);
+			*list = words;
+			ret++;
+		}
+	}
+	return ret;
+}
+
+static int synthetic_completion(struct ccli *ccli, void *data,
+				int argc, char **argv,
+				char ***list, int word, char *match)
+{
+	struct tep_handle *tep = data;
+	struct tep_event *event1, *event2;
+	char **words;
+	char *p;
+	int len;
+	int ret;
+
+	switch (word) {
+	case 0:
+		ccli_printf(ccli, "\n# Name the synthetic event\n");
+		ccli_line_refresh(ccli);
+		return 0;
+	case 1:
+	case 2:
+		len = strlen(match);
+
+		/* See if this already an event */
+		if (strchr(match, '.'))
+			return append_event_field(ccli, tep, list, argv[word], match);
+
+		ret = event_completion(ccli, tep, list, match, '.');
+		/* Do not add a space if this is an event */
+		if (ret > 0 && !match[len])
+			match[len] = CCLI_NOSPACE;
+		return ret;
+	default:
+		p = strchr(match, '=');
+		if (!p) {
+			ccli_printf(ccli, "\n# field=system/event.[field|TIMESTAMP]\n");
+			ccli_line_refresh(ccli);
+			return 0;
+		}
+		if (strchr(p, '.'))
+			return append_field_ts(ccli, tep, list, argv[word]);
+
+		argv[word][p - match] = '\0';
+
+		/* Can only be one of the previous events */
+		p = strchr(argv[1], '.');
+		if (!p) {
+			ccli_printf(ccli, "# %s needs a field\n", argv[1]);
+			ccli_line_refresh(ccli);
+			return 0;
+		}
+		*p = '\0';
+		event1 = find_event(tep, argv[1]);
+		if (!event1) {
+			ccli_printf(ccli, "\n# Event %s not found\n", argv[1]);
+			ccli_line_refresh(ccli);
+			return 0;
+		}
+		p = strchr(argv[2], '.');
+		if (!p) {
+			ccli_printf(ccli, "\n# %s needs a field\n", argv[2]);
+			ccli_line_refresh(ccli);
+			return 0;
+		}
+		*p = '\0';
+		event2 = find_event(tep, argv[2]);
+		if (!event2) {
+			ccli_printf(ccli, "\n# Event %s not found\n", argv[2]);
+			ccli_line_refresh(ccli);
+			return 0;
+		}
+
+		words = calloc(2, sizeof(*words));
+		if (!words)
+			return 0;
+		asprintf(words, "%s=%s/%s", argv[word],
+			 event1->system, event1->name);
+		asprintf(words + 1, "%s=%s/%s", argv[word],
+			 event2->system, event2->name);
+		*list = words;
+		match[strlen(match)] = '.';
+
+		return 2;
+	}
+	printf("\neprobe word=%d match=%s\n", word, match);
+	return 0;
+}
+
 int create_completion(struct ccli *ccli, const char *command,
 		      const char *line, int word,
 		      char *match, char ***list, void *data)
@@ -642,6 +1016,10 @@
 		ret = eprobe_completion(ccli, data, argc - 2, argv + 2,
 					list, word - 2, match);
 
+	if (strcmp(argv[1], "synthetic") == 0)
+		ret = synthetic_completion(ccli, data, argc - 2, argv + 2,
+					   list, word - 2, match);
+
 	ccli_argv_free(argv);
 
 	return ret;