/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe is free software; you can redistribute it and/or modify            *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   Nprobe is distributed in the hope that it will be useful,                 *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
*   GNU General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/

#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <limits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/param.h>


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <net/route.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>


#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <signal.h>
#include <assert.h>

#include "list.h"
#include "pkt.h"
#include "seq.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "print_util.h"
#include "report.h"
#include "output.h"
#include "tcpdump_patch.h"
#include "counters.h"
#include "writer.h"
#include "sundry_records.h"
#include "timeouts.h"
#include "if_stats.h"

#include "probe_config.h"

#if defined TIME_CALLS_RUSAGE || defined TIME_CALLS_TIMES
call_times_t call_times;
#endif

/* probe_main.c */
long long utvsub(struct timeval *t1, struct timeval *t0);

/*
 * Write a record of sk_buff alloc fails to rep file 
 */
void 
record_buf_alloc_fails(struct timeval *ts, int nfails)
{
  sk_buff_alloc_fail_rec_t rec;

  rec.ts = *ts;			/* struct assignment */
  rec.nfails = nfails;
  rec_dump_start();
  DUMP_STRUCT(outp, &rec, sk_buff_alloc_fail_rec_t);
  rec_dump_end(REC_BUF_ALLOC_FAIL);
  
  return;
}

/*
 * Write a record of rusage etc. to rep file 
 */
void 
record_rusage(struct timeval *ts, long long udiff)
{
  static int started = 0;
  
  static np_rusage_t ru;	/* writer accesses asynchronously */

  static struct timeval last_ru_utime = {0L, 0L};
  static struct timeval last_ru_stime = {0L, 0L};
  static struct timeval w_last_ru_utime = {0L, 0L};
  static struct timeval w_last_ru_stime = {0L, 0L};
  
  long long usage, susage, wusage, wsusage;
  double pct, spct, wpct, wspct;

  if (getrusage(RUSAGE_SELF, &ru.ru) != 0)
    error("record_rusage", "getrusage");
  
  if (!strcmp((char *)&ru.ru, "deadbeef"))
    fprintf (stderr, "XXXX Parent rusage not written\n");
  
  usage = utvsub(&ru.ru.ru_utime, &last_ru_utime);
  last_ru_utime = ru.ru.ru_utime;
  
  susage = utvsub(&ru.ru.ru_stime, &last_ru_stime);
  last_ru_stime = ru.ru.ru_stime;
  
  wusage = utvsub(&ru.wru.ru_utime, &w_last_ru_utime);
  w_last_ru_utime = ru.wru.ru_utime;
  
  wsusage = utvsub(&ru.wru.ru_stime, &w_last_ru_stime);
  w_last_ru_stime = ru.wru.ru_stime;
  
  if (!started)
    {
      started = 1;			/* struct */
      pct = 0.0;
      spct = 0.0;
      wpct = 0.0;
      wspct = 0.0;
#if defined TIME_CALLS_RUSAGE || defined TIME_CALLS_TIMES
      memset(&call_times, 0, sizeof(call_times_t));
#endif
    }
  else
    {
      /* try to get writer rusage - will lag one period behind */
      if (wru == NULL)
	{
	  /* writer has written values */
	  writer_rusage_lock = 1;	/* lock it while accessing */
	  wru = &ru.wru;		/* ask again */
	  if (!strcmp((char *)&ru.wru, "deadbeef"))
	    fprintf (stderr, "XXXX Writer rusage not written\n");
	  strcpy((char *)&ru.wru, "deadbeef");
	  wru_ts = &ru.wts;
	}
      pct = (((double)usage)*100)/udiff;
      spct = (((double)susage)*100)/udiff;
      wpct = (((double)wusage)*100)/udiff;
      wspct = (((double)wsusage)*100)/udiff;
    }
  
  
  ru.ts = *ts;			/* struct */
  rec_dump_start();
  DUMP_STRUCT(outp, &ru, np_rusage_t);
  rec_dump_end(REC_RUSAGE);
  
  writer_rusage_lock = 0;	/* let back in */
  strcpy((char *)&ru.ru, "deadbeef");
  
  fprintf(stderr, 
	  " Cycles: %.2fu + %.2fs = %.2f%% writer %.2fu + %.2fs = %.2f%%\n",
	  pct, spct, pct+spct, wpct, wspct, wpct+wspct);
  fprintf(stderr, "\n");

#if defined TIME_CALLS_RUSAGE || defined TIME_CALLS_TIMES
 {
   wrapper_record_t wt;
   wt.type = WRAPPER_TIME_CALLS;
   call_times.ts = *ts;		/* struct */
   memcpy(&wt.data.call_times, &call_times, sizeof(call_times_t));
   dump_wrapper_rec(&wt);

   //fprintf(stderr, "%llu %llu %llu %llu\n", call_times.ip.utm, call_times.ip.stm, wt.data.call_times.ip.utm, wt.data.call_times.ip.stm);
   
   
   memset(&call_times, 0, sizeof(call_times_t));
 }
#endif/* ifdef TIME_CALLS */
  return;
}

