/*
 * This module manages the memory for interval counter blocks, which are
 * used for real-time monitoring of web server perfomance.  An interval
 * counter block holds statistics for a single accumulation period, a list
 * of these blocks is maintained in shareable memory for reading by
 * interested processes.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <descrip.h>		/* VMS string descriptors */
#include <ssdef.h>		/* VMS system service completion codes */
#include <lckdef.h>		/* VMS distrib. lock manager */
#include <secdef.h>		/* VMS global sections */
#include <jpidef.h>		/* VMS get job/process info */
#include <syidef.h>		/* VMS get system informaion */

#include "pthread_1c_np.h"
#include "counters.h"
#include "icb_pool.h"
int tlog_putlog ( int, char *, ... );
int http_log_level;
int SYS$ENQ(), SYS$ENQW(), SYS$DEQ(), SYS$GETTIM(), LIB$GETJPI(), LIB$GETSYI();
int SYS$CRMPSC();
#ifndef PTHREAD_USE_D4
int os_kernel_threads();
#define DLM_EF 128
#else
#define DLM_EF 9
#endif
/*
 * define global structures.  ic_lksb holds lock value block for VMS lock
 * used to synchronize update of global section pointed to by icb_pool.
 */
static char lock_name[64];
static $DESCRIPTOR(lock_name_dx,lock_name);
static struct {
    int match_criteria;
    int version;
} pool_ident = { SEC$K_MATEQU, ICB_POOL_VERSION };

static struct icb_lock_block ic_lksb;
static struct pool_def *icb_pool;
static int my_pid, my_imagecount, my_csid;
/*
 * thread objects used for synchronizing with system services.
 */
static pthread_mutex_t dlm_lock;
static pthread_cond_t dlm_done;
static pthread_cond_t timer_tick;
static int kernel_threads = 0;

/***************************************************************************/
/* Create a lock.
 */
static int enqueue_and_wait ( int mode, int flags, 
	struct dsc$descriptor_s *resnam )
{
    int status;
    /*
     * Fix up flags argument.
     */
    if ( !resnam ) flags |= LCK$M_CONVERT;
    else if ( http_counters->flags & COUNTER_FLAG_INTERVAL_SYS ) {
	flags |= LCK$M_SYSTEM;		/* use system-wide lock */
    }
    /*
     * Make enq call based upon whether using kernel flags.
     */
    if ( kernel_threads ) {
	/*
	 * Easy case, let upcalls manage blocks.
	 */
	status = SYS$ENQW ( DLM_EF, mode, &ic_lksb, flags, resnam,
		0, 0, 0, 0, 0, 0, 0 );
	if ( status&1 ) status = ic_lksb.status;
    } else {
	/*
	 * Use AST with condition signal to void delay.
	 */
	pthread_mutex_lock ( &dlm_lock );
	status = SYS$ENQ ( DLM_EF, mode, &ic_lksb, flags|LCK$M_SYNCSTS,
		resnam, 0, pthread_cond_signal_int_np, &dlm_done,
		0, 0, 0, 0 );
	if ( status & 1 ) {
	    if ( status != SS$_SYNCH ) while ( ic_lksb.status == 0 ) {
		if ( 0 != pthread_cond_wait ( &dlm_done, &dlm_lock ) ) break;
	    }
	    status = ic_lksb.status;
	}
	pthread_mutex_unlock ( &dlm_lock );
    }
    return status;
}
/***************************************************************************/
/* Create global section of specified name and size.  Section will be
 * group global unless counter.flags bit <3> is set.
 */
struct pool_def *create_pool ( struct dsc$descriptor_s *name, int size )
{
    void *inaddr[2], *outaddr[2];
    int flags, system, prot, status;
    struct pool_def *pool;

    inaddr[0] = 0;			/* choose P0 region */
    inaddr[1] = (void *)(size - 1);
    flags = SEC$M_GBL | SEC$M_WRT | SEC$M_PAGFIL | SEC$M_EXPREG;
    if ( http_counters->flags & COUNTER_FLAG_INTERVAL_SYS ) 
	flags |= SEC$M_SYSGBL;
    prot = 0x0EE00;		/* wo:r, gr:r, ow: rwed, sy: rwed */

    status = SYS$CRMPSC ( inaddr, outaddr, 0, flags,
	name, &pool_ident, 0, 0, size/512, 0, prot, 128 );
    if ( ((status&1)==0) || (http_log_level > 9) ) tlog_putlog ( 0,
	"ticker crmpsc status: !SL, addr: !XL !XL!/", status,
	outaddr[0], outaddr[1] );
    if ( (status&1) == 1 ) {
	pool = outaddr[0];
    } else pool = (struct pool_def *) 0;

