/* Statistics gathering */

#include "params.h"
#include "gambit.h"
#include "struct.h"
#include "os.h"
#include "mem.h"
#include "strings.h"
#include "run.h"


/*---------------------------------------------------------------------------*/


#define MAX_STAT_NAME_LENGTH 256

struct stat_rec {
  char *name;
  long count;
  struct stat_list *sub_parts;
  };

typedef struct stat_rec *STAT_PTR;

struct stat_list {
  struct stat_rec stat;
  struct stat_list *next;
  };

typedef struct stat_list *STAT_LIST_PTR;

char stat_name[MAX_STAT_NAME_LENGTH+1];
long stat_multiplier;

char *predefined_stats[] = PREDEFINED_STATS;

#define NB_PREDEFINED_STATS (sizeof(predefined_stats) / sizeof(char *))

char *events[] = EVENTS;

#define NB_EVENTS (sizeof(events) / sizeof(char *))

struct stat_rec stat_root;
STAT_PTR prof_category, event_prof_category;

PSTATE_PTR processor;

char *prog_filename;


/*---------------------------------------------------------------------------*/


void init_stats()
{ sstate->nb_stats = NB_PREDEFINED_STATS;
}


long alloc_stat( index )
long *index;
{ if (sstate->nb_stats >= MAX_NB_STATS) return 1;
  *index = sstate->nb_stats++;
  return 0;
}


void stats_clear( index )
long index;
{ sstate->ofile[index].stats_bot = 0;
  sstate->ofile[index].stats_top = 0;
}


void stats_begin( index )
long index;
{ sstate->ofile[index].stats_bot = sstate->nb_stats;
}


void stats_end( index )
long index;
{ sstate->ofile[index].stats_top = sstate->nb_stats;
}


/*---------------------------------------------------------------------------*/


char *ofile_start;


void read_ofile( index )
long index;
{ if (sstate->ofile[index].len == 0)
  { OS_FILE input;
    char *filename;
    long len;
    char *top;

    ofile_start = pstate->heap_old;
    top = ofile_start + (pstate->heap_mid - pstate->heap_bot);

    filename = string_append( sstate->ofile[index].ptr, ".O" );
    if (filename == NULL)
    { os_warn( "Local memory overflow\n", 0L ); os_quit(); }
    input = os_file_open_input( filename );
    if (input == -1)
    { os_warn( "Can't open %s\n", (long)filename ); os_quit(); }
    len = os_file_length( input );
    if (len < 0)
    { os_warn( "Read error on object file %s\n", (long)filename ); os_quit(); }

    if (ofile_start+len > top)
    { os_file_close( input ); os_warn( "Load memory overflow\n", 0L ); os_quit(); }

    if (os_file_read( input, ofile_start, len ) != len)
    { os_file_close( input ); os_warn( "Read error on object file %s\n", (long)filename ); os_quit(); }

    os_file_close( input );
  }
  else
    ofile_start = sstate->ofile[index].ptr;
}


char *parse_ptr;


char parse_stat_name()
{ long len = 0;
  long level = 0;
  long sign;
  char c = *(parse_ptr++);
  while ((c > ' ') || (level > 0))
    { if (len > MAX_STAT_NAME_LENGTH)
      { os_warn( "Stat name too long\n", 0L ); os_quit(); }
    stat_name[len++] = c;
    if (c == '(') level++; else if (c == ')') level--;
    c = *(parse_ptr++);
  }
  stat_name[len] = '\0';
  stat_multiplier = 0;
  while ((c > '\0') && (c <= ' ')) c = *(parse_ptr++);
  if (c == '-') { sign = -1; c = *(parse_ptr++); }
  else if (c == '+') { sign = 1; c = *(parse_ptr++); }
  else sign = 1;
  while ((c >= '0') && (c <= '9'))
  { stat_multiplier = stat_multiplier*10 + (c - '0');
    c = *(parse_ptr++);
  }
  stat_multiplier = sign * stat_multiplier;
  return c;
}