/*
 * Output period report during run
 */
static void
print_period_report(period_report_t *prep)
{
  fprintf(stderr, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
  fprintf(stderr, " %s\n", time_string(&prep->ts));
  if (prep->buf_alloc_fails)
    fprintf(stderr, " XXX %u sk_buff alloc fails XXX\n", prep->buf_alloc_fails);
  fprintf(stderr, " recvd: tous %uin-%uout=%u : frus %uin-%uout=%u\n",
      prep->tous_in, prep->tous_out, prep->tous_in - prep->tous_out,
      prep->frus_in, prep->frus_out, prep->frus_in - prep->frus_out);
  fprintf(stderr, " WIRE %.3f Mbs, %d pkts\n", prep->wire_mbs, prep->wire_pkts);
  fprintf(stderr, " TCP: %.3f Mbs, %d pkts ", prep->tcp_mbs, prep->tcp_pkts);
  fprintf(stderr, " UDP: %.3f Mbs, %d pkts ", prep->udp_mbs, prep->udp_pkts);
  fprintf(stderr, " HTTP: %.3f Mbs, %d pkts\n", prep->http_mbs, prep->http_pkts);
  fprintf(stderr, " Holding %u buffers lag %u us\n", prep->buff_held, 
	  prep->lag);
  fprintf(stderr, " Max fetch interval %u us\n", prep->max_fetch_interval);
  fprintf(stderr, " Parsed %u bytes HTML\n", prep->html_parsed);
  fprintf(stderr, " report - %3.0f%% (%u) of cycle (%u records %u total)\n",  
	  ((float)counters.rep_bytes_written*100)/report_file_sz,
	  counters.rep_bytes_written,
	  prep->nrecords, 
	  prep->nrecords_tot + prep->nrecords);
  fprintf(stderr, " dump - %3.0f%% (%u) of cycle\n",  
	  ((float)counters.dump_bytes_written*100)/dump_file_sz, 
	  counters.dump_bytes_written);
  fprintf(stderr, " dumped %u rep %u dump\n", prep->repblks_dumped*WRITE_BLKSZ,
	  prep->dumpblks_dumped*WRITE_BLKSZ);
  fprintf(stderr, " Timeouts: flow: tcp: %u  seq: to %u tf %u tq %u ta %u udp: %u\n", 
	  prep->tcp_to, prep->tcp_seq_to, prep->tcp_seq_to_forced, 
	  prep->tcp_seq_to_q, prep->tcp_seq_to_ack, 
	  prep->udp_to);
  fprintf(stderr, " Save rate %.4f MBs\n", prep->save_mBs);
  fprintf(stderr, " Maximum inter get time (sched) %u us\n", 
	  prep->max_int_get);
  fprintf(stderr, " Minimum forced TCP sequence timeout %u ms\n",
	  prep->min_tcp_seq_tp_f);
  fprintf(stderr, " Running time %s\n", us_clock_ts_string(prep->running_time));
  
  return;
}

/*
 * Write stats etc. to rep file - period driven by system clock or pkt arrivals
 */
void 
period_report(np_t *np, struct timeval *ts, long long udiff, 
	      unsigned int max_fetch_per, struct timeval *lastw_stats, 
	      int do_rusage, int do_procstats)
{   	            
  period_report_t p;

  static pr_save_last_t per_last, wper_last;

  pr_save_last_t *last;
  double wire_mbs = 0.0;
  double tcp_mbs = 0.0;
  double udp_mbs = 0.0;
  double http_mbs = 0.0;
  double save_mBs = 0.0;
  
  long long running_time;
  int alloc_fails;
  int reason;

  unsigned int wire_octs;
  unsigned int wire_pkts;
  unsigned int tcp_octs;
  unsigned int tcp_pkts;
  unsigned int udp_octs;
  unsigned int udp_pkts;
  unsigned int http_octs;
  unsigned int http_pkts;
  unsigned int html_octs;
  unsigned int rep_bytes_written;
  unsigned int dump_bytes_written;
  unsigned int rep_blks_dumped;
  unsigned int dump_blks_dumped;
  unsigned int IP_ulen_pkts;
  long nic_errs;

  if (lastw_stats)
    {
      last = &per_last;
      reason = REC_PERIOD_REPORT;
      /* update */
      counters.max_ctrs.wire_bw = MAX(counters.max_ctrs.wire_bw, wire_mbs);
      counters.max_ctrs.http_bw = MAX(counters.max_ctrs.http_bw, http_mbs);
#ifdef PROBE_FED
      /*
       * Don't inspect the interface for drops until some pkts seen 
       *  (ie until sure it is up) - otherwise spurious fails will be seen.
       * This might mean we miss any possible fails in the first two seconds
       *  (which is hard luck but not the end of the world)
       * Also runs the risk of not detecting if ALL incoming pkts are dropped
       *  - but we'll probably notice this anyway.
       */
      if (last->wire_pkts)
	nic_errs = lookat_if(chan, ts);
      else
	nic_errs = 0;
#else
      nic_errs = 0;
#endif
    }
  else
    {
      /* wire driven */
      last = &wper_last;
      reason = REC_WIRE_PERIOD_REPORT;
      nic_errs = 0;
    }
      
  
  wire_octs = (uint)(counters.wire.octs + counters.run_ctrs.wire_all.octs 
		     - last->wire_octs);
  wire_pkts = (uint)(counters.wire.pkts + counters.run_ctrs.wire_all.pkts 
		     - last->wire_pkts);
  tcp_octs = (uint)(counters.TRAFF_TCP.octs + counters.run_ctrs.TCP_all.octs 
		    - last->tcp_octs);
  tcp_pkts = (uint)(counters.TRAFF_TCP.pkts + counters.run_ctrs.TCP_all.pkts 
		    - last->tcp_pkts);
  udp_octs = (uint)(counters.TRAFF_UDP.octs + counters.run_ctrs.UDP_all.octs 
		    - last->udp_octs);
  udp_pkts = (uint)(counters.TRAFF_UDP.pkts + counters.run_ctrs.UDP_all.pkts 
		    - last->udp_pkts);
  http_octs = (uint)(counters.TRAFF_TCP_HTTP.octs 
		     + counters.run_ctrs.HTTP_all.octs 
		     - last->http_octs);
  http_pkts = (uint)(counters.TRAFF_TCP_HTTP.pkts 
		     + counters.run_ctrs.HTTP_all.pkts 
		     - last->http_pkts);
  html_octs = (uint)(counters.html_parsed.octs 
		     + counters.run_ctrs.HTML_all.octs 
		     -last->html_octs_parsed);
  rep_bytes_written = (uint)(counters.rep_bytes_written 
			     + counters.run_ctrs.rep_bytes_all 
			     -last->rep_bytes_written);
  dump_bytes_written = (uint)(counters.dump_bytes_written 
			      + counters.run_ctrs.dump_bytes_all 
			      -last->dump_bytes_written);
  rep_blks_dumped = (uint)(counters.rep_blks_dumped 
			   + counters.run_ctrs.rep_blks_all 
			   -last->rep_blks_dumped);
  dump_blks_dumped = (uint)(counters.dump_blks_dumped 
			    + counters.run_ctrs.dump_blks_all 
			    -last->dump_blks_dumped);
  IP_ulen_pkts = (uint)(counters.IP_ulen.pkts 
			    + counters.run_ctrs.IP_ulen_all.pkts 
			    -last->IP_ulen_pkts);
  
  if (!last->started)
    {
      /* just initialisation */
      last->started++;
      last->start = *ts;		/* struct */
      last->buf_alloc_fails = np->alloc_failed_cnt;
      wire_mbs = 0.0;
      tcp_mbs = 0.0;
      udp_mbs = 0.0;
      http_mbs = 0.0;
      save_mBs = 0.0;
    }
  else
    {
      wire_mbs = ( 8.0 * (double)wire_octs) / udiff;
      tcp_mbs = ( 8.0 * (double)tcp_octs)/udiff;
      udp_mbs = ( 8.0 * (double)udp_octs)/ udiff;
      http_mbs = ( 8.0 * (double)http_octs)/ udiff;
      save_mBs = ((double)(rep_bytes_written) + dump_bytes_written)/udiff;
    }
  
  running_time = utvsub(ts, &last->start);
  alloc_fails = np->alloc_failed_cnt - last->buf_alloc_fails;
  if (alloc_fails)
    {
      BUMP_CTR_N(buf_alloc_fails, alloc_fails);
      record_buf_alloc_fails(ts, (int)alloc_fails);
    }
  
  p.ts = *ts;			/* struct */
  p.buf_alloc_fails = alloc_fails;
  p.nic_errs = nic_errs;
  p.tous_in = np->x[chan].tous_in;        
  p.tous_out = np->x[chan].tous_out;        
  p.frus_in = np->x[chan].frus_in;        
  p.frus_out = np->x[chan].frus_out;
        
  p.wire_mbs =  wire_mbs;        
  p.wire_pkts = wire_pkts;        
  p.tcp_mbs = tcp_mbs;        
  p.tcp_pkts = tcp_pkts;        
  p.udp_mbs =  udp_mbs;        
  p.udp_pkts = udp_pkts;        
  p.http_mbs = http_mbs;        
  p.http_pkts = http_pkts;        
  p.save_mBs = save_mBs;                                    
  p.buff_held = counters.max_ctrs.buffers_held.current;         
  p.nrecords =  counters.nrecords;        
  p.nrecords_tot = counters.run_ctrs.tot_records + counters.nrecords;       
  p.rep_bytes_written = rep_bytes_written;   
  p.dump_bytes_written = dump_bytes_written;
  p.repblks_dumped = rep_blks_dumped;
  p.dumpblks_dumped = dump_blks_dumped;
  p.tcp_to = tcp_flo_timeo;        
  p.tcp_seq_to = sto;        
  p.tcp_seq_to_forced = sto_f;        
  p.tcp_seq_to_q = sto_q;        
  p.tcp_seq_to_ack = sto_a;        
  p.udp_to = udp_flo_timeo;        
  p.max_fetch_interval = max_fetch_per; 
  p.html_parsed = html_octs; 
  p.max_int_get = counters.max_ctrs.int_get;  
  p.min_tcp_seq_tp_f = counters.max_ctrs.min_tcp_seq_to;   
  p.running_time = running_time;  
  if (lastw_stats)
    p.lag = utvsub(ts, lastw_stats);
  else
    p.lag = 0U;
  p.IP_ulen_pkts = IP_ulen_pkts;

  last->buf_alloc_fails = np->alloc_failed_cnt;
  last->wire_pkts = counters.wire.pkts 
    + counters.run_ctrs.wire_all.pkts;
  last->wire_octs = counters.wire.octs 
    + counters.run_ctrs.wire_all.octs;
  last->tcp_pkts = counters.TRAFF_TCP.pkts 
    + counters.run_ctrs.TCP_all.pkts;
  last->tcp_octs = counters.TRAFF_TCP.octs 
    + counters.run_ctrs.TCP_all.octs;
  last->udp_pkts = counters.TRAFF_UDP.pkts 
    + counters.run_ctrs.UDP_all.pkts;
  last->udp_octs = counters.TRAFF_UDP.octs 
    + counters.run_ctrs.UDP_all.octs;
  last->http_pkts = counters.TRAFF_TCP_HTTP.pkts 
    + counters.run_ctrs.HTTP_all.pkts;
  last->http_octs = counters.TRAFF_TCP_HTTP.octs 
    + counters.run_ctrs.HTTP_all.octs;
  last->rep_bytes_written = counters.rep_bytes_written 
    + counters.run_ctrs.rep_bytes_all;
  last->dump_bytes_written = counters.dump_bytes_written 
    + counters.run_ctrs.dump_bytes_all;
  last->rep_blks_dumped = counters.rep_blks_dumped 
    + counters.run_ctrs.rep_blks_all;
  last->dump_blks_dumped = counters.dump_blks_dumped 
    + counters.run_ctrs.dump_blks_all;
  last->html_octs_parsed = counters.html_parsed.octs 
    + counters.run_ctrs.HTML_all.octs;
  last->html_pkts_parsed = counters.html_parsed.pkts
    + counters.run_ctrs.HTML_all.pkts;
  last->IP_ulen_pkts = counters.IP_ulen.pkts
    + counters.run_ctrs.IP_ulen_all.pkts;
       
  
  rec_dump_start();   
  DUMP_STRUCT(outp, &p, period_report_t);  
  rec_dump_end(reason);
  
  if (!lastw_stats)
    {
      print_period_report(&p);
      tcp_flo_timeo = 0U;
      udp_flo_timeo = 0U;
      sto = 0U;
      sto_f = 0U;
      sto_q = 0U;
      sto_a = 0U;
    }

  if (do_rusage)
    /* get and dump rusage */
    record_rusage(ts, udiff);

  if (do_procstats)
    /* get and dump procstats */
    record_procstats(ts);
  
  return;
  
}

/*
 * Save a temporary type record recp (data has been poulated and type noted)
 */
void 
dump_wrapper_rec(wrapper_record_t *recp)
{
  
  rec_dump_start();   
  DUMP_STRUCT(outp, recp, wrapper_record_t);  
  rec_dump_end(REC_OTHER_WRAPPER);

  return;
}

  

  