    return pool;
}
/***************************************************************************/
/* Thread start routine for thread to handle advancing the current ICB pointer
 * to the next in  list.
 */
static void zero_icb ( struct interval_counter *icb )
{
#ifdef VMS
    SYS$GETTIM ( icb->reset_time );
#else
#endif
    icb->peak_concurrency = 0;
    icb->requests.file = 0;
    icb->requests.script = 0;
    icb->requests.redirect = 0;
    icb->requests.invalid = 0;
    icb->responses = 0;
    icb->data_bytes = 0;
    icb->tcp_timeouts = 0;
}

static void *ticker( void *dummy )
{
    struct timespec next_tick, check, delta;
    int skew, status;
    static int ticker_status;
    /*
     * Setup for timer computation.
     */
    delta.tv_sec = icb_pool->update_interval;
    delta.tv_nsec = 0;
    if ( 0 != pthread_get_expiration_np ( &delta, &next_tick ) ) {
	tlog_putlog ( 0, "Error computing expriation time!/" );
    }
    if ( http_log_level > 1 ) tlog_putlog ( 1, 
	"Interval counter ticker thread started, interval: !SL!/",
	icb_pool->update_interval );
    
    for ( ; ; ) {
	/*
	 * Sleep for interval seconds.
	 */
	pthread_mutex_lock ( &dlm_lock );
	for ( status = 0; status == 0; ) {
	    status = pthread_cond_timedwait ( &timer_tick, &dlm_lock,
		&next_tick );
	}
	pthread_mutex_unlock ( &dlm_lock );
	/*
	 * Raise lock and validate the lock value block.
	 */
	status = enqueue_and_wait ( LCK$K_PWMODE, LCK$M_VALBLK, 0 );
	if ( http_log_level > 9 ) tlog_putlog ( 10,
	    "ticker woke at !%D, status of enq: !SL, cur: !SL/!SL!/", 0, 
		status,	icb_pool->cur_icb, icb_pool->icb_count );
	if ( status == SS$_VALNOTVALID ) {
	    /*
	     * Rare error, reset block.
	     */
	    ic_lksb.pid = my_pid;
	    ic_lksb.first_valid = -1;
	    ic_lksb.last_valid = -1;
	    ic_lksb.sequence = 0;
	}
	/*
	 * Initialize new ICB, reusing oldest in list
	 */
	ic_lksb.sequence++;
	if ( ic_lksb.first_valid < 0 ) ic_lksb.first_valid = icb_pool->cur_icb;
	ic_lksb.last_valid = icb_pool->cur_icb;
	icb_pool->cur_icb++;
	if ( icb_pool->cur_icb >= icb_pool->icb_count ) {
	    /* Wrap back to beginning of array */
	    icb_pool->cur_icb = 0;
	}
	if ( icb_pool->cur_icb == ic_lksb.first_valid ) {
	    /*
	     * Trim first icb from beginning of valid range.
	     */
	    ic_lksb.first_valid++;
	    if ( ic_lksb.first_valid >= icb_pool->icb_count )
		ic_lksb.first_valid = 0;
	}
	zero_icb ( &icb_pool->icb[icb_pool->cur_icb] );
	/*
	 * Lock web counters and switch to new icb.
	 */
	http_lock_counters();
	if ( http_counters ) http_counters->icb = 
		&icb_pool->icb[icb_pool->cur_icb];
	http_unlock_counters();
	/*
	 * lower lock to propagate updated value block to others.
	 */
        delta.tv_sec = icb_pool->update_interval;
	status = enqueue_and_wait ( LCK$K_CRMODE, LCK$M_VALBLK, 0 );
	/*
	 * Compute next interval as absolute count from previous tick so
	 * we don't get any drift.  Compare to delta from current time and
	 * update tick if more than 2 seconds off, indicating system clock
	 * was reset or update_interval changed.
	 */
	delta.tv_sec = icb_pool->update_interval;
	next_tick.tv_sec += delta.tv_sec;
	pthread_get_expiration_np ( &delta, &check );
	skew = (next_tick.tv_sec - check.tv_sec);
	if ( skew < 0 ) skew = skew * (-1);
	if ( skew > 2 ) next_tick = check;
    }
    ticker_status = 1;
    return (void *) &ticker_status;
}
/***************************************************************************/
int http_icb_init ( int interval, int port )
{
    pthread_t ticker_id;
    int status, size, code;
    /*
     * Initialize DECthreads objects.
     */
    INITIALIZE_MUTEX ( &dlm_lock );
    SET_MUTEX_NAME ( &dlm_lock, "OSU icb dlm" );
    INITIALIZE_CONDITION ( &dlm_done );
    SET_COND_NAME ( &dlm_done, "OSU icb dlm" );
    INITIALIZE_CONDITION ( &timer_tick );
    SET_COND_NAME ( &timer_tick, "OSU icb timer" );
#ifndef PTHREAD_USE_D4
    kernel_threads = os_kernel_threads();
#endif
    /*
     * Construct unique lock name using server's port number and system's
     * cluster ID.
     */
    code = JPI$_IMAGECOUNT;
    my_pid = 0;
    status = LIB$GETJPI ( &code, &my_pid, 0, &my_imagecount, 0, 0 );
    if ( (status&1) == 0 ) return status;
    code = SYI$_NODE_CSID;
    status = LIB$GETSYI ( &code, &my_csid, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    sprintf ( lock_name, ICB_POOL_NAME_FMT, my_csid, port );
    /*
     * Get lock used to coordinate update of global section.
     */
    lock_name_dx.dsc$w_length = strlen ( lock_name );
    status = enqueue_and_wait ( LCK$K_PWMODE, LCK$M_VALBLK, &lock_name_dx );
    if ( http_log_level > 1 ) tlog_putlog ( 2,
	"Create icb lock (!AZ) status: !SL!/", lock_name, status );
    if ( status != SS$_VALNOTVALID ) {
	ic_lksb.pid = my_pid;
	ic_lksb.first_valid = -1;
	ic_lksb.last_valid = -1;
	ic_lksb.sequence = 0;
    }
    /*
     * Create global sesion and initialize.  Set http_counters->icb to
     * point to first block.
     */
    size = ICB_POOL_SECTION_SIZE;
    icb_pool = create_pool ( &lock_name_dx, size );
    if ( !icb_pool ) {
        tlog_putlog ( 0, "Failed to create global section!/" );
	SYS$DEQ ( ic_lksb.id, 0 );
	return 0;
    }
    size = size - sizeof(struct pool_def) + sizeof(struct interval_counter);
    icb_pool->pid = my_pid;
    icb_pool->image_count = my_imagecount;
    icb_pool->icb_size = sizeof(struct interval_counter);
    icb_pool->icb_count = size / sizeof(struct interval_counter);
    icb_pool->cur_icb = 0;
    icb_pool->update_interval = interval;
    if ( http_counters->flags & COUNTER_FLAG_INTERVAL_FAST ) {
	/* Speed up interval by 10 times for debugging */
	icb_pool->update_interval = (interval/12) + 1;
    }
    zero_icb ( &icb_pool->icb[0] );
    /*
     * Lower global lock, updating value block and allowing concurrent read.
     */
    ic_lksb.pid = my_pid;
    ic_lksb.first_valid = -1;			/* no intervals completed */
    ic_lksb.last_valid = -1;
    ic_lksb.sequence = 0;
    status = enqueue_and_wait ( LCK$K_CRMODE, LCK$M_VALBLK, 0 );
    /*
     * Start thread to periodically swap the current ICB with the next.
     */
#ifdef PTHREAD_USE_D4
    status = pthread_create ( &ticker_id, pthread_attr_default, 
		ticker, (pthread_addr_t) 0 );
#else
    status = pthread_create ( &ticker_id, NULL, ticker, (void *) 0 );
#endif
    if ( status != 0 ) {
	tlog_putlog ( 0, "Error creating interval counter thread!/");
    }
    return 1;
}

/***************************************************************************/
/*
 * Increment the appropriate counter in the current ICB's requests structure,
 * depending upon code.  Upon return the http_counters lock is held by
 * the caller.
 */
int http_increment_icb_requests ( int code, int *lock_state )
{
    struct interval_counter *icb;
    /*
     * Lock counters if not yet locked.
     */
    if ( !(*lock_state) ) {
	http_lock_counters(); *lock_state = 1;
    }
    /*
     * Increment the specified counter.
     */
    if ( http_counters ) icb = http_counters->icb; 
    else icb = (struct interval_counter *) 0;
    if ( !icb ) return 0;

    if ( (code == 1) || (code == 4) ) {
	icb->requests.file++;
    } else if ( code == 3 ) {
	icb->requests.script++;
    } else if ( code == 2 ) {
	icb->requests.redirect++;
    } else {
	icb->requests.invalid++;
    }
    return 1;
}