STAT_PTR enter_sub_part( s, name )
STAT_PTR s;
char *name;
{ STAT_LIST_PTR ptr = s->sub_parts;

  while (ptr != NULL)
  { char *p1 = ptr->stat.name, *p2 = name;
    while ((*p1 != '\0') && (*p1 == *p2)) { p1++; p2++; }
    if (*p1 == *p2) break; /* found entry */
    ptr = ptr->next;
  }

  if (ptr != NULL)
    return &ptr->stat;
  else
  { STAT_LIST_PTR new = (STAT_LIST_PTR)local_malloc8( (long)sizeof(struct stat_list) );
    if (new == NULL)
    { os_warn( "Couldn't allocate stat entry\n", 0L ); os_quit(); }

    new->stat.name = string_copy( name );
    if (new->stat.name == NULL)
    { os_warn( "Couldn't allocate stat entry name\n", 0L ); os_quit(); }

    new->stat.count = 0;
    new->stat.sub_parts = NULL;
    new->next = s->sub_parts;
    s->sub_parts = new;
    return &new->stat;
  }
}


void parse_sub_parts( s, count, c )
STAT_PTR s;
long count;
char c;
{ STAT_PTR sub;

  loop:

  while ((c > '\0') && (c <= ' ')) c = *(parse_ptr++);

  switch (c)
  { case ')': return;
    case '(': c = parse_stat_name();
              sub = enter_sub_part( s, stat_name );
              sub->count += stat_multiplier * count;
              parse_sub_parts( sub, count, c );
              c = *(parse_ptr++);
              goto loop;
    default:
      { os_warn( "Incorrect statistic format\n", 0L ); os_quit(); }
    }
}


void parse_stat( count )
long count;
{ char c = *(parse_ptr++);
  while ((c > '\0') && (c <= ' ')) c = *(parse_ptr++);
  if (c != '(')
  { os_warn( "Incorrect statistic format, '(' expected\n", 0L ); os_quit(); }
  c = *(parse_ptr++);
  parse_sub_parts( &stat_root, count, c );
}


void enter_stat( stat, index )
char *stat;
long index;
{ long count = processor->stats_counters[index];
  if (count > 0)
  { parse_ptr = stat; parse_stat( count ); }
}


void enter_prof_stat( adr, length, name )
long adr, length;
char *name;
{ long end = adr + length;
  if (name == NULL)
  { os_warn( "Local memory overflow\n", 0L ); os_quit(); }
  if ((adr >= (long)sstate->const_bot) && (end < (long)sstate->const_top))
  { short *p = &processor->prof_bot[(adr-(long)sstate->const_bot)>>PROF_SHIFT];
    long ticks = 0;
    long i;
    for (i=length+4; i>0; i -= 1<<PROF_SHIFT) { ticks += *p; *(p++) = 0; }
    if (ticks > 0)
    { long msec = os_ticks_to_msec( ticks );
      STAT_PTR s = enter_sub_part( prof_category, name );
      prof_category->count += msec;
      s->count += msec;
    }
  }
}


void add_prof_stat( msec, name )
long msec;
char *name;
{ if (msec > 0)
  { STAT_PTR s = enter_sub_part( prof_category, name );
    prof_category->count += msec;
    s->count += msec;
  }
}


void stats_compute_profile()
{ short *p;
  long ticks;
  long cpu = (pstate->cpu_times[0] - pstate->stats_cpu_times[0]) +
             (pstate->cpu_times[1] - pstate->stats_cpu_times[1]);
  
  prof_category = enter_sub_part( &stat_root, "profile" );

  for_each_glob_prim_proc( enter_prof_stat );

  p = processor->prof_bot;
  ticks = 0;
  while (p < processor->prof_top) ticks += *(p++);

  if (ticks > 0)
    add_prof_stat( os_ticks_to_msec( ticks ), "(non_global_procedures)" );

  add_prof_stat( cpu-prof_category->count, "(unaccounted_for)" );
}


