├── .gitignore ├── LICENSE ├── README.md ├── c_src ├── .gitignore ├── mzmetrics.c ├── mzmetrics.h ├── mzmetrics_counters_interface.h ├── mzmetrics_counters_no_preallocation.c ├── mzmetrics_hashtable_counters.cc ├── mzmetrics_hashtable_counters.h └── mzmetrics_nif.c ├── include └── mzmetrics_header.hrl ├── rebar.config ├── src ├── mzmetrics.app.src ├── mzmetrics.erl ├── mzmetrics_app.erl ├── mzmetrics_benchmarks.erl └── mzmetrics_sup.erl └── test └── mzmetrics_counters_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | erl_crash.dump 7 | ebin 8 | rel/example_project 9 | .concrete/DEV_MODE 10 | .rebar 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Machine Zone, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of mzmetrics nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MZ Erlang Metrics 2 | 3 | Efficient per-CPU metrics (counters) for Erlang. 4 | 5 | ## Compile 6 | 7 | rebar get 8 | rebar compile 9 | 10 | ## Test 11 | 12 | rebar eunit 13 | 14 | ## Benchmark 15 | 16 | erl -pa deps/*/ebin ebin -noshell -eval "mzmetrics_benchmarks:bench(1000, 1)." -eval "init:stop()." 17 | 18 | # TODO 19 | Verify unit tests 20 | Current library supports only creating new counters 21 | -------------------------------------------------------------------------------- /c_src/.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | *.o 3 | .hg 4 | *.so 5 | -------------------------------------------------------------------------------- /c_src/mzmetrics.c: -------------------------------------------------------------------------------- 1 | #include "mzmetrics.h" 2 | 3 | int metrics_init(unsigned int num_cpus) 4 | { 5 | return capi_init_counters(num_cpus); 6 | } 7 | 8 | void metrics_deinit(void) 9 | { 10 | capi_deinit_counters(); 11 | } 12 | 13 | counter64_t metrics_update_counter(struct GrpCounter *counter, unsigned int key, int val) 14 | { 15 | return capi_update_counter(counter, key, val); 16 | } 17 | 18 | counter64_t metrics_decr_counter(struct GrpCounter *counter, unsigned int key) 19 | { 20 | return capi_decr_counter(counter, key); 21 | } 22 | 23 | counter64_t metrics_incr_counter(struct GrpCounter *counter, unsigned int key) 24 | { 25 | return capi_incr_counter(counter, key); 26 | } 27 | 28 | counter64_t metrics_get_counter_value(struct GrpCounter *counter, unsigned int key) 29 | { 30 | return capi_getval_counter(counter, key); 31 | } 32 | 33 | counter64_t metrics_reset_counter_value(struct GrpCounter *counter, unsigned int key) 34 | { 35 | return capi_resetval_counter(counter, key); 36 | } 37 | 38 | struct GrpCounter* metrics_alloc_counter(size_t num) 39 | { 40 | struct GrpCounter *counter = capi_alloc_counter(num); 41 | metrics_incr_refcnt_counter(counter); 42 | return counter; 43 | } 44 | 45 | void metrics_free_counter(struct GrpCounter *counter) 46 | { 47 | capi_free_counter(counter); 48 | } 49 | -------------------------------------------------------------------------------- /c_src/mzmetrics.h: -------------------------------------------------------------------------------- 1 | #ifndef MZMETRICS_H 2 | #define MZMETRICS_H 3 | 4 | #include 5 | #include "mzmetrics_counters_interface.h" 6 | 7 | /* 8 | * The resource names will be prepended before the counter values when we 9 | * try to get all the counter values for every resource the family. 10 | * We'll be naive and just have a fixed size prefix before the counters, 11 | * and put the resource name there. This is not compact, but good for now. 12 | * However, this does put a system limit on the maximum resource name size. 13 | */ 14 | #define MAX_RESOURCE_NAME_SIZE (1000) 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | static inline size_t max_cntr_data_per_resource(unsigned int ncounters) 21 | { 22 | return sizeof(counter64_t) * ncounters; 23 | } 24 | 25 | static inline size_t max_data_per_resource(unsigned int ncounters) 26 | { 27 | return MAX_RESOURCE_NAME_SIZE + max_cntr_data_per_resource(ncounters); 28 | } 29 | 30 | int metrics_init(unsigned int num_cpus); 31 | void metrics_deinit(void); 32 | counter64_t metrics_incr_counter(struct GrpCounter *counter, unsigned int id); 33 | counter64_t metrics_decr_counter(struct GrpCounter *counter, unsigned int id); 34 | counter64_t metrics_update_counter(struct GrpCounter *counter, 35 | unsigned int key, int val); 36 | counter64_t metrics_get_counter_value(struct GrpCounter *counter, unsigned int id); 37 | counter64_t metrics_reset_counter_value(struct GrpCounter *counter, unsigned int id); 38 | struct GrpCounter* metrics_alloc_counter(size_t num); 39 | void metrics_free_counter(struct GrpCounter *counter); 40 | static inline void metrics_incr_refcnt_counter(struct GrpCounter *counter) 41 | { 42 | __atomic_add_fetch(&counter->ref_cnt, 1, __ATOMIC_SEQ_CST); 43 | } 44 | 45 | static inline void metrics_decr_refcnt_counter(struct GrpCounter *counter) 46 | { 47 | __atomic_sub_fetch(&counter->ref_cnt, 1, __ATOMIC_SEQ_CST); 48 | } 49 | 50 | static inline size_t metrics_refcnt_counter(struct GrpCounter *counter) 51 | { 52 | return __atomic_load_n(&counter->ref_cnt, __ATOMIC_SEQ_CST); 53 | } 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | 59 | #endif /* MZMETRICS_H */ 60 | -------------------------------------------------------------------------------- /c_src/mzmetrics_counters_interface.h: -------------------------------------------------------------------------------- 1 | #ifndef MZMETRICS_COUNTERS_INTERFACE_H 2 | #define MZMETRICS_COUNTERS_INTERFACE_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * This is the interface to actually allocate memory for the counters. 9 | * Assuming that we will have two kinds of counter allocation 10 | * 1) pre-allocate a pool of memory and manage it 11 | * 2) allocate memory on the fly per resource 12 | * pcpu_counters implements 1 but doesn't have the memory management part 13 | * Implementing approach 2 is easier, going with it for now 14 | * 15 | * Another consideration is that for our use case it's better to have group of 16 | * counters rather than just one counter and having another wrapper around it. 17 | */ 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | typedef uint64_t counter64_t; 23 | 24 | struct GrpCounter { 25 | counter64_t *counter; //reference for this group 26 | size_t num_cnt; // number of counters in this group 27 | size_t ref_cnt; //reference count on this counter 28 | }; 29 | 30 | int capi_init_counters(size_t ncpus); 31 | void capi_deinit_counters(void); 32 | // num - number of counters in this group 33 | struct GrpCounter* capi_alloc_counter(size_t num); 34 | void capi_free_counter(struct GrpCounter *counter); 35 | // offset of the counter in this group that needs to be incremented 36 | counter64_t capi_incr_counter(struct GrpCounter *counter, size_t offset); 37 | // offset of the counter in this group that needs to be decremented 38 | counter64_t capi_decr_counter(struct GrpCounter *counter, size_t offset); 39 | // offset of the counter in this group that needs to be updated 40 | counter64_t capi_update_counter(struct GrpCounter *counter, size_t offset, int val); 41 | // getval for counters 42 | counter64_t capi_getval_counter(struct GrpCounter *counter, size_t offset); 43 | // getval & reset for counters 44 | counter64_t capi_resetval_counter(struct GrpCounter *counter, size_t offset); 45 | 46 | #ifdef __cplusplus 47 | } 48 | #endif 49 | 50 | #endif /* MZMETRICS_COUNTERS_INTERFACE_H */ 51 | -------------------------------------------------------------------------------- /c_src/mzmetrics_counters_no_preallocation.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L 2 | #ifdef __linux__ 3 | #define _BSD_SOURCE 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mzmetrics_counters_interface.h" 14 | #define CACHELINE_BYTES 64U 15 | 16 | static size_t num_cpus = 1; 17 | 18 | int capi_init_counters(size_t ncpus) 19 | { 20 | #ifdef PCPU_COUNTERS 21 | num_cpus = ncpus; 22 | #else 23 | num_cpus = 1; 24 | #endif 25 | return 0; 26 | } 27 | 28 | void capi_deinit_counters(void) 29 | { 30 | return; 31 | } 32 | 33 | int mycpu(void) 34 | { 35 | #ifdef PCPU_COUNTERS 36 | #ifdef __linux__ 37 | return sched_getcpu(); 38 | #else 39 | unsigned int eax, ebx, ecx, edx; 40 | if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) 41 | return 0; 42 | return 0; 43 | #endif 44 | #else 45 | return 0; 46 | #endif 47 | } 48 | 49 | // num - number of counters in this group 50 | struct GrpCounter* capi_alloc_counter(size_t num) 51 | { 52 | size_t alloc_size = num * sizeof(counter64_t); 53 | assert(0 == alloc_size % CACHELINE_BYTES); 54 | struct GrpCounter *gcounter = calloc(1, sizeof(*gcounter)); 55 | assert(gcounter); 56 | posix_memalign((void **)&gcounter->counter, CACHELINE_BYTES, alloc_size * num_cpus); 57 | assert(gcounter->counter); 58 | memset(gcounter->counter, 0, alloc_size * num_cpus); 59 | gcounter->num_cnt = num; 60 | gcounter->ref_cnt = 1; 61 | return gcounter; 62 | } 63 | 64 | void capi_free_counter(struct GrpCounter *gcounter) 65 | { 66 | //assert on ref_cnt 67 | free(gcounter->counter); 68 | free(gcounter); 69 | } 70 | 71 | // offset of the counter in this group that needs to be updated 72 | counter64_t capi_update_counter(struct GrpCounter *gcounter, size_t offset, int val) 73 | { 74 | if (val > 0) 75 | return __atomic_add_fetch(gcounter->counter + mycpu() * 76 | gcounter->num_cnt + offset, val, __ATOMIC_SEQ_CST); 77 | else 78 | return __atomic_sub_fetch(gcounter->counter + mycpu() * 79 | gcounter->num_cnt + offset, -val, __ATOMIC_SEQ_CST); 80 | } 81 | 82 | // offset of the counter in this group that needs to be incremented 83 | counter64_t capi_incr_counter(struct GrpCounter *gcounter, size_t offset) 84 | { 85 | return __atomic_add_fetch(gcounter->counter + mycpu() * 86 | gcounter->num_cnt + offset, 1, __ATOMIC_SEQ_CST); 87 | } 88 | 89 | // offset of the counter in this group that needs to be decremented 90 | counter64_t capi_decr_counter(struct GrpCounter *gcounter, size_t offset) 91 | { 92 | return __atomic_sub_fetch(gcounter->counter + mycpu() * 93 | gcounter->num_cnt + offset, 1, __ATOMIC_SEQ_CST); 94 | } 95 | 96 | // getval for counters 97 | counter64_t capi_getval_counter(struct GrpCounter *gcounter, size_t offset) 98 | { 99 | counter64_t ret = 0; 100 | #ifndef PCPU_COUNTERS 101 | __atomic_load(gcounter->counter + mycpu() * 102 | gcounter->num_cnt + offset, &ret, __ATOMIC_SEQ_CST); 103 | #else 104 | for (size_t i = 0; i < num_cpus; i++) { 105 | counter64_t temp; 106 | __atomic_load(gcounter->counter + i * 107 | gcounter->num_cnt + offset, &temp, __ATOMIC_SEQ_CST); 108 | ret += temp; 109 | } 110 | #endif 111 | return ret; 112 | } 113 | 114 | // exchange value for counters 115 | counter64_t capi_resetval_counter(struct GrpCounter *gcounter, size_t offset) 116 | { 117 | counter64_t ret = 0; 118 | #ifndef PCPU_COUNTERS 119 | ret = __atomic_exchange_n(gcounter->counter + mycpu() * 120 | gcounter->num_cnt + offset, 0ULL, __ATOMIC_SEQ_CST); 121 | #else 122 | for (size_t i = 0; i < num_cpus; i++) { 123 | ret += __atomic_exchange_n(gcounter->counter + i * 124 | gcounter->num_cnt + offset, 0ULL, __ATOMIC_SEQ_CST); 125 | } 126 | #endif 127 | return ret; 128 | } 129 | -------------------------------------------------------------------------------- /c_src/mzmetrics_hashtable_counters.cc: -------------------------------------------------------------------------------- 1 | // This is a wrapper around standard c++ unordered_map 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "mzmetrics.h" 7 | #include "mzmetrics_hashtable_counters.h" 8 | 9 | // FIXME: Please rename me 10 | static const uint64_t g_mult_threshold = 50; 11 | typedef std::unordered_map counters_map_t; 12 | 13 | struct hashtable_env { 14 | counters_map_t **counters_map; 15 | size_t num_families; 16 | }; 17 | 18 | static inline counters_map_t* my_counters_map(struct hashtable_env *env, 19 | size_t family) 20 | { 21 | assert(family < env->num_families); 22 | return env->counters_map[family]; 23 | } 24 | 25 | extern "C" 26 | struct hashtable_env* create_hashtable(unsigned int *num_counters, 27 | size_t num_families) 28 | { 29 | counters_map_t **counters_map; 30 | counters_map = new counters_map_t*[num_families]; 31 | assert(counters_map); 32 | for (size_t i = 0; i < num_families; i++) { 33 | counters_map[i] = new counters_map_t(num_counters[i] * g_mult_threshold); 34 | assert(counters_map[i]); 35 | } 36 | struct hashtable_env *env = new hashtable_env(); 37 | env->num_families = num_families; 38 | env->counters_map = counters_map; 39 | return env; 40 | } 41 | 42 | extern "C" 43 | void delete_hashtables(struct hashtable_env *env) 44 | { 45 | for (size_t i = 0; i < env->num_families; i++) { 46 | delete env->counters_map[i]; 47 | env->counters_map[i] = NULL; 48 | } 49 | delete[] env->counters_map; 50 | delete env; 51 | } 52 | 53 | extern "C" 54 | struct GrpCounter* lookup_counter(struct hashtable_env *env, char *resource_name, size_t family) 55 | { 56 | auto counters_map = my_counters_map(env, family); 57 | counters_map_t::const_iterator entry = counters_map->find(resource_name); 58 | if (entry == counters_map->end()) 59 | return NULL; 60 | return entry->second; 61 | } 62 | 63 | extern "C" 64 | int insert_counter(struct hashtable_env *env, char *resource_name, 65 | struct GrpCounter* gcounter, size_t family) 66 | { 67 | auto counters_map = my_counters_map(env, family); 68 | counters_map_t::const_iterator entry = counters_map->find(resource_name); 69 | if (entry != counters_map->end()) 70 | return -1; 71 | (*counters_map)[resource_name] = gcounter; 72 | return 0; 73 | } 74 | 75 | extern "C" 76 | size_t get_size(struct hashtable_env *env, size_t family) 77 | { 78 | return my_counters_map(env, family)->size(); 79 | } 80 | 81 | extern "C" 82 | void get_all_counters(struct hashtable_env *env, unsigned char *data, 83 | size_t family, unsigned int options, 84 | unsigned int ncounters) 85 | { 86 | unsigned int offset = 0; 87 | auto counters_map = my_counters_map(env, family); 88 | auto metrics_val_fn = metrics_get_counter_value; 89 | if (options == 1) 90 | metrics_val_fn = metrics_reset_counter_value; 91 | // Iterate over all the elements and get counter values 92 | for (auto local_iter = counters_map->begin(); 93 | local_iter != counters_map->end(); ) { 94 | //add at offset & increment offset 95 | // 96 | strcpy((char *) data + offset, local_iter->first.c_str()); 97 | offset += MAX_RESOURCE_NAME_SIZE; 98 | auto grp_counter = local_iter->second; 99 | counter64_t *counter_data = (counter64_t *)(data + offset); 100 | assert(grp_counter->num_cnt <= ncounters); 101 | for (unsigned int j = 0; j < grp_counter->num_cnt; j++) { 102 | *counter_data = metrics_val_fn(grp_counter, j); 103 | counter_data++; 104 | } 105 | local_iter++; 106 | offset += max_cntr_data_per_resource(ncounters); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /c_src/mzmetrics_hashtable_counters.h: -------------------------------------------------------------------------------- 1 | #ifndef MZMETRICS_HASHTABLE_COUNTERS 2 | #define MZMETRICS_HASHTABLE_COUNTERS 3 | 4 | #include "mzmetrics_counters_interface.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct hashtable_env; 11 | struct hashtable_env *create_hashtable(unsigned int *num_counters, 12 | size_t num_families); 13 | void delete_hashtables(struct hashtable_env *env); 14 | 15 | struct GrpCounter *lookup_counter(struct hashtable_env *env, 16 | char *resource_name, size_t family); 17 | int insert_counter(struct hashtable_env *env, char *resource_name, 18 | struct GrpCounter *gcounter, size_t family); 19 | void get_all_counters(struct hashtable_env *env, unsigned char *data, 20 | size_t family, unsigned int options, 21 | unsigned int ncounters); 22 | size_t get_size(struct hashtable_env *env, size_t family); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif /* MZMETRICS_HASHTABLE_COUNTERS */ 29 | -------------------------------------------------------------------------------- /c_src/mzmetrics_nif.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mzmetrics.h" 5 | #include "mzmetrics_hashtable_counters.h" 6 | #include "erl_nif.h" 7 | 8 | static const char *g_my_mod_name = "mzmetrics"; 9 | static const char *g_resource_res_name = "counters"; 10 | 11 | struct metrics_resource { 12 | ErlNifResourceType *res_type; 13 | ErlNifResourceDtor *res_dtor; 14 | }; 15 | 16 | struct metrics_env { 17 | ErlNifRWLock **hashtable_lock; 18 | unsigned int num_families; 19 | unsigned int *num_resources; 20 | void *hashtable_env; 21 | struct metrics_resource metrics_module; 22 | }; 23 | 24 | static struct metrics_resource my_metrics_module(ErlNifEnv *env) 25 | { 26 | struct metrics_env* priv= enif_priv_data(env); 27 | return priv->metrics_module; 28 | } 29 | 30 | static ErlNifRWLock **my_rwlocks(ErlNifEnv *env) 31 | { 32 | struct metrics_env* priv= enif_priv_data(env); 33 | return priv->hashtable_lock; 34 | } 35 | 36 | static unsigned int my_num_families(ErlNifEnv *env) 37 | { 38 | struct metrics_env* priv= enif_priv_data(env); 39 | return priv->num_families; 40 | } 41 | 42 | static unsigned int* my_num_resources(ErlNifEnv *env) 43 | { 44 | struct metrics_env* priv= enif_priv_data(env); 45 | return priv->num_resources; 46 | } 47 | 48 | static struct hashtable_env* my_hashtable_env(ErlNifEnv *env) 49 | { 50 | struct metrics_env* priv= enif_priv_data(env); 51 | return priv->hashtable_env; 52 | } 53 | 54 | // Does the counter need to know which family it corresponds to 55 | struct CounterResource { 56 | struct GrpCounter *counter; 57 | char resource_name[MAX_RESOURCE_NAME_SIZE + 1]; 58 | }; 59 | 60 | static void counter_resource_destructor(ErlNifEnv *env, void *obj) 61 | { 62 | struct CounterResource *cnt_res = (struct CounterResource*) obj; 63 | struct GrpCounter *counter = cnt_res->counter; 64 | metrics_decr_refcnt_counter(counter); 65 | } 66 | 67 | unsigned int align_num_resources(unsigned int num) 68 | { 69 | #define CACHELINE_BYTES 64U 70 | unsigned int size = num * sizeof(counter64_t); 71 | return ((size + CACHELINE_BYTES - 1) & ~(CACHELINE_BYTES - 1)) / sizeof(counter64_t); 72 | } 73 | 74 | static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) 75 | { 76 | #define NUM_ARGS (3) 77 | #define NIF_RSRC_FLAGS (ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER) 78 | const ERL_NIF_TERM *args = NULL, *counters = NULL; 79 | unsigned int ncpus, num_families; 80 | int arity, ncounters; 81 | unsigned int *num_resources; 82 | struct metrics_resource metrics_module = { 0, 0 }; 83 | ErlNifRWLock **hashtable_lock; 84 | 85 | if (!enif_get_tuple(env, load_info, &arity, &args) || 86 | arity != NUM_ARGS) 87 | return enif_make_badarg(env); 88 | if (!enif_get_uint(env, args[0], &ncpus)) 89 | return enif_make_badarg(env); 90 | if (!enif_get_uint(env, args[1], &num_families)) 91 | return enif_make_badarg(env); 92 | assert(num_families); 93 | if (!enif_get_tuple(env, args[2], &ncounters, &counters)) 94 | return enif_make_badarg(env); 95 | assert(ncounters == num_families); 96 | num_resources = calloc(num_families, sizeof(*num_resources)); 97 | assert(num_resources); 98 | for (int i = 0; i < ncounters; i++) { 99 | if (!enif_get_uint(env, counters[i], num_resources + i)) { 100 | free(num_resources); 101 | return enif_make_badarg(env); 102 | } 103 | num_resources[i] = align_num_resources(num_resources[i]); 104 | } 105 | metrics_module.res_type = enif_open_resource_type(env, g_my_mod_name, 106 | g_resource_res_name, counter_resource_destructor, 107 | NIF_RSRC_FLAGS, NULL); 108 | assert(metrics_module.res_type); 109 | hashtable_lock = calloc(num_families, sizeof(*hashtable_lock)); 110 | assert(hashtable_lock); 111 | for (int i = 0; i < num_families; i++) { 112 | hashtable_lock[i] = enif_rwlock_create(NULL); 113 | assert(hashtable_lock[i]); 114 | } 115 | assert(0 == metrics_init(ncpus)); 116 | struct hashtable_env *henv = create_hashtable(num_resources, num_families); 117 | struct metrics_env *my_priv = calloc(1, sizeof(*my_priv)); 118 | assert(my_priv); 119 | my_priv->num_resources = num_resources; 120 | my_priv->num_families = num_families; 121 | my_priv->hashtable_lock = hashtable_lock; 122 | my_priv->metrics_module = metrics_module; 123 | my_priv->hashtable_env = henv; 124 | *priv = my_priv; 125 | return 0; 126 | } 127 | 128 | static int upgrade(ErlNifEnv * env, void ** priv, void ** old_priv, 129 | ERL_NIF_TERM load_info) 130 | { 131 | return load(env, priv, load_info); 132 | } 133 | 134 | static void unload(ErlNifEnv * env, void * priv) 135 | { 136 | assert(enif_priv_data(env)==priv); 137 | struct metrics_env* my_priv = priv; 138 | delete_hashtables(my_priv->hashtable_env); 139 | metrics_deinit(); 140 | for (int i = 0; i < my_priv->num_families;i++) { 141 | enif_rwlock_destroy(my_priv->hashtable_lock[i]); 142 | } 143 | free(my_priv->hashtable_lock); 144 | free(my_priv->num_resources); 145 | free(my_priv); 146 | return; 147 | } 148 | 149 | static ERL_NIF_TERM metrics_alloc_resource_nif(ErlNifEnv* env, int argc, 150 | const ERL_NIF_TERM argv[]) 151 | { 152 | unsigned int num_cntrs; 153 | int name_size = 0; 154 | unsigned int family; 155 | struct CounterResource *res = 156 | (struct CounterResource*) enif_alloc_resource( 157 | my_metrics_module(env).res_type, sizeof(*res)); 158 | assert(res); 159 | ERL_NIF_TERM res_term = enif_make_resource(env, res); 160 | 161 | if (!enif_get_uint(env, argv[0], &family) || family >= my_num_families(env)) 162 | return enif_make_badarg(env); 163 | 164 | // FIXME: What encoding to use 165 | if (!(name_size = enif_get_string(env, argv[1], res->resource_name, 166 | MAX_RESOURCE_NAME_SIZE, ERL_NIF_LATIN1))) 167 | return enif_make_badarg(env); 168 | 169 | if (!enif_get_uint(env, argv[2], &num_cntrs) || 170 | num_cntrs > my_num_resources(env)[family]) 171 | return enif_make_badarg(env); 172 | 173 | if (name_size < 0) { 174 | // Add a debug print about this 175 | name_size = MAX_RESOURCE_NAME_SIZE; 176 | res->resource_name[MAX_RESOURCE_NAME_SIZE] = '\0'; 177 | } 178 | enif_rwlock_rwlock(my_rwlocks(env)[family]); 179 | res->counter = lookup_counter(my_hashtable_env(env), res->resource_name, family); 180 | if (!res->counter) { 181 | res->counter = metrics_alloc_counter(align_num_resources(num_cntrs)); 182 | assert(0 == insert_counter(my_hashtable_env(env), res->resource_name, res->counter, family)); 183 | } else { 184 | metrics_incr_refcnt_counter(res->counter); 185 | } 186 | enif_rwlock_rwunlock(my_rwlocks(env)[family]); 187 | enif_release_resource(res); 188 | return res_term; 189 | 190 | } 191 | 192 | static ERL_NIF_TERM metrics_update_resource_counter_nif(ErlNifEnv* env, int argc, 193 | const ERL_NIF_TERM argv[]) 194 | { 195 | int ret; 196 | unsigned int key; 197 | int val; 198 | struct CounterResource *resource = NULL; 199 | if (!enif_get_resource(env, argv[0], my_metrics_module(env).res_type, 200 | (void**) &resource)) 201 | return enif_make_badarg(env); 202 | if (!enif_get_uint(env, argv[1], &key)) 203 | return enif_make_badarg(env); 204 | if (key >= resource->counter->num_cnt) 205 | return enif_make_badarg(env); 206 | if (!enif_get_int(env, argv[2], &val)) 207 | return enif_make_badarg(env); 208 | ret = metrics_update_counter(resource->counter, key, val); 209 | return enif_make_uint64(env, ret); 210 | } 211 | 212 | static ERL_NIF_TERM metrics_decr_resource_counter_nif(ErlNifEnv* env, int argc, 213 | const ERL_NIF_TERM argv[]) 214 | { 215 | int ret; 216 | unsigned int key; 217 | struct CounterResource *resource = NULL; 218 | if (!enif_get_resource(env, argv[0], my_metrics_module(env).res_type, 219 | (void**) &resource)) 220 | return enif_make_badarg(env); 221 | if (!enif_get_uint(env, argv[1], &key)) 222 | return enif_make_badarg(env); 223 | if (key >= resource->counter->num_cnt) 224 | return enif_make_badarg(env); 225 | ret = metrics_decr_counter(resource->counter, key); 226 | return enif_make_uint64(env, ret); 227 | } 228 | 229 | static ERL_NIF_TERM metrics_incr_resource_counter_nif(ErlNifEnv* env, int argc, 230 | const ERL_NIF_TERM argv[]) 231 | { 232 | int ret; 233 | unsigned int key; 234 | struct CounterResource *resource = NULL; 235 | if (!enif_get_resource(env, argv[0], my_metrics_module(env).res_type, 236 | (void**) &resource)) 237 | return enif_make_badarg(env); 238 | if (!enif_get_uint(env, argv[1], &key)) 239 | return enif_make_badarg(env); 240 | if (key >= resource->counter->num_cnt) 241 | return enif_make_badarg(env); 242 | 243 | ret = metrics_incr_counter(resource->counter, key); 244 | return enif_make_uint64(env, ret); 245 | } 246 | 247 | static ERL_NIF_TERM metrics_get_resource_counter_nif(ErlNifEnv* env, int argc, 248 | const ERL_NIF_TERM argv[]) 249 | { 250 | int ret; 251 | unsigned int key; 252 | struct CounterResource *resource = NULL; 253 | if (!enif_get_resource(env, argv[0], my_metrics_module(env).res_type, 254 | (void**) &resource)) 255 | return enif_make_badarg(env); 256 | if (!enif_get_uint(env, argv[1], &key)) 257 | return enif_make_badarg(env); 258 | if (key >= resource->counter->num_cnt) 259 | return enif_make_badarg(env); 260 | 261 | ret = metrics_get_counter_value(resource->counter, key); 262 | return enif_make_uint64(env, ret); 263 | } 264 | 265 | static ERL_NIF_TERM metrics_reset_resource_counter_nif(ErlNifEnv* env, int argc, 266 | const ERL_NIF_TERM argv[]) 267 | { 268 | int ret; 269 | unsigned int key; 270 | struct CounterResource *resource = NULL; 271 | if (!enif_get_resource(env, argv[0], my_metrics_module(env).res_type, 272 | (void**) &resource)) 273 | return enif_make_badarg(env); 274 | if (!enif_get_uint(env, argv[1], &key)) 275 | return enif_make_badarg(env); 276 | if (key >= resource->counter->num_cnt) 277 | return enif_make_badarg(env); 278 | 279 | ret = metrics_reset_counter_value(resource->counter, key); 280 | return enif_make_uint64(env, ret); 281 | } 282 | 283 | static ERL_NIF_TERM metrics_get_all_resources(ErlNifEnv* env, int argc, 284 | const ERL_NIF_TERM argv[]) 285 | { 286 | unsigned int family, options; 287 | if (!enif_get_uint(env, argv[0], &family)) 288 | return enif_make_badarg(env); 289 | if (!enif_get_uint(env, argv[1], &options)) 290 | return enif_make_badarg(env); 291 | assert(family < my_num_families(env)); 292 | ERL_NIF_TERM binary; 293 | unsigned char *data; 294 | size_t data_size; 295 | enif_rwlock_rwlock(my_rwlocks(env)[family]); 296 | data_size = max_data_per_resource(my_num_resources(env)[family]) * 297 | get_size(my_hashtable_env(env), family); 298 | data = enif_make_new_binary(env, data_size, &binary); 299 | assert(data); 300 | get_all_counters(my_hashtable_env(env), data, family, options, my_num_resources(env)[family]); 301 | enif_rwlock_rwunlock(my_rwlocks(env)[family]); 302 | return binary; 303 | } 304 | 305 | static ErlNifFunc nif_funcs[] = { 306 | {"alloc_resource", 3, metrics_alloc_resource_nif}, 307 | {"update_resource_counter", 3, metrics_update_resource_counter_nif}, 308 | {"incr_resource_counter", 2, metrics_incr_resource_counter_nif}, 309 | {"decr_resource_counter", 2, metrics_decr_resource_counter_nif}, 310 | {"get_resource_counter", 2, metrics_get_resource_counter_nif}, 311 | {"reset_resource_counter", 2, metrics_reset_resource_counter_nif}, 312 | {"get_all_resources", 2, metrics_get_all_resources}, 313 | }; 314 | 315 | ERL_NIF_INIT(mzmetrics, nif_funcs, load, NULL, upgrade, unload); 316 | -------------------------------------------------------------------------------- /include/mzmetrics_header.hrl: -------------------------------------------------------------------------------- 1 | -ifndef('mzmetrics_header.hrl'). 2 | -define('mzmetrics_header.hrl', include_protection). 3 | 4 | %% A metric Family is the highest level namespace for the metrics activity. 5 | %% Metrics in one Family are different from metrics in other families. 6 | -type family_name() :: non_neg_integer(). 7 | 8 | %% A resource is a user-level addressable entity for which we keep different 9 | %% counter values. The kinds of counters that we keep for a resource are fixed 10 | %% per family. Typically you'd have the names of your units of accounting 11 | %% (such as domain names, rabbit names, car plates) as resources, 12 | %% and each of these would have a few counters, the list of which is the same 13 | %% across all such resources (across all domains, etc) in one family. 14 | %% 15 | %% Think of family as a hash and resource_name() as a hash key. 16 | -type resource_name() :: nonempty_string(). 17 | 18 | %% Maximum number of families supported by the runtime. 19 | %% Feel free to change that, but it'll require a VM reload to affect change. 20 | -define(MAX_FAMILIES, 2). 21 | 22 | %% For each Rabbit we define 8 counters. 23 | -define(METRICS_FAMILY_RABBITS, 0). 24 | -define(MAX_CNTRS_PER_RABBIT, 8). 25 | 26 | %% For each Car we define 8 counters. 27 | -define(METRICS_FAMILY_CARS, 1). 28 | -define(MAX_CNTRS_PER_CAR, 8). 29 | 30 | -define(MAX_CNTRS_PER_UNKNOWN_FAMILY, 8). 31 | 32 | %% The Flags argument to mzmetrics:get_all_resources(, Flags :: integer()). 33 | -define(CNTR_RESET, 1). 34 | -define(CNTR_DONT_RESET, 0). 35 | 36 | -endif. 37 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {port_specs, 2 | [ 3 | {"priv/mzmetrics.so", 4 | ["c_src/mzmetrics.c", 5 | "c_src/mzmetrics_nif.c", 6 | "c_src/mzmetrics_counters_no_preallocation.c", 7 | "c_src/mzmetrics_hashtable_counters.cc" 8 | ]} 9 | ]}. 10 | 11 | {port_env, 12 | [ 13 | {"CC", "gcc"}, 14 | {"CXX", "g++"}, 15 | {"CFLAGS", "-DPCPU_COUNTERS -O3 -std=c99 -Werror -Wall"}, 16 | {"CXXFLAGS", "-DPCPU_COUNTERS -O3 -std=c++11 -Wno-write-strings"}, 17 | {"LDFLAGS", "-lstdc++"} 18 | ]}. 19 | 20 | {deps, 21 | [ 22 | {lager, ".*", {git, "https://github.com/quasiconvex/lager.git"}}, 23 | {setup, ".*", {git, "https://github.com/uwiger/setup.git", {tag, "1.4"}}}, 24 | {parse_trans, ".*", {git, "https://github.com/uwiger/parse_trans.git", {tag, "2.9"}}}, 25 | % 26 | % The following entries are needed for benchmarking only. 27 | % 28 | {folsom, ".*", {git, "https://github.com/boundary/folsom.git", {tag, "0.8.2"}}}, 29 | {exometer_core, ".*", {git, "https://github.com/Feuerlabs/exometer_core", "35e2f61990b99de66f14aa702af5767b9f5d65c5"}} 30 | ]}. 31 | -------------------------------------------------------------------------------- /src/mzmetrics.app.src: -------------------------------------------------------------------------------- 1 | {application, mzmetrics, 2 | [ 3 | {description, "Performant metric collection API"}, 4 | {vsn, "0.0.2"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { mzmetrics_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/mzmetrics.erl: -------------------------------------------------------------------------------- 1 | -module(mzmetrics). 2 | -export([alloc_resource/3, incr_resource_counter/2, num_counters/1]). 3 | -export([decr_resource_counter/2, get_resource_counter/2]). 4 | -export([reset_resource_counter/2, update_resource_counter/3, get_all_resources/2]). 5 | -export([report_all_counters/3]). 6 | 7 | -include("mzmetrics_header.hrl"). 8 | 9 | -on_load(init/0). 10 | -define(NOT_LOADED, not_loaded(?LINE)). 11 | not_loaded(Line) -> 12 | erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). 13 | 14 | -define(CACHELINE_BYTES, 64). 15 | -define(COUNTER_SIZE, 8). 16 | align_cntr(X) -> ((X * ?COUNTER_SIZE + ?CACHELINE_BYTES - 1) band bnot(?CACHELINE_BYTES - 1)) div ?COUNTER_SIZE. 17 | 18 | -spec num_counters(Family::non_neg_integer()) -> NumCounters::non_neg_integer(). 19 | num_counters(Family) -> 20 | case Family of 21 | ?METRICS_FAMILY_RABBITS -> 22 | align_cntr(?MAX_CNTRS_PER_RABBIT); 23 | ?METRICS_FAMILY_CARS -> 24 | align_cntr(?MAX_CNTRS_PER_CAR); 25 | _ -> 26 | align_cntr(?MAX_CNTRS_PER_UNKNOWN_FAMILY) 27 | end. 28 | 29 | init() -> 30 | PrivDir = case code:priv_dir(?MODULE) of 31 | {error, _} -> 32 | EbinDir = filename:dirname(code:which(?MODULE)), 33 | AppPath = filename:dirname(EbinDir), 34 | filename:join(AppPath, "priv"); 35 | Path -> 36 | Path 37 | end, 38 | CountersTuple = list_to_tuple([num_counters(Family) || Family <- lists:seq(0, ?MAX_FAMILIES - 1)]), 39 | erlang:load_nif(filename:join(PrivDir, "mzmetrics"), 40 | {ncpus(), ?MAX_FAMILIES, CountersTuple}). 41 | 42 | ncpus() -> 43 | case erlang:system_info(logical_processors_available) of 44 | unknown -> % Happens on Mac OS X. 45 | erlang:system_info(schedulers); 46 | N -> N 47 | end. 48 | 49 | -spec alloc_resource(Family::non_neg_integer(), 50 | ResourceName::resource_name(), 51 | NumCounters::non_neg_integer()) -> 52 | Res::binary() | no_return(). 53 | alloc_resource(_X, _Y, _Z) -> 54 | ?NOT_LOADED. 55 | 56 | -spec update_resource_counter(ResourceName::resource_name(), Offset::non_neg_integer(), Val::integer()) -> 57 | Val::non_neg_integer() | no_return(). 58 | update_resource_counter(_X, _Y, _Z) -> 59 | ?NOT_LOADED. 60 | 61 | -spec incr_resource_counter(ResourceName::resource_name(), Offset::non_neg_integer()) -> 62 | Val::non_neg_integer() | no_return(). 63 | incr_resource_counter(_X, _Y) -> 64 | ?NOT_LOADED. 65 | 66 | -spec decr_resource_counter(ResourceName::resource_name(), Offset::non_neg_integer()) -> 67 | Val::non_neg_integer() | no_return(). 68 | decr_resource_counter(_X, _Y) -> 69 | ?NOT_LOADED. 70 | 71 | -spec get_resource_counter(ResourceName::resource_name(), Offset::non_neg_integer()) -> 72 | Val::non_neg_integer() | no_return(). 73 | get_resource_counter(_X, _Y) -> 74 | ?NOT_LOADED. 75 | 76 | -spec reset_resource_counter(ResourceName::resource_name(), Offset::non_neg_integer()) -> 77 | Val::non_neg_integer() | no_return(). 78 | reset_resource_counter(_X, _Y) -> 79 | ?NOT_LOADED. 80 | 81 | -spec get_all_resources(Family::non_neg_integer(), Options::non_neg_integer()) -> 82 | binary() | no_return(). 83 | get_all_resources(_X, _Y) -> 84 | ?NOT_LOADED. 85 | 86 | -define(CounterDataSize, 64). 87 | -define(ResourceNameSize, 1000). 88 | -define(BinDataSizePerResource, 1064). 89 | 90 | json_ensure_binary(Bin) when is_binary(Bin) -> 91 | Bin; 92 | json_ensure_binary(Bin) when is_list(Bin) -> 93 | iolist_to_binary(Bin). 94 | 95 | -spec report_all_counters(Family::family_name(), [ReportFun::fun()], JsonFun::fun()) -> ok. 96 | report_all_counters(Family, ReportFuns, JsonFun) -> 97 | C = mzmetrics:get_all_resources(Family, ?CNTR_DONT_RESET), 98 | NumResources = byte_size(C) div ?BinDataSizePerResource, 99 | extract_resource_data(ReportFuns, JsonFun, NumResources, C). 100 | 101 | extract_counter_values(BinData) -> 102 | <> = binary:part(BinData, 0, 8), 103 | <> = binary:part(BinData, 8, 8), 104 | <> = binary:part(BinData, 16, 8), 105 | {C1, C2, C3}. 106 | 107 | extract_resource_name(BinData) -> 108 | [ResourceName|_] = binary:split(BinData, [<<0>>]), 109 | ResourceName. 110 | 111 | extract_resource_data(_, _, 0, _) -> 112 | ok; 113 | extract_resource_data(ReportFuns, JsonFun, N, BinData) -> 114 | {C1, C2, C3} = extract_counter_values(binary:part(BinData, ?ResourceNameSize, ?CounterDataSize)), 115 | ResourceName = extract_resource_name(BinData), 116 | JSON = json_ensure_binary(JsonFun(ResourceName, C1, C2, C3)), 117 | [ ok = ReportFun(JSON) || ReportFun <- ReportFuns ], 118 | extract_resource_data(ReportFuns, JsonFun, N-1, binary:part(BinData, ?BinDataSizePerResource, byte_size(BinData) - ?BinDataSizePerResource)). 119 | -------------------------------------------------------------------------------- /src/mzmetrics_app.erl: -------------------------------------------------------------------------------- 1 | -module(mzmetrics_app). 2 | 3 | -behaviour(application). 4 | 5 | -type start_args() :: any(). 6 | -type app_state() :: any(). 7 | 8 | %% Application callbacks 9 | -export([start/2, stop/1]). 10 | 11 | %% =================================================================== 12 | %% Application callbacks 13 | %% =================================================================== 14 | -spec start(application:start_type(), start_args()) -> 15 | mzmetrics_sup:start_link(). 16 | start(_StartType, _StartArgs) -> 17 | mzmetrics_sup:start_link(). 18 | 19 | -spec stop(app_state()) -> application:stop(). 20 | stop(_State) -> 21 | ok. 22 | -------------------------------------------------------------------------------- /src/mzmetrics_benchmarks.erl: -------------------------------------------------------------------------------- 1 | %% $ erl -pa deps/*/ebin ebin -noshell -eval "mzmetrics_benchmarks:bench(1000000, 1)." -eval "init:stop()." 2 | %% 3 | 4 | -module(mzmetrics_benchmarks). 5 | 6 | -export([bench/2, bench/3, fc/0]). 7 | 8 | -define(START, start). 9 | -record('DOWN', 10 | {ref :: reference(), 11 | type :: process, 12 | object :: pid() | atom(), 13 | info :: noproc | noconnection | {'EXIT', pid(), any()} 14 | }). 15 | 16 | flush() -> 17 | receive _ -> flush() 18 | after 0 -> ok 19 | end. 20 | 21 | 22 | go(N, Pid, Ref) -> 23 | A = os:timestamp(), 24 | Pid ! ?START, 25 | receive 26 | #'DOWN'{ref = Ref} -> 27 | B = os:timestamp(), 28 | flush(), 29 | (N * 1000000) div timer:now_diff(B, A) 30 | end. 31 | 32 | run_one_loop(N, LoopFun, State) -> 33 | {CallerPid, CallerMon} = erlang:spawn_monitor( 34 | fun() -> 35 | receive 36 | start -> 37 | LoopFun(N, State) 38 | end 39 | end), 40 | go(N, CallerPid, CallerMon). 41 | 42 | gather_results([]) -> 43 | []; 44 | gather_results([Pid | Pids]) -> 45 | receive 46 | {Pid, Result} -> 47 | [Result | gather_results(Pids)] 48 | end. 49 | 50 | concurrently(C, Fun) -> 51 | concurrently(C, Fun, []). 52 | 53 | concurrently(C, Fun, Acc) when C > 0 -> 54 | Parent = self(), 55 | Pid = erlang:spawn(fun() -> 56 | Parent ! {self(), Fun()} 57 | end), 58 | concurrently(C - 1, Fun, [Pid | Acc]); 59 | concurrently(0, _Fun, Acc) -> 60 | gather_results(Acc). 61 | 62 | do_bench(N, C, Setup, LoopFun) -> 63 | do_bench_1(fun() -> 64 | State = Setup(), 65 | Results = concurrently( 66 | C, 67 | fun() -> 68 | run_one_loop(N, LoopFun, State) 69 | end), 70 | lists:sum(Results) div C 71 | end). 72 | 73 | do_bench_1(Fun) -> 74 | Parent = self(), 75 | {Pid, Monitor} = spawn_monitor( 76 | fun() -> 77 | Parent ! {result, self(), Fun()} 78 | end), 79 | receive 80 | {result, Pid, Result} -> 81 | Result; 82 | {'DOWN', Monitor, _, _, Reason} -> 83 | {error, {benchmark_failed, Reason}} 84 | end. 85 | 86 | -spec fc() -> ok. 87 | fc() -> 88 | ok. 89 | 90 | metrics_update_counter_loop(N, Name) when N > 0 -> 91 | mzmetrics:incr_resource_counter(Name, 0), 92 | metrics_update_counter_loop(N - 1, Name); 93 | metrics_update_counter_loop(_, _) -> 94 | ok. 95 | 96 | repeat_loop(Name, N, C, 1, Acc) -> 97 | Fun = atom_to_fun(Name), 98 | [Fun(N, C) | Acc]; 99 | repeat_loop(Name, N, C, R, Acc) -> 100 | Fun = atom_to_fun(Name), 101 | repeat_loop(Name, N, C, R-1, [Fun(N, C) | Acc]). 102 | 103 | metrics_update_counter(N, C) -> 104 | do_bench( 105 | N, 106 | C, 107 | fun() -> 108 | mzmetrics:alloc_resource(0, "myrandomstring", 8) 109 | end, 110 | fun metrics_update_counter_loop/2). 111 | 112 | -define(BENCH(Name, N, C, R), 113 | io:format("~p(~p, ~p) -> ~p/sec~n", [Name, N, C, lists:sum(repeat_loop(Name, N, C, R, [])) div R])). 114 | 115 | exometer_counter_update(N, C) -> 116 | do_bench( 117 | N, 118 | C, 119 | fun() -> 120 | Name = [make_ref()], 121 | exometer:new(Name, counter), 122 | Name 123 | end, 124 | fun exometer_update_loop/2). 125 | 126 | exometer_update_loop(N, Name) when N > 0 -> 127 | exometer:update(Name, 1), 128 | exometer_update_loop(N - 1, Name); 129 | exometer_update_loop(_, _) -> 130 | ok. 131 | 132 | exometer_fast_counter_update(N, C) -> 133 | do_bench( 134 | N, 135 | C, 136 | fun() -> 137 | Name = [make_ref()], 138 | exometer:new(Name, fast_counter, [{function, {?MODULE, fc}}]), 139 | Name 140 | end, 141 | fun exometer_update_loop/2). 142 | 143 | ets_update_counter_loop(N, Name) when N > 0 -> 144 | ets:update_counter(exometer_util:table(), Name, {6, 1}), 145 | ets_update_counter_loop(N - 1, Name); 146 | ets_update_counter_loop(_, _) -> 147 | ok. 148 | 149 | ets_update_counter(N, C) -> 150 | do_bench( 151 | N, 152 | C, 153 | fun() -> 154 | Name = [make_ref()], 155 | exometer:new(Name, counter), 156 | Name 157 | end, 158 | fun ets_update_counter_loop/2). 159 | 160 | folsom_update_counter_loop(N, Name) when N > 0 -> 161 | folsom_metrics:notify({Name, {inc, 1}}), 162 | folsom_update_counter_loop(N - 1, Name); 163 | folsom_update_counter_loop(_, _) -> 164 | ok. 165 | 166 | folsom_update_counter(N, C) -> 167 | do_bench( 168 | N, 169 | C, 170 | fun() -> 171 | Name = [make_ref()], 172 | folsom_metrics:new_counter(Name), 173 | Name 174 | end, 175 | fun folsom_update_counter_loop/2). 176 | 177 | atom_to_fun(metrics_update_counter) -> 178 | fun metrics_update_counter/2; 179 | atom_to_fun(ets_update_counter) -> 180 | fun ets_update_counter/2; 181 | atom_to_fun(folsom_update_counter) -> 182 | fun folsom_update_counter/2; 183 | atom_to_fun(exometer_fast_counter_update) -> 184 | fun exometer_fast_counter_update/2; 185 | atom_to_fun(exometer_counter_update) -> 186 | fun exometer_counter_update/2. 187 | 188 | -spec bench(non_neg_integer(), non_neg_integer()) -> ok. 189 | bench(N, C) -> bench(N, C, 1). 190 | 191 | -spec bench(non_neg_integer(), non_neg_integer(), non_neg_integer()) -> ok. 192 | bench(N, C, R) -> 193 | application:ensure_all_started(mzmetrics), 194 | application:ensure_all_started(exometer_core), 195 | application:ensure_all_started(folsom), 196 | [exometer:delete(M) || {M, _} <- exometer:find_entries(['_'])], 197 | 198 | io:format("# Counter updates per process: ~p~n", [N]), 199 | io:format("# Processes: ~p~n", [C]), 200 | io:format("# Repeats: ~p~n", [R]), 201 | io:format("# Schedulers: ~p~n", [erlang:system_info(schedulers)]), 202 | io:format("~nbenchmark_name(Iterations, Concurrency) -> avg ops/sec:~n"), 203 | 204 | io:format("~n-- Benchmark data for counters --~n"), 205 | ?BENCH(metrics_update_counter, N, C, R), 206 | ?BENCH(ets_update_counter, N, C, R), 207 | ?BENCH(exometer_counter_update, N, C, R), 208 | ?BENCH(exometer_fast_counter_update, N, C, R), 209 | ?BENCH(folsom_update_counter, N, C, R), 210 | ok. 211 | 212 | -------------------------------------------------------------------------------- /src/mzmetrics_sup.erl: -------------------------------------------------------------------------------- 1 | -module(mzmetrics_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | -spec start_link() -> supervisor:start_link(). 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | -spec init([]) -> supervisor:init_result(). 25 | init([]) -> 26 | {ok, { {one_for_one, 5, 10}, []} }. 27 | 28 | -------------------------------------------------------------------------------- /test/mzmetrics_counters_tests.erl: -------------------------------------------------------------------------------- 1 | -module(mzmetrics_counters_tests). 2 | 3 | -export([rabbit_main_handler/4, rabbit_sub_handler/4, per_family/1]). 4 | 5 | -include("mzmetrics_header.hrl"). 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | -define(NUM_INCRS, 200). 9 | -define(INIT_VALUE, 0). 10 | -define(RABBIT_NAME_PREFIX, ?MODULE_STRING"_"). 11 | -define(MY_TAB_NAME, ?MODULE). 12 | -define(NUM_RABBITS, 100). 13 | -define(NUM_PROC_PER_RABBIT, 2). 14 | -define(MAX_CNTR_UPDATE_VAL, 1000). 15 | -define(MAX_RABBIT_NAME_SIZE, 1000). 16 | -define(COUNTER_SIZE, 8). 17 | -define(FAMILY_TO_TEST_RESET, 0). 18 | 19 | %% Following three functions are used to generate a random counter 20 | %% and the counter updates that each thread wants to work on 21 | initialize_random_gen() -> 22 | rand:seed(exs64). 23 | 24 | get_random_counter(Family) -> 25 | rand:uniform(mzmetrics:num_counters(Family)) - 1. 26 | 27 | get_random_counter_update_val(Min) -> 28 | rand:uniform(?MAX_CNTR_UPDATE_VAL) - Min. 29 | 30 | cntr_size_per_rabbit(Family) -> 31 | mzmetrics:num_counters(Family) * ?COUNTER_SIZE. 32 | 33 | bin_data_size_per_rabbit(Family) -> 34 | ?MAX_RABBIT_NAME_SIZE + cntr_size_per_rabbit(Family). 35 | 36 | test_update_counter(_, _, 0, Val) -> 37 | Val; 38 | test_update_counter(Mycounters, Counter, N, Val) -> 39 | UpdateVal = get_random_counter_update_val(Val), 40 | mzmetrics:update_resource_counter(Mycounters, Counter, UpdateVal), 41 | test_update_counter(Mycounters, Counter, N-1, Val+UpdateVal). 42 | 43 | test_initial_value(Mycounters, 0) -> 44 | ?assertEqual(0, mzmetrics:get_resource_counter(Mycounters, 0)); 45 | test_initial_value(Mycounters, X) -> 46 | ?assertEqual(0, mzmetrics:get_resource_counter(Mycounters, X)), 47 | test_initial_value(Mycounters, X-1). 48 | 49 | test_get_all_resources(Family, Mytable, CntrReset) -> 50 | C = mzmetrics:get_all_resources(Family, CntrReset), 51 | ?assertEqual(0, byte_size(C) rem bin_data_size_per_rabbit(Family)), 52 | NumRabbits = byte_size(C) div bin_data_size_per_rabbit(Family), 53 | ?assert(?NUM_RABBITS =< NumRabbits), 54 | CntrVal = fun get_counter_val/3, 55 | extract_rabbit_data(CntrVal, Family, Mytable, ?NUM_RABBITS, C), 56 | case CntrReset of 57 | ?CNTR_RESET -> 58 | InitialCntrVal = fun get_initial_counter_val/3, 59 | D = mzmetrics:get_all_resources(Family, CntrReset), 60 | extract_rabbit_data(InitialCntrVal, Family, Mytable, ?NUM_RABBITS, D); 61 | _ -> 62 | do_nothing 63 | end. 64 | 65 | get_counter_val(Mytable, RabbitName, Num) -> 66 | get_ets_counter_val(Mytable, RabbitName, Num). 67 | 68 | get_initial_counter_val(_, _, _) -> 69 | ?INIT_VALUE. 70 | 71 | check_counter_vals(CntrVal, Family, Mytable, RabbitName, CounterVals) -> 72 | check_counter_vals(CntrVal, Family, Mytable, RabbitName, CounterVals, 0). 73 | check_counter_vals(CntrVal, Family, Mytable, RabbitName, CounterVals, Num) -> 74 | Max = mzmetrics:num_counters(Family), 75 | case Num of 76 | Max -> 77 | ok; 78 | _ -> 79 | [H|T] = CounterVals, 80 | ?assertEqual(CntrVal(Mytable, RabbitName, Num), H), 81 | check_counter_vals(CntrVal, Family, Mytable, RabbitName, T, Num + 1) 82 | end. 83 | 84 | extract_rabbit_data(CntrVal, Family, Mytable, 1, BinData) -> 85 | CounterVals = extract_counter_vals(Family, binary:part(BinData, ?MAX_RABBIT_NAME_SIZE, cntr_size_per_rabbit(Family))), 86 | ?assertEqual(mzmetrics:num_counters(Family), lists:flatlength(CounterVals)), 87 | [RabbitName|_] = binary:split(BinData, [<<0>>]), 88 | check_counter_vals(CntrVal, Family, Mytable, binary_to_list(RabbitName), CounterVals), 89 | delete_ets_counter_val(Mytable, binary_to_list(RabbitName)); 90 | extract_rabbit_data(CntrVal, Family, Mytable, N, BinData) -> 91 | CounterVals = extract_counter_vals(Family, binary:part(BinData, ?MAX_RABBIT_NAME_SIZE, cntr_size_per_rabbit(Family))), 92 | ?assertEqual(mzmetrics:num_counters(Family), lists:flatlength(CounterVals)), 93 | BinDataPerRabbit = bin_data_size_per_rabbit(Family), 94 | case binary:split(BinData, [<<0>>]) of 95 | [<> = RabbitName|_] -> 96 | check_counter_vals(CntrVal, Family, Mytable, binary_to_list(RabbitName), CounterVals), 97 | delete_ets_counter_val(Mytable, binary_to_list(RabbitName)), 98 | extract_rabbit_data(CntrVal, Family, Mytable, N-1, binary:part(BinData, BinDataPerRabbit, byte_size(BinData) - BinDataPerRabbit)); 99 | [_|_] -> 100 | %% test isolation leak from other tests 101 | extract_rabbit_data(CntrVal, Family, Mytable, N, binary:part(BinData, BinDataPerRabbit, byte_size(BinData) - BinDataPerRabbit)) 102 | end. 103 | 104 | extract_counter_vals(Family, BinData) -> 105 | extract_counter_vals(Family, BinData, 0, []). 106 | extract_counter_vals(Family, BinData, Offset, Acc) -> 107 | CntrSize = cntr_size_per_rabbit(Family), 108 | case Offset of 109 | CntrSize -> 110 | Acc; 111 | _ -> 112 | <> = binary:part(BinData, Offset, ?COUNTER_SIZE), 113 | extract_counter_vals(Family, BinData, Offset + ?COUNTER_SIZE, Acc ++ [X]) 114 | end. 115 | 116 | delete_ets_counter_val(Mytable, RabbitName) -> 117 | true=ets:delete(Mytable, RabbitName). 118 | 119 | get_ets_counter_val(Mytable, RabbitName, Counter) -> 120 | ets:lookup_element(Mytable, RabbitName, 2+Counter). 121 | 122 | incr_ets_counter_val(Mytable, RabbitName, Counter, Val) -> 123 | ets:update_counter(Mytable, RabbitName, {2+Counter, Val}). 124 | 125 | create_my_list(0, Acc) -> 126 | Acc; 127 | create_my_list(Num, Acc) -> 128 | create_my_list(Num - 1, [0|Acc]). 129 | 130 | init_counters_in_pdict(Family, Mytable, RabbitName) -> 131 | X = create_my_list(mzmetrics:num_counters(Family), []), 132 | ets:insert(Mytable, list_to_tuple([RabbitName | X])). 133 | 134 | %% This is the function that is spawned by rabbit_main_handler 135 | -spec rabbit_sub_handler(Family::metrics:metric_type(), Pid::pid(), 136 | Mytable::ets:tid(), RabbitName::binary()) -> any(). 137 | rabbit_sub_handler(Family, Pid, Mytable, RabbitName) -> 138 | Mycounters = mzmetrics:alloc_resource(Family, RabbitName, mzmetrics:num_counters(Family)), 139 | initialize_random_gen(), 140 | Counter = get_random_counter(Family), 141 | UpdateVal=test_update_counter(Mycounters, Counter, ?NUM_INCRS, 0), 142 | incr_ets_counter_val(Mytable, RabbitName, Counter, UpdateVal), 143 | Pid ! {self(), ok}. 144 | 145 | %% This function initializes rabbit specific data and spawns 146 | %% children to act upon this rabbit's counters 147 | -spec rabbit_main_handler(Family::metrics:metric_type(), Pid::pid(), 148 | X::non_neg_integer(), Mytable::ets:tid()) -> any(). 149 | rabbit_main_handler(Family, Pid, X, Mytable) -> 150 | initialize_random_gen(), 151 | RabbitName = ?RABBIT_NAME_PREFIX ++ integer_to_list(X), 152 | Mycounters = mzmetrics:alloc_resource(Family, RabbitName, mzmetrics:num_counters(Family)), 153 | init_counters_in_pdict(Family, Mytable, RabbitName), 154 | test_initial_value(Mycounters, mzmetrics:num_counters(Family) - 1), 155 | [spawn(?MODULE, rabbit_sub_handler, [Family, self(), Mytable, RabbitName]) 156 | || _ <- lists:seq(1,?NUM_PROC_PER_RABBIT)], 157 | wait_for_children(?NUM_PROC_PER_RABBIT, []), 158 | Pid ! {self(), ok}. 159 | 160 | %% Wait for all the children to reach the next barrier point 161 | wait_for_children(0, Acc) -> 162 | Acc; 163 | wait_for_children(N, Acc) -> 164 | receive 165 | {Pid, ok} -> 166 | wait_for_children(N-1, Acc ++ [Pid]); 167 | _Unexpected -> 168 | error(unexpected) 169 | end. 170 | 171 | -spec per_family(Family::non_neg_integer()) -> any(). 172 | per_family(Family) -> 173 | undefined=ets:info(?MY_TAB_NAME), 174 | Mytable=ets:new(?MY_TAB_NAME, [set,public]), 175 | Pid = self(), 176 | [spawn(?MODULE, rabbit_main_handler, [Family, Pid, I, Mytable]) 177 | || I <- lists:seq(1,?NUM_RABBITS)], 178 | wait_for_children(?NUM_RABBITS, []), 179 | ?assertEqual(?NUM_RABBITS, ets:info(Mytable, size)), 180 | case Family of 181 | ?FAMILY_TO_TEST_RESET -> 182 | CntrReset = ?CNTR_RESET; 183 | _ -> 184 | CntrReset = ?CNTR_DONT_RESET 185 | end, 186 | test_get_all_resources(Family, Mytable, CntrReset), 187 | ?assertEqual(0, ets:info(Mytable, size)), 188 | ets:delete(Mytable). 189 | 190 | %% In summary, the main test creates an ets table to share with all 191 | %% the children and this ets table is used to track the counter updates 192 | %% that each child does. Once all the children have executed, we get all 193 | %% the counters and then verify them against the values in ets table. 194 | -spec incr_get_test_() -> any(). 195 | incr_get_test_() -> 196 | [ 197 | {"Some simple test", 198 | fun() -> 199 | initialize_random_gen(), 200 | [per_family(Family) || Family <- lists:seq(0,1)] 201 | end 202 | } 203 | ]. 204 | --------------------------------------------------------------------------------