[GSOC 2014][PATCH 1/4] JSON: Add -j option to print traced results in JSON format

Zhu YangMin zym0017d at gmail.com
Wed Jun 18 16:29:47 UTC 2014


The JSON format is used to make it more easy to parse the
trace results by other program. It is enabled by -j option.

* Makefile.am: Add json.c to compile list.
* strace.1: Add documents for -j option.
* strace.c(init, usage, tprintf, tprints, tvprintf): Add support
for the '-j' option. Modify tprintf() to use tvprintf(new function)
to use the hook framework, Modify tprints() to use the hook framework.
* defs.h: Add declarations for the hook framework of JSON output.
* json.c: New file. Implementation of the hook framework.
---
 Makefile.am |   1 +
 defs.h      |  99 +++++++++++++++++++++++++
 json.c      | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 strace.1    |   3 +
 strace.c    |  66 +++++++++++++----
 5 files changed, 391 insertions(+), 13 deletions(-)
 create mode 100644 json.c

diff --git a/Makefile.am b/Makefile.am
index be05946..4ce8d44 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -29,6 +29,7 @@ strace_SOURCES =	\
 	ioctl.c		\
 	ioprio.c	\
 	ipc.c		\
+	json.c		\
 	kexec.c		\
 	keyctl.c	\
 	ldt.c		\
diff --git a/defs.h b/defs.h
index 1a3b483..dc94cb4 100644
--- a/defs.h
+++ b/defs.h
@@ -829,3 +829,102 @@ extern unsigned num_quals;
 /* Only ensures that sysent[scno] isn't out of range */
 #define SCNO_IN_RANGE(scno) \
 	((unsigned long)(scno) < nsyscalls)