#define EVENT(x,y) (((x)<<24)+(y))
#define EVENT_NUM(x) ((x)>>24)
#define EVENT_TIME(x) ((x) & 0xffffff)
#define REL_TIME(x) EVENT_TIME((x)-elog_start)
#define LATER(x,y) ((y)-(x) & 0x800000)
long *elog_ptr[MAX_NB_PROC], *elog_top[MAX_NB_PROC];
long elog_start, elog_end;

#define ELOG_BUF_SIZE 1024
char *elog_buf;
long elog_len;


void elog_setup( start_time, stop_time )
long start_time, stop_time;
{ long i;

  elog_start = EVENT_TIME(start_time);
  elog_end = EVENT_TIME(stop_time);

  for (i=SCM_obj_to_int(pstate->nb_processors)-1; i>=0; i--)
  { long *p1, *p2;
    processor = pstate->ps[i];
    p1 = processor->elog_ptr;
    p2 = processor->elog_top;
    while ((p1 < p2) && LATER(EVENT_TIME(p1[0]), elog_end)) p1++;
    while ((p1 < p2) && LATER(elog_start, EVENT_TIME(p2[-1]))) p2--;
    elog_ptr[i] = p1;
    elog_top[i] = p2;
  }
}


void elog_begin()
{ elog_buf = (char *)local_malloc8( (long)ELOG_BUF_SIZE );
  if (elog_buf == NULL)
  { os_warn( "Couldn't allocate elog buffer\n", 0L ); os_quit(); }
  elog_len = 0;
}


void elog_finish( output )
OS_FILE output;
{ if (elog_len > 0) os_file_write( output, elog_buf, elog_len );
}


void elog_char( output, c )
OS_FILE output;
char c;
{ elog_buf[elog_len++] = c;
  if (elog_len >= (long)ELOG_BUF_SIZE)
  { os_file_write( output, elog_buf, elog_len );
    elog_len = 0;
  }
}


void elog_long( output, val )
OS_FILE output;
long val;
{ char *p = (char *)&val;
  long i;
  for (i=(long)sizeof(val); i>0; i--)
  { elog_buf[elog_len++] = *p++;
    if (elog_len >= (long)ELOG_BUF_SIZE)
    { os_file_write( output, elog_buf, elog_len );
      elog_len = 0;
    }
  }
}


void elog_generate()
{ if (elog_start != elog_end)
  { OS_FILE output;
    long i;
    char *mark = local_mark();

    output = os_file_open_output( string_append( prog_filename, ".elog" ) );
    if (output == -1)
    { os_warn( "Can't open event log file\n", 0L ); os_quit(); }

    elog_begin();

    elog_long( output, 12L );
    elog_long( output, 1L );
    elog_long( output, 0x80000000L );
    elog_long( output, 2L );

    for (i=0; i<NB_EVENTS; i++)
    { long j, len = string_length( events[i] );
      elog_long( output, (((len+2+3)/sizeof(long))+2)*sizeof(long) );
      elog_long( output, 2L );
      elog_long( output, i+1 );
      for (j=0; j<len; j++) elog_char( output, events[i][j] );
      elog_char( output, '\0' );
      elog_char( output, '\0' );
      for (j=j+2; j%sizeof(long)!=0; j++) elog_char( output, '\0' );
    }

    for (i=0; i<SCM_obj_to_int(pstate->nb_processors); i++)
    { long *p;
      long len;
      p = elog_top[i];
      len = p-elog_ptr[i];
      elog_long( output, (len*3+5)*sizeof(long) );
      elog_long( output, 3L );
      elog_long( output, i );
      elog_long( output, 0L );
      elog_long( output, 20L );
      elog_long( output, 0L );
      while (len > 0)
      { long event = *(--p);
        len--;
        elog_long( output, REL_TIME(EVENT_TIME(event)) );
        elog_long( output, EVENT_NUM(event)+1 );
        elog_long( output, 0L );
      }
    }

    elog_finish( output );

    os_file_close( output );

    local_release( mark );
  }
}


void add_event_prof_stat( ticks, name )
long ticks;
char *name;
{ if (ticks > 0)
  { STAT_PTR s = enter_sub_part( event_prof_category, name );
    event_prof_category->count += ticks;
    s->count += ticks;
  }
}


