diff options
author | Erlang/OTP <[email protected]> | 2010-02-10 13:03:54 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2010-02-10 13:03:54 +0000 |
commit | ada6afd00530d6569c41741cfd9d63311ff60f25 (patch) | |
tree | 3ff92e054e2babf7478fa0a96e7d82fd42ab5595 | |
parent | e7d6097b0f015d5a489ea3a3ad71e064a87c0576 (diff) | |
parent | 9a22cca549f88f955163e165b8849a6129925e8b (diff) | |
download | otp-ada6afd00530d6569c41741cfd9d63311ff60f25.tar.gz otp-ada6afd00530d6569c41741cfd9d63311ff60f25.tar.bz2 otp-ada6afd00530d6569c41741cfd9d63311ff60f25.zip |
Merge branch 'egil/lcnt' into ccase/r13b04_dev
* egil/lcnt:
Add test suite for lcnt in tools
Add lcnt:rt_opt/1 bindings to erts_debug
Add runtime option to enable/disable lcnt stats
Add auto width on string output
Add lcnt documentation
Add lock profiling tool
OTP-8424 Add lock profiling tool.
The Lock profiling tool, lcnt, can make use of the internal lock
statistics when the runtime system is built with this feature
enabled.
This provides a mechanism to examine potential lock bottlenecks
within the runtime itself.
- Add erts_debug:lock_counters({copy_save, bool()}). This option
enables or disables statistics saving for destroyed processes and
ets-tables. Enabling this might consume a lot of memory.
- Add id-numbering for lock classes which is otherwise undefined.
-rw-r--r-- | erts/emulator/beam/erl_bif_info.c | 92 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db.c | 23 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_hash.c | 16 | ||||
-rw-r--r-- | erts/emulator/beam/erl_lock_count.c | 68 | ||||
-rw-r--r-- | erts/emulator/beam/erl_lock_count.h | 25 | ||||
-rw-r--r-- | erts/emulator/beam/erl_process.c | 6 | ||||
-rw-r--r-- | erts/emulator/beam/io.c | 12 | ||||
-rw-r--r-- | lib/tools/doc/src/Makefile | 12 | ||||
-rw-r--r-- | lib/tools/doc/src/lcnt.xml | 465 | ||||
-rw-r--r-- | lib/tools/doc/src/lcnt_chapter.xml | 301 | ||||
-rw-r--r-- | lib/tools/doc/src/part.xml | 9 | ||||
-rw-r--r-- | lib/tools/doc/src/ref_man.xml | 12 | ||||
-rw-r--r-- | lib/tools/src/Makefile | 14 | ||||
-rw-r--r-- | lib/tools/src/lcnt.erl | 840 | ||||
-rw-r--r-- | lib/tools/test/Makefile | 7 | ||||
-rw-r--r-- | lib/tools/test/lcnt_SUITE.erl | 154 | ||||
-rw-r--r-- | lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt | bin | 0 -> 226100 bytes |
17 files changed, 1930 insertions, 126 deletions
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index 5ff1f794df..a34d400ed8 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -3704,12 +3704,11 @@ static Eterm lcnt_build_lock_term(Eterm **hpp, Uint *szp, erts_lcnt_lock_t *lock ASSERT(ltype); type = am_atom_put(ltype, strlen(ltype)); - name = am_atom_put(lock->name, strlen(lock->name)); if (lock->flag & ERTS_LCNT_LT_ALLOC) { /* use allocator types names as id's for allocator locks */ - ltype = ERTS_ALC_A2AD(signed_val(lock->id)); + ltype = (char *) ERTS_ALC_A2AD(signed_val(lock->id)); id = am_atom_put(ltype, strlen(ltype)); } else if (lock->flag & ERTS_LCNT_LT_PROCLOCK) { /* use registered names as id's for process locks if available */ @@ -3778,17 +3777,28 @@ BIF_RETTYPE erts_debug_lock_counters_1(BIF_ALIST_1) { #ifdef ERTS_ENABLE_LOCK_COUNT Eterm res = NIL; - erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); - erts_smp_block_system(0); +#endif + + + if (BIF_ARG_1 == am_enabled) { +#ifdef ERTS_ENABLE_LOCK_COUNT + BIF_RET(am_true); +#else + BIF_RET(am_false); +#endif + } +#ifdef ERTS_ENABLE_LOCK_COUNT - if (BIF_ARG_1 == am_info) { + else if (BIF_ARG_1 == am_info) { erts_lcnt_data_t *data; Uint hsize = 0; Uint *szp; Eterm* hp; - erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_SUSPEND); + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_block_system(0); + erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_SUSPEND); data = erts_lcnt_get_data(); /* calculate size */ @@ -3803,29 +3813,65 @@ BIF_RETTYPE erts_debug_lock_counters_1(BIF_ALIST_1) res = lcnt_build_result_term(&hp, NULL, data, res); erts_lcnt_clear_rt_opt(ERTS_LCNT_OPT_SUSPEND); + + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); - goto done; + BIF_RET(res); } else if (BIF_ARG_1 == am_clear) { + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_block_system(0); + erts_lcnt_clear_counters(); - res = am_ok; - goto done; + + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + + BIF_RET(am_ok); } else if (is_tuple(BIF_ARG_1)) { - Uint prev = 0; Eterm* tp = tuple_val(BIF_ARG_1); + switch (arityval(tp[0])) { case 2: - if (ERTS_IS_ATOM_STR("process_locks", tp[1])) { + if (ERTS_IS_ATOM_STR("copy_save", tp[1])) { + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_block_system(0); if (tp[2] == am_true) { - prev = erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_PROCLOCK); - if (prev) res = am_true; - else res = am_false; - goto done; + + res = erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_COPYSAVE) ? am_true : am_false; + } else if (tp[2] == am_false) { - prev = erts_lcnt_clear_rt_opt(ERTS_LCNT_OPT_PROCLOCK); - if (prev) res = am_true; - else res = am_false; - goto done; + + res = erts_lcnt_clear_rt_opt(ERTS_LCNT_OPT_COPYSAVE) ? am_true : am_false; + + } else { + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + BIF_ERROR(BIF_P, BADARG); } + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + BIF_RET(res); + + } else if (ERTS_IS_ATOM_STR("process_locks", tp[1])) { + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_block_system(0); + if (tp[2] == am_true) { + + res = erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_PROCLOCK) ? am_true : am_false; + + } else if (tp[2] == am_false) { + + res = erts_lcnt_set_rt_opt(ERTS_LCNT_OPT_PROCLOCK) ? am_true : am_false; + + } else { + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + BIF_ERROR(BIF_P, BADARG); + } + erts_smp_release_system(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + BIF_RET(res); } break; @@ -3834,16 +3880,8 @@ BIF_RETTYPE erts_debug_lock_counters_1(BIF_ALIST_1) } } - erts_smp_release_system(); - erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); #endif BIF_ERROR(BIF_P, BADARG); -#ifdef ERTS_ENABLE_LOCK_COUNT -done: - erts_smp_release_system(); - erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); - BIF_RET(res); -#endif } void diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index b02150008f..15b1c6bb56 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1996-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -261,13 +261,8 @@ static ERTS_INLINE void db_init_lock(DbTable* tb, char *rwname, char* fixname) erts_refc_init(&tb->common.ref, 1); erts_refc_init(&tb->common.fixref, 0); #ifdef ERTS_SMP -# ifdef ERTS_ENABLE_LOCK_COUNT erts_smp_rwmtx_init_x(&tb->common.rwlock, rwname, tb->common.the_name); erts_smp_mtx_init_x(&tb->common.fixlock, fixname, tb->common.the_name); -# else - erts_smp_rwmtx_init(&tb->common.rwlock, rwname); - erts_smp_mtx_init(&tb->common.fixlock, fixname); -# endif tb->common.is_thread_safe = !(tb->common.status & DB_FINE_LOCKED); #endif } @@ -2597,19 +2592,11 @@ void init_db(void) #ifdef ERTS_SMP for (i=0; i<META_MAIN_TAB_LOCK_CNT; i++) { -#ifdef ERTS_ENABLE_LOCK_COUNT erts_smp_spinlock_init_x(&meta_main_tab_locks[i].lck, "meta_main_tab_slot", make_small(i)); -#else - erts_smp_spinlock_init(&meta_main_tab_locks[i].lck, "meta_main_tab_slot"); -#endif } erts_smp_spinlock_init(&meta_main_tab_main_lock, "meta_main_tab_main"); for (i=0; i<META_NAME_TAB_LOCK_CNT; i++) { -#ifdef ERTS_ENABLE_LOCK_COUNT erts_smp_rwmtx_init_x(&meta_name_tab_rwlocks[i].lck, "meta_name_tab", make_small(i)); -#else - erts_smp_rwmtx_init(&meta_name_tab_rwlocks[i].lck, "meta_name_tab"); -#endif } #endif diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c index dea45053df..4141f9766b 100644 --- a/erts/emulator/beam/erl_db_hash.c +++ b/erts/emulator/beam/erl_db_hash.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -628,11 +628,7 @@ int db_create_hash(Process *p, DbTable *tbl) (DbTable *) tb, sizeof(DbTableHashFineLocks)); for (i=0; i<DB_HASH_LOCK_CNT; ++i) { - #ifdef ERTS_ENABLE_LOCK_COUNT - erts_rwmtx_init_x(&tb->locks->lck_vec[i].lck, "db_hash_slot", tb->common.the_name); - #else - erts_rwmtx_init(&tb->locks->lck_vec[i].lck, "db_hash_slot"); - #endif + erts_rwmtx_init_x(&tb->locks->lck_vec[i].lck, "db_hash_slot", make_small(i)); } /* This important property is needed to guarantee that the buckets * involved in a grow/shrink operation it protected by the same lock: diff --git a/erts/emulator/beam/erl_lock_count.c b/erts/emulator/beam/erl_lock_count.c index 6211983f4b..0d7e1335c1 100644 --- a/erts/emulator/beam/erl_lock_count.c +++ b/erts/emulator/beam/erl_lock_count.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2008-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2008-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -162,7 +162,6 @@ static void print_lock_x(erts_lcnt_lock_t *lock, Uint16 flag, char *action, char long int colls, tries, w_state, r_state; erts_lcnt_lock_stats_t *stats = NULL; - float rate; char *type; int i; @@ -170,8 +169,6 @@ static void print_lock_x(erts_lcnt_lock_t *lock, Uint16 flag, char *action, char ethr_atomic_read(&lock->r_state, &r_state); ethr_atomic_read(&lock->w_state, &w_state); - if (tries > 0) rate = (float)(colls/(float)tries)*100; - else rate = 0.0f; if (lock->flag & flag) { erts_printf("%20s [%30s] [r/w state %4ld/%4ld] id %T %s\r\n", @@ -181,26 +178,13 @@ static void print_lock_x(erts_lcnt_lock_t *lock, Uint16 flag, char *action, char w_state, lock->id, extra); - - for(i = 0; i < lock->n_stats; i++) { - stats = &(lock->stats[i]); - ethr_atomic_read(&stats->tries, &tries); - ethr_atomic_read(&stats->colls, &colls); - fprintf(stderr, "%69s:%5d [tries %9ld] [colls %9ld] [timer_n %8ld] [timer %4ld s %6ld us]\r\n", - stats->file, - stats->line, - tries, - colls, - stats->timer_n, - stats->timer.s, - (unsigned long)stats->timer.ns/1000); - } - fprintf(stderr, "\r\n"); } } static void print_lock(erts_lcnt_lock_t *lock, char *action) { - print_lock_x(lock, ERTS_LCNT_LT_ALL, action, ""); + if (strcmp(lock->name, "proc_main") == 0) { + print_lock_x(lock, ERTS_LCNT_LT_ALL, action, ""); + } } #endif @@ -230,8 +214,8 @@ static void lcnt_update_stats(erts_lcnt_lock_stats_t *stats, int lock_in_conflic ethr_atomic_inc(&stats->tries); - /* beware of trylock */ - if (lock_in_conflict) ethr_atomic_inc(&stats->colls); + if (lock_in_conflict) + ethr_atomic_inc(&stats->colls); if (time_wait) { lcnt_time_add(&(stats->timer), time_wait); @@ -366,6 +350,7 @@ void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eter for (i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) { lcnt_clear_stats(&lock->stats[i]); } + erts_lcnt_list_insert(erts_lcnt_data->current_locks, lock); lcnt_unlock(); @@ -373,18 +358,20 @@ void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eter void erts_lcnt_destroy_lock(erts_lcnt_lock_t *lock) { erts_lcnt_lock_t *deleted_lock; - - /* copy structure and insert the copy */ - deleted_lock = (erts_lcnt_lock_t*)malloc(sizeof(erts_lcnt_lock_t)); lcnt_lock(); - memcpy(deleted_lock, lock, sizeof(erts_lcnt_lock_t)); - deleted_lock->next = NULL; - deleted_lock->prev = NULL; + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_COPYSAVE) { + /* copy structure and insert the copy */ - erts_lcnt_list_insert(erts_lcnt_data->deleted_locks, deleted_lock); + deleted_lock = (erts_lcnt_lock_t*)malloc(sizeof(erts_lcnt_lock_t)); + memcpy(deleted_lock, lock, sizeof(erts_lcnt_lock_t)); + deleted_lock->next = NULL; + deleted_lock->prev = NULL; + + erts_lcnt_list_insert(erts_lcnt_data->deleted_locks, deleted_lock); + } /* delete original */ erts_lcnt_list_delete(erts_lcnt_data->current_locks, lock); @@ -416,9 +403,10 @@ void erts_lcnt_lock_opt(erts_lcnt_lock_t *lock, Uint16 option) { /* we cannot acquire w_lock if either w or r are taken */ /* we cannot acquire r_lock if w_lock is taken */ - if ((w_state > 0) || (r_state > 0)){ + if ((w_state > 0) || (r_state > 0)) { eltd->lock_in_conflict = 1; - if (eltd->timer_set == 0) lcnt_time(&eltd->timer); + if (eltd->timer_set == 0) + lcnt_time(&eltd->timer); eltd->timer_set++; } else { eltd->lock_in_conflict = 0; @@ -445,7 +433,8 @@ void erts_lcnt_lock(erts_lcnt_lock_t *lock) { * 'atomicly'. All other locks will block the thread if w_state > 0 * i.e. locked. */ - if (eltd->timer_set == 0) lcnt_time(&eltd->timer); + if (eltd->timer_set == 0) + lcnt_time(&eltd->timer); eltd->timer_set++; } else { @@ -494,24 +483,23 @@ void erts_lcnt_lock_post_x(erts_lcnt_lock_t *lock, char *file, unsigned int line eltd = lcnt_get_thread_data(); ASSERT(eltd); - + /* if lock was in conflict, time it */ stats = lcnt_get_lock_stats(lock, file, line); if (eltd->timer_set) { lcnt_time(&timer); - - eltd->timer_set--; lcnt_time_diff(&time_wait, &timer, &(eltd->timer)); lcnt_update_stats(stats, eltd->lock_in_conflict, &time_wait); + eltd->timer_set--; ASSERT(eltd->timer_set >= 0); } else { lcnt_update_stats(stats, eltd->lock_in_conflict, NULL); } - + } /* unlock */ diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h index 8564c36203..e3044c371f 100644 --- a/erts/emulator/beam/erl_lock_count.h +++ b/erts/emulator/beam/erl_lock_count.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2008-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2008-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -39,6 +39,20 @@ * Each instance of a lock is the unique lock, i.e. set and id in that set. * For each lock there is a set of statistics with where and what impact * the lock aqusition had. + * + * Runtime options + * - suspend, used when internal lock-counting can't be applied. For instance + * when allocating a term for the outside and halloc needs to be used. + * Default: off. + * - location, reserved and not used. + * - proclock, disable proclock counting. Used when performance might be an + * issue. Accessible from erts_debug:lock_counters({process_locks, bool()}). + * Default: off. + * - copysave, enable saving of destroyed locks (and thereby its statistics). + * If memory constraints is an issue this need to be disabled. + * Accessible from erts_debug:lock_counters({copy_save, bool()}). + * Default: off. + * */ #include "sys.h" @@ -74,6 +88,7 @@ #define ERTS_LCNT_OPT_SUSPEND (((Uint16) 1) << 0) #define ERTS_LCNT_OPT_LOCATION (((Uint16) 1) << 1) #define ERTS_LCNT_OPT_PROCLOCK (((Uint16) 1) << 2) +#define ERTS_LCNT_OPT_COPYSAVE (((Uint16) 1) << 3) typedef struct { unsigned long s; diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 2789691c55..bf08bc7a86 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -2095,7 +2095,11 @@ erts_init_scheduling(int mrq, int no_schedulers, int no_schedulers_online) rq->ix = ix; erts_smp_atomic_init(&rq->info_flags, ERTS_RUNQ_IFLG_NONEMPTY); - erts_smp_mtx_init(&rq->mtx, "run_queue"); + /* make sure that the "extra" id correponds to the schedulers + * id if the esdp->no <-> ix+1 mapping change. + */ + + erts_smp_mtx_init_x(&rq->mtx, "run_queue", make_small(ix + 1)); erts_smp_cnd_init(&rq->cnd); erts_smp_atomic_init(&rq->spin_waiter, 0); diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index 61985271e6..c5de180cb2 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1996-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -1227,7 +1227,7 @@ void init_io(void) erts_smp_atomic_init(&erts_port[i].refc, 0); erts_port[i].lock = NULL; erts_port[i].xports = NULL; - erts_smp_spinlock_init(&erts_port[i].state_lck, "port_state"); + erts_smp_spinlock_init_x(&erts_port[i].state_lck, "port_state", make_small(i)); #endif erts_port[i].tracer_proc = NIL; erts_port[i].trace_flags = 0; diff --git a/lib/tools/doc/src/Makefile b/lib/tools/doc/src/Makefile index bab607c4bd..433f123ae5 100644 --- a/lib/tools/doc/src/Makefile +++ b/lib/tools/doc/src/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# -# Copyright Ericsson AB 1997-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 1997-2010. All Rights Reserved. +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # include $(ERL_TOP)/make/target.mk @@ -41,6 +41,7 @@ XML_REF3_FILES = \ eprof.xml \ fprof.xml \ cprof.xml \ + lcnt.xml \ instrument.xml \ make.xml \ tags.xml \ @@ -53,6 +54,7 @@ XML_CHAPTER_FILES = \ cover_chapter.xml \ fprof_chapter.xml \ cprof_chapter.xml \ + lcnt_chapter.xml \ erlang_mode_chapter.xml \ xref_chapter.xml \ notes.xml \ diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml new file mode 100644 index 0000000000..3c55e4e422 --- /dev/null +++ b/lib/tools/doc/src/lcnt.xml @@ -0,0 +1,465 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2009</year> + <year>2010</year> + <holder>Ericsson AB, All Rights Reserved</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + </legalnotice> + + <title>lcnt</title> + <prepared>Björn-Egil Dahlberg</prepared> + <responsible>nobody</responsible> + <docno></docno> + <approved>nobody</approved> + <checked></checked> + <date>2009-11-26</date> + <rev>PA1</rev> + <file>lcnt.xml</file> + </header> + <module>lcnt</module> + <modulesummary>A runtime system Lock Profiling tool.</modulesummary> + <description> + <p>The <c>lcnt</c> module is used to profile the internal ethread locks in the + Erlang Runtime System. With <c>lcnt</c> enabled, Internal counters in the + runtime system are updated each time a lock is taken. The counters stores + information about the number of acquisition tries and the number of collisions + that has occurred during the acquisition tries. The counters also record the + waiting time a lock has caused for a blocked thread when a collision has occurred. + </p> + <p> + The data produced by the lock counters will give an estimate on how well + the runtime system will behave from a parallelizable view point for the + scenarios tested. This tool was mainly developed to help erlang runtime + developers iron out potential and generic bottlenecks. + </p> + <p>Locks in the emulator are named after what type of resource they protect and where + in the emulator they are initialized, those are lock 'classes'. Most of those + locks are also instantiated several times, and given unique identifiers, to increase + locking granularity. Typically an instantiated lock protects a disjunct set of + the resource, i.e ets-tables, processes or ports. In other cases it protects a + specific range of a resource, e.g. <c>pix_lock</c> which protects index to process + mappings, and is given a unique number within the class. A unique lock in <c>lcnt</c> + is referenced by a name (class) and an identifier, <c>{Name, Id}</c>. + </p> + <p>Some locks in the system are static and protects global resources, for example + <c>bif_timers</c> and the <c>run_queue</c> locks. Other locks are dynamic and not + necessarily long lived, for example process locks and ets-table locks. The + statistics data from short lived locks can be stored separately when the locks + are deleted. This behavior is by default turned off to save memory but can be + turned on via <c>lcnt:rt_opt({copy_save, true})</c>. The <c>lcnt:apply/1,2,3</c> + functions enables this behavior during profiling. + </p> + </description> + <funcs> + + <func> + <name>start() -> {ok, Pid} | {error, {already_started, Pid}} </name> + <fsummary>Starts the lock profiler server.</fsummary> + <type> + <v>Pid = pid()</v> + </type> + <desc> + <p>Starts the lock profiler server. The server only act as a medium for the + user and performs filtering and printing of data collected by <c>lcnt:collect/1</c>. + </p> + </desc> + </func> + + <func> + <name>stop() -> ok</name> + <fsummary>Stops the lock profiler server.</fsummary> + <desc> + <p>Stops the lock profiler server.</p> + </desc> + </func> + + <func> + <name>collect() -> ok</name> + <fsummary>Same as <c>collect(node())</c>.</fsummary> + <desc><p>Same as <c>collect(node())</c>.</p></desc> + </func> + + <func> + <name>collect(Node) -> ok</name> + <fsummary>Collects lock statistics from the runtime system.</fsummary> + <type> + <v>Node = node()</v> + </type> + <desc> + <p>Collects lock statistics from the runtime system. The function starts a + server if it is not already started. It then populates the server with lock + statistics. If the server held any lock statistics data before the collect then + that data is lost. + </p> + <note> + <p> + When collection occurs the runtime system transitions to a single thread, + blocking all other threads. No other tasks will be scheduled during this + operation. Depending on the size of the data this might take a long time + (several seconds) and cause timeouts in the system. + </p> + </note> + </desc> + </func> + + <func> + <name>clear() -> ok</name> + <fsummary>Same as <c>clear(node())</c>.</fsummary> + <desc><p>Same as <c>clear(node())</c>.</p></desc> + </func> + + <func> + <name>clear(Node) -> ok</name> + <fsummary>Clears the internal lock statistics from runtime system.</fsummary> + <type> + <v>Node = node()</v> + </type> + <desc> + <p>Clears the internal lock statistics from the runtime system. This does not clear the + data on the server only on runtime system. All counters for static locks are zeroed, + all dynamic locks currently alive are zeroed and all saved locks now destroyed are removed. + It also resets the duration timer. + </p> + </desc> + </func> + <func> + <name>conflicts() -> ok</name> + <fsummary>Same as <c>conflicts([])</c>.</fsummary> + <desc><p>Same as <c>conflicts([])</c>.</p></desc> + </func> + <func> + <name>conflicts([Option]) -> ok</name> + <fsummary>Prints a list of internal lock counters.</fsummary> + <type> + <v>Option = {sort, Sort} | {reverse, bool()} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v> + <v>Sort = name | id | type | tries | colls | ratio | time | entry</v> + <v>Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()}</v> + <v>Print = name | id | type | entry | tries | colls | ratio | time | duration</v> + <v>MaxLocks = integer() | none</v> + </type> + <desc> + <p>Prints a list of internal locks and its statistics.</p> + <p>For option description, see <seealso marker="#inspect/2">lcnt:inspect/2</seealso>.</p> + </desc> + </func> + + <func> + <name>locations() -> ok</name> + <fsummary>Same as <c>locations([])</c>.</fsummary> + <desc> + <p>Same as <c>locations([])</c>.</p> + </desc> + </func> + <func> + <name>locations([Option]) -> ok</name> + <fsummary>Prints a list of internal lock counters by source code locations.</fsummary> + <type> + <v>Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v> + <v>Sort = name | id | type | tries | colls | ratio | time | entry</v> + <v>Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()}</v> + <v>Print = name | id | type | entry | tries | colls | ratio | time | duration</v> + <v>MaxLocks = integer() | none</v> + </type> + <desc> + <p>Prints a list of internal lock counters by source code locations.</p> + <p>For option description, see <seealso marker="#inspect/2">lcnt:inspect/2</seealso>.</p> + </desc> + </func> + + <func> + <name>inspect(Lock) -> ok</name> + <fsummary>Same as <c>inspect(Lock, [])</c>.</fsummary> + <desc><p>Same as <c>inspect(Lock, [])</c>.</p></desc> + </func> + <func> + <name>inspect(Lock, [Option]) -> ok</name> + <fsummary>Prints a list of internal lock counters for a specific lock.</fsummary> + <type> + <v>Lock = Name | {Name, Id | [Id]}</v> + <v>Name = atom() | pid() | port()</v> + <v>Id = atom() | integer() | pid() | port()</v> + <v>Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()} | {locations, bool()}</v> + <v>Sort = name | id | type | tries | colls | ratio | time</v> + <v>Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()}</v> + <v>Print = name | id | type | entry | tries | colls | ratio | time | duration</v> + <v>MaxLocks = integer() | none</v> + </type> + <desc> + <p>Prints a list of internal lock counters for a specific lock.</p> + <p>Lock <c>Name</c> and <c>Id</c> for ports and processes are interchangeable with the use of <c>lcnt:swap_pid_keys/0</c> and is the reason why <c>pid()</c> and <c>port()</c> options can be used in both <c>Name</c> and <c>Id</c> space. Both pids and ports are special identifiers with stripped creation and can be recreated with <seealso marker="#pid/3">lcnt:pid/2,3</seealso> and <seealso marker="#port/2">lcnt:port/1,2</seealso>. </p> + <p>Option description:</p> + <taglist> + <tag><c>{combine, bool()}</c></tag> + <item>Combine the statistics from different instances of a lock class. + <br/>Default: <c>true</c> + </item> + + <tag><c>{locations, bool()}</c></tag> + <item>Print the statistics by source file and line numbers. + <br/>Default: <c>false</c> + </item> + + <tag><c>{max_locks, MaxLocks}</c></tag> + <item>Maximum number of locks printed or no limit with <c>none</c>. + <br/>Default: <c>20</c> + </item> + + <tag><c>{print, PrintOptions}</c></tag> + <item>Printing options: + <taglist> + <tag><c>name</c></tag> + <item>Named lock or named set of locks (classes). The same name used for initializing the lock in the VM.</item> + + <tag><c>id</c></tag> + <item>Internal id for set of locks, not always unique. This could be table name for ets tables (db_tab), port id for ports, integer identifiers for allocators, etc.</item> + + <tag><c>type</c></tag> + <item>Type of lock: <c>rw_mutex</c>, <c>mutex</c>, <c>spinlock</c>, <c>rw_spinlock</c> or <c>proclock</c>.</item> + + <tag><c>entry</c></tag> + <item>In combination with <c>{locations, true}</c> this option prints the lock operations source file and line number entry-points along with statistics for each entry. </item> + + <tag><c>tries</c></tag> + <item>Number of acquisitions of this lock.</item> + + <tag><c>colls</c></tag> + <item>Number of collisions when a thread tried to acquire this lock. This is when a trylock is EBUSY, a write try on read held rw_lock, a try read on write held rw_lock, a thread tries to lock an already locked lock. (Internal states supervises this).</item> + + <tag><c>ratio</c></tag> + <item>The ratio between the number of collisions and the number of tries (acquisitions) in percentage.</item> + + <tag><c>time</c></tag> + <item>Accumulated waiting time for this lock. This could be greater than actual wall clock time, it is accumulated for all threads. Trylock conflicts does not accumulate time.</item> + + <tag><c>duration</c></tag> + <item>Percentage of accumulated waiting time of wall clock time. This percentage can be higher than 100% since accumulated time is from all threads.</item> + </taglist> + <br/>Default: <c>[name,id,tries,colls,ratio,time,duration]</c> + </item> + + <tag><c>{reverse, bool()}</c></tag> + <item>Reverses the order of sorting. + <br/>Default: <c>false</c> + </item> + + <tag><c>{sort, Sort}</c></tag> + <item>Column sorting orders. + <br/>Default: <c>time</c> + </item> + + <tag><c>{thresholds, Thresholds}</c></tag> + <item>Filtering thresholds. Anything values above the threshold value are passed through. + <br/>Default: <c>[{tries, 0}, {colls, 0}, {time, 0}]</c> + </item> + + </taglist> + + </desc> + </func> + + <func> + <name>information() -> ok</name> + <fsummary>Prints lcnt server state and generic information about collected lock statistics.</fsummary> + <desc> + <p>Prints lcnt server state and generic information about collected lock statistics.</p> + </desc> + </func> + + <func> + <name>swap_pid_keys() -> ok</name> + <fsummary>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</fsummary> + <desc> + <p>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</p> + </desc> + </func> + + <func> + <name>load(Filename) -> ok</name> + <fsummary>Restores previously saved data to the server.</fsummary> + <type> + <v>Filename = filename()</v> + </type> + <desc> + <p>Restores previously saved data to the server.</p> + </desc> + </func> + + <func> + <name>save(Filename) -> ok</name> + <fsummary>Saves the collected data to file.</fsummary> + <type> + <v>Filename = filename()</v> + </type> + <desc> + <p>Saves the collected data to file.</p> + </desc> + </func> + </funcs> + + <section> + <title>Convenience functions</title> + <p>The following functions are used for convenience.</p> + </section> + <funcs> + <func> + <name>apply(Fun) -> term()</name> + <fsummary>Same as <c>apply(Fun, [])</c>.</fsummary> + <desc> + <p>Same as <c>apply(Fun, [])</c>.</p> + </desc> + </func> + <func> + <name>apply(Fun, Args) -> term()</name> + <fsummary>Clears counters, applies function and collects the profiling results.</fsummary> + <type> + <v>Fun = fun()</v> + <v>Args = [term()]</v> + </type> + <desc> + <p> Clears the lock counters and then setups the instrumentation to save all destroyed locks. + After setup the fun is called, passing the elements in <c>Args</c> as arguments. + When the fun returns the statistics are immediately collected to the server. After the + collection the instrumentation is returned to its previous behavior. + The result of the applied fun is returned. + </p> + </desc> + </func> + <func> + <name>apply(Module, Function, Args) -> term()</name> + <fsummary>Clears counters, applies function and collects the profiling results.</fsummary> + <type> + <v>Module = atom()</v> + <v>Function = atom()</v> + <v>Args = [term()]</v> + </type> + <desc> + <p> Clears the lock counters and then setups the instrumentation to save all destroyed locks. + After setup the function is called, passing the elements in <c>Args</c> as arguments. + When the function returns the statistics are immediately collected to the server. After the + collection the instrumentation is returned to its previous behavior. + The result of the applied function is returned. + </p> + </desc> + </func> + + <func> + <name>pid(Id, Serial) -> pid()</name> + <fsummary>Same as <c>pid(node(), Id, Serial)</c>.</fsummary> + <desc><p>Same as <c>pid(node(), Id, Serial)</c>.</p></desc> + </func> + <func> + <name>pid(Node, Id, Serial) -> pid()</name> + <fsummary>Creates a process id with creation 0.</fsummary> + <type> + <v>Node = node()</v> + <v>Id = integer()</v> + <v>Serial = integer()</v> + </type> + <desc> + <p>Creates a process id with creation 0. Example:</p> + </desc> + </func> + + <func> + <name>port(Id) -> port()</name> + <fsummary>Same as <c>port(node(), Id)</c>.</fsummary> + <desc><p>Same as <c>port(node(), Id)</c>.</p></desc> + </func> + <func> + <name>port(Node, Id) -> port()</name> + <fsummary>Creates a port id with creation 0.</fsummary> + <type> + <v>Node = node()</v> + <v>Id = integer()</v> + </type> + <desc><p>Creates a port id with creation 0.</p></desc> + </func> + + </funcs> + + <section> + <title>Internal runtime lock counter controllers</title> + <p> The following functions control the behavior of the internal counters. </p> + </section> + + <funcs> + <func> + <name>rt_collect() -> [lock_counter_data()]</name> + <fsummary>Same as <c>rt_collect(node())</c>.</fsummary> + <desc> <p>Same as <c>rt_collect(node())</c>.</p> </desc> + </func> + <func> + <name>rt_collect(Node) -> [lock_counter_data()]</name> + <fsummary>Returns a list of raw lock counter data.</fsummary> + <type> + <v>Node = node()</v> + </type> + <desc> <p>Returns a list of raw lock counter data.</p> </desc> + </func> + + <func> + <name>rt_clear() -> ok</name> + <fsummary>Same as <c>rt_clear(node())</c>.</fsummary> + <desc> <p>Same as <c>rt_clear(node())</c>.</p> </desc> + </func> + <func> + <name>rt_clear(Node) -> ok</name> + <fsummary>Clears the internal counters.</fsummary> + <type> + <v>Node = node()</v> + </type> + <desc> <p>Clear the internal counters. Same as <c>lcnt:clear(Node)</c>.</p></desc> + </func> + + <func> + <name>rt_opt({Type, bool()}) -> bool()</name> + <fsummary>Same as <c>rt_opt(node(), {Type, Opt})</c>.</fsummary> + <desc> <p>Same as <c>rt_opt(node(), {Type, Opt})</c>.</p> </desc> + </func> + <func> + <name>rt_opt(Node, {Type, bool()}) -> bool()</name> + <fsummary>Changes the lock counter behavior and returns the previous behaviour.</fsummary> + <type> + <v>Node = node()</v> + <v>Type = copy_save | process_locks</v> + </type> + <desc> + <p>Changes the lock counter behavior and returns the previous behaviour.</p> + <p>Option description:</p> + <taglist> + <tag><c>{copy_save, bool()}</c></tag> + <item>Enable statistics saving from destroyed locks by copying. This might consume a lot of memory. + <br/>Default: <c>false</c> + </item> + + <tag><c>{process_locks, bool()}</c></tag> + <item>Profile process locks. + <br/>Default: <c>true</c> + </item> + </taglist> + </desc> + </func> + </funcs> + + <section> + <title>See Also</title> + <p> <seealso marker="lcnt_chapter">LCNT User's Guide</seealso></p> + </section> +</erlref> diff --git a/lib/tools/doc/src/lcnt_chapter.xml b/lib/tools/doc/src/lcnt_chapter.xml new file mode 100644 index 0000000000..8f44b23f59 --- /dev/null +++ b/lib/tools/doc/src/lcnt_chapter.xml @@ -0,0 +1,301 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2009</year><year>2010</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>lcnt - The Lock Profiler</title> + <prepared>Björn-Egil Dahlberg</prepared> + <responsible>nobody</responsible> + <docno></docno> + <approved>nobody</approved> + <checked>no</checked> + <date>2009-11-26</date> + <rev>PA1</rev> + <file>lcnt_chapter.xml</file> + </header> + <p> + Internally in the Erlang runtime system locks are used to protect resources from being updated from multiple threads in a fatal way. Locks are necessary + to ensure that the runtime system works properly but it also introduces a couple of limitations. Lock contention and locking overhead. + </p> + <p> + With lock contention we mean when one thread locks a resource and another thread, or threads, tries to acquire the same resource at the same time. The lock will deny + the other thread access to the resource and the thread will be blocked from continuing its execution. The second thread has to wait until the first thread has + completed its access to the resource and unlocked it. The <c>lcnt</c> tool measures these lock conflicts. + </p> + <p> + Locks has an inherent cost in execution time and memory space. It takes time initialize, destroy, aquiring or releasing locks. To decrease lock contention it + some times necessary to use finer grained locking strategies. This will usually also increase the locking overhead and hence there is a tradeoff + between lock contention and overhead. In general, lock contention increases with the number of threads running concurrently. The <c>lcnt</c> tool does not measure locking overhead. + </p> + <section> + <title> Enabling lock-counting </title> + <p>For investigation of locks in the emulator we use an internal tool called <c>lcnt</c> (short for lock-count). The VM needs to be compiled with this option enabled. To enable this, use:</p> + + <pre> +cd $ERL_TOP +./configure --enable-lock-counter + </pre> + + <p> + Another way to enable this alongside a normal VM is to compile it at emulator directory level, much like a debug build. To compile it this way do the following, + </p> + <pre> +cd $ERL_TOP/erts/emulator +make lcnt FLAVOR=smp + </pre> + <p> and then starting Erlang with,</p> + <pre> +$ERL_TOP/bin/cerl -lcnt + </pre> + <p>To verify that you lock-counting enabled check that <c>[lock-counting]</c> appears in the status text when the VM is started.</p> + <pre> +Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] + [kernel-poll:false] [lock-counting] + </pre> + </section> + <section> + <title>Getting started</title> + <p>Once you have a lock counting enabled VM the module <c>lcnt</c> can be used. The module is intended to be used from the current running nodes shell. To access remote nodes use <c>lcnt:clear(Node)</c> and <c>lcnt:collect(Node)</c>. </p> + + <p>All locks are continuously monitored and its statistics updated. Use <c>lcnt:clear/0</c> to initially clear all counters before running any specific tests. This command will also reset the duration timer internally.</p> + <p>To retrieve lock statistics information use, <c>lcnt:collect/0,1</c>. The collect operation will start a <c>lcnt</c> server if it not already started. All collected data will be built into an erlang term and uploaded to the server and a duration time will also be uploaded. This duration is the time between <c>lcnt:clear/0,1</c> and <c>lcnt:collect/0,1</c>.</p> + + <p>Once the data is collected to the server it can be filtered, sorted and printed in many different ways.</p> + <p>See the <seealso marker="lcnt">reference manual</seealso> for a description of each function.</p> + </section> + <section> + <title> Example of usage </title> + <p>From the Erlang shell:</p> + <pre> +Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] + [kernel-poll:false] [lock-counting] +1> lcnt:rt_opt({copy_save, true}). +false +2> lcnt:clear(), big:bang(1000), lcnt:collect(). +ok +3> lcnt:conflicts(). + lock id #tries #collisions collisions [%] time [us] duration [%] + ----- --- ------- ------------ --------------- ---------- ------------- + alcu_allocator 50 4113692 158921 3.8632 215464 4.4962 + pix_lock 256 4007140 4882 0.1218 12221 0.2550 + run_queue 8 2287246 6949 0.3038 9825 0.2050 + proc_main 1029 3115778 25755 0.8266 1199 0.0250 + proc_msgq 1029 2467022 1910 0.0774 1048 0.0219 + proc_status 1029 5708439 2435 0.0427 706 0.0147 + message_pre_alloc_lock 8 2008569 134 0.0067 90 0.0019 + timeofday 1 54065 8 0.0148 22 0.0005 + gc_info 1 7071 7 0.0990 5 0.0001 +ok +</pre> +<p> + Another way to to profile a specific function is to use <c>lcnt:apply/3</c> or <c>lcnt:apply/1</c> which does <c>lcnt:clear/0</c> before the function and <c>lcnt:collect/0</c> after its invocation. + It also sets <c>copy_save</c> to <c>true</c> for the duration of the function call +</p> +<pre> +Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] + [kernel-poll:false] [lock-counting] +1> lcnt:apply(fun() -> big:bang(1000) end). +4384.338 +2> lcnt:conflicts(). + lock id #tries #collisions collisions [%] time [us] duration [%] + ----- --- ------- ------------ --------------- ---------- ------------- + alcu_allocator 50 4117913 183091 4.4462 234232 5.1490 + run_queue 8 2050398 3801 0.1854 6700 0.1473 + pix_lock 256 4007080 4943 0.1234 2847 0.0626 + proc_main 1028 3000178 28247 0.9415 1022 0.0225 + proc_msgq 1028 2293677 1352 0.0589 545 0.0120 + proc_status 1028 5258029 1744 0.0332 442 0.0097 + message_pre_alloc_lock 8 2009322 147 0.0073 82 0.0018 + timeofday 1 48616 9 0.0185 13 0.0003 + gc_info 1 7455 12 0.1610 9 0.0002 +ok +</pre> +<p> The process locks are sorted after its class like all other locks. It is convenient to look at specific processes and ports as classes. We can do this by swapping class and class identifiers with <c>lcnt:swap_pid_keys/0</c>. </p> +<pre> +3> lcnt:swap_pid_keys(). +ok +4> lcnt:conflicts([{print, [name, tries, ratio, time]}]). + lock #tries collisions [%] time [us] + ----- ------- --------------- ---------- + alcu_allocator 4117913 4.4462 234232 + run_queue 2050398 0.1854 6700 + pix_lock 4007080 0.1234 2847 + message_pre_alloc_lock 2009322 0.0073 82 + <[email protected]> 13493 1.4452 41 + <[email protected]> 13504 1.1404 36 + <[email protected]> 13181 1.6235 35 + <[email protected]> 13534 0.8202 22 + <[email protected]> 8744 5.8326 22 + <[email protected]> 13335 1.1174 19 + <[email protected]> 13452 1.3678 19 + <[email protected]> 13497 1.8745 18 + <[email protected]> 11009 2.5343 18 + <[email protected]> 13131 1.2566 16 + <[email protected]> 13216 1.7327 15 + <[email protected]> 13156 1.1098 15 + <[email protected]> 13420 0.7303 14 + <[email protected]> 13141 1.6437 14 + <[email protected]> 13346 1.2064 13 + <[email protected]> 13076 1.1701 13 +ok +</pre> + </section> + <section> + <title> Example with Mnesia Transaction Benchmark </title> + <p>From the Erlang shell:</p> +<pre> +Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] + [kernel-poll:false] [lock-counting] + +Eshell V5.7.4 (abort with ^G) +1> Conf=[{db_nodes, [node()]}, {driver_nodes, [node()]}, {replica_nodes, [node()]}, + {n_drivers_per_node, 10}, {n_branches, 1000}, {n_accounts_per_branch, 10}, + {replica_type, ram_copies}, {stop_after, 60000}, {reuse_history_id, true}]. +[{db_nodes,[nonode@nohost]}, + {driver_nodes,[nonode@nohost]}, + {replica_nodes,[nonode@nohost]}, + {n_drivers_per_node,10}, + {n_branches,1000}, + {n_accounts_per_branch,10}, + {replica_type,ram_copies}, + {stop_after,60000}, + {reuse_history_id,true}] +2> mnesia_tpcb:init([{use_running_mnesia, false}|Conf]). +ignore +</pre> +<p>Initial configuring of the benchmark is done. It is time to profile the actual benchmark and Mnesia</p> +<pre> +3> lcnt:apply(fun() -> {ok,{time, Tps,_,_,_,_}} = mnesia_tpcb:run([{use_running_mnesia, + true}|Conf]), Tps/60 end). +12037.483333333334 +ok +4> lcnt:swap_pid_keys(). +ok +</pre> +<p>The <c>id</c> header represents the number of unique identifiers under a class when the option <c>{combine, true}</c> is used (which is on by default). It will otherwise show the specific identifier. +The <c>db_tab</c> listing shows 722287 unique locks, it is one for each ets-table created and Mnesia creates one for each transaction. +</p> +<pre> +5> lcnt:conflicts(). + lock id #tries #collisions collisions [%] time [us] duration [%] + ----- --- ------- ------------ --------------- ---------- ------------- + alcu_allocator 50 56355118 732662 1.3001 2934747 4.8862 + db_tab 722287 94513441 63203 0.0669 1958797 3.2613 + timeofday 1 2701048 175854 6.5106 1746079 2.9071 + pix_lock 256 24306168 163214 0.6715 918309 1.5289 + run_queue 8 11813811 152637 1.2920 357040 0.5945 + message_pre_alloc_lock 8 17671449 57203 0.3237 263043 0.4380 + mnesia_locker 4 17477633 1618548 9.2607 97092 0.1617 + mnesia_tm 4 9891408 463788 4.6888 86353 0.1438 + gc_info 1 823460 628 0.0763 24826 0.0413 + meta_main_tab_slot 16 41393400 7193 0.0174 11393 0.0190 + <[email protected]> 4 4331412 333 0.0077 7148 0.0119 + timer_wheel 1 203185 30 0.0148 3108 0.0052 + <[email protected]> 4 4291098 210 0.0049 885 0.0015 + <[email protected]> 4 4294702 288 0.0067 442 0.0007 + <[email protected]> 4 4346066 235 0.0054 390 0.0006 + <[email protected]> 4 4348159 287 0.0066 379 0.0006 + <[email protected]> 4 4279309 290 0.0068 325 0.0005 + <[email protected]> 4 4292190 302 0.0070 315 0.0005 + <[email protected]> 4 4208858 265 0.0063 276 0.0005 + <[email protected]> 4 4377502 267 0.0061 276 0.0005 +ok +</pre> +<p>The listing shows <c>mnesia_locker</c>, a process, has highly contended locks.</p> +<pre> +6> lcnt:inspect(mnesia_locker). + lock id #tries #collisions collisions [%] time [us] duration [%] + ----- --- ------- ------------ --------------- ---------- ------------- + mnesia_locker proc_msgq 5449930 59374 1.0894 69781 0.1162 + mnesia_locker proc_main 4462782 1487374 33.3284 14398 0.0240 + mnesia_locker proc_status 7564921 71800 0.9491 12913 0.0215 + mnesia_locker proc_link 0 0 0.0000 0 0.0000 +ok +</pre> +<p>Listing without class combiner.</p> + +<pre> +7> lcnt:conflicts([{combine, false}, {print, [name, id, tries, ratio, time]}]). + lock id #tries collisions [%] time [us] + ----- --- ------- --------------- ---------- + db_tab mnesia_transient_decision 722250 3.9463 1856852 + timeofday undefined 2701048 6.5106 1746079 + alcu_allocator ets_alloc 7490696 2.2737 692655 + alcu_allocator ets_alloc 7081771 2.3294 664522 + alcu_allocator ets_alloc 7047750 2.2520 658495 + alcu_allocator ets_alloc 5883537 2.3177 610869 + pix_lock 58 11011355 1.1924 564808 + pix_lock 60 4426484 0.7120 262490 + alcu_allocator ets_alloc 1897004 2.4248 219543 + message_pre_alloc_lock undefined 4211267 0.3242 128299 + run_queue 3 2801555 1.3003 116792 + run_queue 2 2799988 1.2700 100091 + run_queue 1 2966183 1.2712 78834 + mnesia_locker proc_msgq 5449930 1.0894 69781 + message_pre_alloc_lock undefined 3495672 0.3262 65773 + message_pre_alloc_lock undefined 4189752 0.3174 58607 + mnesia_tm proc_msgq 2094144 1.7184 56361 + run_queue 4 2343585 1.3115 44300 + db_tab branch 1446529 0.5229 38244 + gc_info undefined 823460 0.0763 24826 +ok +</pre> +<p> +In this scenario the lock that protects ets-table <c>mnesia_transient_decision</c> has spent most of its waiting for. That is 1.8 seconds in a test that run for 60 seconds. The time is also spread on eight different scheduler threads. +</p> +<pre> +8> lcnt:inspect(db_tab, [{print, [name, id, tries, colls, ratio, duration]}]). + lock id #tries #collisions collisions [%] duration [%] + ----- --- ------- ------------ --------------- ------------- + db_tab mnesia_transient_decision 722250 28502 3.9463 3.0916 + db_tab branch 1446529 7564 0.5229 0.0637 + db_tab account 1464500 8203 0.5601 0.0357 + db_tab teller 1464529 8110 0.5538 0.0291 + db_tab history 722250 3767 0.5216 0.0232 + db_tab mnesia_stats 750332 7057 0.9405 0.0180 + db_tab mnesia_trans_store 61 0 0.0000 0.0000 + db_tab mnesia_trans_store 61 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 + db_tab mnesia_trans_store 53 0 0.0000 0.0000 +ok +</pre> + + </section> + <section> + <title> Deciphering the output </title> + <p> Typically high <c>time</c> values are bad and this is often the thing to look for. However, one should also look for high lock acquisition frequencies (#tries) since locks generate overhead and because high frequency could become problematic if they begin to have conflicts even if it is not shown in a particular test. </p> + </section> + + <section> + <title>See Also</title> + <p> <seealso marker="lcnt">LCNT Reference Manual</seealso></p> + </section> +</chapter> diff --git a/lib/tools/doc/src/part.xml b/lib/tools/doc/src/part.xml index 3e02086b80..bf9e1ebbec 100644 --- a/lib/tools/doc/src/part.xml +++ b/lib/tools/doc/src/part.xml @@ -4,7 +4,7 @@ <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2009</year> + <year>1996</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>Tools User's Guide</title> @@ -55,6 +55,8 @@ <tag><em>instrument</em></tag> <item>Utility functions for obtaining and analysing resource usage in an instrumented Erlang runtime system.</item> + <tag><em>lcnt</em></tag> + <item>A lock profiling tool for the Erlang runtime system.</item> <tag><em>make</em></tag> <item>A make utility for Erlang similar to UNIX make.</item> <tag><em>tags</em></tag> @@ -69,6 +71,7 @@ <xi:include href="cprof_chapter.xml"/> <xi:include href="erlang_mode_chapter.xml"/> <xi:include href="fprof_chapter.xml"/> + <xi:include href="lcnt_chapter.xml"/> <xi:include href="xref_chapter.xml"/> </part> diff --git a/lib/tools/doc/src/ref_man.xml b/lib/tools/doc/src/ref_man.xml index aea74e3746..d4861af9f3 100644 --- a/lib/tools/doc/src/ref_man.xml +++ b/lib/tools/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2009</year> + <year>1996</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>Tools Reference Manual</title> @@ -51,9 +51,14 @@ Erlang programs. Uses trace to file to minimize runtime performance impact, and displays time for calling and called functions.</item> + <tag><em>instrument</em></tag> <item>Utility functions for obtaining and analysing resource usage in an instrumented Erlang runtime system.</item> + + <tag><em>lcnt</em></tag> + <item>A lock profiling tool for the Erlang runtime system.</item> + <tag><em>make</em></tag> <item>A make utility for Erlang similar to UNIX make.</item> <tag><em>tags</em></tag> @@ -70,6 +75,7 @@ <xi:include href="erlang_mode.xml"/> <xi:include href="fprof.xml"/> <xi:include href="instrument.xml"/> + <xi:include href="lcnt.xml"/> <xi:include href="make.xml"/> <xi:include href="tags.xml"/> <xi:include href="xref.xml"/> diff --git a/lib/tools/src/Makefile b/lib/tools/src/Makefile index 81933cda14..360f4f8f29 100644 --- a/lib/tools/src/Makefile +++ b/lib/tools/src/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# -# Copyright Ericsson AB 1996-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 1996-2010. All Rights Reserved. +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # include $(ERL_TOP)/make/target.mk @@ -34,11 +34,13 @@ RELSYSDIR = $(RELEASE_PATH)/lib/tools-$(VSN) # Common Macros # ---------------------------------------------------- -MODULES= cover \ +MODULES= \ + cover \ cover_web \ eprof \ fprof \ cprof \ + lcnt \ instrument \ make \ tags \ diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl new file mode 100644 index 0000000000..989a661b75 --- /dev/null +++ b/lib/tools/src/lcnt.erl @@ -0,0 +1,840 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(lcnt). +-behaviour(gen_server). +-author("Björn-Egil Dahlberg"). + +%% gen_server callbacks +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +%% start/stop +-export([ + start/0, + stop/0 + ]). + +%% erts_debug:lock_counters api +-export([ + rt_collect/0, + rt_collect/1, + rt_clear/0, + rt_clear/1, + rt_opt/1, + rt_opt/2 + ]). + + +%% gen_server call api +-export([ + raw/0, + collect/0, + collect/1, + clear/0, + clear/1, + conflicts/0, + conflicts/1, + locations/0, + locations/1, + inspect/1, + inspect/2, + information/0, + swap_pid_keys/0, + % set options + set/1, + set/2, + + load/1, + save/1 + ]). + +%% convenience +-export([ + apply/3, + apply/2, + apply/1, + all_conflicts/0, + all_conflicts/1, + pid/2, pid/3, + port/1, port/2 + ]). + +-define(version, "1.0"). + +-record(state, { + locks = [], + duration = 0 + }). + + +-record(stats, { + file, + line, + tries, + colls, + time, % us + nt % #timings collected + }). + +-record(lock, { + name, + id, + type, + stats = [] + }). + +-record(print, { + name, + id, + type, + entry, + tries, + colls, + cr, % collision ratio + time, + dtr % time duration ratio + }). + + + +%% -------------------------------------------------------------------- %% +%% +%% start/stop/init +%% +%% -------------------------------------------------------------------- %% + +start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). +stop() -> gen_server:cast(?MODULE, stop). +init([]) -> {ok, #state{ locks = [], duration = 0 } }. + +%% -------------------------------------------------------------------- %% +%% +%% API erts_debug:lock_counters +%% +%% -------------------------------------------------------------------- %% + +rt_collect() -> + erts_debug:lock_counters(info). + +rt_collect(Node) -> + rpc:call(Node, erts_debug, lock_counters, [info]). + +rt_clear() -> + erts_debug:lock_counters(clear). + +rt_clear(Node) -> + rpc:call(Node, erts_debug, lock_counters, [clear]). + +rt_opt({Type, Opt}) -> + erts_debug:lock_counters({Type, Opt}). + +rt_opt(Node, {Type, Opt}) -> + rpc:call(Node, erts_debug, lock_counters, [{Type, Opt}]). + +%% -------------------------------------------------------------------- %% +%% +%% API implementation +%% +%% -------------------------------------------------------------------- %% + +clear() -> rt_clear(). +clear(Node) -> rt_clear(Node). +collect() -> call({collect, rt_collect()}). +collect(Node) -> call({collect, rt_collect(Node)}). + +locations() -> call({locations,[]}). +locations(Opts) -> call({locations, Opts}). +conflicts() -> call({conflicts, []}). +conflicts(Opts) -> call({conflicts, Opts}). +inspect(Lock) -> call({inspect, Lock, []}). +inspect(Lock, Opts) -> call({inspect, Lock, Opts}). +information() -> call(information). +swap_pid_keys() -> call(swap_pid_keys). +raw() -> call(raw). +set(Option, Value) -> call({set, Option, Value}). +set({Option, Value}) -> call({set, Option, Value}). +save(Filename) -> call({save, Filename}). +load(Filename) -> start(), call({load, Filename}). + +call(Msg) -> gen_server:call(?MODULE, Msg, infinity). + +%% -------------------------------------------------------------------- %% +%% +%% convenience implementation +%% +%% -------------------------------------------------------------------- %% + +apply(M,F,As) when is_atom(M), is_atom(F), is_list(As) -> + lcnt:start(), + Opt = lcnt:rt_opt({copy_save, true}), + lcnt:clear(), + Res = erlang:apply(M,F,As), + lcnt:collect(), + lcnt:rt_opt({copy_save, Opt}), + Res. + +apply(Fun) when is_function(Fun) -> + lcnt:apply(Fun, []). + +apply(Fun, As) when is_function(Fun) -> + lcnt:start(), + Opt = lcnt:rt_opt({copy_save, true}), + lcnt:clear(), + Res = erlang:apply(Fun, As), + lcnt:collect(), + lcnt:rt_opt({copy_save, Opt}), + Res. + +all_conflicts() -> all_conflicts(time). +all_conflicts(Sort) -> + conflicts([{max_locks, none}, {thresholds, []},{combine,false}, {sort, Sort}, {reverse, true}]). + +pid(Id, Serial) -> pid(node(), Id, Serial). +pid(Node, Id, Serial) when is_atom(Node) -> + Header = <<131,103,100>>, + String = atom_to_list(Node), + L = length(String), + binary_to_term(list_to_binary([Header, bytes16(L), String, bytes32(Id), bytes32(Serial),0])). + +port(Id) -> port(node(), Id). +port(Node, Id ) when is_atom(Node) -> + Header = <<131,102,100>>, + String = atom_to_list(Node), + L = length(String), + binary_to_term(list_to_binary([Header, bytes16(L), String, bytes32(Id), 0])). + +%% -------------------------------------------------------------------- %% +%% +%% handle_call +%% +%% -------------------------------------------------------------------- %% + +% printing + +handle_call({conflicts, InOpts}, _From, #state{ locks = Locks } = State) when is_list(InOpts) -> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,id,tries,colls,ratio,time,duration]}, + {max_locks, 20}, + {combine, true}, + {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {locations, false}], + + Opts = options(InOpts, Default), + Flocks = filter_locks_type(Locks, proplists:get_value(type, Opts)), + Combos = combine_classes(Flocks, proplists:get_value(combine, Opts)), + Printables = locks2print(Combos, State#state.duration), + Filtered = filter_print(Printables, Opts), + + print_lock_information(Filtered, proplists:get_value(print, Opts)), + + {reply, ok, State}; + +handle_call(information, _From, State) -> + print_state_information(State), + {reply, ok, State}; + +handle_call({locations, InOpts}, _From, #state{ locks = Locks } = State) when is_list(InOpts) -> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,entry,tries,colls,ratio,time,duration]}, + {max_locks, 20}, + {combine, true}, + {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {locations, true}], + + Opts = options(InOpts, Default), + Printables = filter_print([#print{ + name = string_names(Names), + entry = term2string("~p:~p", [Stats#stats.file, Stats#stats.line]), + colls = Stats#stats.colls, + tries = Stats#stats.tries, + cr = percent(Stats#stats.colls, Stats#stats.tries), + time = Stats#stats.time, + dtr = percent(Stats#stats.time, State#state.duration) + } || {Stats, Names} <- combine_locations(Locks) ], Opts), + + print_lock_information(Printables, proplists:get_value(print, Opts)), + + {reply, ok, State}; + +handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, locks = Locks } = State) when is_list(InOpts) -> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,id,tries,colls,ratio,time,duration]}, + {max_locks, 20}, + {combine, false}, + {thresholds, [] }, + {locations, false}], + + Opts = options(InOpts, Default), + Filtered = filter_locks(Locks, Lockname), + IDs = case {proplists:get_value(full_id, Opts), proplists:get_value(combine, Opts)} of + {true, true} -> locks_ids(Filtered); + _ -> [] + end, + Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)), + case proplists:get_value(locations, Opts) of + true -> + lists:foreach(fun + (#lock{ name = Name, id = Id, type = Type, stats = Stats }) -> + IdString = case proplists:get_value(full_id, Opts) of + true -> term2string(proplists:get_value(Name, IDs, Id)); + _ -> term2string(Id) + end, + Combined = [CStats || {CStats,_} <- combine_locations(Stats)], + case Combined of + [] -> + ok; + _ -> + %io:format("Combined ~p~n", [Combined]), + print("lock: " ++ term2string(Name)), + print("id: " ++ IdString), + print("type: " ++ term2string(Type)), + Ps = stats2print(Combined, Duration), + Opts1 = options([{print, [entry, tries,colls,ratio,time,duration]}, + {thresholds, [{tries, -1}, {colls, -1}, {time, -1}]}], Opts), + print_lock_information(filter_print(Ps, Opts1), proplists:get_value(print, Opts1)) + end + % (#lock{ name = Name, id = Id}) -> + % io:format("Empty lock ~p ~p~n", [Name, Id]) + end, Combos); + _ -> + Print1 = locks2print(Combos, Duration), + Print2 = filter_print(Print1, Opts), + print_lock_information(Print2, proplists:get_value(print, Opts)) + end, + {reply, ok, State}; + +handle_call(raw, _From, #state{ locks = Locks} = State)-> + {reply, Locks, State}; + +% collecting +handle_call({collect, Data}, _From, State)-> + {reply, ok, data2state(Data, State)}; + +% manipulate +handle_call(swap_pid_keys, _From, #state{ locks = Locks } = State)-> + SwappedLocks = lists:map(fun + (L) when L#lock.name =:= port_lock; L#lock.type =:= proclock -> + L#lock{ id = L#lock.name, name = L#lock.id }; + (L) -> + L + end, Locks), + + {reply, ok, State#state{ locks = SwappedLocks}}; + +% settings +handle_call({set, data, Data}, _From, State)-> + {reply, ok, data2state(Data, State)}; + +handle_call({set, duration, Duration}, _From, State)-> + {reply, ok, State#state{ duration = Duration}}; + +% file operations +handle_call({load, Filename}, _From, State) -> + case file:read_file(Filename) of + {ok, Binary} -> + case binary_to_term(Binary) of + {?version, Statelist} -> + {reply, ok, list2state(Statelist)}; + {Version, _} -> + {reply, {error, {mismatch, Version, ?version}}, State} + end; + Error -> + {reply, {error, Error}, State} + end; + +handle_call({save, Filename}, _From, State) -> + Binary = term_to_binary({?version, state2list(State)}), + case file:write_file(Filename, Binary) of + ok -> + {reply, ok, State}; + Error -> + {reply, {error, Error}, State} + end; + + +handle_call(Command, _From, State) -> + {reply, {error, {undefined, Command}}, State}. + +%% -------------------------------------------------------------------- %% +%% +%% handle_cast +%% +%% -------------------------------------------------------------------- %% + +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- %% +%% +%% handle_info +%% +%% -------------------------------------------------------------------- %% + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- %% +%% +%% termination +%% +%% -------------------------------------------------------------------- %% + +terminate(_Reason, _State) -> + ok. + +%% -------------------------------------------------------------------- %% +%% +%% code_change +%% +%% -------------------------------------------------------------------- %% + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% -------------------------------------------------------------------- %% +%% +%% AUX +%% +%% -------------------------------------------------------------------- %% + +% summate + +summate_locks(Locks) -> summate_locks(Locks, #stats{ tries = 0, colls = 0, time = 0, nt = 0}). +summate_locks([], Stats) -> Stats; +summate_locks([L|Ls], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt}) -> + S = summate_stats(L#lock.stats), + summate_locks(Ls, #stats{ tries = Tries + S#stats.tries, colls = Colls + S#stats.colls, time = Time + S#stats.time, nt = Nt + S#stats.nt}). + +summate_stats(Stats) -> summate_stats(Stats, #stats{ tries = 0, colls = 0, time = 0, nt = 0}). +summate_stats([], Stats) -> Stats; +summate_stats([S|Ss], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt}) -> + summate_stats(Ss, #stats{ tries = Tries + S#stats.tries, colls = Colls + S#stats.colls, time = Time + S#stats.time, nt = Nt + S#stats.nt}). + + +%% manipulators +filter_locks_type(Locks, undefined) -> Locks; +filter_locks_type(Locks, all) -> Locks; +filter_locks_type(Locks, Types) when is_list(Types) -> + [ L || L <- Locks, lists:member(L#lock.type, Types)]; +filter_locks_type(Locks, Type) -> + [ L || L <- Locks, L#lock.type =:= Type]. + +filter_locks(Locks, {Lockname, Ids}) when is_list(Ids) -> + [ L || L <- Locks, L#lock.name =:= Lockname, lists:member(L#lock.id, Ids)]; +filter_locks(Locks, {Lockname, Id}) -> + [ L || L <- Locks, L#lock.name =:= Lockname, L#lock.id =:= Id ]; +filter_locks(Locks, Lockname) -> + [ L || L <- Locks, L#lock.name =:= Lockname ]. +% order of processing +% 2. cut thresholds +% 3. sort locks +% 4. max length of locks + +filter_print(PLs, Opts) -> + TLs = threshold_locks(PLs, proplists:get_value(thresholds, Opts, [])), + SLs = sort_locks(TLs, proplists:get_value(sort, Opts, time)), + CLs = cut_locks(SLs, proplists:get_value(max_locks, Opts, none)), + reverse_locks(CLs, proplists:get_value(reverse, Opts, false)). + +sort_locks(Locks, Type) -> lists:reverse(sort_locks0(Locks, Type)). +sort_locks0(Locks, name) -> lists:keysort(#print.name, Locks); +sort_locks0(Locks, id) -> lists:keysort(#print.id, Locks); +sort_locks0(Locks, type) -> lists:keysort(#print.type, Locks); +sort_locks0(Locks, tries) -> lists:keysort(#print.tries, Locks); +sort_locks0(Locks, colls) -> lists:keysort(#print.colls, Locks); +sort_locks0(Locks, ratio) -> lists:keysort(#print.cr, Locks); +sort_locks0(Locks, time) -> lists:keysort(#print.time, Locks); +sort_locks0(Locks, _) -> sort_locks0(Locks, time). + +% cut locks not above certain thresholds +threshold_locks(Locks, Thresholds) -> + Tries = proplists:get_value(tries, Thresholds, -1), + Colls = proplists:get_value(colls, Thresholds, -1), + Time = proplists:get_value(time, Thresholds, -1), + [ L || L <- Locks, L#print.tries > Tries, L#print.colls > Colls, L#print.time > Time]. + +cut_locks(Locks, N) when is_integer(N), N > 0 -> lists:sublist(Locks, N); +cut_locks(Locks, _) -> Locks. + +%% reversal +reverse_locks(Locks, true) -> lists:reverse(Locks); +reverse_locks(Locks, _) -> Locks. + + +%% +string_names([]) -> ""; +string_names(Names) -> string_names(Names, []). +string_names([Name], Strings) -> strings(lists:reverse([term2string(Name) | Strings])); +string_names([Name|Names],Strings) -> string_names(Names, [term2string(Name) ++ ","|Strings]). + +%% combine_locations +%% In: +%% Locations :: [#lock{}] | [#stats{}] +%% Out: +%% [{{File,Line}, #stats{}, [Lockname]}] + + +combine_locations(Locations) -> gb_trees:values(combine_locations(Locations, gb_trees:empty())). +combine_locations([], Tree) -> Tree; +combine_locations([S|_] = Stats, Tree) when is_record(S, stats) -> + combine_locations(Stats, undefined, Tree); +combine_locations([#lock{ stats = Stats, name = Name}|Ls], Tree) -> + combine_locations(Ls, combine_locations(Stats, Name, Tree)). + +combine_locations([], _, Tree) -> Tree; +combine_locations([S|Ss], Name, Tree) when is_record(S, stats)-> + Key = {S#stats.file, S#stats.line}, + Tree1 = case gb_trees:lookup(Key, Tree) of + none -> + gb_trees:insert(Key, {S, [Name]}, Tree); + {value, {C, Names}} -> + NewNames = case lists:member(Name, Names) of + true -> Names; + _ -> [Name | Names] + end, + gb_trees:update(Key, { + C#stats{ + tries = C#stats.tries + S#stats.tries, + colls = C#stats.colls + S#stats.colls, + time = C#stats.time + S#stats.time, + nt = C#stats.nt + S#stats.nt + }, NewNames}, Tree) + end, + combine_locations(Ss, Name, Tree1). + +%% combines all statistics for a class (name) lock +%% id's are translated to #id's. + +combine_classes(Locks, true) -> combine_classes1(Locks, gb_trees:empty()); +combine_classes(Locks, _) -> Locks. + +combine_classes1([], Tree) -> gb_trees:values(Tree); +combine_classes1([L|Ls], Tree) -> + Key = L#lock.name, + case gb_trees:lookup(Key, Tree) of + none -> + combine_classes1(Ls, gb_trees:insert(Key, L#lock{ id = 1 }, Tree)); + {value, C} -> + combine_classes1(Ls, gb_trees:update(Key, C#lock{ + id = C#lock.id + 1, + stats = L#lock.stats ++ C#lock.stats + }, Tree)) + end. + +locks_ids(Locks) -> locks_ids(Locks, []). +locks_ids([], Out) -> Out; +locks_ids([#lock{ name = Key } = L|Ls], Out) -> + case proplists:get_value(Key, Out) of + undefined -> + locks_ids(Ls, [{Key, [L#lock.id] } | Out]); + Ids -> + locks_ids(Ls, [{Key, [L#lock.id | Ids] } | proplists:delete(Key,Out)]) + end. + +stats2print(Stats, Duration) -> + lists:map(fun + (S) -> + #print{ + entry = term2string("~p:~p", [S#stats.file, S#stats.line]), + colls = S#stats.colls, + tries = S#stats.tries, + cr = percent(S#stats.colls, S#stats.tries), + time = S#stats.time, + dtr = percent(S#stats.time, Duration) + } + end, Stats). + +locks2print(Locks, Duration) -> + lists:map( fun + (L) -> + Tries = lists:sum([T || #stats{ tries = T} <- L#lock.stats]), + Colls = lists:sum([C || #stats{ colls = C} <- L#lock.stats]), + Time = lists:sum([T || #stats{ time = T} <- L#lock.stats]), + Cr = percent(Colls, Tries), + Dtr = percent(Time, Duration), + #print{ + name = L#lock.name, + id = L#lock.id, + type = L#lock.type, + tries = Tries, + colls = Colls, + cr = Cr, + time = Time, + dtr = Dtr + } + end, Locks). + +%% state making + +data2state(Data, State) -> + Duration = time2us(proplists:get_value(duration, Data)), + Rawlocks = proplists:get_value(locks, Data), + Locks = locks2records(Rawlocks), + State#state{ + duration = Duration, + locks = Locks + }. + +locks2records(Locks) -> locks2records(Locks, []). +locks2records([], Out) -> Out; +locks2records([{Name, Id, Type, Stats}|Locks], Out) -> + Lock = #lock{ + name = Name, + id = clean_id_creation(Id), + type = Type, + stats = [ #stats{ + file = File, + line = Line, + tries = Tries, + colls = Colls, + time = time2us({S, Ns}), + nt = N + } || {{File, Line}, {Tries, Colls, {S, Ns, N}}} <- Stats] }, + locks2records(Locks, [Lock|Out]). + +clean_id_creation(Id) when is_pid(Id) -> + Bin = term_to_binary(Id), + <<H:3/binary, L:16, Node:L/binary, Ids:8/binary, _Creation/binary>> = Bin, + Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]), + binary_to_term(Bin2); +clean_id_creation(Id) when is_port(Id) -> + Bin = term_to_binary(Id), + <<H:3/binary, L:16, Node:L/binary, Ids:4/binary, _Creation/binary>> = Bin, + Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]), + binary_to_term(Bin2); +clean_id_creation(Id) -> + Id. + +%% serializer + +state_default(Field) -> proplists:get_value(Field, state2list(#state{})). + +state2list(State) -> + [_|Values] = tuple_to_list(State), + lists:zipwith(fun + (locks, Locks) -> {locks, [lock2list(Lock) || Lock <- Locks]}; + (X, Y) -> {X,Y} + end, record_info(fields, state), Values). + +list2state(List) -> list2state(record_info(fields, state), List, [state]). +list2state([], _, Out) -> list_to_tuple(lists:reverse(Out)); +list2state([locks|Fs], List, Out) -> + Locks = [ list2lock(Lock) || Lock <- proplists:get_value(locks, List, [])], + list2state(Fs, List, [Locks|Out]); +list2state([F|Fs], List, Out) -> list2state(Fs, List, [proplists:get_value(F, List, state_default(F))|Out]). + +lock_default(Field) -> proplists:get_value(Field, lock2list(#lock{})). + +lock2list(Lock) -> + [_|Values] = tuple_to_list(Lock), + lists:zip(record_info(fields, lock), Values). + +list2lock(List) -> list2lock(record_info(fields, lock), List, [lock]). +list2lock([], _, Out) -> list_to_tuple(lists:reverse(Out)); +list2lock([F|Fs], List, Out) -> list2lock(Fs, List, [proplists:get_value(F, List, lock_default(F))|Out]). + +%% printing + +%% print_lock_information +%% In: +%% Locks :: [#lock{}] +%% Print :: [Type | {Type, integer()}] +%% +%% Out: +%% ok + +auto_print_width(Locks, Print) -> + % iterate all lock entries to save all max length values + % these are records, so we do a little tuple <-> list smashing + R = lists:foldl(fun + (L, Max) -> + list_to_tuple(lists:reverse(lists:foldl(fun + ({print,print}, Out) -> [print|Out]; + ({Str, Len}, Out) -> [erlang:min(erlang:max(length(s(Str))+1,Len),80)|Out] + end, [], lists:zip(tuple_to_list(L), tuple_to_list(Max))))) + end, #print{ id = 4, type = 5, entry = 5, name = 6, tries = 8, colls = 13, cr = 16, time = 11, dtr = 14 }, + Locks), + % Setup the offsets for later pruning + Offsets = [ + {id, R#print.id}, + {name, R#print.name}, + {type, R#print.type}, + {entry, R#print.entry}, + {tries, R#print.tries}, + {colls, R#print.colls}, + {ratio, R#print.cr}, + {time, R#print.time}, + {duration, R#print.dtr}], + % Prune offsets to only allow specified print options + lists:foldr(fun + ({Type, W}, Out) -> [{Type, W}|Out]; + (Type, Out) -> [proplists:lookup(Type, Offsets)|Out] + end, [], Print). + +print_lock_information(Locks, Print) -> + % remake Print to autosize entries + AutoPrint = auto_print_width(Locks, Print), + + print_header(AutoPrint), + + lists:foreach(fun + (L) -> + print_lock(L, AutoPrint) + end, Locks), + ok. + +print_header(Opts) -> + Header = #print{ + name = "lock", + id = "id", + type = "type", + entry = "location", + tries = "#tries", + colls = "#collisions", + cr = "collisions [%]", + time = "time [us]", + dtr = "duration [%]" + }, + Divider = #print{ + name = lists:duplicate(1 + length(Header#print.name), 45), + id = lists:duplicate(1 + length(Header#print.id), 45), + type = lists:duplicate(1 + length(Header#print.type), 45), + entry = lists:duplicate(1 + length(Header#print.entry), 45), + tries = lists:duplicate(1 + length(Header#print.tries), 45), + colls = lists:duplicate(1 + length(Header#print.colls), 45), + cr = lists:duplicate(1 + length(Header#print.cr), 45), + time = lists:duplicate(1 + length(Header#print.time), 45), + dtr = lists:duplicate(1 + length(Header#print.dtr), 45) + }, + print_lock(Header, Opts), + print_lock(Divider, Opts), + ok. + + +print_lock(L, Opts) -> print_lock(L, Opts, []). +print_lock(_, [], Formats) -> print(strings(lists:reverse(Formats))); +print_lock(L, [Opt|Opts], Formats) -> + case Opt of + id -> print_lock(L, Opts, [{space, 25, s(L#print.id) } | Formats]); + {id, W} -> print_lock(L, Opts, [{space, W, s(L#print.id) } | Formats]); + type -> print_lock(L, Opts, [{space, 18, s(L#print.type) } | Formats]); + {type, W} -> print_lock(L, Opts, [{space, W, s(L#print.type) } | Formats]); + entry -> print_lock(L, Opts, [{space, 30, s(L#print.entry)} | Formats]); + {entry, W} -> print_lock(L, Opts, [{space, W, s(L#print.entry)} | Formats]); + name -> print_lock(L, Opts, [{space, 22, s(L#print.name) } | Formats]); + {name, W} -> print_lock(L, Opts, [{space, W, s(L#print.name) } | Formats]); + tries -> print_lock(L, Opts, [{space, 12, s(L#print.tries)} | Formats]); + {tries, W} -> print_lock(L, Opts, [{space, W, s(L#print.tries)} | Formats]); + colls -> print_lock(L, Opts, [{space, 14, s(L#print.colls)} | Formats]); + {colls, W} -> print_lock(L, Opts, [{space, W, s(L#print.colls)} | Formats]); + ratio -> print_lock(L, Opts, [{space, 20, s(L#print.cr) } | Formats]); + {ratio, W} -> print_lock(L, Opts, [{space, W, s(L#print.cr) } | Formats]); + time -> print_lock(L, Opts, [{space, 15, s(L#print.time) } | Formats]); + {time, W} -> print_lock(L, Opts, [{space, W, s(L#print.time) } | Formats]); + duration -> print_lock(L, Opts, [{space, 20, s(L#print.dtr) } | Formats]); + {duration, W} -> print_lock(L, Opts, [{space, W, s(L#print.dtr) } | Formats]); + _ -> print_lock(L, Opts, Formats) + end. + +print_state_information(#state{ locks = Locks} = State) -> + Stats = summate_locks(Locks), + print("information:"), + print(kv("#locks", s(length(Locks)))), + print(kv("duration", s(State#state.duration) ++ " us" ++ " (" ++ s(State#state.duration/1000000) ++ " s)")), + print("\nsummated stats:"), + print(kv("#tries", s(Stats#stats.tries))), + print(kv("#colls", s(Stats#stats.colls))), + print(kv("wait time", s(Stats#stats.time) ++ " us" ++ " ( " ++ s(Stats#stats.time/1000000) ++ " s)")), + print(kv("percent of duration", s(Stats#stats.time/State#state.duration*100) ++ " %")), + ok. + +%% AUX + +time2us({S, Ns}) -> round(S*1000000 + Ns/1000). + +percent(_,0) -> 0.0; +percent(T,N) -> T/N*100. + +options(Opts, Default) when is_list(Default) -> + options1(proplists:unfold(Opts), Default). +options1([], Defaults) -> Defaults; +options1([{Key, Value}|Opts], Defaults) -> + case proplists:get_value(Key, Defaults) of + undefined -> options1(Opts, [{Key, Value} | Defaults]); + _ -> options1(Opts, [{Key, Value} | proplists:delete(Key, Defaults)]) + end. + +%%% AUX STRING FORMATTING + +print(String) -> io:format("~s~n", [String]). + +kv(Key, Value) -> kv(Key, Value, 20). +kv(Key, Value, Offset) -> term2string(term2string("~~~ps : ~~s", [Offset]),[Key, Value]). + +s(T) when is_float(T) -> term2string("~.4f", [T]); +s(T) when is_list(T) -> term2string("~s", [T]); +s(T) -> term2string(T). + +strings(Strings) -> strings(Strings, []). +strings([], Out) -> Out; +strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ps", [N]), [S])); +strings([{format, Format, S} | Ss], Out) -> strings(Ss, Out ++ term2string(Format, [S])); +strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~s", [S])). + + +term2string({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> term2string("~p:~p/~p", [M,F,A]); +term2string(Term) when is_port(Term) -> + % ex #Port<6442.816> + <<_:3/binary, L:16, Node:L/binary, Ids:32, _/binary>> = term_to_binary(Term), + term2string("#Port<~s.~w>", [Node, Ids]); +term2string(Term) when is_pid(Term) -> + % ex <0.80.0> + <<_:3/binary, L:16, Node:L/binary, Ids:32, Serial:32, _/binary>> = term_to_binary(Term), + term2string("<~s.~w.~w>", [Node, Ids, Serial]); +term2string(Term) -> term2string("~w", [Term]). +term2string(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). + +%%% AUD id binary + +bytes16(Value) -> + B0 = Value band 255, + B1 = (Value bsr 8) band 255, + <<B1, B0>>. + +bytes32(Value) -> + B0 = Value band 255, + B1 = (Value bsr 8) band 255, + B2 = (Value bsr 16) band 255, + B3 = (Value bsr 24) band 255, + <<B3, B2, B1, B0>>. diff --git a/lib/tools/test/Makefile b/lib/tools/test/Makefile index a846a3a6f4..3a59be758a 100644 --- a/lib/tools/test/Makefile +++ b/lib/tools/test/Makefile @@ -26,6 +26,7 @@ MODULES = \ fprof_SUITE \ cprof_SUITE \ instrument_SUITE \ + lcnt_SUITE \ make_SUITE \ tools_SUITE \ xref_SUITE \ @@ -49,7 +50,8 @@ RELSYSDIR = $(RELEASE_PATH)/tools_test # FLAGS # ---------------------------------------------------- ERL_MAKE_FLAGS += -ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \ + -I$(ERL_TOP)/lib/percept/include EBIN = . @@ -68,10 +70,11 @@ tests debug opt: make_emakefile clean: rm -f $(EMAKEFILE) rm -f $(TARGET_FILES) - rm -f core + rm -f core *~ docs: + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- diff --git a/lib/tools/test/lcnt_SUITE.erl b/lib/tools/test/lcnt_SUITE.erl new file mode 100644 index 0000000000..e6866f721d --- /dev/null +++ b/lib/tools/test/lcnt_SUITE.erl @@ -0,0 +1,154 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(lcnt_SUITE). +-include("test_server.hrl"). + +%% Test server specific exports +-export([all/1]). +-export([init_per_suite/1, end_per_suite/1]). +-export([init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + load_v1/1, + conflicts/1, + locations/1, + swap_keys/1 + ]). + +%% Default timetrap timeout (set in init_per_testcase) +-define(default_timeout, ?t:minutes(2)). + +init_per_suite(Config) when is_list(Config) -> + Config. + +end_per_suite(Config) when is_list(Config) -> + Config. + +init_per_testcase(_Case, Config) -> + Dog = ?t:timetrap(?default_timeout), + [{watchdog,Dog} | Config]. + +end_per_testcase(_Case, Config) -> + Dog = ?config(watchdog, Config), + ?t:timetrap_cancel(Dog), + ok. + +all(suite) -> + % Test cases + [load_v1, conflicts, locations, swap_keys]. + +%%---------------------------------------------------------------------- +%% Tests +%%---------------------------------------------------------------------- + +load_v1(suite) -> + []; +load_v1(doc) -> + ["Load data from file."]; +load_v1(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:stop(), + ok. + +conflicts(suite) -> + []; +conflicts(doc) -> + ["API: conflicts"]; +conflicts(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:conflicts(), + THs = [-1, 0, 100, 1000], + Print = [name , id , type , entry , tries , colls , ratio , time , duration], + Opts = [ + [{sort, Sort}, {reverse, Rev}, {max_locks, ML}, {combine, Combine}, {thresholds, [TH]}, {print, [Print]}] || + Sort <- [name , id , type , tries , colls , ratio , time , entry], + ML <- [none, 1 , 32, 4096], + Combine <- [true, false], + TH <- [{tries, Tries} || Tries <- THs] ++ [{colls, Colls} || Colls <- THs] ++ [{time, Time} || Time <- THs], + Rev <- [true, false] + ], + ?line ok = test_conflicts_opts(Opts), + ?line ok = lcnt:stop(), + ok. + +test_conflicts_opts([]) -> ok; +test_conflicts_opts([Opt|Opts]) -> + ?line ok = lcnt:conflicts(Opt), + test_conflicts_opts(Opts). + +locations(suite) -> + []; +locations(doc) -> + ["API: locations"]; +locations(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:locations(), + THs = [-1, 0, 100, 1000], + Print = [name , id , type , entry , tries , colls , ratio , time , duration], + Opts = [ + [{full_id, Id}, {sort, Sort}, {max_locks, ML}, {combine, Combine}, {thresholds, [TH]}, {print, Print}] || + Sort <- [name , id , type , tries , colls , ratio , time , entry], + ML <- [none, 1 , 64], + Combine <- [true, false], + TH <- [{tries, Tries} || Tries <- THs] ++ [{colls, Colls} || Colls <- THs] ++ [{time, Time} || Time <- THs], + Id <- [true, false] + ], + ?line ok = test_locations_opts(Opts), + ?line ok = lcnt:stop(), + ok. + +test_locations_opts([]) -> ok; +test_locations_opts([Opt|Opts]) -> + ?line ok = lcnt:locations(Opt), + test_locations_opts(Opts). + +swap_keys(suite) -> + []; +swap_keys(doc) -> + ["Test interchanging port/process id with class"]; +swap_keys(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:conflicts(), + ?line ok = lcnt:swap_pid_keys(), + ?line ok = lcnt:conflicts(), + ?line ok = lcnt:stop(), + ok. + + +%%---------------------------------------------------------------------- +%% Auxiliary tests +%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Auxiliary +%%---------------------------------------------------------------------- diff --git a/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt b/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt Binary files differnew file mode 100644 index 0000000000..6087f6f37e --- /dev/null +++ b/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt |