[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