/***************************************
  $Revision: 1.2 $

  thread accounting (ta). ta.c - functions to keep track of activities
                            of threads within the server

  Status: NOT REVUED, TESTED, COMPLETE

  Design and implementation by: Marek Bukowy

  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/

#define TA_IMPL
#include <ta.h>

static
ta_str_t *ta_findcreate_l( GList **list, pthread_t thread_id )
{
  GList *item;
  ta_str_t *newtas;

  /* try to find first */
  for(item = g_list_first(*list);
      item != NULL;
      item = g_list_next(item)) {
    ta_str_t *tas = (ta_str_t *) (item->data);

    if( tas->thread_id == thread_id ) {
      return tas;
    }
  }
  
  /* not found => add */  /* zero everything*/
  dieif( !NOERR( wr_calloc( (void **) &newtas, 1, sizeof( ta_str_t ))));
  newtas->thread_id = thread_id;

  *list = g_list_append( *list, newtas );

  return newtas;
}


/* find and remove */
static
void ta_remove_l(GList **list, pthread_t thread_id )
{ 
 GList *item;

 for(item = g_list_first(*list);
     item != NULL;
     item = g_list_next(item)) {
   ta_str_t *tas = (ta_str_t *) (item->data);

   if( tas->thread_id == thread_id ) {
     *list = g_list_remove_link(*list, item);
     wr_clear_list( &item );
     break;
   }
 }
   
 return;
}

/* set the activity field */
static
void ta_setactivity_l(ta_str_t *tas, char *activity)
{
  char *nl;

  strncpy(tas->activity, activity, TA_ACT_LEN-1);
  tas->activity[TA_ACT_LEN]=0;
  /* convert last newline to a space, if any */
  if( (nl=strrchr(tas->activity, '\n')) != NULL ) {
    *nl=' ';
  }
}


#define TA_HEADER "%-8s %15s %4s %4s %5s %5s %4s %5s %s\n"
#define TA_FORMAT "%-8s %15s %4d %4d %5.1f %5.1f %4d %5.2f %s\n"

static 
void ta_print_header(char *buf, int length)
{
  snprintf(buf, length, TA_HEADER,
	   "type", "from", "sock", "thr", "sess", "task", "#", 
	   "avg", "current"
	   ); 
}

/* fill in one entry */
static
void ta_printone_l(ta_str_t *tas, char *buf, int length, 
		   ut_timer_t *reftime)
{
  float session, task;  /* duration of the session/task */
  char *address = SK_getpeername(tas->sock); /* allocated! */
  /* can be NULL for example if the socket has just closed
     or the file descriptor is not a socket */

  session = UT_timediff( &tas->sessionstart, reftime );
  task    = UT_timediff( &tas->taskstart, reftime );

  snprintf(buf, length, TA_FORMAT ,
	   tas->type,
	   address ? address : "", 
	   tas->sock, 
	   tas->thread_id, 
	   session,
	   task,
	   tas->tasks,
	   (tas->tasks > 0) ? session / tas->tasks : 0,
	   tas->activity);
  
  if (address) {
    wr_free(address);
  }
}

/* PUBLIC adding function */
void TA_add(int sock, char *type)
{
  ta_str_t *newtas;
  
  /* lock the list */
  pthread_mutex_lock( &ta_mutex );
  
  /* find/create node and set peer/thread_id */
  newtas = ta_findcreate_l( &ta_list, pthread_self());
  newtas->sock = sock;
  newtas->tasks = 0;
  UT_timeget( &newtas->sessionstart );
  UT_timeget( &newtas->taskstart ); /* just to get it a reasonable value */

  snprintf(newtas->type, TA_TYPE_LEN, type);
  ta_setactivity_l(newtas,"--");
  
  /* unlock */
  pthread_mutex_unlock( &ta_mutex );
}


/* PUBLIC deletion function */
void TA_delete(void)
{
  /* lock the list */
  pthread_mutex_lock( &ta_mutex );
  
  /* find & remove */
  ta_remove_l( &ta_list, pthread_self() );
  
  /* unlock */
  pthread_mutex_unlock( &ta_mutex );
}


/* PUBLIC activity-setting function */
void TA_setactivity(char *activity)
{
  ta_str_t *newtas;

  /* lock the list */
  pthread_mutex_lock( &ta_mutex );
  
  /* find */
  newtas = ta_findcreate_l( &ta_list, pthread_self());
  
  /* set the activity field */
  ta_setactivity_l(newtas, activity);

  /* unlock */
  pthread_mutex_unlock( &ta_mutex );
}

void TA_increment(void)
{
  ta_str_t *newtas;

  /* lock the list */
  pthread_mutex_lock( &ta_mutex );
  
  /* find */
  newtas = ta_findcreate_l( &ta_list, pthread_self());
  /* increment task */
  newtas->tasks++;
  /* set task starting time */
  UT_timeget( &newtas->taskstart );

  /* unlock */
  pthread_mutex_unlock( &ta_mutex );
}

char * TA_tostring(void)
{
  GList *item;
  char *bigbuf = NULL;
  char smallbuf[TA_PRINT_LEN];
  ut_timer_t reftime;

  ta_print_header(smallbuf, TA_PRINT_LEN);
  dieif( !NOERR(wr_realloc( (void **) &bigbuf, strlen(smallbuf)+2 )));
  strcat(bigbuf, smallbuf);
  strcat(bigbuf, "\n");
  
  /* lock the list */
  pthread_mutex_lock( &ta_mutex );
  
  /* get reference time */
  UT_timeget( &reftime );
  
  /* iterate */
  for(item = g_list_first(ta_list);
      item != NULL;
      item = g_list_next(item)) {
    ta_str_t *tas = (ta_str_t *) (item->data);
    int smalllen;
    int biglen = ( bigbuf == NULL ) ? 0 : strlen(bigbuf);

    ta_printone_l(tas, smallbuf, TA_PRINT_LEN, &reftime);
    smalllen = strlen(smallbuf);

    dieif( !NOERR(wr_realloc( (void **) &bigbuf, biglen+smalllen+3 )));
    
    strcat(bigbuf, smallbuf);
  }
  /* unlock */
  pthread_mutex_unlock( &ta_mutex );
  
  return bigbuf;
}
