diff options
| author | chenzizhan <[email protected]> | 2023-09-15 18:14:26 +0800 |
|---|---|---|
| committer | chenzizhan <[email protected]> | 2023-10-08 17:03:28 +0800 |
| commit | 9d452b417bbe330496347ad2c066d2541921ebd4 (patch) | |
| tree | d523a9fc043c6810747f49296a8dbb961aaf2581 | |
| parent | 230821b167fda5ed30333dc9f5ca8c752e468a16 (diff) | |
stash4.3.0-4.3.4(python api)
Signed-off-by: chenzizhan <[email protected]>
| -rw-r--r-- | include/fieldstat/fieldstat.h | 9 | ||||
| -rw-r--r-- | include/fieldstat/fieldstat_exporter.h | 10 | ||||
| -rw-r--r-- | src/exporter/cjson_exporter.c | 366 | ||||
| -rw-r--r-- | src/fieldstat.c | 26 | ||||
| -rw-r--r-- | src/metrics/histogram_encoder.c | 88 | ||||
| -rw-r--r-- | src/metrics/histogram_encoder.h | 5 | ||||
| -rw-r--r-- | src/metrics/metric.c | 13 | ||||
| -rw-r--r-- | src/metrics/python_api.c | 140 | ||||
| -rw-r--r-- | src/utils/very_fast_json_writer.c | 9 | ||||
| -rw-r--r-- | src/utils/very_fast_json_writer.h | 1 | ||||
| -rw-r--r-- | test/test_exporter_json.cpp | 427 | ||||
| -rw-r--r-- | test/test_metric_histogram.cpp | 38 | ||||
| -rw-r--r-- | test/test_metric_hll.cpp | 26 | ||||
| -rw-r--r-- | test/test_register_and_reset.cpp | 10 | ||||
| -rw-r--r-- | test/utils.hpp | 1 | ||||
| -rw-r--r-- | vendors/hdr/hdr_histogram_log.c | 2 | ||||
| -rw-r--r-- | vendors/hdr/hdr_histogram_log.h | 3 |
17 files changed, 1120 insertions, 54 deletions
diff --git a/include/fieldstat/fieldstat.h b/include/fieldstat/fieldstat.h index 9f38845..c757606 100644 --- a/include/fieldstat/fieldstat.h +++ b/include/fieldstat/fieldstat.h @@ -16,15 +16,6 @@ enum metric_type METRIC_TYPE_HLL, METRIC_TYPE_HISTOGRAM, }; -//todo: -// poc -// tracing -// 性能 -// 配置 -// stream -// 怎么和C 对接 -// 类型系统? - enum fs_tag_type { diff --git a/include/fieldstat/fieldstat_exporter.h b/include/fieldstat/fieldstat_exporter.h index 83fd52f..d095dd4 100644 --- a/include/fieldstat/fieldstat_exporter.h +++ b/include/fieldstat/fieldstat_exporter.h @@ -84,6 +84,16 @@ char *fieldstat_json_exporter_export(const struct fieldstat_json_exporter *expor */ void fieldstat_json_exporter_export_array(const struct fieldstat_json_exporter *exporter, const struct timeval *timestamp, char ***output, size_t *output_size); +/* + let json exporter output delta value by the side of the original value(accumulated). If a cell / metric is new, the delta value is the same as the original value. + Outputting delta value is disabled by default. + When the exporter name or exporter global tags are changed, or the fieldstat instance is reset, or one of the cube is deleted, the delta value will be reset. (next output will be the original value) + it is recommended to call this function after fieldstat_json_exporter_set_name and fieldstat_json_exporter_set_global_tag. + Only affects the metrics of counter type. + Outputting delta value is time-consuming. +*/ +void fieldstat_json_exporter_enable_delta(struct fieldstat_json_exporter *exporter); + #ifdef __cplusplus } #endif
\ No newline at end of file diff --git a/src/exporter/cjson_exporter.c b/src/exporter/cjson_exporter.c index 45b113c..5ab1836 100644 --- a/src/exporter/cjson_exporter.c +++ b/src/exporter/cjson_exporter.c @@ -8,6 +8,7 @@ #include <stddef.h> #include "utils/very_fast_json_writer.h" +#include "uthash.h" #include "fieldstat.h" #include "fieldstat_exporter.h" @@ -28,7 +29,7 @@ {"in_pkts":123}, {"sessions":<hll blob base64-encoded string>} ] - "timestamp":123456789 + "timestamp_ms":123456789 }. { "name":"exporter_name", @@ -40,11 +41,13 @@ {"in_bytes":1}, {"in_pkts":2}, ] - "timestamp":123456789 + "timestamp_ms":123456789 } ] */ +#define DEFAULT_EXPORTER_NAME "-" +struct counter_history; struct export_kv_pair { char *key; enum fs_tag_type type; @@ -59,6 +62,7 @@ struct fieldstat_json_exporter { const struct fieldstat *instance; char *name; struct fieldstat_tag_list *global_tag_list; + struct counter_history *history; }; struct cell_iter { @@ -77,9 +81,37 @@ struct cell_iter { const struct fieldstat *instance; }; -struct json_tag_field_pair { +struct cellwise_rec_for_export { struct json_writer *cjson_tags; struct json_writer *cjson_fields; + + char **metric_name; // used for delta export + long long *metric_value; // used for delta export + size_t n_metric; +}; + +struct name_value_map { + char *name; + long long value; + UT_hash_handle hh; +}; + +struct tag_metric_map { + char *key; + struct name_value_map *value; + UT_hash_handle hh; +}; + +struct counter_history { + struct tag_metric_map *rec; + long long ts; + + unsigned long cell_version; + unsigned long *cube_version; + size_t n_cube; + + char *exporter_name; + struct fieldstat_tag_list *global_tag_list; }; void kv_pair_free(struct export_kv_pair *pair) { @@ -116,6 +148,249 @@ long long cal_ms_time(const struct timeval *ts) return time_stamp_in_ms; } +/* -------------------------------------------------------------------------- */ +/* history rec */ +/* -------------------------------------------------------------------------- */ +struct counter_history *counter_history_new() +{ + struct counter_history *history = calloc(1, sizeof(struct counter_history)); + history->exporter_name = strdup(DEFAULT_EXPORTER_NAME); + return history; +} + +void counter_history_add(struct counter_history *history, const char *json, const char *name, long long value) +{ + struct name_value_map *name_value_node = malloc(sizeof(struct name_value_map)); + name_value_node->name = strdup(name); + name_value_node->value = value; + + struct tag_metric_map *tag_node = malloc(sizeof(struct tag_metric_map)); + tag_node->key = strdup(json); + tag_node->value = NULL; + + HASH_ADD_KEYPTR(hh, history->rec, tag_node->key, strlen(tag_node->key), tag_node); + HASH_ADD_KEYPTR(hh, tag_node->value, name_value_node->name, strlen(name_value_node->name), name_value_node); +} + +struct name_value_map *counter_history_find(struct counter_history *history, const char *json, const char *name) +{ + struct tag_metric_map *tag_node; + HASH_FIND_STR(history->rec, json, tag_node); + if (tag_node == NULL) { + return NULL; + } + + struct name_value_map *name_value_node; + HASH_FIND_STR(tag_node->value, name, name_value_node); + return name_value_node; +} + +void counter_history_free(struct counter_history *history) +{ + if (history == NULL) { + return; + } + + struct tag_metric_map *tag_node, *tmp; + HASH_ITER(hh, history->rec, tag_node, tmp) { + struct name_value_map *name_value_node, *tmp2; + HASH_ITER(hh, tag_node->value, name_value_node, tmp2) { + HASH_DEL(tag_node->value, name_value_node); + free(name_value_node->name); + free(name_value_node); + } + HASH_DEL(history->rec, tag_node); + free(tag_node->key); + free(tag_node); + } + + free(history->cube_version); + + free(history->exporter_name); + + if (history->global_tag_list != NULL) { + fieldstat_tag_list_arr_free(history->global_tag_list, 1); + } + + free(history); +} + +bool fieldstat_tag_list_cmp(const struct fieldstat_tag_list *a, const struct fieldstat_tag_list *b) +{ + if (a->n_tag != b->n_tag) { + return false; + } + + for (int i = 0; i < a->n_tag; i++) { + if (strcmp(a->tag[i].key, b->tag[i].key) != 0) { + return false; + } + if (a->tag[i].type != b->tag[i].type) { + return false; + } + switch (a->tag[i].type) + { + case TAG_INTEGER: + if (a->tag[i].value_longlong != b->tag[i].value_longlong) { + return false; + } + break; + case TAG_DOUBLE: + if (a->tag[i].value_double != b->tag[i].value_double) { + return false; + } + break; + case TAG_CSTRING: + if (strcmp(a->tag[i].value_str, b->tag[i].value_str) != 0) { + return false; + } + break; + default: + assert(0); + } + } + + return true; +} + +struct fieldstat_tag_list *my_copy_fs_tag_list(const struct fieldstat_tag_list *src) +{ + struct fieldstat_tag_list *dest = malloc(sizeof(struct fieldstat_tag_list)); + dest->n_tag = src->n_tag; + dest->tag = malloc(sizeof(struct fieldstat_tag) * dest->n_tag); + for (int i = 0; i < dest->n_tag; i++) { + dest->tag[i].key = strdup(src->tag[i].key); + dest->tag[i].type = src->tag[i].type; + switch (src->tag[i].type) + { + case TAG_INTEGER: + dest->tag[i].value_longlong = src->tag[i].value_longlong; + break; + case TAG_DOUBLE: + dest->tag[i].value_double = src->tag[i].value_double; + break; + case TAG_CSTRING: + dest->tag[i].value_str = strdup(src->tag[i].value_str); + break; + default: + assert(0); + } + } + + return dest; +} + +bool counter_history_check_if_need_to_update(const struct fieldstat_json_exporter *exporter) +{ + if (exporter->history == NULL) { + return false; // delta export disabled + } + + unsigned long cur_cell_version = fieldstat_get_cell_version(exporter->instance); + const char *cur_exporter_name = exporter->name ? exporter->name : DEFAULT_EXPORTER_NAME; + const struct fieldstat_tag_list *cur_global_tag_list = exporter->global_tag_list; + + if (exporter->history->cell_version != cur_cell_version) { + return true; + } + + if (strcmp(exporter->history->exporter_name, cur_exporter_name) != 0) { + return true; + } + + if (exporter->history->global_tag_list == NULL && cur_global_tag_list != NULL) { + return true; + } + if (exporter->history->global_tag_list == NULL && cur_global_tag_list == NULL) { + return false; + } + // global tag cant be deleted, so no need to check if cur_global_tag_list == NULL && exporter->history->global_tag_list != NULL + if (fieldstat_tag_list_cmp(cur_global_tag_list, exporter->history->global_tag_list) == false) { + return true; + } + + int *cube_ids = NULL; + int n_cube = 0; + fieldstat_get_cubes(exporter->instance, &cube_ids, &n_cube); + if (n_cube != exporter->history->n_cube) { + return true; + } + for (int i = 0; i < n_cube; i++) { + unsigned long cube_version = fieldstat_get_cube_version(exporter->instance, cube_ids[i]); + if (cube_version != exporter->history->cube_version[i]) { + return true; + } + } + free(cube_ids); + + return false; +} + +void counter_history_fill_version_info(struct counter_history *history, struct fieldstat_json_exporter *exporter) +{ + unsigned long cur_cell_version = fieldstat_get_cell_version(exporter->instance); + const char *cur_exporter_name = exporter->name ? exporter->name : DEFAULT_EXPORTER_NAME; + const struct fieldstat_tag_list *cur_global_tag_list = exporter->global_tag_list; + + free(history->exporter_name); + history->exporter_name = strdup(cur_exporter_name); + if (cur_global_tag_list != NULL) { + history->global_tag_list = my_copy_fs_tag_list(cur_global_tag_list); + } + + history->cell_version = cur_cell_version; + + int *cube_ids = NULL; + int n_cube = 0; + fieldstat_get_cubes(exporter->instance, &cube_ids, &n_cube); + history->n_cube = n_cube; + history->cube_version = malloc(sizeof(unsigned long) * n_cube); + for (int i = 0; i < n_cube; i++) { + history->cube_version[i] = fieldstat_get_cube_version(exporter->instance, cube_ids[i]); + } + free(cube_ids); +} + +void fieldstat_json_exporter_update_history(struct fieldstat_json_exporter *exporter) +{ + if (counter_history_check_if_need_to_update(exporter) == false) { + return; + } + + counter_history_free(exporter->history); + exporter->history = counter_history_new(); + + counter_history_fill_version_info(exporter->history, exporter); +} + +void fieldstat_json_exporter_write_delta(struct fieldstat_json_exporter *exporter, struct cellwise_rec_for_export *tag_field_pair_arr, size_t arr_len) +{ + // for every tag_field_pair, get the tag json string + for (int i = 0; i < arr_len; i++) { + const char *tag_json = json_writer_unwrap(tag_field_pair_arr[i].cjson_tags); + for (int j = 0; j < tag_field_pair_arr[i].n_metric; j++) { + const char *metric_name = tag_field_pair_arr[i].metric_name[j]; + long long cur_value = tag_field_pair_arr[i].metric_value[j]; + struct name_value_map *node = counter_history_find(exporter->history, tag_json, metric_name); + long long last_value = node == NULL ? 0 : node->value; + long long delta = cur_value - last_value; + + if (node == NULL) { // new added cell/cube/metric + counter_history_add(exporter->history, tag_json, metric_name, cur_value); + } else { // previous one + node->value = cur_value; + } + // cell will never delete. If delete cube / cell happen, the exporter history will be reset. + + size_t delta_metric_name_len = strlen(metric_name) + 6; // 6 for "_delta" + char *delta_metric_name = malloc(delta_metric_name_len + 1); + snprintf(delta_metric_name, delta_metric_name_len + 1, "%s_delta", metric_name); + delta_metric_name[delta_metric_name_len] = '\0'; + json_writer_longlong_field(tag_field_pair_arr[i].cjson_fields, delta_metric_name, delta); + free(delta_metric_name); + } + } +} /* -------------------------------------------------------------------------- */ /* iter */ @@ -318,7 +593,7 @@ void kv_pair_free_list(struct export_kv_pair *pairs, size_t len) } // return 1 if added, 0 if not added -int cjson_map_add(struct json_tag_field_pair *tag_field_pair, const struct cell_iter *iter) +int cjson_map_add(struct cellwise_rec_for_export *tag_field_pair, const struct cell_iter *iter) { struct json_writer *writer = NULL; @@ -353,7 +628,30 @@ int cjson_map_add(struct json_tag_field_pair *tag_field_pair, const struct cell_ return 1; } -struct json_tag_field_pair *read_tag_and_field(const struct fieldstat *instance, size_t *n_pair_out) +void cjson_map_record_metrics(struct cellwise_rec_for_export *tag_field_pair, const struct cell_iter *iter) +{ + tag_field_pair->metric_name = malloc(sizeof(char *) * (iter->max_metric_id + 1)); + tag_field_pair->metric_value = malloc(sizeof(long long) * (iter->max_metric_id + 1)); + + int n_counter_metric = 0; + for (int metric_id = 0; metric_id <= iter->max_metric_id; metric_id++) { + int cube_id = iter->cube_ids[iter->curr_cube_idx]; + if (fieldstat_get_metric_type(iter->instance, cube_id, metric_id) != METRIC_TYPE_COUNTER) { + continue; + } + if (fieldstat_counter_get(iter->instance, cube_id, metric_id, iter->cell_ids[iter->curr_cell_idx]) == -1) { // no value, happens when the two cells in one cube has different metrics + continue; + } + + tag_field_pair->metric_name[n_counter_metric] = strdup(fieldstat_get_metric_name(iter->instance, cube_id, metric_id)); + tag_field_pair->metric_value[n_counter_metric] = fieldstat_counter_get(iter->instance, cube_id, metric_id, iter->cell_ids[iter->curr_cell_idx]); + + n_counter_metric++; + } + tag_field_pair->n_metric = n_counter_metric; +} + +struct cellwise_rec_for_export *read_tag_and_field(const struct fieldstat *instance, size_t *n_pair_out, bool output_delta) { // clock_t prepare_start = clock(); int *cube_id = NULL; @@ -373,13 +671,17 @@ struct json_tag_field_pair *read_tag_and_field(const struct fieldstat *instance, } struct cell_iter *iter = cell_iter_new(instance); - struct json_tag_field_pair *tag_field_pair = calloc(n_cell_total, sizeof(struct json_tag_field_pair)); + struct cellwise_rec_for_export *tag_field_pair = calloc(n_cell_total, sizeof(struct cellwise_rec_for_export)); int i = 0; while (cell_iter_next(iter)) { // next_num++; if (cjson_map_add(&tag_field_pair[i], iter) == 0) { continue; } + + if (output_delta) { + cjson_map_record_metrics(&tag_field_pair[i], iter); + } i++; } @@ -389,7 +691,7 @@ struct json_tag_field_pair *read_tag_and_field(const struct fieldstat *instance, return tag_field_pair; } -void fieldstat_json_exporter_write_global_tags(const struct fieldstat_json_exporter *exporter, struct json_tag_field_pair *tag_field_pair_arr, size_t arr_len) +void fieldstat_json_exporter_write_global_tags(const struct fieldstat_json_exporter *exporter, struct cellwise_rec_for_export *tag_field_pair_arr, size_t arr_len) { if (exporter->global_tag_list == NULL) { return; @@ -457,7 +759,7 @@ int add_object_to_json_array(char **buf, int *buf_len, int start, const char *st "in_latency":<blob of histogram> "client_ip_sketch":<blob of hyperloglog> } - "timestamp":<timestamp long> + "timestamp_ms":<timestamp long> } */ /* @@ -465,10 +767,15 @@ int add_object_to_json_array(char **buf, int *buf_len, int start, const char *st */ void fieldstat_json_exporter_export_array(const struct fieldstat_json_exporter *exporter, const struct timeval *timestamp, char ***output, size_t *output_size) { + long long timestamp_ms = cal_ms_time(timestamp); + if (exporter->history != NULL) { + fieldstat_json_exporter_update_history((struct fieldstat_json_exporter *)exporter); + } + const struct fieldstat *instance = exporter->instance; size_t n_pair; - struct json_tag_field_pair *tag_field_pair = read_tag_and_field(instance, &n_pair); + struct cellwise_rec_for_export *tag_field_pair = read_tag_and_field(instance, &n_pair, exporter->history != NULL); if (tag_field_pair == NULL || n_pair == 0) { free(tag_field_pair); // tag_field_pair is not NULL when there are registered metrics but no valid cells *output = NULL; @@ -477,19 +784,27 @@ void fieldstat_json_exporter_export_array(const struct fieldstat_json_exporter * } fieldstat_json_exporter_write_global_tags(exporter, tag_field_pair, n_pair); + if (exporter->history != NULL) { // not null when fieldstat_json_exporter_enable_delta is called + fieldstat_json_exporter_write_delta((struct fieldstat_json_exporter *)exporter, tag_field_pair, n_pair); + } char **cjson_str_arr = (char **)malloc(sizeof(char *) * n_pair); for (int i = 0; i < n_pair; i++) { - struct json_tag_field_pair *current = &tag_field_pair[i]; + struct cellwise_rec_for_export *current = &tag_field_pair[i]; struct json_writer *root = json_writer_init(); json_writer_start_map(root); - const char *tmp_name = exporter->name ? exporter->name : "-"; + const char *tmp_name = exporter->name ? exporter->name : DEFAULT_EXPORTER_NAME; json_writer_str_field(root, "name", tmp_name, strlen(tmp_name)); json_writer_object_item(root, "tags", current->cjson_tags); json_writer_object_item(root, "fields", current->cjson_fields); - json_writer_longlong_field(root, "timestamp", cal_ms_time(timestamp)); + json_writer_longlong_field(root, "timestamp_ms", timestamp_ms); + if (exporter->history != NULL) { + json_writer_longlong_field(root, "timestamp_ms_delta", timestamp_ms - exporter->history->ts); + exporter->history->ts = timestamp_ms; + } + json_writer_end_map(root); char *cjson_str; @@ -501,7 +816,17 @@ void fieldstat_json_exporter_export_array(const struct fieldstat_json_exporter * *output = cjson_str_arr; *output_size = n_pair; - free(tag_field_pair); // cjson object will be freed with cjson root + if (exporter->history != NULL) { // read_tag_and_field does not allocate memory for metric_name and metric_value when exporter->history == NULL + for (int i = 0; i < n_pair; i++) { + for (int j = 0; j < tag_field_pair[i].n_metric; j++) { + free(tag_field_pair[i].metric_name[j]); + } + free(tag_field_pair[i].metric_name); + free(tag_field_pair[i].metric_value); + } + } + // json object is added in json_writer_object_item + free(tag_field_pair); } char *fieldstat_json_exporter_export(const struct fieldstat_json_exporter *exporter, const struct timeval *timestamp) @@ -536,6 +861,7 @@ struct fieldstat_json_exporter *fieldstat_json_exporter_new(const struct fieldst exporter->instance = instance; exporter->name = NULL; exporter->global_tag_list = NULL; + exporter->history = NULL; return exporter; } @@ -587,5 +913,19 @@ void fieldstat_json_exporter_free(struct fieldstat_json_exporter *exporter) if (exporter->global_tag_list != NULL) { fieldstat_tag_list_arr_free(exporter->global_tag_list, 1); } + + if (exporter->history != NULL) { + counter_history_free(exporter->history); + } + free(exporter); } + +void fieldstat_json_exporter_enable_delta(struct fieldstat_json_exporter *exporter) +{ + if (exporter->history != NULL) { + return; + } + exporter->history = counter_history_new(); + counter_history_fill_version_info(exporter->history, exporter); +}
\ No newline at end of file diff --git a/src/fieldstat.c b/src/fieldstat.c index 2e01d10..9a26d84 100644 --- a/src/fieldstat.c +++ b/src/fieldstat.c @@ -53,6 +53,7 @@ struct fieldstat *fieldstat_new() instance->max_n_cube = 100; instance->cube = calloc(instance->max_n_cube, sizeof(struct fs_cube *)); instance->cube_version = calloc(instance->max_n_cube, sizeof(unsigned long)); + instance->shared_tag_cube_manager = cube_manager_new(); return instance; @@ -173,16 +174,6 @@ void name_id_map_free(struct metric_name_id_map *map) } } -void name_id_map_init(struct metric_name_id_map **map, const struct fs_cube *cube) -{ - *map = NULL; - const char *name; - for (int i = 0; i < cube->n_metric; i++) { - name = metric_get_name(cube->metrics[i]); - name_id_map_add(map, name, i); - } -} - void fieldstat_clear_one_tag(const struct fieldstat_tag *tag) { free((char *)tag->key); @@ -398,9 +389,7 @@ void fieldstat_cube_free(struct fieldstat *instance, int cube_id) } free(cube->metrics); - if (cube->metric_name_id_map != NULL) { - name_id_map_free(cube->metric_name_id_map); - } + name_id_map_free(cube->metric_name_id_map); free(cube); instance->cube[cube_id] = NULL; @@ -417,8 +406,10 @@ static int append_metric_to_cube(struct fs_cube *cube, struct metric *metric) cube->max_n_metric *= 2; cube->metrics = realloc(cube->metrics, sizeof(struct metric *) * cube->max_n_metric); } + name_id_map_add(&cube->metric_name_id_map, metric_get_name(metric), cube->n_metric); cube->metrics[cube->n_metric] = metric; cube->n_metric++; + return cube->n_metric - 1; } @@ -434,6 +425,11 @@ int fieldstat_register_counter(struct fieldstat *instance, int cube_id, const ch if (cube == NULL) { return -1; } + + if (name_id_map_get_id_by_name(cube->metric_name_id_map, field_name) != -1) { + return -2; + } + struct metric *metric = metric_counter_new(field_name, mode); return append_metric_to_cube(cube, metric); @@ -823,10 +819,6 @@ void fieldstat_cube_merge_topk(struct fs_cube *dest, const struct fs_cube *src, void fieldstat_cube_merge(struct fs_cube *dest, const struct fs_cube *src) { struct metric_name_id_map *name_id_map_dest = dest->metric_name_id_map; - if (name_id_map_dest == NULL) { - name_id_map_init(&name_id_map_dest, dest); - dest->metric_name_id_map = name_id_map_dest; - } int metric_id_src_dest_map[src->n_metric]; for (int metric_id_src = 0; metric_id_src < src->n_metric; metric_id_src++) { diff --git a/src/metrics/histogram_encoder.c b/src/metrics/histogram_encoder.c index a92589b..14e3865 100644 --- a/src/metrics/histogram_encoder.c +++ b/src/metrics/histogram_encoder.c @@ -119,6 +119,81 @@ void histogram_encode_into_b64(struct hdr_histogram *hdr, char **buffer, size_t *buffer = enc; } +static uint32_t get_cookie_base(uint32_t cookie) +{ + return (cookie & ~0xf0U); +} + +static double int64_bits_to_double(int64_t i) +{ + union uint64_dbl_cvt x; + + x.l = (uint64_t) i; + return x.d; +} + +static int hdr_decode_v2_buf(const unsigned char* buffer, size_t length, struct hdr_histogram** histogram) +{ + struct hdr_histogram* h = NULL; + int result = 0; + int rc = 0; + const uint8_t* counts_array = NULL; + uint32_t encoding_cookie; + int32_t counts_limit, significant_figures; + int64_t lowest_discernible_value, highest_trackable_value; + + encoding_flyweight_v1_t encoding_flyweight; + memcpy(&encoding_flyweight, buffer, SIZEOF_ENCODING_FLYWEIGHT_V1); + + encoding_cookie = get_cookie_base(be32toh(encoding_flyweight.cookie)); + if (V2_ENCODING_COOKIE != encoding_cookie) { + return -1; + } + + counts_limit = be32toh(encoding_flyweight.payload_len); + lowest_discernible_value = be64toh(encoding_flyweight.lowest_discernible_value); + highest_trackable_value = be64toh(encoding_flyweight.highest_trackable_value); + significant_figures = be32toh(encoding_flyweight.significant_figures); + + rc = hdr_init(lowest_discernible_value, highest_trackable_value, significant_figures, &h); + if (rc) { + return -1; + } + + encoding_flyweight_v1_t *buffer_aligner = (encoding_flyweight_v1_t *)buffer; + + counts_array = buffer_aligner->counts; + + rc = apply_to_counts_zz(h, counts_array, counts_limit); + if (rc) { + free(h); + return -1; + } + + h->normalizing_index_offset = be32toh(encoding_flyweight.normalizing_index_offset); + h->conversion_ratio = int64_bits_to_double(be64toh(encoding_flyweight.conversion_ratio_bits)); + hdr_reset_internal_counters(h); + + *histogram = h; + return result; +} + +struct hdr_histogram *histogram_decode_from_b64(const char *buffer, size_t buffer_size) +{ + size_t dec_size = 0; + unsigned char *dec = b64_decode_ex(buffer, buffer_size, &dec_size); + + struct hdr_histogram *hdr = NULL; + int ret = hdr_decode_v2_buf(dec, dec_size, &hdr); + free(dec); + + if (ret != 0) { + return NULL; + } + + return hdr; +} + void histogram_encode_into_blob(const struct hdr_histogram *hdr, char **blob, size_t *blob_len) { struct simple_encoded *encoded = NULL; @@ -151,4 +226,17 @@ struct hdr_histogram *histogram_decode_from_blob(const char *blob, size_t blob_l hdr_reset_internal_counters(hdr); return hdr; +} + +long long hdr_count_le_value(const struct hdr_histogram* h, long long value) +{ + struct hdr_iter iter; + long long count = 0; + hdr_iter_recorded_init(&iter, h); + while (hdr_iter_next(&iter)) + { + if((long long)iter.value <= value) + count ++; + } + return count; }
\ No newline at end of file diff --git a/src/metrics/histogram_encoder.h b/src/metrics/histogram_encoder.h index 32c44a3..9ed9a15 100644 --- a/src/metrics/histogram_encoder.h +++ b/src/metrics/histogram_encoder.h @@ -26,6 +26,9 @@ extern "C" exported buffer is after base64 encoding./ */ void histogram_encode_into_b64(struct hdr_histogram *hdr, char **buffer, size_t *buffer_size); + +struct hdr_histogram *histogram_decode_from_b64(const char *buffer, size_t buffer_size); + /* the most simple and fast version of encoding. No compression, no base64 encoding, no LEB128 encoding, no user-defined extra parameters, only carry the most basic information. */ @@ -35,6 +38,8 @@ void histogram_encode_into_blob(const struct hdr_histogram *hdr, char **buffer, */ struct hdr_histogram *histogram_decode_from_blob(const char *buffer, size_t blob_len); +long long hdr_count_le_value(const struct hdr_histogram* h, long long value); + #ifdef __cplusplus } #endif
\ No newline at end of file diff --git a/src/metrics/metric.c b/src/metrics/metric.c index 320ac20..ed525db 100644 --- a/src/metrics/metric.c +++ b/src/metrics/metric.c @@ -818,19 +818,6 @@ long long metric_histogram_value_at_percentile(const struct metric *pthis, int c return hdr_value_at_percentile(data->hdr, percentile); } -static long long hdr_count_le_value(const struct hdr_histogram* h, long long value) -{ - struct hdr_iter iter; - long long count = 0; - hdr_iter_recorded_init(&iter, h); - while (hdr_iter_next(&iter)) - { - if((long long)iter.value <= value) - count ++; - } - return count; -} - long long metric_histogram_count_le_value(const struct metric *pthis, int cell_id, long long value) { const struct metric_measure_data *data = metric_find_one_cell(pthis, cell_id); diff --git a/src/metrics/python_api.c b/src/metrics/python_api.c new file mode 100644 index 0000000..83ad75c --- /dev/null +++ b/src/metrics/python_api.c @@ -0,0 +1,140 @@ +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> + +#include "histogram_encoder.h" +#include "base64/b64.h" +#include "st_hyperloglog.h" + +// user must free the buf after use +void *histogram_base64_decode(char *buf) +{ + size_t buffer_size = strlen(buf); + struct hdr_histogram *hdr = histogram_decode_from_b64(buf, buffer_size); + + return hdr; +} + +long long histogram_value_at_percentile(void* h, double percentile) +{ + return hdr_value_at_percentile((const struct hdr_histogram *)h, percentile); +} + +long long histogram_count_le_value(void* h, long long value) +{ + return hdr_count_le_value((const struct hdr_histogram *)h, value); +} + +long long histogram_value_min(void* h) +{ + return hdr_min((const struct hdr_histogram *)h); +} + +long long histogram_value_max(void* h) +{ + return hdr_max((const struct hdr_histogram *)h); +} + +double histogram_value_mean(void* h) +{ + return hdr_mean((const struct hdr_histogram *)h); +} + +double histogram_value_stddev(void* h) +{ + return hdr_stddev((const struct hdr_histogram *)h); +} + +long long histogram_value_total_count(void *h) +{ + return ((struct hdr_histogram *)h)->total_count; +} + +long long histogram_value_sum(void *h) +{ + struct hdr_iter iter; + long long sum = 0; + hdr_iter_recorded_init(&iter, (const struct hdr_histogram *)h); + while (hdr_iter_next(&iter)) + { + sum+=(long long)iter.value; + } + return sum; +} + +void histogram_free(void *h) +{ + hdr_close((struct hdr_histogram *)h); +} + + +/* -------------------------------------------------------------------------- */ +/* hyperloglog */ +/* -------------------------------------------------------------------------- */ +void *hll_base64_decode(char *buf); +double hll_base64_to_count(char *buf) +{ + struct ST_hyperloglog *hll = hll_base64_decode(buf); + double count = ST_hyperloglog_count(hll); + ST_hyperloglog_free(hll); + return count; +} + +#define BigLittleSwap32(A) ((((uint32_t)(A) & 0xff000000) >> 24) | \ + (((uint32_t)(A) & 0x00ff0000) >> 8) | \ + (((uint32_t)(A) & 0x0000ff00) << 8) | \ + (((uint32_t)(A) & 0x000000ff) << 24)) + +void *hll_base64_decode(char *buf) +{ + size_t buffer_size = strlen(buf); + size_t dec_size = 0; + unsigned char *dec = b64_decode_ex(buf, buffer_size, &dec_size); + + unsigned char version; + memcpy(&version, dec, sizeof(unsigned char)); + if (version != 1) { + return NULL; + } + + unsigned char precision; + memcpy(&precision, dec + sizeof(unsigned char), sizeof(unsigned char)); + + struct ST_hyperloglog *hll_from_blob = ST_hyperloglog_new(precision); + + int num_reg = NUM_REG(precision); + int words = INT_CEIL(num_reg, REG_PER_WORD); + size_t reg_size = words * sizeof(uint32_t); + uint32_t *registers = (uint32_t *)malloc(reg_size); + memcpy(registers, dec + 2 * sizeof(unsigned char), reg_size); + for (int i = 0; i < words; i++) { + registers[i] = BigLittleSwap32(registers[i]); + } + memcpy(hll_from_blob->registers, registers, reg_size); + free(registers); + + free(dec); + return hll_from_blob; +} + +void hll_free(void *hll) +{ + ST_hyperloglog_free((struct ST_hyperloglog *)hll); +} + +// cppcheck-suppress [constParameterPointer, unmatchedSuppression] +bool is_hll(char *buf) +{ + size_t dec_size = 0; + unsigned char *dec = b64_decode_ex(buf, 4, &dec_size); // 4: base64 convert every 3 bytes to 4 bytes + char cookie = *(char *)dec; + free(dec); + + if (cookie == 1) { // hll version is 1 + return true; + } + + // histogram cookie is also fixed, it's first byte is always 28, refer to V2_ENCODING_COOKIE (1c == 28) + return false; +} diff --git a/src/utils/very_fast_json_writer.c b/src/utils/very_fast_json_writer.c index df2bf01..e512b78 100644 --- a/src/utils/very_fast_json_writer.c +++ b/src/utils/very_fast_json_writer.c @@ -131,6 +131,15 @@ void json_writer_finish(struct json_writer *writer, char **result, size_t *resul free(writer); } +const char *json_writer_unwrap(const struct json_writer *writer) +{ + if (writer->cursor == 0) { + return NULL; + } + + return writer->buffer; +} + void json_writer_object_item(struct json_writer *writer, const char *key, struct json_writer *writer_in) { json_writer_str_item(writer, key, strlen(key)); diff --git a/src/utils/very_fast_json_writer.h b/src/utils/very_fast_json_writer.h index ec7b858..912a580 100644 --- a/src/utils/very_fast_json_writer.h +++ b/src/utils/very_fast_json_writer.h @@ -21,6 +21,7 @@ void json_writer_double_field(struct json_writer *writer, const char *key, doubl void json_writer_longlong_field(struct json_writer *writer, const char *key, long long value); void json_writer_object_item(struct json_writer *writer, const char *key, struct json_writer *writer_in); void json_writer_finish(struct json_writer *writer, char **result, size_t *result_len); +const char *json_writer_unwrap(const struct json_writer *writer); #ifdef __cplusplus } diff --git a/test/test_exporter_json.cpp b/test/test_exporter_json.cpp index 07f7a39..13e6b5d 100644 --- a/test/test_exporter_json.cpp +++ b/test/test_exporter_json.cpp @@ -402,6 +402,433 @@ TEST(export_test, skip_empty_metrics_given_cube_deleted) { cJSON_Delete(root_arr); } +TEST(export_test, enable_delta_and_export_twice_without_new_metric) +{ + struct fieldstat *instance = fieldstat_new(); + int cube_id = fieldstat_register_cube(instance, TEST_TAG_SHARED1, 3, SAMPLING_MODE_COMPREHENSIVE, 10); + int id_counter = fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + int cell_id = fieldstat_cube_add(instance, cube_id, &TEST_TAG_INT, 1, 1); + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_id, 1); + + // export test + struct fieldstat_json_exporter *fieldstat_json_exporter = fieldstat_json_exporter_new(instance); + fieldstat_json_exporter_enable_delta(fieldstat_json_exporter); + char *json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + cJSON *root_arr = cJSON_Parse(json_string); + free(json_string); + + int arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + for (int i = 0; i < arr_num; i++) + { + cJSON *root = cJSON_GetArrayItem(root_arr, i); + + // check metrics + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 1); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 1); + + // timestamp + cJSON *timestamp = cJSON_GetObjectItem(root, "timestamp_ms"); + EXPECT_EQ(timestamp->valueint, TEST_TIMEVAL_LONG); + cJSON *timestamp_delta = cJSON_GetObjectItem(root, "timestamp_ms_delta"); + EXPECT_EQ(timestamp_delta->valueint, TEST_TIMEVAL_LONG); + } + cJSON_Delete(root_arr); + + struct timeval new_ts = TEST_TIMEVAL; + new_ts.tv_sec += 1; + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &new_ts); + root_arr = cJSON_Parse(json_string); + free(json_string); + + arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + for (int i = 0; i < arr_num; i++) + { + cJSON *root = cJSON_GetArrayItem(root_arr, i); + + // check metrics + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 1); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 0); + + // timestamp + cJSON *timestamp = cJSON_GetObjectItem(root, "timestamp_ms"); + EXPECT_EQ(timestamp->valueint, TEST_TIMEVAL_LONG + 1000); + cJSON *timestamp_delta = cJSON_GetObjectItem(root, "timestamp_ms_delta"); + EXPECT_EQ(timestamp_delta->valueint, 1000); + } + cJSON_Delete(root_arr); + + fieldstat_json_exporter_free(fieldstat_json_exporter); + + fieldstat_free(instance); +} + +TEST(export_test, enable_delta_and_export_twice_with_new_metric_and_omit_histogram) +{ + struct fieldstat *instance = fieldstat_new(); + int cube_id = fieldstat_register_cube(instance, TEST_TAG_SHARED1, 3, SAMPLING_MODE_COMPREHENSIVE, 10); + int id_counter = fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + int id_histogram = fieldstat_register_hist(instance, cube_id, "histogram", + g_histogram_standard->lowest_discernible_value, g_histogram_standard->highest_trackable_value, g_histogram_standard->significant_figures); + int cell_id = fieldstat_cube_add(instance, cube_id, &TEST_TAG_INT, 1, 1); + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_id, 1); + fieldstat_hist_record(instance, cube_id, id_histogram, cell_id, 123); + + // export test + struct fieldstat_json_exporter *fieldstat_json_exporter = fieldstat_json_exporter_new(instance); + fieldstat_json_exporter_enable_delta(fieldstat_json_exporter); + char *json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + cJSON *root_arr = cJSON_Parse(json_string); + free(json_string); + + int arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + for (int i = 0; i < arr_num; i++) + { + cJSON *root = cJSON_GetArrayItem(root_arr, i); + + // check metrics + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 1); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 1); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") != nullptr); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + + // timestamp + cJSON *timestamp = cJSON_GetObjectItem(root, "timestamp_ms"); + EXPECT_EQ(timestamp->valueint, TEST_TIMEVAL_LONG); + cJSON *timestamp_delta = cJSON_GetObjectItem(root, "timestamp_ms_delta"); + EXPECT_EQ(timestamp_delta->valueint, TEST_TIMEVAL_LONG); + } + cJSON_Delete(root_arr); + + struct timeval new_ts = TEST_TIMEVAL; + new_ts.tv_sec += 1; + int id_counter2 = fieldstat_register_counter(instance, cube_id, "counter2", COUNTER_MERGE_BY_SUM); + fieldstat_counter_incrby(instance, cube_id, id_counter2, cell_id, 1); + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_id, 10); + + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &new_ts); + root_arr = cJSON_Parse(json_string); + free(json_string); + + arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + for (int i = 0; i < arr_num; i++) + { + cJSON *root = cJSON_GetArrayItem(root_arr, i); + + // check metrics + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 11); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 10); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") != nullptr); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + cJSON *counter2 = cJSON_GetObjectItem(metrics, "counter2"); + EXPECT_EQ(counter2->valueint, 1); + cJSON *counter2_delta = cJSON_GetObjectItem(metrics, "counter2_delta"); + EXPECT_EQ(counter2_delta->valueint, 1); + + // timestamp + cJSON *timestamp = cJSON_GetObjectItem(root, "timestamp_ms"); + EXPECT_EQ(timestamp->valueint, TEST_TIMEVAL_LONG + 1000); + cJSON *timestamp_delta = cJSON_GetObjectItem(root, "timestamp_ms_delta"); + EXPECT_EQ(timestamp_delta->valueint, 1000); + } + cJSON_Delete(root_arr); + fieldstat_json_exporter_free(fieldstat_json_exporter); + fieldstat_free(instance); +} + +TEST(export_test, enable_delta_and_export_three_times_skipping_cube_with_no_counter) +{ + struct fieldstat *instance = fieldstat_new(); + int cube_id = fieldstat_register_cube(instance, TEST_TAG_SHARED1, 3, SAMPLING_MODE_COMPREHENSIVE, 10); + int id_counter = fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + int id_histogram = fieldstat_register_hist(instance, cube_id, "histogram", + g_histogram_standard->lowest_discernible_value, g_histogram_standard->highest_trackable_value, g_histogram_standard->significant_figures); + int cell_with_counter = fieldstat_cube_add(instance, cube_id, &TEST_TAG_INT, 1, 1); + int cell_with_hdr = fieldstat_cube_add(instance, cube_id, &TEST_TAG_STRING, 1, 1); + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_with_counter, 1); + fieldstat_hist_record(instance, cube_id, id_histogram, cell_with_hdr, 123); + + /* -------------------------- export test, 1st time ------------------------- */ + struct fieldstat_json_exporter *fieldstat_json_exporter = fieldstat_json_exporter_new(instance); + fieldstat_json_exporter_enable_delta(fieldstat_json_exporter); + char *json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + cJSON *root_arr = cJSON_Parse(json_string); + free(json_string); + + int arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 2); + + cJSON *root = cJSON_GetArrayItem(root_arr, 0); + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 1); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 1); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + + root = cJSON_GetArrayItem(root_arr, 1); + metrics = cJSON_GetObjectItem(root, "fields"); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") != NULL); + cJSON_Delete(root_arr); + + /* -------------------------------- 2nd time -------------------------------- */ + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + root_arr = cJSON_Parse(json_string); + free(json_string); + root = cJSON_GetArrayItem(root_arr, 0); + metrics = cJSON_GetObjectItem(root, "fields"); + counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 1); + counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 0); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + root = cJSON_GetArrayItem(root_arr, 1); + metrics = cJSON_GetObjectItem(root, "fields"); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") != NULL); + cJSON_Delete(root_arr); + + /* -------------------------------- 3rd time -------------------------------- */ + // add some new recs + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_with_counter, 10); + fieldstat_hist_record(instance, cube_id, id_histogram, cell_with_hdr, 1234); + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + root_arr = cJSON_Parse(json_string); + free(json_string); + root = cJSON_GetArrayItem(root_arr, 0); + metrics = cJSON_GetObjectItem(root, "fields"); + counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 11); + counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 10); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + root = cJSON_GetArrayItem(root_arr, 1); + metrics = cJSON_GetObjectItem(root, "fields"); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "counter_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram_delta") == NULL); + EXPECT_TRUE(cJSON_GetObjectItem(metrics, "histogram") != NULL); + + cJSON_Delete(root_arr); + fieldstat_json_exporter_free(fieldstat_json_exporter); + fieldstat_free(instance); +} + +void test_check_delta_for_one_json(const struct fieldstat_tag_list *expect_cell_tag, const struct fieldstat_tag_list *expect_shared_tag, long long expect_value, long long expect_delta, cJSON *root) +{ + // check tag + cJSON *tag = cJSON_GetObjectItem(root, "tags"); + Fieldstat_tag_list_wrapper cell_wrapper = Fieldstat_tag_list_wrapper(expect_cell_tag); + test_check_if_tag_list_is_in_json(tag, &cell_wrapper); + Fieldstat_tag_list_wrapper shared_wrapper = Fieldstat_tag_list_wrapper(expect_shared_tag); + test_check_if_tag_list_is_in_json(tag, &shared_wrapper); + test_check_if_global_tag_is_in_json(tag); + + // check metrics + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, expect_value); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, expect_delta); +} + +TEST(export_test, enable_delta_and_export_instance_with_many_cells_with_global_tags) +{ + struct fieldstat *instance = fieldstat_new(); + int cube_id1 = fieldstat_register_cube(instance, TEST_TAG_SHARED1, 3, SAMPLING_MODE_COMPREHENSIVE, 10); + int id_counter1 = fieldstat_register_counter(instance, cube_id1, "counter", COUNTER_MERGE_BY_SUM); + int cell_id1_1 = fieldstat_cube_add(instance, cube_id1, &TEST_TAG_INT, 1, 1); + fieldstat_counter_incrby(instance, cube_id1, id_counter1, cell_id1_1, 11); + int cell_id1_2 = fieldstat_cube_add(instance, cube_id1, &TEST_TAG_STRING, 1, 1); + fieldstat_counter_incrby(instance, cube_id1, id_counter1, cell_id1_2, 12); + + int cube_id2 = fieldstat_register_cube(instance, TEST_TAG_SHARED3, 1, SAMPLING_MODE_COMPREHENSIVE, 10); + int id_counter2 = fieldstat_register_counter(instance, cube_id2, "counter", COUNTER_MERGE_BY_SUM); + int cell_id2_1 = fieldstat_cube_add(instance, cube_id2, &TEST_TAG_INT, 1, 1); + fieldstat_counter_incrby(instance, cube_id2, id_counter2, cell_id2_1, 21); + int cell_id2_2 = fieldstat_cube_add(instance, cube_id2, &TEST_TAG_DOUBLE, 1, 1); + fieldstat_counter_incrby(instance, cube_id2, id_counter2, cell_id2_2, 22); + + // export test + struct fieldstat_json_exporter *fieldstat_json_exporter = fieldstat_json_exporter_new(instance); + fieldstat_json_exporter_set_global_tag(fieldstat_json_exporter, TEST_TAG_GLOBAL, 3); + fieldstat_json_exporter_set_name(fieldstat_json_exporter, "test_instance"); + fieldstat_json_exporter_enable_delta(fieldstat_json_exporter); + char *json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + cJSON *root_arr = cJSON_Parse(json_string); + free(json_string); + + int arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 4); + + /* ------------------------------ cube 0 cell 0 ----------------------------- */ + cJSON *root = cJSON_GetArrayItem(root_arr, 0); + struct fieldstat_tag_list tmp_tag_cell = {(struct fieldstat_tag *)&TEST_TAG_INT, 1}; + struct fieldstat_tag_list tmp_tag_shared = {(struct fieldstat_tag *)&TEST_TAG_SHARED1, 3}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 11, 11, root); + /* ------------------------------ cube 0 cell 1 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 1); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_STRING, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 12, 12, root); + /* ------------------------------ cube 1 cell 0 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 2); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_INT, 1}; + tmp_tag_shared = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_SHARED3, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 21, 21, root); + /* ------------------------------ cube 1 cell 1 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 3); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_DOUBLE, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 22, 22, root); + + // new turn + fieldstat_counter_incrby(instance, cube_id1, id_counter1, cell_id1_1, 100); + fieldstat_counter_incrby(instance, cube_id1, id_counter1, cell_id1_2, 200); + fieldstat_counter_incrby(instance, cube_id2, id_counter2, cell_id2_1, 300); + fieldstat_counter_incrby(instance, cube_id2, id_counter2, cell_id2_2, 400); + + cJSON_Delete(root_arr); + + struct timeval new_ts = TEST_TIMEVAL; + new_ts.tv_sec += 1; + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &new_ts); + root_arr = cJSON_Parse(json_string); + free(json_string); + + arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 4); + + /* ------------------------------ cube 0 cell 0 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 0); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_INT, 1}; + tmp_tag_shared = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_SHARED1, 3}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 111, 100, root); + /* ------------------------------ cube 0 cell 1 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 1); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_STRING, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 212, 200, root); + /* ------------------------------ cube 1 cell 0 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 2); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_INT, 1}; + tmp_tag_shared = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_SHARED3, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 321, 300, root); + /* ------------------------------ cube 1 cell 1 ----------------------------- */ + root = cJSON_GetArrayItem(root_arr, 3); + tmp_tag_cell = (struct fieldstat_tag_list){(struct fieldstat_tag *)&TEST_TAG_DOUBLE, 1}; + test_check_delta_for_one_json(&tmp_tag_cell, &tmp_tag_shared, 422, 400, root); + + cJSON_Delete(root_arr); + + fieldstat_json_exporter_free(fieldstat_json_exporter); + fieldstat_free(instance); +} + +void test_reset_one_round(std::function<void(struct fieldstat *, struct fieldstat_json_exporter *)> trigger_reset) +{ + struct fieldstat *instance = fieldstat_new(); + int cube_id = fieldstat_register_cube(instance, &TEST_SHARED_TAG, 1, SAMPLING_MODE_COMPREHENSIVE, 1); + int id_counter = fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + int cell_id = fieldstat_cube_add(instance, cube_id, &TEST_TAG_INT, 1, 1); + fieldstat_counter_incrby(instance, cube_id, id_counter, cell_id, 11); + + // export test + struct fieldstat_json_exporter *fieldstat_json_exporter = fieldstat_json_exporter_new(instance); + fieldstat_json_exporter_enable_delta(fieldstat_json_exporter); + char *json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + cJSON *root_arr = cJSON_Parse(json_string); + free(json_string); + + int arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + cJSON *root = cJSON_GetArrayItem(root_arr, 0); + cJSON *metrics = cJSON_GetObjectItem(root, "fields"); + cJSON *counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 11); + cJSON *counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 11); + cJSON_Delete(root_arr); + + trigger_reset(instance, fieldstat_json_exporter); + + cell_id = fieldstat_cube_add(instance, cube_id, &TEST_TAG_INT, 1, 1); + fieldstat_counter_set(instance, cube_id, id_counter, cell_id, 123); + + json_string = fieldstat_json_exporter_export(fieldstat_json_exporter, &TEST_TIMEVAL); + root_arr = cJSON_Parse(json_string); + free(json_string); + + arr_num = cJSON_GetArraySize(root_arr); + EXPECT_EQ(arr_num, 1); + root = cJSON_GetArrayItem(root_arr, 0); + metrics = cJSON_GetObjectItem(root, "fields"); + counter = cJSON_GetObjectItem(metrics, "counter"); + EXPECT_EQ(counter->valueint, 123); + counter_delta = cJSON_GetObjectItem(metrics, "counter_delta"); + EXPECT_EQ(counter_delta->valueint, 123); + cJSON_Delete(root_arr); + + fieldstat_json_exporter_free(fieldstat_json_exporter); + fieldstat_free(instance); +} + +TEST(export_test, enable_delta_and_reset_on_change_exporter_name) { + auto trigger = [](struct fieldstat *instance, struct fieldstat_json_exporter *fieldstat_json_exporter) { + fieldstat_json_exporter_set_name(fieldstat_json_exporter, "test_instance"); + }; + + test_reset_one_round(trigger); +} + +TEST(export_test, enable_delta_and_reset_on_change_exporter_tag) { + auto trigger = [](struct fieldstat *instance, struct fieldstat_json_exporter *fieldstat_json_exporter) { + fieldstat_json_exporter_set_global_tag(fieldstat_json_exporter, TEST_TAG_GLOBAL, 3); + }; + + test_reset_one_round(trigger); +} + +TEST(export_test, enable_delta_and_reset_on_reset_instance) { + auto trigger = [](struct fieldstat *instance, struct fieldstat_json_exporter *fieldstat_json_exporter) { + fieldstat_reset(instance); + }; + + test_reset_one_round(trigger); +} + +TEST(export_test, enable_delta_and_reset_on_delete_cube) { + auto trigger = [](struct fieldstat *instance, struct fieldstat_json_exporter *fieldstat_json_exporter) { + fieldstat_unregister_cube(instance, 0); + fieldstat_register_cube(instance, TEST_TAG_SHARED1, 3, SAMPLING_MODE_COMPREHENSIVE, 3); + fieldstat_register_counter(instance, 0, "counter", COUNTER_MERGE_BY_SUM); + fieldstat_cube_add(instance, 0, &TEST_TAG_INT, 1, 1); + }; + + test_reset_one_round(trigger); +} + + extern "C" { extern int add_object_to_json_array_start(char *buf, int buf_len); extern int add_object_to_json_array_end(char **buf, int buf_len, int start); diff --git a/test/test_metric_histogram.cpp b/test/test_metric_histogram.cpp index 3f94245..d720efa 100644 --- a/test/test_metric_histogram.cpp +++ b/test/test_metric_histogram.cpp @@ -3,6 +3,9 @@ #include "fieldstat.h" #include "utils.hpp" +#include "hdr/hdr_histogram.h" +#include "histogram_encoder.h" + struct fieldstat *test_init_standard_instance_one_cube_one_metric_one_cell_hdr() { struct fieldstat *instance = fieldstat_new(); @@ -179,6 +182,41 @@ TEST(metric_test_histogram, can_add_0value) // histogram only allow min_val > 0, fieldstat_free(instance); } +extern "C" { + bool is_hll(char *buf); +} + +TEST(metric_test_histogram, encode_decode_b64) +{ + struct hdr_histogram *h = NULL; + hdr_init(1, 10000, 5, &h); + hdr_record_value(h, 1234); + hdr_record_value(h, 1234); + hdr_record_value(h, 4312); + + char *b64 = NULL; + size_t b64_len = 0; + histogram_encode_into_b64(h, &b64, &b64_len); + + EXPECT_FALSE(is_hll(b64)); + + struct hdr_histogram *h2 = histogram_decode_from_b64(b64, b64_len); + + EXPECT_TRUE(h2 != NULL); + EXPECT_EQ(h->lowest_discernible_value, h2->lowest_discernible_value); + EXPECT_EQ(h->highest_trackable_value, h2->highest_trackable_value); + EXPECT_EQ(h->significant_figures, h2->significant_figures); + EXPECT_EQ(h->counts_len, h2->counts_len); + for (int i = 0; i < h2->counts_len; i++) + { + EXPECT_EQ(h->counts[i], h2->counts[i]); + } + + free(b64); + hdr_close(h); + hdr_close(h2); +} + int main(int argc, char *argv[]) { testing::InitGoogleTest(&argc, argv); diff --git a/test/test_metric_hll.cpp b/test/test_metric_hll.cpp index 0a63f34..d886810 100644 --- a/test/test_metric_hll.cpp +++ b/test/test_metric_hll.cpp @@ -165,6 +165,32 @@ TEST(metric_test_hll, serialize_with_b64_and_query) ST_hyperloglog_free(hll_from_blob); } + +extern "C" { + void *hll_base64_decode(char *buf); + bool is_hll(char *buf); +} +TEST(metric_test_hll, serialize_with_b64_and_query_with_python_api) +{ + struct fieldstat *instance = test_init_standard_instance_one_cube_one_metric_one_cell_hll(); + fieldstat_hll_add(instance, 0, 0, 0, "hello", 5); + fieldstat_hll_add(instance, 0, 0, 0, "wor", 3); + fieldstat_hll_add(instance, 0, 0, 0, "world", 5); + + char *blob = NULL; + size_t blob_len = 0; + fieldstat_get_serialized_blob(instance, 0, 0, 0, &blob, &blob_len); + + bool flag = is_hll(blob); + EXPECT_EQ(flag, true); + + void *hll_from_blob = hll_base64_decode(blob); + EXPECT_NEAR(ST_hyperloglog_count((struct ST_hyperloglog *)hll_from_blob), 3, 0.5); + free(blob); + fieldstat_free(instance); + ST_hyperloglog_free((struct ST_hyperloglog *)hll_from_blob); +} + TEST(metric_test_hll, add_or_set_with_wrong_cell_id_expecting_fail) { struct fieldstat *instance = fieldstat_new(); diff --git a/test/test_register_and_reset.cpp b/test/test_register_and_reset.cpp index 4732f87..c325d75 100644 --- a/test/test_register_and_reset.cpp +++ b/test/test_register_and_reset.cpp @@ -332,7 +332,15 @@ TEST(test_register, find_cube) { fieldstat_free(instance); } -// int fieldstat_find_cube(const struct fieldstat *instance, const struct fieldstat_tag *shared_tags, size_t n_shared_tags); +TEST(test_register, register_metric_twice) { + struct fieldstat *instance = fieldstat_new(); + int cube_id = fieldstat_register_cube(instance, &TEST_SHARED_TAG, 1, SAMPLING_MODE_TOPK, 10); + fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + int metric_id2 = fieldstat_register_counter(instance, cube_id, "counter", COUNTER_MERGE_BY_SUM); + EXPECT_EQ(metric_id2, -2); + fieldstat_free(instance); +} + int main(int argc, char *argv[]) { diff --git a/test/utils.hpp b/test/utils.hpp index 75475a9..5da5daf 100644 --- a/test/utils.hpp +++ b/test/utils.hpp @@ -11,6 +11,7 @@ const struct fieldstat_tag TEST_TAG_DOUBLE = {"DOUBLE key_", TAG_DOUBLE, {.value const struct fieldstat_tag TEST_TAG_DOUBLE_collided = {"collided", TAG_DOUBLE, {.value_double = 2.0}}; const struct fieldstat_tag TEST_SHARED_TAG = {"shared", TAG_INTEGER, {.value_longlong = 1}}; const struct timeval TEST_TIMEVAL = {100, 10000}; +const long long TEST_TIMEVAL_LONG = 100010; // 100s * 1000 + 10000us / 1000 = 100010ms std::string gen_rand_string(int len); diff --git a/vendors/hdr/hdr_histogram_log.c b/vendors/hdr/hdr_histogram_log.c index 06ab81a..8e46f62 100644 --- a/vendors/hdr/hdr_histogram_log.c +++ b/vendors/hdr/hdr_histogram_log.c @@ -292,7 +292,7 @@ static void apply_to_counts_64(struct hdr_histogram* h, const int64_t* counts_da } } -static int apply_to_counts_zz(struct hdr_histogram* h, const uint8_t* counts_data, const int32_t data_limit) +int apply_to_counts_zz(struct hdr_histogram* h, const uint8_t* counts_data, const int32_t data_limit) { int64_t data_index = 0; int32_t counts_index = 0; diff --git a/vendors/hdr/hdr_histogram_log.h b/vendors/hdr/hdr_histogram_log.h index c1842b2..20be409 100644 --- a/vendors/hdr/hdr_histogram_log.h +++ b/vendors/hdr/hdr_histogram_log.h @@ -229,6 +229,9 @@ int hdr_log_read_entry( */ const char* hdr_strerror(int errnum); +/* ----------------------------- used by my code ---------------------------- */ +int apply_to_counts_zz(struct hdr_histogram* h, const uint8_t* counts_data, const int32_t data_limit); + #ifdef __cplusplus } #endif |