void stats_compute_event_profile()
{ long *p;
  long i;
  long prof[NB_EVENTS];

  event_prof_category = enter_sub_part( &stat_root, "event_profile" );

  for (i=0; i<NB_EVENTS; i++) prof[i] = 0;

  p = elog_top[SCM_obj_to_int(processor->id)];
  if (p == elog_ptr[SCM_obj_to_int(processor->id)])
    prof[EVENT_IDLE] = REL_TIME(elog_end);
  else
  { long last_event_num = EVENT_IDLE;
    long last_event_time = 0;
    while (p > elog_ptr[SCM_obj_to_int(processor->id)])
    { long event = *(--p);
      long event_num = EVENT_NUM(event);
      long event_time = REL_TIME(EVENT_TIME(event));
      prof[last_event_num] += EVENT_TIME(event_time - last_event_time);
      last_event_num = event_num;
      last_event_time = event_time;
    }
    prof[last_event_num] += EVENT_TIME(REL_TIME(elog_end) - last_event_time);
  }

  for (i=0; i<NB_EVENTS; i++)
    add_event_prof_stat( prof[i], events[i] );
}


void stats_compute()
{ long index, i;

  stat_root.sub_parts = NULL;

  for (i=NB_PREDEFINED_STATS-1; i>=0; i--)
    enter_stat( predefined_stats[i], i );

  for (index=0; index<sstate->nb_ofiles; index++)
    if (sstate->ofile[index].stats_top >
        sstate->ofile[index].stats_bot )
    { read_ofile( index );
      for (i=sstate->ofile[index].stats_bot;
           i<sstate->ofile[index].stats_top;
           i++)
        enter_stat( ofile_start+sstate->stats_offsets[i], i );
    }

  if (sstate->profiling) stats_compute_profile();

  if (elog_start != elog_end) stats_compute_event_profile();
}


/*---------------------------------------------------------------------------*/


void sort_sub_parts( s )
STAT_PTR s;
{ STAT_LIST_PTR i = s->sub_parts;

  while (i != NULL)
  { STAT_LIST_PTR j = i->next, k = i;
    struct stat_rec temp;

    while (j != NULL)
    { if (j->stat.count > k->stat.count) k = j;
      j = j->next;
    }

    temp = i->stat; i->stat = k->stat; k->stat = temp;

    i = i->next;
  }
}


long thousanths( x, y )
long x, y;
{ while (x > 2147483) { x = x>>1; y = y>>1; }
  return x * 1000 / y;
}


void stats_write_sub_parts( output, s, indent )
OS_FILE output;
STAT_PTR s;
long indent;
{ STAT_LIST_PTR ptr;
  long newlines = 0;

  sort_sub_parts( s );

  ptr = s->sub_parts;
  while (ptr != NULL)
    if (ptr->stat.sub_parts != NULL)
    { newlines = 1; break; }
    else
      ptr = ptr->next;

  ptr = s->sub_parts;
  while (ptr != NULL)
  { long i, thous;
    os_file_printf( output, "\n", 0L );
    if (newlines) os_file_printf( output, "\n", 0L );
    for (i=indent; i>0; i--) os_file_printf( output, " ", 0L );
    thous = thousanths( ptr->stat.count, s->count );
    os_file_printf( output, "(", 0L );
    if (thous < 1000) os_file_printf( output, " ", 0L );
    if (thous < 100) os_file_printf( output, " ", 0L );
    os_file_printf( output, "%d", thous/10 );
    os_file_printf( output, ".%d", thous%10 );
    os_file_printf( output, " %s", (long)ptr->stat.name );
    os_file_printf( output, " %d", ptr->stat.count );
    stats_write_sub_parts( output, &ptr->stat, indent+12 );
    os_file_printf( output, ")", 0L );
    ptr = ptr->next;
  }
}


void stats_write_categories( output, ptr )
OS_FILE output;
STAT_LIST_PTR ptr;
{ while (ptr != NULL)
  { os_file_printf( output, "(%s", (long)ptr->stat.name );
    os_file_printf( output, " %d", ptr->stat.count );
    stats_write_sub_parts( output, &ptr->stat, 2L );
    os_file_printf( output, ")\n\n", 0L );
    ptr = ptr->next;
  }
}


