summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchenzizhan <[email protected]>2023-11-02 16:01:26 +0800
committerchenzizhan <[email protected]>2023-11-02 16:01:26 +0800
commit4ff17defbc7df64c905210a18a9a63dd26860c3f (patch)
tree83d68232b2bd1f75d43b161382b655d32886123f
parente5139c45f2ac53b1fdf8538f01092cf4a78a32bd (diff)
fieldstat easy
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/fieldstat/fieldstat_easy.h94
-rw-r--r--include/fieldstat/fieldstat_exporter.h8
-rw-r--r--src/fieldstat_easy.c297
-rw-r--r--test/CMakeLists.txt2
-rw-r--r--test/deps/stub/addr_pri.h177
-rw-r--r--test/deps/stub/stub.h434
-rw-r--r--test/test_easy_fs.cpp164
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, &timestamp);
+ 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, &timestamp);
+ 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