382 lines
10 KiB
C
382 lines
10 KiB
C
|
#ifndef JEMALLOC_INTERNAL_EMITTER_H
|
||
|
#define JEMALLOC_INTERNAL_EMITTER_H
|
||
|
|
||
|
typedef enum emitter_output_e emitter_output_t;
|
||
|
enum emitter_output_e {
|
||
|
emitter_output_json,
|
||
|
emitter_output_table
|
||
|
};
|
||
|
|
||
|
typedef enum emitter_justify_e emitter_justify_t;
|
||
|
enum emitter_justify_e {
|
||
|
emitter_justify_left,
|
||
|
emitter_justify_right,
|
||
|
/* Not for users; just to pass to internal functions. */
|
||
|
emitter_justify_none
|
||
|
};
|
||
|
|
||
|
typedef enum emitter_type_e emitter_type_t;
|
||
|
enum emitter_type_e {
|
||
|
emitter_type_bool,
|
||
|
emitter_type_int,
|
||
|
emitter_type_unsigned,
|
||
|
emitter_type_uint32,
|
||
|
emitter_type_uint64,
|
||
|
emitter_type_size,
|
||
|
emitter_type_ssize,
|
||
|
emitter_type_string,
|
||
|
};
|
||
|
|
||
|
typedef struct emitter_s emitter_t;
|
||
|
struct emitter_s {
|
||
|
emitter_output_t output;
|
||
|
/* The output information. */
|
||
|
void (*write_cb)(void *, const char *);
|
||
|
void *cbopaque;
|
||
|
int nesting_depth;
|
||
|
/* True if we've already emitted a value at the given depth. */
|
||
|
bool item_at_depth;
|
||
|
};
|
||
|
|
||
|
static inline void
|
||
|
emitter_init(emitter_t *emitter, emitter_output_t emitter_output,
|
||
|
void (*write_cb)(void *, const char *), void *cbopaque) {
|
||
|
emitter->output = emitter_output;
|
||
|
emitter->write_cb = write_cb;
|
||
|
emitter->cbopaque = cbopaque;
|
||
|
emitter->item_at_depth = false;
|
||
|
emitter->nesting_depth = 0;
|
||
|
}
|
||
|
|
||
|
/* Internal convenience function. Write to the emitter the given string. */
|
||
|
JEMALLOC_FORMAT_PRINTF(2, 3)
|
||
|
static inline void
|
||
|
emitter_printf(emitter_t *emitter, const char *format, ...) {
|
||
|
va_list ap;
|
||
|
|
||
|
va_start(ap, format);
|
||
|
malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
/* Write to the emitter the given string, but only in table mode. */
|
||
|
JEMALLOC_FORMAT_PRINTF(2, 3)
|
||
|
static inline void
|
||
|
emitter_table_printf(emitter_t *emitter, const char *format, ...) {
|
||
|
if (emitter->output == emitter_output_table) {
|
||
|
va_list ap;
|
||
|
va_start(ap, format);
|
||
|
malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_gen_fmt(char *out_fmt, size_t out_size, const char *fmt_specifier,
|
||
|
emitter_justify_t justify, int width) {
|
||
|
size_t written;
|
||
|
if (justify == emitter_justify_none) {
|
||
|
written = malloc_snprintf(out_fmt, out_size,
|
||
|
"%%%s", fmt_specifier);
|
||
|
} else if (justify == emitter_justify_left) {
|
||
|
written = malloc_snprintf(out_fmt, out_size,
|
||
|
"%%-%d%s", width, fmt_specifier);
|
||
|
} else {
|
||
|
written = malloc_snprintf(out_fmt, out_size,
|
||
|
"%%%d%s", width, fmt_specifier);
|
||
|
}
|
||
|
/* Only happens in case of bad format string, which *we* choose. */
|
||
|
assert(written < out_size);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Internal. Emit the given value type in the relevant encoding (so that the
|
||
|
* bool true gets mapped to json "true", but the string "true" gets mapped to
|
||
|
* json "\"true\"", for instance.
|
||
|
*
|
||
|
* Width is ignored if justify is emitter_justify_none.
|
||
|
*/
|
||
|
static inline void
|
||
|
emitter_print_value(emitter_t *emitter, emitter_justify_t justify, int width,
|
||
|
emitter_type_t value_type, const void *value) {
|
||
|
size_t str_written;
|
||
|
#define BUF_SIZE 256
|
||
|
#define FMT_SIZE 10
|
||
|
/*
|
||
|
* We dynamically generate a format string to emit, to let us use the
|
||
|
* snprintf machinery. This is kinda hacky, but gets the job done
|
||
|
* quickly without having to think about the various snprintf edge
|
||
|
* cases.
|
||
|
*/
|
||
|
char fmt[FMT_SIZE];
|
||
|
char buf[BUF_SIZE];
|
||
|
|
||
|
#define EMIT_SIMPLE(type, format) \
|
||
|
emitter_gen_fmt(fmt, FMT_SIZE, format, justify, width); \
|
||
|
emitter_printf(emitter, fmt, *(const type *)value); \
|
||
|
|
||
|
switch (value_type) {
|
||
|
case emitter_type_bool:
|
||
|
emitter_gen_fmt(fmt, FMT_SIZE, "s", justify, width);
|
||
|
emitter_printf(emitter, fmt, *(const bool *)value ?
|
||
|
"true" : "false");
|
||
|
break;
|
||
|
case emitter_type_int:
|
||
|
EMIT_SIMPLE(int, "d")
|
||
|
break;
|
||
|
case emitter_type_unsigned:
|
||
|
EMIT_SIMPLE(unsigned, "u")
|
||
|
break;
|
||
|
case emitter_type_ssize:
|
||
|
EMIT_SIMPLE(ssize_t, "zd")
|
||
|
break;
|
||
|
case emitter_type_size:
|
||
|
EMIT_SIMPLE(size_t, "zu")
|
||
|
break;
|
||
|
case emitter_type_string:
|
||
|
str_written = malloc_snprintf(buf, BUF_SIZE, "\"%s\"",
|
||
|
*(const char *const *)value);
|
||
|
/*
|
||
|
* We control the strings we output; we shouldn't get anything
|
||
|
* anywhere near the fmt size.
|
||
|
*/
|
||
|
assert(str_written < BUF_SIZE);
|
||
|
|
||
|
/*
|
||
|
* We don't support justified quoted string primitive values for
|
||
|
* now. Fortunately, we don't want to emit them.
|
||
|
*/
|
||
|
|
||
|
emitter_gen_fmt(fmt, FMT_SIZE, "s", justify, width);
|
||
|
emitter_printf(emitter, fmt, buf);
|
||
|
break;
|
||
|
case emitter_type_uint32:
|
||
|
EMIT_SIMPLE(uint32_t, FMTu32)
|
||
|
break;
|
||
|
case emitter_type_uint64:
|
||
|
EMIT_SIMPLE(uint64_t, FMTu64)
|
||
|
break;
|
||
|
default:
|
||
|
unreachable();
|
||
|
}
|
||
|
#undef BUF_SIZE
|
||
|
#undef FMT_SIZE
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Internal functions. In json mode, tracks nesting state. */
|
||
|
static inline void
|
||
|
emitter_nest_inc(emitter_t *emitter) {
|
||
|
emitter->nesting_depth++;
|
||
|
emitter->item_at_depth = false;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_nest_dec(emitter_t *emitter) {
|
||
|
emitter->nesting_depth--;
|
||
|
emitter->item_at_depth = true;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_indent(emitter_t *emitter) {
|
||
|
int amount = emitter->nesting_depth;
|
||
|
const char *indent_str;
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
indent_str = "\t";
|
||
|
} else {
|
||
|
amount *= 2;
|
||
|
indent_str = " ";
|
||
|
}
|
||
|
for (int i = 0; i < amount; i++) {
|
||
|
emitter_printf(emitter, "%s", indent_str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_key_prefix(emitter_t *emitter) {
|
||
|
emitter_printf(emitter, "%s\n", emitter->item_at_depth ? "," : "");
|
||
|
emitter_indent(emitter);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_begin(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth == 0);
|
||
|
emitter_printf(emitter, "{");
|
||
|
emitter_nest_inc(emitter);
|
||
|
} else {
|
||
|
// tabular init
|
||
|
emitter_printf(emitter, "");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth == 1);
|
||
|
emitter_nest_dec(emitter);
|
||
|
emitter_printf(emitter, "\n}\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Note emits a different kv pair as well, but only in table mode. Omits the
|
||
|
* note if table_note_key is NULL.
|
||
|
*/
|
||
|
static inline void
|
||
|
emitter_kv_note(emitter_t *emitter, const char *json_key, const char *table_key,
|
||
|
emitter_type_t value_type, const void *value,
|
||
|
const char *table_note_key, emitter_type_t table_note_value_type,
|
||
|
const void *table_note_value) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth > 0);
|
||
|
emitter_json_key_prefix(emitter);
|
||
|
emitter_printf(emitter, "\"%s\": ", json_key);
|
||
|
emitter_print_value(emitter, emitter_justify_none, -1,
|
||
|
value_type, value);
|
||
|
} else {
|
||
|
emitter_indent(emitter);
|
||
|
emitter_printf(emitter, "%s: ", table_key);
|
||
|
emitter_print_value(emitter, emitter_justify_none, -1,
|
||
|
value_type, value);
|
||
|
if (table_note_key != NULL) {
|
||
|
emitter_printf(emitter, " (%s: ", table_note_key);
|
||
|
emitter_print_value(emitter, emitter_justify_none, -1,
|
||
|
table_note_value_type, table_note_value);
|
||
|
emitter_printf(emitter, ")");
|
||
|
}
|
||
|
emitter_printf(emitter, "\n");
|
||
|
}
|
||
|
emitter->item_at_depth = true;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_kv(emitter_t *emitter, const char *json_key, const char *table_key,
|
||
|
emitter_type_t value_type, const void *value) {
|
||
|
emitter_kv_note(emitter, json_key, table_key, value_type, value, NULL,
|
||
|
emitter_type_bool, NULL);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_kv(emitter_t *emitter, const char *json_key,
|
||
|
emitter_type_t value_type, const void *value) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_kv(emitter, json_key, NULL, value_type, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_table_kv(emitter_t *emitter, const char *table_key,
|
||
|
emitter_type_t value_type, const void *value) {
|
||
|
if (emitter->output == emitter_output_table) {
|
||
|
emitter_kv(emitter, NULL, table_key, value_type, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_dict_begin(emitter_t *emitter, const char *json_key,
|
||
|
const char *table_header) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_json_key_prefix(emitter);
|
||
|
emitter_printf(emitter, "\"%s\": {", json_key);
|
||
|
emitter_nest_inc(emitter);
|
||
|
} else {
|
||
|
emitter_indent(emitter);
|
||
|
emitter_printf(emitter, "%s\n", table_header);
|
||
|
emitter_nest_inc(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_dict_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth > 0);
|
||
|
emitter_nest_dec(emitter);
|
||
|
emitter_printf(emitter, "\n");
|
||
|
emitter_indent(emitter);
|
||
|
emitter_printf(emitter, "}");
|
||
|
} else {
|
||
|
emitter_nest_dec(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_dict_begin(emitter_t *emitter, const char *json_key) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_dict_begin(emitter, json_key, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_dict_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_dict_end(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_table_dict_begin(emitter_t *emitter, const char *table_key) {
|
||
|
if (emitter->output == emitter_output_table) {
|
||
|
emitter_dict_begin(emitter, NULL, table_key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_table_dict_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_table) {
|
||
|
emitter_dict_end(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_arr_begin(emitter_t *emitter, const char *json_key) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_json_key_prefix(emitter);
|
||
|
emitter_printf(emitter, "\"%s\": [", json_key);
|
||
|
emitter_nest_inc(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_arr_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth > 0);
|
||
|
emitter_nest_dec(emitter);
|
||
|
emitter_printf(emitter, "\n");
|
||
|
emitter_indent(emitter);
|
||
|
emitter_printf(emitter, "]");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_arr_obj_begin(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_json_key_prefix(emitter);
|
||
|
emitter_printf(emitter, "{");
|
||
|
emitter_nest_inc(emitter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_arr_obj_end(emitter_t *emitter) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
assert(emitter->nesting_depth > 0);
|
||
|
emitter_nest_dec(emitter);
|
||
|
emitter_printf(emitter, "\n");
|
||
|
emitter_indent(emitter);
|
||
|
emitter_printf(emitter, "}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
emitter_json_arr_value(emitter_t *emitter, emitter_type_t value_type,
|
||
|
const void *value) {
|
||
|
if (emitter->output == emitter_output_json) {
|
||
|
emitter_json_key_prefix(emitter);
|
||
|
emitter_print_value(emitter, emitter_justify_none, -1,
|
||
|
value_type, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif /* JEMALLOC_INTERNAL_EMITTER_H */
|