/*
* %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 */