#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

# include <sys/mman.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>

# include <httpd.h>
# include <http_config.h>
# include <http_protocol.h>
# include <ap_config.h>
# include <scoreboard.h>

struct sb {
  union {
    char dummy[APR_ALIGN_DEFAULT(sizeof(apr_size_t))];
    apr_size_t sz;
  } prefix;
  global_score gscore;
  process_score pscore[0];
};
typedef struct sb* Apache2__ScoreBoardFile;
typedef process_score* Apache2__ScoreBoardFile__Process;
typedef worker_score* Apache2__ScoreBoardFile__Worker;

static char ws_status_letters[]=".S_RWKLDCGIP";

# define STAT_NUM_REQ -2
# define STAT_NUM_BYTE -3
# define STAT_BUSY_WORKER -4
# define STAT_IDLE_WORKER -5
# define STAT_CURR_WORKER -6
# define MAX_STAT_RESULTS 17	/* number of letters in ws_status_letters
				 * plus number of STAT_* constants */

struct summary {
  int what;
  apr_off_t count;
};

static inline int
status_char_to_status(char *s) {
  if(!s[0]) return -1;		/* empty string */
  if(!(s[1]==0 || s[2]==0))	/* strlen must be 1 or 2 */
    return -1;
  if( s[1]==0 ) {		/* 1-letter code */
    switch(s[0]) {
    case '.': return SERVER_DEAD;
    case '_': return SERVER_READY;
    case 'S': return SERVER_STARTING;
    case 'R': return SERVER_BUSY_READ;
    case 'W': return SERVER_BUSY_WRITE;
    case 'K': return SERVER_BUSY_KEEPALIVE;
    case 'L': return SERVER_BUSY_LOG;
    case 'D': return SERVER_BUSY_DNS;
    case 'C': return SERVER_CLOSING;
    case 'G': return SERVER_GRACEFUL;
    case 'I': return SERVER_IDLE_KILL;
    case 'P':
# ifdef SERVER_WAIT_INTERP
      return SERVER_WAIT_INTERP;
# else
      return -1;
# endif
    }
  } else {			/* 2-letter code */
    switch(s[1]) {
    case 'w':
      switch(s[0]) {
      case 'b': return STAT_BUSY_WORKER;  /* bw */
      case 'i': return STAT_IDLE_WORKER;  /* iw */
      case 'c': return STAT_CURR_WORKER;  /* cw */
      }
      break;
    case 'r':
      switch(s[0]) {
      case 'n': return STAT_NUM_REQ;      /* nr */
      }
      break;
    case 'b':
      switch(s[0]) {
      case 'n': return STAT_NUM_BYTE;     /* nb */
      }
      break;
    }
  }
  return -1;
}

static inline void
collect_summary(Apache2__ScoreBoardFile obj, struct summary *result, int len) {
  int i, j, k, plim, tlim;
  worker_score *ws;
  process_score *ps;

  plim=obj->gscore.server_limit;
  tlim=obj->gscore.thread_limit;
  for(i=0; i<plim; i++) {
    ps=&(obj->pscore[i]);
    for(j=0; j<tlim; j++) {
      ws=&(((worker_score*)(&(obj->pscore[plim])))[tlim*i+j]);
      /*warn("i: %d, j=%d, pid: %d, status: %c\n",
	i, j, (ws->pid ? ws->pid : ps->pid),
	(ws->status<sizeof(ws_status_letters)-1 ? ws_status_letters[ws->status] : '?'));*/
      for(k=0; k<len; k++) {
	if( result[k].what<0 ) {
	  switch(result[k].what) {
	  case STAT_NUM_REQ:
	    if( ws->access_count!=0 ||
		(ws->status!=SERVER_READY &&
		 ws->status!=SERVER_DEAD) )
	      result[k].count+=ws->access_count;
	    break;
	  case STAT_NUM_BYTE:
	    if( ws->access_count!=0 ||
		(ws->status!=SERVER_READY &&
		 ws->status!=SERVER_DEAD) )
	      result[k].count+=ws->bytes_served;
	    break;
	  case STAT_BUSY_WORKER:
	    if( !ps->quiescing && ps->pid ) {
	      switch(ws->status) {
	      case SERVER_DEAD:
	      case SERVER_STARTING:
	      case SERVER_IDLE_KILL:
	      case SERVER_READY:
		break;
	      default:
		result[k].count++;
		break;
	      }
	    }
	    break;
	  case STAT_IDLE_WORKER:
	    if( !ps->quiescing && ps->pid ) {
	      switch(ws->status) {
	      case SERVER_READY:
		result[k].count++;
		break;
	      }
	    }
	    break;
	  case STAT_CURR_WORKER:
	    if( !ps->quiescing && ps->pid ) {
	      switch(ws->status) {
	      case SERVER_DEAD:
	      case SERVER_STARTING:
	      case SERVER_IDLE_KILL:
		break;
	      default:
		result[k].count++;
		break;
	      }
	    }
	    break;
	  }
	} else if(result[k].what==ws->status) {
	  result[k].count++;
	}
      }
    }
  }
}

