From 399c9dc9351449488e423262905076c08911799d Mon Sep 17 00:00:00 2001 From: Simon Sundberg Date: Tue, 22 Jun 2021 15:02:34 +0200 Subject: [PATCH] pping: Refactor json code and format Use a JSON-writer library from iproute instead of complicated printf statement. Also output timestamp, rtt and min_rtt as integers in nanoseconds, rather than floats in seconds. Signed-off-by: Simon Sundberg --- lib/util/json_writer.c | 392 +++++++++++++++++++++++++++++++++++++++++ lib/util/json_writer.h | 79 +++++++++ lib/util/util.mk | 2 +- pping/Makefile | 2 +- pping/pping.c | 52 +++--- 5 files changed, 497 insertions(+), 30 deletions(-) create mode 100644 lib/util/json_writer.c create mode 100644 lib/util/json_writer.h diff --git a/lib/util/json_writer.c b/lib/util/json_writer.c new file mode 100644 index 0000000..6117a69 --- /dev/null +++ b/lib/util/json_writer.c @@ -0,0 +1,392 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ + +/* Copied from iproute2/lib/json_writer.c */ + +/* + * Simple streaming JSON writer + * + * This takes care of the annoying bits of JSON syntax like the commas + * after elements + * + * Authors: Stephen Hemminger + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "json_writer.h" + +struct json_writer { + FILE *out; /* output file */ + unsigned depth; /* nesting */ + bool pretty; /* optional whitepace */ + char sep; /* either nul or comma */ +}; + +/* indentation for pretty print */ +static void jsonw_indent(json_writer_t *self) +{ + unsigned i; + for (i = 0; i < self->depth; ++i) + fputs(" ", self->out); +} + +/* end current line and indent if pretty printing */ +static void jsonw_eol(json_writer_t *self) +{ + if (!self->pretty) + return; + + putc('\n', self->out); + jsonw_indent(self); +} + +/* If current object is not empty print a comma */ +static void jsonw_eor(json_writer_t *self) +{ + if (self->sep != '\0') + putc(self->sep, self->out); + self->sep = ','; +} + + +/* Output JSON encoded string */ +/* Handles C escapes, does not do Unicode */ +static void jsonw_puts(json_writer_t *self, const char *str) +{ + putc('"', self->out); + for (; *str; ++str) + switch (*str) { + case '\t': + fputs("\\t", self->out); + break; + case '\n': + fputs("\\n", self->out); + break; + case '\r': + fputs("\\r", self->out); + break; + case '\f': + fputs("\\f", self->out); + break; + case '\b': + fputs("\\b", self->out); + break; + case '\\': + fputs("\\\\", self->out); + break; + case '"': + fputs("\\\"", self->out); + break; + case '\'': + fputs("\\\'", self->out); + break; + default: + putc(*str, self->out); + } + putc('"', self->out); +} + +/* Create a new JSON stream */ +json_writer_t *jsonw_new(FILE *f) +{ + json_writer_t *self = malloc(sizeof(*self)); + if (self) { + self->out = f; + self->depth = 0; + self->pretty = false; + self->sep = '\0'; + } + return self; +} + +/* End output to JSON stream */ +void jsonw_destroy(json_writer_t **self_p) +{ + json_writer_t *self = *self_p; + + assert(self->depth == 0); + fputs("\n", self->out); + fflush(self->out); + free(self); + *self_p = NULL; +} + +void jsonw_pretty(json_writer_t *self, bool on) +{ + self->pretty = on; +} + +/* Basic blocks */ +static void jsonw_begin(json_writer_t *self, int c) +{ + jsonw_eor(self); + putc(c, self->out); + ++self->depth; + self->sep = '\0'; +} + +static void jsonw_end(json_writer_t *self, int c) +{ + assert(self->depth > 0); + + --self->depth; + if (self->sep != '\0') + jsonw_eol(self); + putc(c, self->out); + self->sep = ','; +} + + +/* Add a JSON property name */ +void jsonw_name(json_writer_t *self, const char *name) +{ + jsonw_eor(self); + jsonw_eol(self); + self->sep = '\0'; + jsonw_puts(self, name); + putc(':', self->out); + if (self->pretty) + putc(' ', self->out); +} + +__attribute__((format(printf, 2, 3))) +void jsonw_printf(json_writer_t *self, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + jsonw_eor(self); + vfprintf(self->out, fmt, ap); + va_end(ap); +} + +/* Collections */ +void jsonw_start_object(json_writer_t *self) +{ + jsonw_begin(self, '{'); +} + +void jsonw_end_object(json_writer_t *self) +{ + jsonw_end(self, '}'); +} + +void jsonw_start_array(json_writer_t *self) +{ + jsonw_begin(self, '['); + if (self->pretty) + putc(' ', self->out); +} + +void jsonw_end_array(json_writer_t *self) +{ + if (self->pretty && self->sep) + putc(' ', self->out); + self->sep = '\0'; + jsonw_end(self, ']'); +} + +/* JSON value types */ +void jsonw_string(json_writer_t *self, const char *value) +{ + jsonw_eor(self); + jsonw_puts(self, value); +} + +void jsonw_bool(json_writer_t *self, bool val) +{ + jsonw_printf(self, "%s", val ? "true" : "false"); +} + +void jsonw_null(json_writer_t *self) +{ + jsonw_printf(self, "null"); +} + +void jsonw_float(json_writer_t *self, double num) +{ + jsonw_printf(self, "%g", num); +} + +void jsonw_hhu(json_writer_t *self, unsigned char num) +{ + jsonw_printf(self, "%hhu", num); +} + +void jsonw_hu(json_writer_t *self, unsigned short num) +{ + jsonw_printf(self, "%hu", num); +} + +void jsonw_uint(json_writer_t *self, unsigned int num) +{ + jsonw_printf(self, "%u", num); +} + +void jsonw_u64(json_writer_t *self, uint64_t num) +{ + jsonw_printf(self, "%"PRIu64, num); +} + +void jsonw_xint(json_writer_t *self, uint64_t num) +{ + jsonw_printf(self, "%"PRIx64, num); +} + +void jsonw_luint(json_writer_t *self, unsigned long num) +{ + jsonw_printf(self, "%lu", num); +} + +void jsonw_lluint(json_writer_t *self, unsigned long long num) +{ + jsonw_printf(self, "%llu", num); +} + +void jsonw_int(json_writer_t *self, int num) +{ + jsonw_printf(self, "%d", num); +} + +void jsonw_s64(json_writer_t *self, int64_t num) +{ + jsonw_printf(self, "%"PRId64, num); +} + +/* Basic name/value objects */ +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val) +{ + jsonw_name(self, prop); + jsonw_string(self, val); +} + +void jsonw_bool_field(json_writer_t *self, const char *prop, bool val) +{ + jsonw_name(self, prop); + jsonw_bool(self, val); +} + +void jsonw_float_field(json_writer_t *self, const char *prop, double val) +{ + jsonw_name(self, prop); + jsonw_float(self, val); +} + +void jsonw_uint_field(json_writer_t *self, const char *prop, unsigned int num) +{ + jsonw_name(self, prop); + jsonw_uint(self, num); +} + +void jsonw_u64_field(json_writer_t *self, const char *prop, uint64_t num) +{ + jsonw_name(self, prop); + jsonw_u64(self, num); +} + +void jsonw_xint_field(json_writer_t *self, const char *prop, uint64_t num) +{ + jsonw_name(self, prop); + jsonw_xint(self, num); +} + +void jsonw_hhu_field(json_writer_t *self, const char *prop, unsigned char num) +{ + jsonw_name(self, prop); + jsonw_hhu(self, num); +} + +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num) +{ + jsonw_name(self, prop); + jsonw_hu(self, num); +} + +void jsonw_luint_field(json_writer_t *self, + const char *prop, + unsigned long num) +{ + jsonw_name(self, prop); + jsonw_luint(self, num); +} + +void jsonw_lluint_field(json_writer_t *self, + const char *prop, + unsigned long long num) +{ + jsonw_name(self, prop); + jsonw_lluint(self, num); +} + +void jsonw_int_field(json_writer_t *self, const char *prop, int num) +{ + jsonw_name(self, prop); + jsonw_int(self, num); +} + +void jsonw_s64_field(json_writer_t *self, const char *prop, int64_t num) +{ + jsonw_name(self, prop); + jsonw_s64(self, num); +} + +void jsonw_null_field(json_writer_t *self, const char *prop) +{ + jsonw_name(self, prop); + jsonw_null(self); +} + +#ifdef TEST +int main(int argc, char **argv) +{ + json_writer_t *wr = jsonw_new(stdout); + + jsonw_start_object(wr); + jsonw_pretty(wr, true); + jsonw_name(wr, "Vyatta"); + jsonw_start_object(wr); + jsonw_string_field(wr, "url", "http://vyatta.com"); + jsonw_uint_field(wr, "downloads", 2000000ul); + jsonw_float_field(wr, "stock", 8.16); + + jsonw_name(wr, "ARGV"); + jsonw_start_array(wr); + while (--argc) + jsonw_string(wr, *++argv); + jsonw_end_array(wr); + + jsonw_name(wr, "empty"); + jsonw_start_array(wr); + jsonw_end_array(wr); + + jsonw_name(wr, "NIL"); + jsonw_start_object(wr); + jsonw_end_object(wr); + + jsonw_null_field(wr, "my_null"); + + jsonw_name(wr, "special chars"); + jsonw_start_array(wr); + jsonw_string_field(wr, "slash", "/"); + jsonw_string_field(wr, "newline", "\n"); + jsonw_string_field(wr, "tab", "\t"); + jsonw_string_field(wr, "ff", "\f"); + jsonw_string_field(wr, "quote", "\""); + jsonw_string_field(wr, "tick", "\'"); + jsonw_string_field(wr, "backslash", "\\"); + jsonw_end_array(wr); + + jsonw_end_object(wr); + + jsonw_end_object(wr); + jsonw_destroy(&wr); + return 0; +} + +#endif diff --git a/lib/util/json_writer.h b/lib/util/json_writer.h new file mode 100644 index 0000000..3e1a660 --- /dev/null +++ b/lib/util/json_writer.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ + +/* Copied from iproute2/include/json_writer.h */ + +/* + * Simple streaming JSON writer + * + * This takes care of the annoying bits of JSON syntax like the commas + * after elements + * + * Authors: Stephen Hemminger + */ + +#ifndef _JSON_WRITER_H_ +#define _JSON_WRITER_H_ + +#include +#include + +/* Opaque class structure */ +typedef struct json_writer json_writer_t; + +/* Create a new JSON stream */ +json_writer_t *jsonw_new(FILE *f); +/* End output to JSON stream */ +void jsonw_destroy(json_writer_t **self_p); + +/* Cause output to have pretty whitespace */ +void jsonw_pretty(json_writer_t *self, bool on); + +/* Add property name */ +void jsonw_name(json_writer_t *self, const char *name); + +/* Add value */ +__attribute__((format(printf, 2, 3))) +void jsonw_printf(json_writer_t *self, const char *fmt, ...); +void jsonw_string(json_writer_t *self, const char *value); +void jsonw_bool(json_writer_t *self, bool value); +void jsonw_float(json_writer_t *self, double number); +void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num); +void jsonw_uint(json_writer_t *self, unsigned int number); +void jsonw_u64(json_writer_t *self, uint64_t number); +void jsonw_xint(json_writer_t *self, uint64_t number); +void jsonw_hhu(json_writer_t *self, unsigned char num); +void jsonw_hu(json_writer_t *self, unsigned short number); +void jsonw_int(json_writer_t *self, int number); +void jsonw_s64(json_writer_t *self, int64_t number); +void jsonw_null(json_writer_t *self); +void jsonw_luint(json_writer_t *self, unsigned long num); +void jsonw_lluint(json_writer_t *self, unsigned long long num); + +/* Useful Combinations of name and value */ +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val); +void jsonw_bool_field(json_writer_t *self, const char *prop, bool value); +void jsonw_float_field(json_writer_t *self, const char *prop, double num); +void jsonw_uint_field(json_writer_t *self, const char *prop, unsigned int num); +void jsonw_u64_field(json_writer_t *self, const char *prop, uint64_t num); +void jsonw_xint_field(json_writer_t *self, const char *prop, uint64_t num); +void jsonw_hhu_field(json_writer_t *self, const char *prop, unsigned char num); +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num); +void jsonw_int_field(json_writer_t *self, const char *prop, int num); +void jsonw_s64_field(json_writer_t *self, const char *prop, int64_t num); +void jsonw_null_field(json_writer_t *self, const char *prop); +void jsonw_luint_field(json_writer_t *self, const char *prop, + unsigned long num); +void jsonw_lluint_field(json_writer_t *self, const char *prop, + unsigned long long num); + +/* Collections */ +void jsonw_start_object(json_writer_t *self); +void jsonw_end_object(json_writer_t *self); + +void jsonw_start_array(json_writer_t *self); +void jsonw_end_array(json_writer_t *self); + +/* Override default exception handling */ +typedef void (jsonw_err_handler_fn)(const char *); + +#endif /* _JSON_WRITER_H_ */ diff --git a/lib/util/util.mk b/lib/util/util.mk index 3a6ba97..b7f160a 100644 --- a/lib/util/util.mk +++ b/lib/util/util.mk @@ -1,2 +1,2 @@ # list of objects in this directory -UTIL_OBJS := +UTIL_OBJS := json_writer.o diff --git a/pping/Makefile b/pping/Makefile index 0c68add..48a8030 100644 --- a/pping/Makefile +++ b/pping/Makefile @@ -3,7 +3,7 @@ USER_TARGETS := pping BPF_TARGETS := pping_kern -LDFLAGS += -pthread +LDLIBS += -pthread EXTRA_DEPS += pping.h LIB_DIR = ../lib diff --git a/pping/pping.c b/pping/pping.c index 41c9db2..9aa9e58 100644 --- a/pping/pping.c +++ b/pping/pping.c @@ -23,6 +23,7 @@ static const char *__doc__ = #include #include +#include "json_writer.h" #include "pping.h" //common structs for user-space and BPF parts #define NS_PER_SECOND 1000000000UL @@ -83,7 +84,7 @@ struct pping_config { }; static volatile int keep_running = 1; -static bool json_started = false; +static json_writer_t *json_ctx = NULL; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, @@ -565,32 +566,25 @@ static void print_rtt_event_json(void *ctx, int cpu, void *data, format_ip_address(saddr, sizeof(saddr), e->flow.ipv, &e->flow.saddr.ip); format_ip_address(daddr, sizeof(daddr), e->flow.ipv, &e->flow.daddr.ip); - if (json_started) { - printf(","); - } else { - printf("["); - json_started = true; + if (!json_ctx) { + json_ctx = jsonw_new(stdout); + jsonw_start_array(json_ctx); } - printf("\n{\"timestamp\":%llu.%09llu, \"rtt\":%llu.%09llu, " - "\"min_rtt\":%llu.%09llu, \"src_ip\":\"%s\", \"src_port\":%d, " - "\"dest_ip\":\"%s\", \"dest_port\":%d, \"protocol\":\"%s\", " - "\"sent_pkts\":%llu, \"sent_bytes\":%llu, \"rec_pkts\":%llu, " - "\"rec_bytes\":%llu }", - time / NS_PER_SECOND, time % NS_PER_SECOND, - e->rtt / NS_PER_SECOND, e->rtt % NS_PER_SECOND, - e->min_rtt / NS_PER_SECOND, e->min_rtt % NS_PER_SECOND, saddr, - ntohs(e->flow.saddr.port), daddr, ntohs(e->flow.daddr.port), - proto_to_str(e->flow.proto), e->sent_pkts, e->sent_bytes, - e->rec_pkts, e->rec_bytes); -} - -static void end_json_output(void) -{ - if (json_started) - printf("\n]\n"); - else - printf("[]\n"); + jsonw_start_object(json_ctx); + jsonw_u64_field(json_ctx, "timestamp", time); + jsonw_u64_field(json_ctx, "rtt", e->rtt); + jsonw_u64_field(json_ctx, "min_rtt", e->min_rtt); + jsonw_string_field(json_ctx, "src_ip", saddr); + jsonw_hu_field(json_ctx, "src_port", ntohs(e->flow.saddr.port)); + jsonw_string_field(json_ctx, "dest_ip", daddr); + jsonw_hu_field(json_ctx, "dest_port", ntohs(e->flow.daddr.port)); + jsonw_string_field(json_ctx, "protocol", proto_to_str(e->flow.proto)); + jsonw_u64_field(json_ctx, "sent_packets", e->sent_pkts); + jsonw_u64_field(json_ctx, "sent_bytes", e->sent_bytes); + jsonw_u64_field(json_ctx, "rec_packets", e->rec_pkts); + jsonw_u64_field(json_ctx, "rec_bytes", e->rec_bytes); + jsonw_end_object(json_ctx); } static void handle_missed_rtt_event(void *ctx, int cpu, __u64 lost_cnt) @@ -797,9 +791,6 @@ int main(int argc, char *argv[]) cleanup: perf_buffer__free(pb); - if (config.json_format) - end_json_output(); - if (xdp_attached) { err = xdp_detach(config.ifindex, config.xdp_flags); if (err) @@ -825,5 +816,10 @@ cleanup: config.pin_dir, strerror(-err)); } + if (config.json_format && json_ctx) { + jsonw_end_array(json_ctx); + jsonw_destroy(&json_ctx); + } + return err != 0; }