+
+
+/*
+ * Support JSON output enabled by option '-j'
+ */
+
+typedef enum {
+	EVENT_NONE = 0,
+
+	EVENT_SEPARATE,
+	EVENT_NEWLINE,
+
+	EVENT_CALL_BEGIN,
+	EVENT_NAME,
+	EVENT_CALL_END,
+
+	EVENT_ARGS_BEGIN,
+	EVENT_ARG,
+	EVENT_ARGS_END,
+
+	EVENT_RET,
+	EVENT_DESC,
+	EVENT_DESC_LONG,
+	EVENT_AUXSTR,
+	EVENT_ERRNO,
+	EVENT_ERROR,
+
+	EVENT_SIGNAL_BEGIN,
+	EVENT_SIGNAL_END,
+	EVENT_SIGCODE,
+
+	BEF_SEPRATOR = 0x10000,
+	SEPARATOR    = 0x20000,
+	NEWLINE      = 0x40000,
+} json_type_event;
+
+/*
+ * Control the behaviour of tprints() and tprintf()
+ */
+typedef enum {
+	SKIP,          // skip the output, do nothing
+	NORMAL,        // output directly, the same to the normal tprints()/tprintf()
+	META_ALL,      // extract all outputs of a snippet code to a single meta buffer
+	META_SINGLE,   // extract the meta parameter based on the format string of a tprintf() call
+} json_type_output;
+
+/*
+ * JSON_META_SIZE is largest number of metas
+ * we could handle in one json_event() call.
+ *
+ * So We must make sure the first argument of json_event() is <= 10 !
+ */
+#define JSON_META_SIZE (10)
+typedef struct {
+	int top;
+	size_t size[JSON_META_SIZE];
+	FILE *hook[JSON_META_SIZE];
+	char *data[JSON_META_SIZE];
+} json_type_meta;
+
+
+// defined in strace.c
+extern int jflag;
+
+// defined in json.c
+extern json_type_output json_output_control;
+extern json_type_meta	json_hook_metas;
+
+#define JSON_META_BEGIN                                 \
+        do {                                            \
+        json_type_output __json_last_output;            \
+        if (jflag == 1) {                               \
+            __json_last_output = json_output_control;   \
+            json_output_control = META_ALL;             \
+        }                                               \
+        do {
+
+#define JSON_META_END                                   \
+        } while(0);                                     \
+        if (jflag == 1)                                 \
+            json_output_control = __json_last_output;   \
+            json_hook_metas.top++;                      \
+    } while(0)
+
+#define JSON_META(wrapped_call)  \
+        JSON_META_BEGIN          \
+        wrapped_call ;           \
+        JSON_META_END
+
+#define JSON_BEGIN_META_MODE do { json_hook_metas.top = 0; json_output_control = META_SINGLE; } while(0)
+
+
+void json_meta_vprintf(const char *fmt, va_list args);
+void json_event(int counts, ...);
+int json_init(void);
+
+/*
+ * End of Support JSON output enabled by option '-j'
+ */
\ No newline at end of file
diff --git a/json.c b/json.c
new file mode 100644
index 0000000..c7c908b
--- /dev/null
+++ b/json.c
@@ -0,0 +1,235 @@
+#include "defs.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+/*
+ * We also modified tprintf()/tprints() to implement this hook framework
+*/
+
+json_type_meta json_hook_metas;
+json_type_output json_output_control = SKIP;
+
+static void flush_buffer(void)
+{
+	int i;
+	for (i = 0; i < json_hook_metas.top; i++) {
+		fflush(json_hook_metas.hook[i]);
+	}
+	return;
+}
+static void clear_buffer(void)
+{
+	int i;
+	for (i = 0; i < json_hook_metas.top; i++) {
+		rewind(json_hook_metas.hook[i]);
+		fflush(json_hook_metas.hook[i]);
+	}
+	json_hook_metas.top = 0;
+	return;
+}
+
+/*
+ * We need to escape the illegal characters in JSON string
+ */
+static void escape_output(const char *str)
+{
+	if (str == NULL) return;
+	while (*str != '\0') {
+		switch (*str) {
+			case '\"':
+				tprints("\\\"");
+				break;
+			case '\\':
+				tprints("\\\\");
+				break;
+
+			default:
+				tprintf("%c", *str);
+		}
+		str++;
+	}
+}
+
+/*
+ * The main event handler for JSON output
+ */
+#define JSON_META(i) (json_hook_metas.data[i])
+static int json_event_handler(int counts, va_list ap)
+{
+	int i = 0;
+	int meta_i = 0;
+
+	while(i++ < counts) {
+		json_type_event event = va_arg(ap, json_type_event);
+
+		const char *param1 = NULL;
+		const char *key = NULL;
+
+		char has_seperator = 0, has_newline = 0;
+		if (event & SEPARATOR) has_seperator = 1;
+		if (event & NEWLINE) has_newline = 1;
+
+		if (event & BEF_SEPRATOR) tprints(", ");
+
+		event &= 0x0000FFFF;
+		switch (event) {
+			case EVENT_NONE:
+				break;
+
+			case EVENT_ARG:
+				param1 = JSON_META(meta_i++);
+				if (param1 == NULL) continue;
+
+				tprints("\"");
+				escape_output(param1);
+				tprints("\"");
+				break;
+
+			case EVENT_SEPARATE:
+				tprints(", ");
+				break;
+			case EVENT_NEWLINE:
+				tprints("\n");
+				break;
+
+			case EVENT_CALL_BEGIN:
+				tprints("{ \"type\" : \"syscall\"");
+				break;
+			case EVENT_CALL_END:
+				tprints(" }");
+				break;
+
+			case EVENT_ARGS_BEGIN:
+				tprints("\"args\" : [");
+				break;
+			case EVENT_ARGS_END:
+				tprints("]");
+				break;
+
+			case EVENT_SIGNAL_BEGIN:
+				tprints("{ \"type\" : \"+++\"");
+				break;
+			case EVENT_SIGNAL_END:
+				tprints("}");
+				break;
+
+			case EVENT_NAME:
+				key = "name";
+				break;
+			case EVENT_RET:
+				key = "ret";
+				break;
+			case EVENT_ERROR:
+				key = "error";
+				break;
+			case EVENT_DESC:
+				key = "desc";
+				break;
+			case EVENT_DESC_LONG:
+				key = "desc_long";
+				break;
+			case EVENT_ERRNO:
+				key = "errno";
+				break;
+			case EVENT_AUXSTR:
+				key = "auxstr";
+				break;
+			case EVENT_SIGCODE:
+				key = "sigcode";
+				break;
+
+			default:
+				break;
+		}
+		if (key) {
+			param1 = JSON_META(meta_i++);
+			tprintf("\"%s\" : \"%s\"", key, param1);
+		}
+		if (has_seperator == 1)
+			tprints(", ");
+		if (has_newline == 1)
+			tprints("\n");
+	}
+	return 0;
+}
+
+/*
+ * User call this function to trigger the correspond event for output
+ * later maybe changed to support multiple event handler
+ */
+void json_event(int counts, ...)
+{
+	if (jflag == 0) return;
+
+	flush_buffer();
+	json_output_control = NORMAL;
+
+	va_list ap;
+	va_start(ap, counts);
+	json_event_handler(counts, ap);
+	va_end(ap);
+
+	clear_buffer();
+	json_output_control = SKIP;
+	return;
+}
+
+/*
+ * Extract the % specifier in a format string
+ * buf should be long enough to contain the whole specifier string
+ *
+ * NOTE: this function could not handle all the situation in a format string.
+ * For example: The "%.*s" which consumes 2 arguments and we always use only one
+ * argument a time when handling each event.
+ * But in strace it seems enough now.
+ *
+ * Return the next start position or NULL(when reach the '\0' of a string)
+*/
+static const char* json_meta_build_fmt(const char *fmt, char *buf)
+{
+	while (*fmt != '%' && *fmt != '\0') ++fmt;
+	if (*fmt == '\0') return NULL;
+
+	const char *end = fmt + 1;
+	end += strcspn(end, "diuoxXfFeEgGaAcspn%");
+
+	if (*end == '\0')
+		return NULL;
+	else if (*end == '%')
+		return fmt + 1; // two %% don't consume any parameter
+
+	++end;
+	while (fmt != end) *buf++ = *fmt++;
+	*buf = '\0';
+	return fmt;
+}
+
+void json_meta_vprintf(const char *fmt, va_list args)
+{
+	char fmtbuf[101];
+	while ((fmt = json_meta_build_fmt(fmt, fmtbuf)) != NULL) {
+		FILE *meta_file = json_hook_metas.hook[json_hook_metas.top++];
+		vfprintf(meta_file, fmtbuf, args);
+	}
+	return;
+}
+
+/*
+ * Init the internal buffer
+ * Return 1 when success, return -1 when failed.
+ */
+int json_init(void)
+{
+	json_hook_metas.top = 0;
+	int i;
+	for (i = 0; i < JSON_META_SIZE; i++) {
+		json_hook_metas.hook[i] = open_memstream(&json_hook_metas.data[i], &json_hook_metas.size[i]);
+		if (json_hook_metas.hook[i] == NULL) {
+			return -1;
+		}
+	}
+	return 1;
+}
\ No newline at end of file
diff --git a/strace.1 b/strace.1
index c310d65..901712e 100644
--- a/strace.1
+++ b/strace.1
@@ -267,6 +267,9 @@ Print the help summary.
 .B \-i
 Print the instruction pointer at the time of the system call.
 .TP