void stats_write( output )
OS_FILE output;
{ char *mark = local_mark();
  os_file_printf( output, "( ; *** PROCESSOR %d ***\n\n", SCM_obj_to_int(processor->id) );
  stats_compute();
  sort_sub_parts( &stat_root );
  stats_write_categories( output, stat_root.sub_parts );
  os_file_printf( output, ")\n\n", 0L );
  local_release( mark );
}


void stats_generate()
{ OS_FILE output;
  long i;
  char *p;

  prog_filename = sstate->program_filename;
  p = prog_filename;
  while (*p != '\0') if (*p++ == '/') prog_filename = p;
    
  elog_setup( sstate->stats_start_time, sstate->stats_stop_time );

  output = os_file_open_output( string_append( prog_filename, ".stats" ) );
  if (output == -1)
  { os_warn( "Can't open statistics file\n", 0L ); os_quit(); }

  os_file_printf( output, "(\n\n", 0L );

  for (i=0; i<SCM_obj_to_int(pstate->nb_processors); i++)
  { processor = pstate->ps[i];
    stats_write( output );
  }

  os_file_printf( output, ")\n", 0L );

  os_file_close( output );

  elog_generate();
}


/*---------------------------------------------------------------------------*/


void stats_start1( id )
long id;
{ long i;

  if (sstate->profiling)
  { short *p = (short *)pstate->prof_bot;
    os_profil( (short *)NULL, 0L, 0L, 0L );
    while (p < (short *)pstate->prof_top) *(p++) = 0;
  }

  for (i=0; i<MAX_NB_STATS; i++) pstate->stats_counters[i] = 0;

  if (SCM_obj_to_int(pstate->id) == 0) sstate->stats_on = 1;

  if (sstate->profiling)
  { os_cpu_times( pstate->stats_cpu_times );
    os_profil( pstate->prof_bot,
               (long)(pstate->prof_top - pstate->prof_bot),
               (long)sstate->const_bot,
               (long)PROF_SHIFT );
  }

  pstate->elog_ptr = pstate->elog_top;

  if (id == SCM_obj_to_int(pstate->id))
  { pstate->elog_top[0] = EVENT(EVENT_WORKING,0);
    pstate->elog_top[1] = EVENT(EVENT_WORKING,0);
  }
  else
  { pstate->elog_top[0] = EVENT(EVENT_IDLE,0);
    pstate->elog_top[1] = EVENT(EVENT_IDLE,0);
  }
}


void stats_start2()
{ long start;
  start = os_real_time_clock();
  sstate->stats_start_time = start;
  while (os_clock_to_msec(sstate->stats_start_time - start) < 100)
    sstate->stats_start_time = os_real_time_clock(); /* wait for 100 msec */
}


long stats_stop1()
{ sstate->stats_stop_time = os_real_time_clock();
  if (sstate->profiling)
  { os_profil( (short *)NULL, 0L, 0L, 0L );
    os_cpu_times( pstate->cpu_times );
  }

  return os_clock_to_msec( sstate->stats_stop_time - sstate->stats_start_time );
}


void stats_stop2()
{ if ((SCM_obj_to_int(pstate->id) == 0) && sstate->stats_on)
  { sstate->stats_on = 0;
    if ((!sstate->profiling) && (sstate->nb_stats == NB_PREDEFINED_STATS))
    { long i, j;
      for (i=SCM_obj_to_int(pstate->nb_processors)-1; i>=0; i--)
      { if (pstate->ps[i]->elog_ptr != pstate->ps[i]->elog_top) goto stats_gen;
        for (j=sstate->nb_stats-1; j>=FIRST_AUTO_STAT; j--)
          if (pstate->ps[i]->stats_counters[j] != 0) goto stats_gen;
      }
      return;
      stats_gen: ;
    }
    stats_generate();
  }
}


/*---------------------------------------------------------------------------*/
