diff options
| author | chenzizhan <[email protected]> | 2023-11-02 16:01:26 +0800 |
|---|---|---|
| committer | chenzizhan <[email protected]> | 2023-11-02 16:01:26 +0800 |
| commit | 4ff17defbc7df64c905210a18a9a63dd26860c3f (patch) | |
| tree | 83d68232b2bd1f75d43b161382b655d32886123f | |
| parent | e5139c45f2ac53b1fdf8538f01092cf4a78a32bd (diff) | |
fieldstat easy
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | include/fieldstat/fieldstat_easy.h | 94 | ||||
| -rw-r--r-- | include/fieldstat/fieldstat_exporter.h | 8 | ||||
| -rw-r--r-- | src/fieldstat_easy.c | 297 | ||||
| -rw-r--r-- | test/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | test/deps/stub/addr_pri.h | 177 | ||||
| -rw-r--r-- | test/deps/stub/stub.h | 434 | ||||
| -rw-r--r-- | test/test_easy_fs.cpp | 164 |
8 files changed, 1177 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e1e4d0..a06aec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ if (CMAKE_CXX_CPPCHECK) "--inline-suppr"
"--suppress=*:${PROJECT_SOURCE_DIR}/vendors/*"
"--suppress=*:${PROJECT_SOURCE_DIR}/test/utils.hpp"
+ "--suppress=*:${PROJECT_SOURCE_DIR}/test/deps/*"
"--suppress=*:${PROJECT_SOURCE_DIR}/test/unit_test_serialize.cpp"
"--suppress=*:${PROJECT_SOURCE_DIR}/src/logger/log_handle.c"
)
diff --git a/include/fieldstat/fieldstat_easy.h b/include/fieldstat/fieldstat_easy.h new file mode 100644 index 0000000..9a356a7 --- /dev/null +++ b/include/fieldstat/fieldstat_easy.h @@ -0,0 +1,94 @@ +#pragma once +#include <stddef.h> +#include "fieldstat.h" // for fieldstat_tag + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct fieldstat_easy; + +struct fieldstat_easy *fieldstat_easy_new(int max_thread_num, const struct fieldstat_tag *tags, size_t n_tag); +void fieldstat_easy_free(struct fieldstat_easy *fse); +// both data of accumulated and delta will be output. +int fieldstat_easy_enable_auto_output(struct fieldstat_easy *pthis, const char *output_path, int interval_second); +int fieldstat_easy_register_counter(struct fieldstat_easy *fse, const char *name); +int fieldstat_easy_register_histogram(struct fieldstat_easy *fse, const char *name, long long lowest_trackable_value, long long highest_trackable_value, int significant_figures); +// buff is a json string of accumulated data. +void fieldstat_easy_output(const struct fieldstat_easy *fse, char **buff, size_t *buff_len); + +int fieldstat_easy_counter_incrby(struct fieldstat_easy *fse, int thread_id, int metric_id, const struct fieldstat_tag *tags, size_t n_tag, long long increment); +int fieldstat_easy_histogram_record(struct fieldstat_easy *fse, int thread_id, int metric_id, const struct fieldstat_tag *tags, size_t n_tag, long long value); + +#ifdef __cplusplus +} +#endif + + +/* +关于输出delta: +fs_write; +fs_accumulate; +fs_delta; + +一个在write上register的metric,同样会随merge进入accumulate,delta的metric 名字后面希望跟一个delta,但是因为它是切换过来的,而且最终要加到accumulate上,所以。。。 +delta的形成一定靠reset。 + +输出的时候,acc 和 delta的本来分别在两个json上,但是文件里只有一个json。 +最好提供一个exportPair 的接口,由json聚合。如果输出两个json,我还要读出来再写一套,更恶心了。 + +另外一个方法,干脆给histogram算差值,这样是不是更干净一点?从算法上讲,histogram没道理不能算差。 +然后就变成fs_write, fs_reading. 最终的输出是一个总量。 +这个方法总体很好,但是需要我在exporter上保存metric数据,依赖metric。光这一条就可以直接否了方案。 + +这个easy自己维护一套metric呢?查询到的histogram和counter都merge,然后改名,再merge进来。。。这个改名就特别莫名其妙,输出的东西当然要输出自己来搞。 +算了,调用两遍exporter得了,然后我把一个的加到另外一个上?这样的问题是我要用cjson来解,真的不要,太恶心了。。 + +还是搞个duo吧..? + +不行,因为我真的没办法保证acc 和 delta的cell一样,delta的cell是调用者来搞的, 太容易顺序错了。于是counter history 那套是必须的。 +不过传入delta而不是由exporter保留,这部分还是对的。如果还是想要这个history,那么exporter就还得是有状态的,不然总不能每次都拿delta重建一份。(感觉就重建一份吧) +算了,果然只能这样了。。 + +那么干脆每次都新来一个acc呢?我记录acc,合成acc,delta咋办?也不对。 +得得得得,算delta这个逃不掉了。 + +提供一个fieldstat减法怎么样?首先hll不支持减。另外我不希望动fs4的接口。 + + + ---------------------------------- plan1 --------------------------------- +改动最小,性能影响最大,所有的还都得搞两份 + +instance = new() +register_metric(i,"A"); +intance_delt +register_metric(i_delta, "A_delta"); +counter_incyby(instance, id, 1); +counter_incyby(instance_delta, id, 1); + + +dst = merge(instance); +dst_delta = merge(instance_delta); + +merge(dst, dst_delta) + + + ---------------------------------- plan2 --------------------------------- + // 最莫名其妙,而且merge 改起来容易出问题 +instance_delta = new() +incyby + +instance = merge_and_add_delta(instance_delta) // 新增接口 + + +---------------------------------- plan3 --------------------------------- +// 仅仅需要修改exporter,但是编码量比较大,选择这种。export只有一遍,而merge是每个线程一遍,还是慢export吧 +json_total = exporter_export_duo(acc, delta); // 新增接口 + + + ---------------------------------- plan4 --------------------------------- +enable-delta() +histogram_decode() +histogram_substract() +*/
\ No newline at end of file diff --git a/include/fieldstat/fieldstat_exporter.h b/include/fieldstat/fieldstat_exporter.h index 2eafb0e..c7fa80c 100644 --- a/include/fieldstat/fieldstat_exporter.h +++ b/include/fieldstat/fieldstat_exporter.h @@ -36,6 +36,14 @@ void fieldstat_json_exporter_export_array(const struct fieldstat_json_exporter * Outputting delta value is time-consuming. */ void fieldstat_json_exporter_enable_delta(struct fieldstat_json_exporter *exporter); +/* + let json exporter output delta value by the side of the original value(accumulated). Instead of let exporter record the delta value, user can provide the delta value by this function. + All the delta metrics are provided through argument `instance_delta`. + `instance` and `instance_delta` must has exactly the same configurations, including the metrics and cubes. + since this function is only used by fieldstat_easy, users are not expected to use this function directly. As a result, the configuration check is not implemented. + return NULL when instance has no cell records. +*/ +char *fieldstat_json_exporter_export_with_delta(struct fieldstat_json_exporter *exporter, const struct fieldstat *instance, const struct fieldstat *instance_delta, const struct timeval *timestamp); #ifdef __cplusplus } diff --git a/src/fieldstat_easy.c b/src/fieldstat_easy.c new file mode 100644 index 0000000..9ec3e7b --- /dev/null +++ b/src/fieldstat_easy.c @@ -0,0 +1,297 @@ +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> + +#include "fieldstat.h" +#include "fieldstat_exporter.h" + + +struct fs_unit { + struct fieldstat *write_only; + struct fieldstat *read_only; + pthread_spinlock_t lock; +}; + +static volatile int g_output_thread_running = 0; + + +void close_output_thread() +{ + // atomic + (void)__sync_lock_test_and_set(&g_output_thread_running, 0); +} + +struct fieldstat_easy +{ + struct fs_unit *fsu; + int max_thread_num; + + struct fieldstat *delta; + struct fieldstat *accumulate; + pthread_t output_thread; + struct fieldstat_json_exporter *exporter; + FILE *output_fp; + int output_interval_second; + pthread_spinlock_t outputting_lock; +}; + +void fs_unit_switch_role(struct fs_unit *fsu) { + fieldstat_reset(fsu->read_only); + + pthread_spin_lock(&fsu->lock); + struct fieldstat *tmp = fsu->write_only; + fsu->write_only = fsu->read_only; + fsu->read_only = tmp; + pthread_spin_unlock(&fsu->lock); +} + +char *output_work(struct fieldstat_easy *fs, const struct timeval *timestamp) +{ + fieldstat_reset(fs->delta); + + for (int i = 0; i < fs->max_thread_num; i++) { + fs_unit_switch_role(fs->fsu + i); + fieldstat_merge(fs->delta, fs->fsu[i].read_only); + } + fieldstat_merge(fs->accumulate, fs->delta); + // char *ret = fieldstat_json_exporter_export_with_delta(fs->exporter, fs->accumulate, fs->delta, timestamp); + char *ret = fieldstat_json_exporter_export(fs->exporter, fs->accumulate, timestamp); + + return ret; +} + +void *output_main(void *arg) // return void * for pthread_create check only +{ + long long last_run_time = 0; + struct timeval timestamp; + struct timespec this_output_time; + struct fieldstat_easy *fs = (struct fieldstat_easy *)arg; + long long output_interval = fs->output_interval_second * 1000; + + while (g_output_thread_running) { + clock_gettime(CLOCK_MONOTONIC, &this_output_time); + long long now = this_output_time.tv_sec * 1000 + this_output_time.tv_nsec / 1000000; + if (now - last_run_time < output_interval) { + usleep(50000); // 50ms + continue; + } + last_run_time = now; + + printf("output_main: outputting\n"); + timestamp.tv_sec = this_output_time.tv_sec; + timestamp.tv_usec = this_output_time.tv_nsec / 1000; + pthread_spin_lock(&fs->outputting_lock); + char *ret = output_work(fs, ×tamp); + pthread_spin_unlock(&fs->outputting_lock); + if (ret == NULL) { + ret = strdup("[]"); + } + printf("output_main: outputting done, ret: %s\n", ret); + + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + if (fcntl(fileno(fs->output_fp), F_SETLKW, &lock) < 0) { + fprintf(stderr, "output_main: fcntl failed to lock\n"); + free(ret); + continue; + } + + // clear file and write + ftruncate(fileno(fs->output_fp), 0); + rewind(fs->output_fp); + fprintf(fs->output_fp, "%s\n", ret); + + lock.l_type = F_UNLCK; + if (fcntl(fileno(fs->output_fp), F_SETLK, &lock) < 0) { + fprintf(stderr, "output_main: fcntl failed to release\n"); + } + + free(ret); + } + return NULL; +} + +struct fieldstat_easy *fieldstat_easy_new(int max_thread_num, const struct fieldstat_tag *tags, size_t n_tag) { + struct fieldstat_easy *fse = calloc(1, sizeof(struct fieldstat_easy)); + fse->fsu = malloc(sizeof(struct fs_unit) * max_thread_num); + fse->max_thread_num = max_thread_num; + fse->delta = fieldstat_new(); + fieldstat_create_cube(fse->delta, NULL, 0, SAMPLING_MODE_COMPREHENSIVE, 0); + + fse->accumulate = fieldstat_fork(fse->delta); + fse->exporter = fieldstat_json_exporter_new(); + fieldstat_json_exporter_set_global_tag(fse->exporter, tags, n_tag); + pthread_spin_init(&fse->outputting_lock, PTHREAD_PROCESS_PRIVATE); + + for (int i = 0; i < max_thread_num; i++) { + fse->fsu[i].write_only = fieldstat_fork(fse->delta); + fse->fsu[i].read_only = fieldstat_fork(fse->delta); + pthread_spin_init(&fse->fsu[i].lock, PTHREAD_PROCESS_PRIVATE); + } + + return fse; +} + +void fieldstat_easy_free(struct fieldstat_easy *fse) { + if (g_output_thread_running) { + close_output_thread(); + pthread_join(fse->output_thread, NULL); + fclose(fse->output_fp); + } + + pthread_spin_destroy(&fse->outputting_lock); + fieldstat_free(fse->delta); + fieldstat_free(fse->accumulate); + fieldstat_json_exporter_free(fse->exporter); + + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_lock(&fse->fsu[i].lock); + fieldstat_free(fse->fsu[i].write_only); + fieldstat_free(fse->fsu[i].read_only); + pthread_spin_unlock(&fse->fsu[i].lock); + pthread_spin_destroy(&fse->fsu[i].lock); + } + free(fse->fsu); + + free(fse); +} + + +int fieldstat_easy_enable_auto_output(struct fieldstat_easy *pthis, const char *output_path, int interval_second) { + if (g_output_thread_running) { + return -2; + } + + FILE *fp = fopen(output_path, "w"); + if (!fp) { + return -1; + } + + pthis->output_fp = fp; + g_output_thread_running = 1; + pthis->output_interval_second = interval_second; + pthread_create(&pthis->output_thread, NULL, output_main, pthis); + + return 0; +} + +int fieldstat_easy_register_counter(struct fieldstat_easy *fse, const char *name) { + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_lock(&fse->fsu[i].lock); + } + + int ret = fieldstat_register_counter(fse->fsu[0].write_only, name); // try to register + if (ret < 0) { + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_unlock(&fse->fsu[i].lock); + } + return ret; + } + fieldstat_register_counter(fse->fsu[0].read_only, name); + for (int i = 1; i < fse->max_thread_num; i++) { + fieldstat_register_counter(fse->fsu[i].write_only, name); + fieldstat_register_counter(fse->fsu[i].read_only, name); + } + + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_unlock(&fse->fsu[i].lock); + } + + return ret; +} + +int fieldstat_easy_register_histogram(struct fieldstat_easy *fse, const char *name, long long lowest_trackable_value, long long highest_trackable_value, int significant_figures) +{ + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_lock(&fse->fsu[i].lock); + } + + int ret = fieldstat_register_hist(fse->fsu[0].write_only, name, lowest_trackable_value, highest_trackable_value, significant_figures); // try to register + if (ret < 0) { + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_unlock(&fse->fsu[i].lock); + } + return ret; + } + fieldstat_register_hist(fse->fsu[0].read_only, name, lowest_trackable_value, highest_trackable_value, significant_figures); + for (int i = 1; i < fse->max_thread_num; i++) { + fieldstat_register_hist(fse->fsu[i].write_only, name, lowest_trackable_value, highest_trackable_value, significant_figures); + fieldstat_register_hist(fse->fsu[i].read_only, name, lowest_trackable_value, highest_trackable_value, significant_figures); + } + + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_unlock(&fse->fsu[i].lock); + } + + return ret; +} + +// output an json string for accumulated data +void fieldstat_easy_output(struct fieldstat_easy *fse, char **buff, size_t *buff_len) +{ + struct timeval timestamp; + struct timespec this_output_time; + clock_gettime(CLOCK_MONOTONIC, &this_output_time); // use the same method as output_main + timestamp.tv_sec = this_output_time.tv_sec; + timestamp.tv_usec = this_output_time.tv_nsec / 1000; + struct fieldstat *dst = fieldstat_new(); + // collect all the data delta recorded since last passive output, if it happened. If fieldstat_easy_enable_auto_output is not called, its the data since the program started. + for (int i = 0; i < fse->max_thread_num; i++) { + pthread_spin_lock(&fse->fsu[i].lock); + fieldstat_merge(dst, fse->fsu[i].write_only); + pthread_spin_unlock(&fse->fsu[i].lock); + } + + // add the outputted data + pthread_spin_lock(&fse->outputting_lock); + fieldstat_merge(dst, fse->accumulate); + pthread_spin_unlock(&fse->outputting_lock); + + *buff = fieldstat_json_exporter_export(fse->exporter, dst, ×tamp); + if (*buff == NULL) { + *buff = strdup("[]"); + } + fieldstat_free(dst); + *buff_len = strlen(*buff); +} + +int fieldstat_easy_counter_incrby(struct fieldstat_easy *fse, int thread_id, int metric_id, const struct fieldstat_tag *tags, size_t n_tag, long long increment) +{ + if (thread_id < 0) { + return -1; + } + if (thread_id >= fse->max_thread_num) { + return -1; + } + + pthread_spin_lock(&fse->fsu[thread_id].lock); + int ret = fieldstat_counter_incrby(fse->fsu[thread_id].write_only, 0, metric_id, tags, n_tag, increment); + pthread_spin_unlock(&fse->fsu[thread_id].lock); + + return ret; +} + +int fieldstat_easy_histogram_record(struct fieldstat_easy *fse, int thread_id, int metric_id, const struct fieldstat_tag *tags, size_t n_tag, long long value) +{ + if (thread_id < 0) { + return -1; + } + if (thread_id >= fse->max_thread_num) { + return -1; + } + + pthread_spin_lock(&fse->fsu[thread_id].lock); + int ret = fieldstat_hist_record(fse->fsu[thread_id].write_only, 0, metric_id, tags, n_tag, value); + pthread_spin_unlock(&fse->fsu[thread_id].lock); + + return ret; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0f1f7ed..8d82d55 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ cmake_minimum_required(VERSION 2.8...3.10) set(DEBUG_FLAGS "-O3")
include_directories(${PROJECT_SOURCE_DIR}/test)
+include_directories(${PROJECT_SOURCE_DIR}/test/deps)
include_directories(${PROJECT_SOURCE_DIR}/src)
include_directories(${PROJECT_SOURCE_DIR}/src/tags)
include_directories(${PROJECT_SOURCE_DIR}/src/metrics)
@@ -46,6 +47,7 @@ function (add_unit_test file_name) set_property(TARGET ${file_name} PROPERTY CXX_STANDARD 17)
endfunction()
+add_unit_test(test_easy_fs)
add_unit_test(test_empty_tags)
add_unit_test(test_exporter_json)
add_unit_test(test_fuzz_test)
diff --git a/test/deps/stub/addr_pri.h b/test/deps/stub/addr_pri.h new file mode 100644 index 0000000..9174bb0 --- /dev/null +++ b/test/deps/stub/addr_pri.h @@ -0,0 +1,177 @@ +#ifndef __ADDR_PRI_H__ +#define __ADDR_PRI_H__ + + +#include <utility> +#include <type_traits> + + + +//base on C++11 + +/********************************************************** + access private function +**********************************************************/ + + +namespace std { + template <bool B, class T = void> + using enable_if_t = typename enable_if<B, T>::type; + template <class T> + using remove_reference_t = typename remove_reference<T>::type; +} // std + +// Unnamed namespace is used to avoid duplicate symbols if the macros are used +namespace { + namespace private_access_detail { + + // @tparam TagType, used to declare different "get" funciton overloads for + // different members/statics + template <typename PtrType, PtrType PtrValue, typename TagType> + struct private_access { + // Normal lookup cannot find in-class defined (inline) friend functions. + friend PtrType get(TagType) { return PtrValue; } + }; + + } // namespace private_access_detail +} // namespace + +// Used macro naming conventions: +// The "namespace" of this macro library is PRIVATE_ACCESS, i.e. all +// macro here has this prefix. +// All implementation macro, which are not meant to be used directly have the +// PRIVATE_ACCESS_DETAIL prefix. +// Some macros have the ABCD_IMPL form, which means they contain the +// implementation details for the specific ABCD macro. + +#define PRIVATE_ACCESS_DETAIL_CONCATENATE_IMPL(x, y) x##y +#define PRIVATE_ACCESS_DETAIL_CONCATENATE(x, y) \ + PRIVATE_ACCESS_DETAIL_CONCATENATE_IMPL(x, y) + +// @param PtrTypeKind E.g if we have "class A", then it can be "A::*" in case of +// members, or it can be "*" in case of statics. +#define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, \ + PtrTypeKind) \ + namespace { \ + namespace private_access_detail { \ + /* Tag type, used to declare different get funcitons for different \ + * members \ + */ \ + struct Tag {}; \ + /* Explicit instantiation */ \ + template struct private_access<decltype(&Class::Name), &Class::Name, \ + Tag>; \ + /* We can build the PtrType only with two aliases */ \ + /* E.g. using PtrType = int(int) *; would be illformed */ \ + using PRIVATE_ACCESS_DETAIL_CONCATENATE(Alias_, Tag) = Type; \ + using PRIVATE_ACCESS_DETAIL_CONCATENATE(PtrType_, Tag) = \ + PRIVATE_ACCESS_DETAIL_CONCATENATE(Alias_, Tag) PtrTypeKind; \ + /* Declare the friend function, now it is visible in namespace scope. \ + * Note, \ + * we could declare it inside the Tag type too, in that case ADL would \ + * find \ + * the declaration. By choosing to declare it here, the Tag type remains \ + * a \ + * simple tag type, it has no other responsibilities. */ \ + PRIVATE_ACCESS_DETAIL_CONCATENATE(PtrType_, Tag) get(Tag); \ + } \ + } + +#define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FIELD(Tag, Class, Type, Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, Class::*) \ + namespace { \ + namespace access_private_field { \ + Type &Class##Name(Class &&t) { return t.*get(private_access_detail::Tag{}); } \ + Type &Class##Name(Class &t) { return t.*get(private_access_detail::Tag{}); } \ + /* The following usings are here to avoid duplicate const qualifier \ + * warnings \ + */ \ + using PRIVATE_ACCESS_DETAIL_CONCATENATE(X, Tag) = Type; \ + using PRIVATE_ACCESS_DETAIL_CONCATENATE(Y, Tag) = \ + const PRIVATE_ACCESS_DETAIL_CONCATENATE(X, Tag); \ + PRIVATE_ACCESS_DETAIL_CONCATENATE(Y, Tag) & Class##Name(const Class &t) {\ + return t.*get(private_access_detail::Tag{}); \ + } \ + } \ + } + +#define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FUN(Tag, Class, Type, Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, Class::*) \ + namespace { \ + namespace call_private_fun { \ + /* We do perfect forwarding, but we want to restrict the overload set \ + * only for objects which have the type Class. */ \ + template <typename Obj, \ + std::enable_if_t<std::is_same<std::remove_reference_t<Obj>, \ + Class>::value> * = nullptr, \ + typename... Args> \ + auto Class##Name(Obj &&o, Args &&... args) -> decltype( \ + (std::forward<Obj>(o).* \ + get(private_access_detail::Tag{}))(std::forward<Args>(args)...)) { \ + return (std::forward<Obj>(o).*get(private_access_detail::Tag{}))( \ + std::forward<Args>(args)...); \ + } \ + } \ + namespace get_private_fun { \ + auto Class##Name() -> decltype( \ + get(private_access_detail::Tag{})) { \ + return (get(private_access_detail::Tag{})); \ + } \ + } \ + } + +#define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FIELD(Tag, Class, Type, \ + Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, *) \ + namespace { \ + namespace access_private_static_field { \ + namespace Class { \ + Type &Class##Name() { return *get(private_access_detail::Tag{}); } \ + } \ + } \ + } + +#define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FUN(Tag, Class, Type, \ + Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, *) \ + namespace { \ + namespace call_private_static_fun { \ + namespace Class { \ + template <typename... Args> \ + auto Class##Name(Args &&... args) -> decltype( \ + get(private_access_detail::Tag{})(std::forward<Args>(args)...)) { \ + return get(private_access_detail::Tag{})( \ + std::forward<Args>(args)...); \ + } \ + } \ + } \ + namespace get_private_static_fun { \ + namespace Class { \ + auto Class##Name() -> decltype(get(private_access_detail::Tag{})) { \ + return get(private_access_detail::Tag{}); \ + } \ + } \ + } \ + } + +#define PRIVATE_ACCESS_DETAIL_UNIQUE_TAG \ + PRIVATE_ACCESS_DETAIL_CONCATENATE(PrivateAccessTag, __COUNTER__) + +#define ACCESS_PRIVATE_FIELD(Class, Type, Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FIELD(PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, \ + Class, Type, Name) + +#define ACCESS_PRIVATE_FUN(Class, Type, Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FUN(PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, \ + Class, Type, Name) + +#define ACCESS_PRIVATE_STATIC_FIELD(Class, Type, Name) \ + Type Class::Name; \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FIELD( \ + PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, Class, Type, Name) + +#define ACCESS_PRIVATE_STATIC_FUN(Class, Type, Name) \ + PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FUN( \ + PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, Class, Type, Name) + +#endif diff --git a/test/deps/stub/stub.h b/test/deps/stub/stub.h new file mode 100644 index 0000000..f6e04ce --- /dev/null +++ b/test/deps/stub/stub.h @@ -0,0 +1,434 @@ +#ifndef __STUB_H__ +#define __STUB_H__ + +#ifdef _WIN32 +//windows +#include <windows.h> +#include <processthreadsapi.h> +#else +//linux or macos +#include <unistd.h> +#include <sys/mman.h> +#endif +//c +#include <cstddef> +#include <cstring> +//c++ +#include <map> + + +#define ADDR(CLASS_NAME,MEMBER_NAME) (&CLASS_NAME::MEMBER_NAME) + +/********************************************************** + replace function +**********************************************************/ +#ifdef _WIN32 +#define CACHEFLUSH(addr, size) FlushInstructionCache(GetCurrentProcess(), addr, size) +#else +#define CACHEFLUSH(addr, size) __builtin___clear_cache(addr, addr + size) +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) + #define CODESIZE 16U + #define CODESIZE_MIN 16U + #define CODESIZE_MAX CODESIZE + // ldr x9, +8 + // br x9 + // addr + #define REPLACE_FAR(t, fn, fn_stub)\ + ((uint32_t*)fn)[0] = 0x58000040 | 9;\ + ((uint32_t*)fn)[1] = 0xd61f0120 | (9 << 5);\ + *(long long *)(fn + 8) = (long long )fn_stub;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) +#elif defined(__arm__) || defined(_M_ARM) + #define CODESIZE 8U + #define CODESIZE_MIN 8U + #define CODESIZE_MAX CODESIZE + // ldr pc, [pc, #-4] + #define REPLACE_FAR(t, fn, fn_stub)\ + ((uint32_t*)fn)[0] = 0xe51ff004;\ + ((uint32_t*)fn)[1] = (uint32_t)fn_stub;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) +#elif defined(__thumb__) || defined(_M_THUMB) + #define CODESIZE 12 + #define CODESIZE_MIN 12 + #define CODESIZE_MAX CODESIZE + // NOP + // LDR.W PC, [PC] + #define REPLACE_FAR(t, fn, fn_stub)\ + uint32_t clearBit0 = fn & 0xfffffffe;\ + char *f = (char *)clearBit0;\ + if (clearBit0 % 4 != 0) {\ + *(uint16_t *)&f[0] = 0xbe00;\ + }\ + *(uint16_t *)&f[2] = 0xf8df;\ + *(uint16_t *)&f[4] = 0xf000;\ + *(uint16_t *)&f[6] = (uint16_t)(fn_stub & 0xffff);\ + *(uint16_t *)&f[8] = (uint16_t)(fn_stub >> 16);\ + CACHEFLUSH((char *)f, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) +#elif defined(__mips64) + #define CODESIZE 80U + #define CODESIZE_MIN 80U + #define CODESIZE_MAX CODESIZE + //MIPS has no PC pointer, so you need to manually enter and exit the stack + //120000ce0: 67bdffe0 daddiu sp, sp, -32 //enter the stack + //120000ce4: ffbf0018 sd ra, 24(sp) + //120000ce8: ffbe0010 sd s8, 16(sp) + //120000cec: ffbc0008 sd gp, 8(sp) + //120000cf0: 03a0f025 move s8, sp + + //120000d2c: 03c0e825 move sp, s8 //exit the stack + //120000d30: dfbf0018 ld ra, 24(sp) + //120000d34: dfbe0010 ld s8, 16(sp) + //120000d38: dfbc0008 ld gp, 8(sp) + //120000d3c: 67bd0020 daddiu sp, sp, 32 + //120000d40: 03e00008 jr ra + + #define REPLACE_FAR(t, fn, fn_stub)\ + ((uint32_t *)fn)[0] = 0x67bdffe0;\ + ((uint32_t *)fn)[1] = 0xffbf0018;\ + ((uint32_t *)fn)[2] = 0xffbe0010;\ + ((uint32_t *)fn)[3] = 0xffbc0008;\ + ((uint32_t *)fn)[4] = 0x03a0f025;\ + *(uint16_t *)(fn + 20) = (long long)fn_stub >> 32;\ + *(fn + 22) = 0x19;\ + *(fn + 23) = 0x24;\ + ((uint32_t *)fn)[6] = 0x0019cc38;\ + *(uint16_t *)(fn + 28) = (long long)fn_stub >> 16;\ + *(fn + 30) = 0x39;\ + *(fn + 31) = 0x37;\ + ((uint32_t *)fn)[8] = 0x0019cc38;\ + *(uint16_t *)(fn + 36) = (long long)fn_stub;\ + *(fn + 38) = 0x39;\ + *(fn + 39) = 0x37;\ + ((uint32_t *)fn)[10] = 0x0320f809;\ + ((uint32_t *)fn)[11] = 0x00000000;\ + ((uint32_t *)fn)[12] = 0x00000000;\ + ((uint32_t *)fn)[13] = 0x03c0e825;\ + ((uint32_t *)fn)[14] = 0xdfbf0018;\ + ((uint32_t *)fn)[15] = 0xdfbe0010;\ + ((uint32_t *)fn)[16] = 0xdfbc0008;\ + ((uint32_t *)fn)[17] = 0x67bd0020;\ + ((uint32_t *)fn)[18] = 0x03e00008;\ + ((uint32_t *)fn)[19] = 0x00000000;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) + +#elif defined(__riscv) && __riscv_xlen == 64 + #define CODESIZE 24U + #define CODESIZE_MIN 24U + #define CODESIZE_MAX CODESIZE + // absolute offset(64) + // auipc t1,0 + // addi t1, t1, 16 + // ld t1,0(t1) + // jalr x0, t1, 0 + // addr + #define REPLACE_FAR(t, fn, fn_stub)\ + unsigned int auipc = 0x317;\ + *(unsigned int *)(fn) = auipc;\ + unsigned int addi = 0x1030313;\ + *(unsigned int *)(fn + 4) = addi;\ + unsigned int ld = 0x33303;\ + *(unsigned int *)(fn + 8) = ld;\ + unsigned int jalr = 0x30067;\ + *(unsigned int *)(fn + 12) = jalr;\ + *(unsigned long long*)(fn + 16) = (unsigned long long)fn_stub;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) +#elif defined(__riscv) && __riscv_xlen == 32 + #define CODESIZE 20U + #define CODESIZE_MIN 20U + #define CODESIZE_MAX CODESIZE + // absolute offset(32) + // auipc t1,0 + // addi t1, t1, 16 + // lw t1,0(t1) + // jalr x0, t1, 0 + // addr + #define REPLACE_FAR(t, fn, fn_stub)\ + unsigned int auipc = 0x317;\ + *(unsigned int *)(fn) = auipc;\ + unsigned int addi = 0x1030313;\ + *(unsigned int *)(fn + 4) = addi;\ + unsigned int lw = 0x32303;\ + *(unsigned int *)(fn + 8) = lw;\ + unsigned int jalr = 0x30067;\ + *(unsigned int *)(fn + 12) = jalr;\ + *(unsigned int*)(fn + 16) = (unsigned int)fn_stub;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) + +#elif defined(__loongarch64) + #define CODESIZE 20U + #define CODESIZE_MIN 20U + #define CODESIZE_MAX CODESIZE + // absolute offset(64) + // PCADDI rd, si20 | 0 0 0 1 1 0 0 si20 rd + // LD.D rd, rj, si12 | 0 0 1 0 1 0 0 0 1 1 si12 rj rd + // JIRL rd, rj, offs | 0 1 0 0 1 1 offs[15:0] rj rd + // addr + #define REPLACE_FAR(t, fn, fn_stub)\ + unsigned int rd = 17;\ + unsigned int off = 12 >> 2;\ + unsigned int pcaddi = 0x0c << (32 - 7) | off << 5 | rd ;\ + rd = 17;\ + int rj = 17;\ + off = 0;\ + unsigned int ld_d = 0xa3 << 22 | off << 10 | rj << 5 | rd ;\ + rd = 0;\ + rj = 17;\ + off = 0;\ + unsigned int jirl = 0x13 << 26 | off << 10 | rj << 5| rd;\ + *(unsigned int *)fn = pcaddi;\ + *(unsigned int *)(fn + 4) = ld_d;\ + *(unsigned int *)(fn + 8) = jirl;\ + *(unsigned long long*)(fn + 12) = (unsigned long long)fn_stub;\ + CACHEFLUSH((char *)fn, CODESIZE); + #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) + +#else //__i386__ _x86_64__ _M_IX86 _M_X64 + #define CODESIZE 13U + #define CODESIZE_MIN 5U + #define CODESIZE_MAX CODESIZE + //13 byte(jmp m16:64) + //movabs $0x102030405060708,%r11 + //jmpq *%r11 + #define REPLACE_FAR(t, fn, fn_stub)\ + *fn = 0x49;\ + *(fn + 1) = 0xbb;\ + *(long long *)(fn + 2) = (long long)fn_stub;\ + *(fn + 10) = 0x41;\ + *(fn + 11) = 0xff;\ + *(fn + 12) = 0xe3;\ + CACHEFLUSH((char *)fn, CODESIZE); + + //5 byte(jmp rel32) + #define REPLACE_NEAR(t, fn, fn_stub)\ + *fn = 0xE9;\ + *(int *)(fn + 1) = (int)(fn_stub - fn - CODESIZE_MIN);\ + CACHEFLUSH((char *)fn, CODESIZE); +#endif + +struct func_stub +{ + char *fn; + unsigned char code_buf[CODESIZE]; + bool far_jmp; +}; + +class Stub +{ +public: + Stub() + { +#ifdef _WIN32 + SYSTEM_INFO sys_info; + GetSystemInfo(&sys_info); + m_pagesize = sys_info.dwPageSize; +#else + m_pagesize = sysconf(_SC_PAGE_SIZE); +#endif + + if (m_pagesize < 0) + { + m_pagesize = 4096; + } + } + ~Stub() + { + clear(); + } + void clear() + { + std::map<char*,func_stub*>::iterator iter; + struct func_stub *pstub; + for(iter=m_result.begin(); iter != m_result.end(); iter++) + { + pstub = iter->second; +#ifdef _WIN32 + DWORD lpflOldProtect; + if(0 != VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) +#else + if (0 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) +#endif + { + + if(pstub->far_jmp) + { + std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX); + } + else + { + std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN); + } + + CACHEFLUSH(pstub->fn, CODESIZE); + +#ifdef _WIN32 + VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect); +#else + mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC); +#endif + } + + iter->second = NULL; + delete pstub; + } + + return; + } + template<typename T,typename S> + void set(T addr, S addr_stub) + { + char * fn; + char * fn_stub; + fn = addrof(addr); + fn_stub = addrof(addr_stub); + struct func_stub *pstub; + pstub = new func_stub; + //start + reset(fn); // + pstub->fn = fn; + + if(distanceof(fn, fn_stub)) + { + pstub->far_jmp = true; + std::memcpy(pstub->code_buf, fn, CODESIZE_MAX); + } + else + { + pstub->far_jmp = false; + std::memcpy(pstub->code_buf, fn, CODESIZE_MIN); + } + +#ifdef _WIN32 + DWORD lpflOldProtect; + if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) +#else + if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) +#endif + { + throw("stub set memory protect to w+r+x faild"); + } + + if(pstub->far_jmp) + { + REPLACE_FAR(this, fn, fn_stub); + } + else + { + REPLACE_NEAR(this, fn, fn_stub); + } + +#ifdef _WIN32 + if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect)) +#else + if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC)) +#endif + { + throw("stub set memory protect to r+x failed"); + } + m_result.insert(std::pair<char*,func_stub*>(fn,pstub)); + return; + } + + template<typename T> + void reset(T addr) + { + char * fn; + fn = addrof(addr); + + std::map<char*,func_stub*>::iterator iter = m_result.find(fn); + + if (iter == m_result.end()) + { + return; + } + struct func_stub *pstub; + pstub = iter->second; + +#ifdef _WIN32 + DWORD lpflOldProtect; + if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) +#else + if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) +#endif + { + throw("stub reset memory protect to w+r+x faild"); + } + + if(pstub->far_jmp) + { + std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX); + } + else + { + std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN); + } + + CACHEFLUSH(pstub->fn, CODESIZE); + + +#ifdef _WIN32 + if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect)) +#else + if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC)) +#endif + { + throw("stub reset memory protect to r+x failed"); + } + m_result.erase(iter); + delete pstub; + + return; + } +private: + char *pageof(char* addr) + { +#ifdef _WIN32 + return (char *)((unsigned long long)addr & ~(m_pagesize - 1)); +#else + return (char *)((unsigned long)addr & ~(m_pagesize - 1)); +#endif + } + + template<typename T> + char* addrof(T addr) + { + union + { + T _s; + char* _d; + }ut; + ut._s = addr; + return ut._d; + } + + bool distanceof(char* addr, char* addr_stub) + { + std::ptrdiff_t diff = addr_stub >= addr ? addr_stub - addr : addr - addr_stub; + if((sizeof(addr) > 4) && (((diff >> 31) - 1) > 0)) + { + return true; + } + return false; + } + +private: +#ifdef _WIN32 + //LLP64 + long long m_pagesize; +#else + //LP64 + long m_pagesize; +#endif + std::map<char*, func_stub*> m_result; + +}; + + +#endif diff --git a/test/test_easy_fs.cpp b/test/test_easy_fs.cpp new file mode 100644 index 0000000..117963d --- /dev/null +++ b/test/test_easy_fs.cpp @@ -0,0 +1,164 @@ +#include <gtest/gtest.h> +#include <stub/stub.h> +#include <fstream> + +#include "utils.hpp" +#include "cjson/cJSON.h" + +#include "fieldstat_easy.h" + +#define FILENAME "./test_easy_fieldstat.json" + +// TEST(test_easy_fieldstat, new_and_free) +// { +// struct fieldstat_easy *fse = fieldstat_easy_new(10, &TEST_TAG_STRING, 1); +// fieldstat_easy_free(fse); +// } + +// TEST(test_easy_fieldstat, output_to_buff) +// { +// struct fieldstat_easy *fse = fieldstat_easy_new(10, &TEST_TAG_STRING, 1); +// fieldstat_easy_register_counter(fse, "metric counter"); +// fieldstat_easy_counter_incrby(fse, 0, 0, &TEST_TAG_INT, 1, 1); +// // read file, should be empty +// char *buff = NULL; +// size_t buff_len = 0; +// fieldstat_easy_output(fse, &buff, &buff_len); +// cJSON *root = cJSON_Parse(buff); +// cJSON *cell = cJSON_GetArrayItem(root, 0); +// cJSON *metric = cJSON_GetObjectItem(cell, "fields"); +// long long value = cJSON_GetObjectItem(metric, "metric counter")->valueint; +// EXPECT_EQ(value, 1); +// cJSON *tags = cJSON_GetObjectItem(cell, "tags"); +// EXPECT_EQ(cJSON_GetObjectItem(tags, TEST_TAG_INT.key)->valueint, TEST_TAG_INT.value_longlong); +// EXPECT_STREQ(cJSON_GetObjectItem(tags, TEST_TAG_STRING.key)->valuestring, TEST_TAG_STRING.value_str); + +// cJSON_Delete(root); +// free(buff); +// fieldstat_easy_free(fse); +// } + +// cJSON *read_file() +// { +// std::ifstream ifs(FILENAME); +// if (!ifs.is_open()) { +// return NULL; +// } +// std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); +// printf("content: %s\n", content.c_str()); +// cJSON *root = cJSON_Parse(content.c_str()); +// ifs.close(); +// return root; +// } + +// TEST(test_easy_fieldstat, output_to_file) +// { +// struct fieldstat_easy *fse = fieldstat_easy_new(10, NULL, 0); +// int counter_id = fieldstat_easy_register_counter(fse, "metric counter"); +// // fieldstat_easy_register_histogram(fse, "metric histogram", 1, 10000, 1); +// fieldstat_easy_enable_auto_output(fse, FILENAME, 1); +// sleep(2); // long enough to output +// // 1st interval: read file, should be empty +// printf("1st interval\n"); +// cJSON *root = read_file(); +// EXPECT_EQ(cJSON_GetArraySize(root), 0); // is empty and valid +// cJSON_Delete(root); + +// fieldstat_easy_counter_incrby(fse, 0, counter_id, &TEST_TAG_INT, 1, 1); +// fieldstat_easy_counter_incrby(fse, 1, counter_id, &TEST_TAG_INT, 1, 10); +// fieldstat_easy_counter_incrby(fse, 2, counter_id, &TEST_TAG_INT, 1, 100); +// sleep(2); + +// // 2nd interval: merge 3 thread's data, and output + +// printf("2nd interval\n"); +// root = read_file(); +// EXPECT_EQ(cJSON_GetArraySize(root), 1); + +// cJSON *tagged_by_int = cJSON_GetArrayItem(root, 0); +// cJSON *metric = cJSON_GetObjectItem(tagged_by_int, "fields"); +// EXPECT_TRUE(metric != NULL); + +// long long value = cJSON_GetObjectItem(metric, "metric counter")->valueint; +// EXPECT_EQ(value, 111); +// cJSON_Delete(root); + +// // 3nd interval: no new data, just output again +// sleep(1); +// printf("3rd interval\n"); +// root = read_file(); +// EXPECT_EQ(cJSON_GetArraySize(root), 1); +// tagged_by_int = cJSON_GetArrayItem(root, 0); +// metric = cJSON_GetObjectItem(tagged_by_int, "fields"); +// value = cJSON_GetObjectItem(metric, "metric counter")->valueint; +// EXPECT_EQ(value, 111); // should not change +// cJSON_Delete(root); + +// // 4th interval: new data, output again +// fieldstat_easy_counter_incrby(fse, 0, counter_id, &TEST_TAG_DOUBLE, 1, 10086); +// sleep(2); +// printf("4th interval\n"); +// root = read_file(); +// cJSON *tagged_by_double = cJSON_GetArrayItem(root, 1); +// metric = cJSON_GetObjectItem(tagged_by_double, "fields"); +// value = cJSON_GetObjectItem(metric, "metric counter")->valueint; +// EXPECT_EQ(value, 10086); +// cJSON_Delete(root); + +// fieldstat_easy_free(fse); +// remove(FILENAME); +// } + + +// extern "C" { +// extern char *output_work(struct fieldstat_easy *fs, struct timeval *timestamp); +// } + +// int g_output_count = 0; +// char *output_work_stub(struct fieldstat_easy *fs, struct timeval *timestamp) +// { +// g_output_count++; +// return NULL; +// } + +// TEST(test_easy_fieldstat, output_interval_ok) +// { +// Stub stub; +// stub.set(output_work, output_work_stub); + +// struct fieldstat_easy *fse = fieldstat_easy_new(10, NULL, 0); +// fieldstat_easy_enable_auto_output(fse, FILENAME, 1); + +// sleep(20); // output 20s/1s = 20 times +// fieldstat_easy_free(fse); + +// EXPECT_EQ(g_output_count, 20); +// } + +TEST(test_easy_fieldstat, ensure_data_racing_of_two_output_and_of_incyby) +{ + struct fieldstat_easy *fse = fieldstat_easy_new(10, NULL, 0); + int counter_id = fieldstat_easy_register_counter(fse, "metric counter"); + int hdr_id = fieldstat_easy_register_histogram(fse, "metric histogram", 1, 10000, 1); + fieldstat_easy_enable_auto_output(fse, FILENAME, 1); + + char *out; + size_t out_len; + for (size_t i = 0; i < 1000000ULL; i++) { // loop million times to ensure no core dump + fieldstat_easy_output(fse, &out, &out_len); + fieldstat_easy_counter_incrby(fse, 0, counter_id, &TEST_TAG_INT, 1, 1); + fieldstat_easy_histogram_record(fse, 0, hdr_id, &TEST_TAG_INT, 1, rand() % 10000); + cJSON *root = cJSON_Parse(out); + EXPECT_TRUE(root != NULL); + cJSON_Delete(root); + free(out); + } + + fieldstat_easy_free(fse); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
\ No newline at end of file |