+.B \-j
+Print the trace results in JSON format.
+.TP
 .B \-k
 Print the execution stack trace of the traced processes after each system call.
 .TP
diff --git a/strace.c b/strace.c
index 46c9d63..800f229 100644
--- a/strace.c
+++ b/strace.c
@@ -89,6 +89,8 @@ static unsigned int syscall_trap_sig = SIGTRAP;
 static unsigned int tflag = 0;
 static bool rflag = 0;
 static bool print_pid_pfx = 0;
+/* Support option '-j' to enable JSON output */
+int jflag = 0;
 
 /* -I n */
 enum {
@@ -210,6 +212,7 @@ usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\n\
 -D -- run tracer process as a detached grandchild, not as parent\n\
 -f -- follow forks, -ff -- with output into separate files\n\
 -i -- print instruction pointer at time of syscall\n\
+-j -- print the trace results in JSON format\n\
 -q -- suppress messages about attaching, detaching, etc.\n\
 -r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs\n\
 -T -- print time spent in each syscall\n\
@@ -534,12 +537,9 @@ strace_popen(const char *command)
 	return fp;
 }
 
-void
-tprintf(const char *fmt, ...)
+static void
+tvprintf(const char *fmt, va_list args)
 {
-	va_list args;
-
-	va_start(args, fmt);
 	if (current_tcp) {
 		int n = strace_vfprintf(current_tcp->outf, fmt, args);
 		if (n < 0) {
@@ -548,6 +548,32 @@ tprintf(const char *fmt, ...)
 		} else
 			current_tcp->curcol += n;
 	}
+}
+
+void
+tprintf(const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	if (jflag == 0)
+		tvprintf(fmt, args);
+	else {
+		switch (json_output_control) {
+			case META_ALL:
+				vfprintf(json_hook_metas.hook[json_hook_metas.top], fmt, args);
+				break;
+			case META_SINGLE:
+				json_meta_vprintf(fmt, args);
+				break;
+			case NORMAL:
+				tvprintf(fmt, args);
+				break;
+			case SKIP:
+			default:
+				break;
+		}
+	}
 	va_end(args);
 }
 
@@ -558,15 +584,23 @@ tprintf(const char *fmt, ...)
 void
 tprints(const char *str)
 {
-	if (current_tcp) {
-		int n = fputs_unlocked(str, current_tcp->outf);
-		if (n >= 0) {
-			current_tcp->curcol += strlen(str);
-			return;
+	if (jflag == 0 || (jflag == 1 && json_output_control == NORMAL)) {
+		if (current_tcp) {
+			int n = fputs_unlocked(str, current_tcp->outf);
+			if (n >= 0) {
+				current_tcp->curcol += strlen(str);
+				return;
+			}
+			if (current_tcp->outf != stderr)
+				perror_msg("%s", outfname);
 		}
-		if (current_tcp->outf != stderr)
-			perror_msg("%s", outfname);
 	}
+	else if (jflag == 1 && json_output_control == META_ALL) {
+		fprintf(json_hook_metas.hook[json_hook_metas.top], "%s", str);
+		return;
+	}
+	else if (jflag == 1 && (json_output_control == SKIP || json_output_control == META_SINGLE))
+		return;
 }
 
 void
@@ -1672,7 +1706,7 @@ init(int argc, char *argv[])
 #endif
 	qualify("signal=all");
 	while ((c = getopt(argc, argv,
-		"+b:cCdfFhiqrtTvVwxyz"
+		"+b:cCdfFhijqrtTvVwxyz"
 #ifdef USE_LIBUNWIND
 		"k"
 #endif
@@ -1715,6 +1749,12 @@ init(int argc, char *argv[])
 		case 'i':
 			iflag = 1;
 			break;
+		case 'j':
+			if (json_init() == -1)
+				fprintf(stderr, "json_init() failed, back to normal output.\n");
+			else
+				jflag = 1;
+			break;
 		case 'q':
 			qflag++;
 			break;
-- 
1.9.1





More information about the Strace-devel mailing list