diff options
Diffstat (limited to 'erts/emulator/beam/erl_lock_count.c')
-rw-r--r-- | erts/emulator/beam/erl_lock_count.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/erts/emulator/beam/erl_lock_count.c b/erts/emulator/beam/erl_lock_count.c new file mode 100644 index 0000000000..6211983f4b --- /dev/null +++ b/erts/emulator/beam/erl_lock_count.c @@ -0,0 +1,675 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2008-2009. 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% + */ + +/* + * Description: Statistics for locks. + * + * Author: Bj�rn-Egil Dahlberg + * Date: 2008-07-03 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Needed for VxWorks va_arg */ +#include "sys.h" + +#ifdef ERTS_ENABLE_LOCK_COUNT + +#include "erl_lock_count.h" +#include "ethread.h" +#include "erl_term.h" +#include "atom.h" +#include <stdio.h> + +/* globals, dont access these without locks or blocks */ + +ethr_mutex lcnt_data_lock; +erts_lcnt_data_t *erts_lcnt_data; +Uint16 erts_lcnt_rt_options; +erts_lcnt_time_t timer_start; +const char *str_undefined = "undefined"; + +static ethr_tsd_key lcnt_thr_data_key; +static int lcnt_n_thr; +static erts_lcnt_thread_data_t *lcnt_thread_data[1024]; + +/* local functions */ + +static ERTS_INLINE void lcnt_lock(void) { + ethr_mutex_lock(&lcnt_data_lock); +} + +static ERTS_INLINE void lcnt_unlock(void) { + ethr_mutex_unlock(&lcnt_data_lock); +} + + +static char* lcnt_lock_type(Uint16 flag) { + switch(flag & ERTS_LCNT_LT_ALL) { + case ERTS_LCNT_LT_SPINLOCK: return "spinlock"; + case ERTS_LCNT_LT_RWSPINLOCK: return "rw_spinlock"; + case ERTS_LCNT_LT_MUTEX: return "mutex"; + case ERTS_LCNT_LT_RWMUTEX: return "rw_mutex"; + case ERTS_LCNT_LT_PROCLOCK: return "proclock"; + default: return ""; + } +} + +static void lcnt_clear_stats(erts_lcnt_lock_stats_t *stats) { + ethr_atomic_set(&stats->tries, 0); + ethr_atomic_set(&stats->colls, 0); + stats->timer.s = 0; + stats->timer.ns = 0; + stats->timer_n = 0; + stats->file = (char *)str_undefined; + stats->line = 0; +} + +static void lcnt_time(erts_lcnt_time_t *time) { +#ifdef HAVE_GETHRTIME + SysHrTime hr_time; + hr_time = sys_gethrtime(); + time->s = (unsigned long)(hr_time / 1000000000LL); + time->ns = (unsigned long)(hr_time - 1000000000LL*time->s); +#else + SysTimeval tv; + sys_gettimeofday(&tv); + time->s = tv.tv_sec; + time->ns = tv.tv_usec*1000LL; +#endif +} + +static void lcnt_time_diff(erts_lcnt_time_t *d, erts_lcnt_time_t *t1, erts_lcnt_time_t *t0) { + long ds; + long dns; + + ds = t1->s - t0->s; + dns = t1->ns - t0->ns; + + /* the difference should not be able to get bigger than 1 sec in ns*/ + + if (dns < 0) { + ds -= 1; + dns += 1000000000LL; + } + + d->s = ds; + d->ns = dns; +} + +/* difference d must be positive */ + +static void lcnt_time_add(erts_lcnt_time_t *t, erts_lcnt_time_t *d) { + unsigned long ngns = 0; + + t->s += d->s; + t->ns += d->ns; + + ngns = t->ns / 1000000000LL; + t->ns = t->ns % 1000000000LL; + + t->s += ngns; +} + +static erts_lcnt_thread_data_t *lcnt_thread_data_alloc(void) { + erts_lcnt_thread_data_t *eltd; + + eltd = (erts_lcnt_thread_data_t*)malloc(sizeof(erts_lcnt_thread_data_t)); + eltd->timer_set = 0; + eltd->lock_in_conflict = 0; + + eltd->id = lcnt_n_thr++; + /* set thread data to array */ + lcnt_thread_data[eltd->id] = eltd; + + return eltd; +} + +static erts_lcnt_thread_data_t *lcnt_get_thread_data(void) { + return (erts_lcnt_thread_data_t *)ethr_tsd_get(lcnt_thr_data_key); +} + + +/* debug */ + +#if 0 +static char* lock_opt(Uint16 flag) { + if ((flag & ERTS_LCNT_LO_WRITE) && (flag & ERTS_LCNT_LO_READ)) return "rw"; + if (flag & ERTS_LCNT_LO_READ ) return "r "; + if (flag & ERTS_LCNT_LO_WRITE) return " w"; + return "--"; +} + +static void print_lock_x(erts_lcnt_lock_t *lock, Uint16 flag, char *action, char *extra) { + long int colls, tries, w_state, r_state; + erts_lcnt_lock_stats_t *stats = NULL; + + float rate; + char *type; + int i; + + type = lcnt_lock_type(lock->flag); + 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", + action, + lock->name, + r_state, + 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, ""); +} + +#endif + +static erts_lcnt_lock_stats_t *lcnt_get_lock_stats(erts_lcnt_lock_t *lock, char *file, unsigned int line) { + unsigned int i; + erts_lcnt_lock_stats_t *stats = NULL; + + for (i = 0; i < lock->n_stats; i++) { + if ((lock->stats[i].file == file) && (lock->stats[i].line == line)) { + return &(lock->stats[i]); + } + } + if (lock->n_stats < ERTS_LCNT_MAX_LOCK_LOCATIONS) { + stats = &lock->stats[lock->n_stats]; + lock->n_stats++; + + stats->file = file; + stats->line = line; + return stats; + } + return &lock->stats[0]; + +} + +static void lcnt_update_stats(erts_lcnt_lock_stats_t *stats, int lock_in_conflict, erts_lcnt_time_t *time_wait) { + + ethr_atomic_inc(&stats->tries); + + /* beware of trylock */ + if (lock_in_conflict) ethr_atomic_inc(&stats->colls); + + if (time_wait) { + lcnt_time_add(&(stats->timer), time_wait); + stats->timer_n++; + } +} + +/* + * interface + */ + +void erts_lcnt_init() { + erts_lcnt_thread_data_t *eltd = NULL; + + /* init lock */ + if (ethr_mutex_init(&lcnt_data_lock) != 0) abort(); + + /* init tsd */ + lcnt_n_thr = 0; + + ethr_tsd_key_create(&lcnt_thr_data_key); + + lcnt_lock(); + + erts_lcnt_rt_options = ERTS_LCNT_OPT_PROCLOCK; + + eltd = lcnt_thread_data_alloc(); + + ethr_tsd_set(lcnt_thr_data_key, eltd); + + /* init lcnt structure */ + erts_lcnt_data = (erts_lcnt_data_t*)malloc(sizeof(erts_lcnt_data_t)); + erts_lcnt_data->current_locks = erts_lcnt_list_init(); + erts_lcnt_data->deleted_locks = erts_lcnt_list_init(); + + lcnt_unlock(); + + /* set start timer and zero statistics */ + erts_lcnt_clear_counters(); +} + +/* list operations */ + +/* BEGIN ASSUMPTION: lcnt_data_lock taken */ + +erts_lcnt_lock_list_t *erts_lcnt_list_init(void) { + erts_lcnt_lock_list_t *list; + + list = (erts_lcnt_lock_list_t*)malloc(sizeof(erts_lcnt_lock_list_t)); + list->head = NULL; + list->tail = NULL; + list->n = 0; + return list; +} + +/* only do this on the list with the deleted locks! */ +void erts_lcnt_list_clear(erts_lcnt_lock_list_t *list) { + erts_lcnt_lock_t *lock = NULL, + *next = NULL; + + lock = list->head; + + while(lock != NULL) { + next = lock->next; + free(lock); + lock = next; + } + + list->head = NULL; + list->tail = NULL; + list->n = 0; +} + +void erts_lcnt_list_insert(erts_lcnt_lock_list_t *list, erts_lcnt_lock_t *lock) { + erts_lcnt_lock_t *tail = NULL; + + tail = list->tail; + if (tail) { + tail->next = lock; + lock->prev = tail; + } else { + list->head = lock; + lock->prev = NULL; + ASSERT(!lock->next); + } + lock->next = NULL; + list->tail = lock; + + list->n++; +} + +void erts_lcnt_list_delete(erts_lcnt_lock_list_t *list, erts_lcnt_lock_t *lock) { + + if (lock->next) lock->next->prev = lock->prev; + if (lock->prev) lock->prev->next = lock->next; + if (list->head == lock) list->head = lock->next; + if (list->tail == lock) list->tail = lock->prev; + + lock->prev = NULL; + lock->next = NULL; + list->n--; +} +/* END ASSUMPTION: lcnt_data_lock taken */ + + +/* lock operations */ + +/* interface to erl_threads.h */ +/* only lock on init and destroy, all others should use atomics */ +void erts_lcnt_init_lock(erts_lcnt_lock_t *lock, char *name, Uint16 flag ) { + erts_lcnt_init_lock_x(lock, name, flag, am_undefined); +} +void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eterm id) { + int i; + lcnt_lock(); + + lock->next = NULL; + lock->prev = NULL; + lock->flag = flag; + lock->name = name; + lock->id = id; + + ethr_atomic_init(&lock->r_state, 0); + ethr_atomic_init(&lock->w_state, 0); + +#ifdef DEBUG + ethr_atomic_init(&lock->flowstate, 0); +#endif + + lock->n_stats = 1; + + 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(); +} + +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; + + erts_lcnt_list_insert(erts_lcnt_data->deleted_locks, deleted_lock); + + /* delete original */ + erts_lcnt_list_delete(erts_lcnt_data->current_locks, lock); + + lcnt_unlock(); +} + +/* lock */ + +void erts_lcnt_lock_opt(erts_lcnt_lock_t *lock, Uint16 option) { + long r_state = 0, w_state = 0; + erts_lcnt_thread_data_t *eltd; + + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + + eltd = lcnt_get_thread_data(); + + ASSERT(eltd); + + ethr_atomic_read(&lock->w_state, &w_state); + + if (option & ERTS_LCNT_LO_WRITE) { + ethr_atomic_read(&lock->r_state, &r_state); + ethr_atomic_inc( &lock->w_state); + } + if (option & ERTS_LCNT_LO_READ) { + ethr_atomic_inc( &lock->r_state); + } + + /* 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)){ + eltd->lock_in_conflict = 1; + if (eltd->timer_set == 0) lcnt_time(&eltd->timer); + eltd->timer_set++; + } else { + eltd->lock_in_conflict = 0; + } +} + +void erts_lcnt_lock(erts_lcnt_lock_t *lock) { + long w_state; + erts_lcnt_thread_data_t *eltd; + + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + + ethr_atomic_read(&lock->w_state, &w_state); + ethr_atomic_inc( &lock->w_state); + + eltd = lcnt_get_thread_data(); + + ASSERT(eltd); + + if (w_state > 0) { + eltd->lock_in_conflict = 1; + /* only set the timer if nobody else has it + * This should only happen when proc_locks aquires several locks + * 'atomicly'. All other locks will block the thread if w_state > 0 + * i.e. locked. + */ + if (eltd->timer_set == 0) lcnt_time(&eltd->timer); + eltd->timer_set++; + + } else { + eltd->lock_in_conflict = 0; + } +} + +/* if a lock wasn't really a lock operation, bad bad process locks */ + +void erts_lcnt_lock_unaquire(erts_lcnt_lock_t *lock) { + /* should check if this thread was "waiting" */ + + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + + ethr_atomic_dec( &lock->w_state); +} + +/* erts_lcnt_lock_post + * used when we get a lock (i.e. directly after a lock operation) + * if the timer was set then we had to wait for the lock + * lock_post will calculate the wait time. + */ +void erts_lcnt_lock_post(erts_lcnt_lock_t *lock) { + erts_lcnt_lock_post_x(lock, (char*)str_undefined, 0); +} + +void erts_lcnt_lock_post_x(erts_lcnt_lock_t *lock, char *file, unsigned int line) { + erts_lcnt_thread_data_t *eltd; + erts_lcnt_time_t timer; + erts_lcnt_time_t time_wait; + erts_lcnt_lock_stats_t *stats; +#ifdef DEBUG + long flowstate; +#endif + + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + +#ifdef DEBUG + if (!(lock->flag & (ERTS_LCNT_LT_RWMUTEX | ERTS_LCNT_LT_RWSPINLOCK))) { + ethr_atomic_read(&lock->flowstate, &flowstate); + ASSERT(flowstate == 0); + ethr_atomic_inc( &lock->flowstate); + } +#endif + + 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); + + ASSERT(eltd->timer_set >= 0); + } else { + lcnt_update_stats(stats, eltd->lock_in_conflict, NULL); + } + +} + +/* unlock */ + +void erts_lcnt_unlock_opt(erts_lcnt_lock_t *lock, Uint16 option) { + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + if (option & ERTS_LCNT_LO_WRITE) ethr_atomic_dec(&lock->w_state); + if (option & ERTS_LCNT_LO_READ ) ethr_atomic_dec(&lock->r_state); +} + +void erts_lcnt_unlock(erts_lcnt_lock_t *lock) { +#ifdef DEBUG + long w_state; + long flowstate; +#endif + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; +#ifdef DEBUG + /* flowstate */ + ethr_atomic_read(&lock->flowstate, &flowstate); + ASSERT(flowstate == 1); + ethr_atomic_dec( &lock->flowstate); + + /* write state */ + ethr_atomic_read(&lock->w_state, &w_state); + ASSERT(w_state > 0) +#endif + ethr_atomic_dec(&lock->w_state); +} + +/* trylock */ + +void erts_lcnt_trylock_opt(erts_lcnt_lock_t *lock, int res, Uint16 option) { + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + /* Determine lock_state via res instead of state */ + if (res != EBUSY) { + if (option & ERTS_LCNT_LO_WRITE) ethr_atomic_inc(&lock->w_state); + if (option & ERTS_LCNT_LO_READ ) ethr_atomic_inc(&lock->r_state); + lcnt_update_stats(&(lock->stats[0]), 0, NULL); + } else { + ethr_atomic_inc(&lock->stats[0].tries); + ethr_atomic_inc(&lock->stats[0].colls); + } +} + + +void erts_lcnt_trylock(erts_lcnt_lock_t *lock, int res) { + /* Determine lock_state via res instead of state */ +#ifdef DEBUG + long flowstate; +#endif + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; + if (res != EBUSY) { + +#ifdef DEBUG + ethr_atomic_read(&lock->flowstate, &flowstate); + ASSERT(flowstate == 0); + ethr_atomic_inc( &lock->flowstate); +#endif + ethr_atomic_inc(&lock->w_state); + + lcnt_update_stats(&(lock->stats[0]), 0, NULL); + + } else { + ethr_atomic_inc(&lock->stats[0].tries); + ethr_atomic_inc(&lock->stats[0].colls); + } +} + +/* thread operations */ + +static void *lcnt_thr_init(erts_lcnt_thread_data_t *eltd) { + void *(*function)(void *); + void *argument; + void *res; + function = eltd->function; + argument = eltd->argument; + + ethr_tsd_set(lcnt_thr_data_key, eltd); + + res = (void *)function(argument); + free(eltd); + return (void *)res; +} + + + +int erts_lcnt_thr_create(ethr_tid *tid, void * (*function)(void *), void *arg, ethr_thr_opts *opts) { + erts_lcnt_thread_data_t *eltd; + + lcnt_lock(); + /* lock for thread id global update */ + eltd = lcnt_thread_data_alloc(); + lcnt_unlock(); + + eltd->function = function; + eltd->argument = arg; + + return ethr_thr_create(tid, (void *)lcnt_thr_init, (void *)eltd, opts); +} + + +/* bindings for bifs */ + +Uint16 erts_lcnt_set_rt_opt(Uint16 opt) { + Uint16 prev; + prev = (erts_lcnt_rt_options & opt); + erts_lcnt_rt_options |= opt; + return prev; +} + +Uint16 erts_lcnt_clear_rt_opt(Uint16 opt) { + Uint16 prev; + prev = (erts_lcnt_rt_options & opt); + erts_lcnt_rt_options &= ~opt; + return prev; +} + +void erts_lcnt_clear_counters(void) { + erts_lcnt_lock_t *lock; + erts_lcnt_lock_list_t *list; + erts_lcnt_lock_stats_t *stats; + int i; + + lcnt_lock(); + + list = erts_lcnt_data->current_locks; + + for (lock = list->head; lock != NULL; lock = lock->next) { + for( i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) { + stats = &lock->stats[i]; + lcnt_clear_stats(stats); + } + lock->n_stats = 1; + } + + /* empty deleted locks in lock list */ + erts_lcnt_list_clear(erts_lcnt_data->deleted_locks); + + lcnt_time(&timer_start); + + lcnt_unlock(); +} + +erts_lcnt_data_t *erts_lcnt_get_data(void) { + erts_lcnt_time_t timer_stop; + + lcnt_lock(); + + lcnt_time(&timer_stop); + lcnt_time_diff(&(erts_lcnt_data->duration), &timer_stop, &timer_start); + + lcnt_unlock(); + + return erts_lcnt_data; +} + +char *erts_lcnt_lock_type(Uint16 type) { + return lcnt_lock_type(type); +} + +#endif /* ifdef ERTS_ENABLE_LOCK_COUNT */ |