static inline int
init(int fd, Apache2__ScoreBoardFile *result) {
  void *map;
  struct stat statbuf;

  *result=NULL;
  if(fstat(fd, &statbuf)) return -1;

  map=mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if( map==MAP_FAILED ) return -1;

  *result=map;
  return 0;
}

static inline double
time2double( apr_time_t time ) {
  return (double)apr_time_sec(time) +
         (double)apr_time_usec(time)/(double)APR_USEC_PER_SEC;
}

static inline int
destroy( Apache2__ScoreBoardFile map ) {
  return munmap(map, map->prefix.sz);
}

MODULE = Apache2::ScoreBoardFile  PACKAGE = Apache2::ScoreBoardFile

Apache2::ScoreBoardFile
new(class, stream)
	SV *class
        SV *stream
      PROTOTYPE: $$
      CODE: 
        PERL_UNUSED_VAR(class); /* -W */
	{
	  SV *sv;
	  IO* io;

	  if( SvROK(stream) &&
	      (sv=SvRV(stream)) &&
	      SvTYPE(sv)==SVt_PVGV &&
	      (io=GvIO(sv)) &&
	      IoIFP(io) ) {
	    init( PerlIO_fileno(IoIFP(io)), &RETVAL );
	  } else {
	    int fd=open(SvPV_nolen(stream), O_RDONLY);
	    if(fd<0) RETVAL=NULL;
	    else {
	      init(fd, &RETVAL);
	      close(fd);
	    }
	  }
	}
      OUTPUT:
	RETVAL

void
summary(obj, ...)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $@
      PPCODE:
        {
	  int i;
	  struct summary result[MAX_STAT_RESULTS];

	  memset(result, 0, sizeof(result));
	  items--;
	  if( items>MAX_STAT_RESULTS )
	    croak("Parameter list too long %d", items+1);
	  for( i=0; i<items; i++ ) {
	    char *p=(char*)SvPV_nolen(ST(i+1));
	    if( (result[i].what=status_char_to_status(p))==-1 ) {
	      croak("Unknown parameter %s", p);
	    }
	  }
	  collect_summary(obj,result, items);
	  for( i=0; i<items; i++ ) {
	    PUSHs(sv_2mortal(newSVnv((double)result[i].count)));
	  }
	}

unsigned int
shmsize(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->prefix.sz;
      OUTPUT:
	RETVAL

unsigned int
server_limit(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->gscore.server_limit;
      OUTPUT:
	RETVAL

unsigned int
thread_limit(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->gscore.thread_limit;
      OUTPUT:
	RETVAL

unsigned int
type(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->gscore.sb_type;
      OUTPUT:
	RETVAL

unsigned int
generation(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->gscore.running_generation;
      OUTPUT:
	RETVAL

unsigned int
lb_limit(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->gscore.lb_limit;
      OUTPUT:
	RETVAL

double
restart_time(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	RETVAL=time2double(obj->gscore.restart_time);
      OUTPUT:
	RETVAL

Apache2::ScoreBoardFile::Process
process(obj, index)
	Apache2::ScoreBoardFile obj
	unsigned int index
      PROTOTYPE: $$
      INIT:
	if( !(0<=index && index<obj->gscore.server_limit) )
	  XSRETURN_UNDEF;
      CODE: 
	RETVAL=&(obj->pscore[index]);
      OUTPUT:
	RETVAL

Apache2::ScoreBoardFile::Worker
worker(obj, pindex, tindex=-1)
	Apache2::ScoreBoardFile obj
	unsigned int pindex
	unsigned int tindex
	PROTOTYPE: $$;$
      INIT:
	int sl=obj->gscore.server_limit;
	int tl=obj->gscore.thread_limit;
	if( items>2 ) pindex=tl*pindex+tindex; /* 2 indices: procnr, threadnr */
	if( !(0<=pindex && pindex<sl*tl) ) XSRETURN_UNDEF;
      CODE: 
        /* warn("sl=%d, tl=%d, start_of_ws=%x\n", sl, tl, */
	/*    (char*)(&(obj->pscore[sl]))-(char*)obj); */
        RETVAL=&(((worker_score*)(&(obj->pscore[sl])))[pindex]);
      OUTPUT:
	RETVAL

void
DESTROY(obj)
	Apache2::ScoreBoardFile obj
      PROTOTYPE: $
      CODE: 
	destroy(obj);

MODULE = Apache2::ScoreBoardFile  PACKAGE = Apache2::ScoreBoardFile::Process

unsigned int
pid(obj)
        Apache2::ScoreBoardFile::Process obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->pid;
      OUTPUT:
	RETVAL

unsigned int
generation(obj)
        Apache2::ScoreBoardFile::Process obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->generation;
      OUTPUT:
	RETVAL

unsigned int
quiescing(obj)
        Apache2::ScoreBoardFile::Process obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->quiescing;
      OUTPUT:
	RETVAL

MODULE = Apache2::ScoreBoardFile  PACKAGE = Apache2::ScoreBoardFile::Worker

int
thread_num(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->thread_num;
      OUTPUT:
	RETVAL

unsigned int
pid(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->pid;
      OUTPUT:
	RETVAL

unsigned int
generation(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->generation;
      OUTPUT:
	RETVAL

char
status(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
        unsigned status=obj->status;
        if( status>=sizeof(ws_status_letters)-1 ) {
	  RETVAL='?';
	} else {
	  RETVAL=ws_status_letters[status];
	}
      OUTPUT:
	RETVAL

unsigned int
access_count(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->access_count;
      OUTPUT:
	RETVAL

unsigned int
bytes_served(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->bytes_served;
      OUTPUT:
	RETVAL

unsigned int
my_access_count(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->my_access_count;
      OUTPUT:
	RETVAL

unsigned int
my_bytes_served(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->my_bytes_served;
      OUTPUT:
	RETVAL

unsigned int
conn_count(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->conn_count;
      OUTPUT:
	RETVAL

unsigned int
conn_bytes(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->conn_bytes;
      OUTPUT:
	RETVAL

double
start_time(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=time2double(obj->start_time);
      OUTPUT:
	RETVAL

double
stop_time(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=time2double(obj->stop_time);
      OUTPUT:
	RETVAL

double
last_used(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=time2double(obj->last_used);
      OUTPUT:
	RETVAL

char*
client(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->client;
      OUTPUT:
	RETVAL

char*
request(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->request;
      OUTPUT:
	RETVAL

char*
vhost(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
	RETVAL=obj->vhost;
      OUTPUT:
	RETVAL

unsigned int
tid(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
#if APR_HAS_THREADS
	RETVAL=obj->tid;
#else
	RETVAL=(unsigned)-1;
#endif
      OUTPUT:
	RETVAL

unsigned int
utime(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
#if HAVE_TIMES
	RETVAL=obj->times.tms_utime;
#else
	RETVAL=(unsigned)-1;
#endif
      OUTPUT:
	RETVAL

unsigned int
stime(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
#if HAVE_TIMES
	RETVAL=obj->times.tms_stime;
#else
	RETVAL=(unsigned)-1;
#endif
      OUTPUT:
	RETVAL

unsigned int
cutime(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
#if HAVE_TIMES
	RETVAL=obj->times.tms_cutime;
#else
	RETVAL=(unsigned)-1;
#endif
      OUTPUT:
	RETVAL

unsigned int
cstime(obj)
        Apache2::ScoreBoardFile::Worker obj
      PROTOTYPE: $
      CODE: 
#if HAVE_TIMES
	RETVAL=obj->times.tms_cstime;
#else
	RETVAL=(unsigned)-1;
#endif
      OUTPUT:
	RETVAL

 # Local Variables:
 # mode: c
 # End: