/*
 * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
 *               2002, 2003, 2004
 *	Ohio University.
 *
 * ---
 * 
 * Starting with the release of tcptrace version 6 in 2001, tcptrace
 * is licensed under the GNU General Public License (GPL).  We believe
 * that, among the available licenses, the GPL will do the best job of
 * allowing tcptrace to continue to be a valuable, freely-available
 * and well-maintained tool for the networking community.
 *
 * Previous versions of tcptrace were released under a license that
 * was much less restrictive with respect to how tcptrace could be
 * used in commercial products.  Because of this, I am willing to
 * consider alternate license arrangements as allowed in Section 10 of
 * the GNU GPL.  Before I would consider licensing tcptrace under an
 * alternate agreement with a particular individual or company,
 * however, I would have to be convinced that such an alternative
 * would be to the greater benefit of the networking community.
 * 
 * ---
 *
 * This file is part of Tcptrace.
 *
 * Tcptrace was originally written and continues to be maintained by
 * Shawn Ostermann with the help of a group of devoted students and
 * users (see the file 'THANKS').  The work on tcptrace has been made
 * possible over the years through the generous support of NASA GRC,
 * the National Science Foundation, and Sun Microsystems.
 *
 * Tcptrace 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.
 *
 * Tcptrace 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 Tcptrace (in the file 'COPYING'); if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 * 
 * Author:	Shawn Ostermann
 * 		School of Electrical Engineering and Computer Science
 * 		Ohio University
 * 		Athens, OH
 *		ostermann@cs.ohiou.edu
 *		http://www.tcptrace.org/
 */
#include "tcptrace.h"
static char const GCC_UNUSED rcsid[] =
   "$Header: /usr/local/cvs/tcptrace/mod_rttgraph.c,v 5.8 2003/11/19 14:38:03 sdo Exp $";

#ifdef LOAD_MODULE_RTTGRAPH

#include <limits.h>
#include "mod_rttgraph.h"


/* a histogram structure */
struct hist {
    u_long num_buckets;
    u_long num_samples;
    u_long *buckets;
    u_long z;
};


#define NUM_SLICES 10
struct hist3d {
    struct hist rtt;
    struct hist rtt_diff_slices[NUM_SLICES+1];
};


struct samples {
    u_long num_samples;
    u_long max_samples;
    u_short max;
    u_short min;
    u_short *samples;
};

/* what we keep for each tcb */
struct rtt_tcb {
    tcb *ptcb;
    struct samples samples;
};


/* info kept for each connection */
static struct rttgraph_info {
    tcp_pair *ptp;
    struct rtt_tcb a2b;
    struct rtt_tcb b2a;
    struct rttgraph_info *next;
} *rttgraphhead = NULL, *rttgraphtail = NULL;




/* local routines */
static struct rttgraph_info *MakeRttgraphRec();
static void MakeBuckets(struct hist *phist, u_int num_buckets);
static void AddSample(struct samples *psamp, u_short sample);
static void PlotHist(MFILE *f,struct hist *phist);
static void PlotOne(struct rttgraph_info *prttg);



/* Mostly as a module example, here's a plug in that records RTTGRAPH info */
int
rttgraph_init(
    int argc,
    char *argv[])
{
    int i;
    int enable=0;

    /* look for "-xrttgraph[N]" */
    for (i=1; i < argc; ++i) {
	if (!argv[i])
	    continue;  /* argument already taken by another module... */
	if (strncmp(argv[i],"-x",2) == 0) {
	    if (strncasecmp(argv[i]+2,"rttgraph",4) == 0) {
		/* I want to be called */
		enable = 1;
		printf("mod_rttgraph: Capturing RTTGRAPH traffic\n");
		argv[i] = NULL;
	    }
	}
    }

    if (!enable)
	return(0);	/* don't call me again */

    return(1);	/* TRUE means call rttgraph_read and rttgraph_done later */
}


static void
MakeBuckets(
    struct hist *phist,
    u_int num_buckets)
{
    u_long *new_ptr;

    ++num_buckets;  /* 0-based arrays */

    if (num_buckets <= phist->num_buckets)
	return;  /* nothing to do */

    /* round num_buckets up to multiple of 100 */
    if ((num_buckets % 100) != 0) {
	num_buckets = 100 * ((num_buckets+99)/100);
    }

    /* either create the space or expand it */
    if (phist->buckets) {
	new_ptr = ReallocZ(phist->buckets,
			   phist->num_buckets * sizeof(u_long),
			   num_buckets * sizeof(u_long));
    } else {
	new_ptr = MallocZ(num_buckets * sizeof(u_long));
    }

    /* remember what we did */
    phist->num_buckets = num_buckets;
    phist->buckets = new_ptr;
}



static void
AddSample(
    struct samples *psamp,
    u_short sample)
{

/*     printf("AddSample(%d) called\n", sample); */
    
    /* make sure we have enough space */
    if ((psamp->num_samples+2) > (psamp->max_samples)) {
	u_long new_samples = psamp->max_samples + 100;
	u_short *new_ptr;
	if (psamp->samples) {
	    new_ptr = ReallocZ(psamp->samples,
			       psamp->max_samples * sizeof(u_short),
			       new_samples * sizeof(u_short));
	} else {
	    new_ptr = MallocZ(new_samples * sizeof(u_short));
	}
	psamp->max_samples = new_samples;
	psamp->samples = new_ptr;
    }

    /* remember what we did */
    psamp->samples[psamp->num_samples++] = sample;
    if (sample > psamp->max)
	psamp->max = sample;
    if (sample < psamp->max)
	psamp->min = sample;
}


static struct rttgraph_info *
MakeRttgraphRec()
{
    struct rttgraph_info *prttg;

    prttg = MallocZ(sizeof(struct rttgraph_info));

    /* (...leave the samples pointer NULL until first needed) */
    prttg->a2b.samples.max_samples = 0;
    prttg->a2b.samples.max = 0; prttg->a2b.samples.min = USHRT_MAX;
    prttg->b2a.samples.max_samples = 0;
    prttg->b2a.samples.max = 0; prttg->b2a.samples.min = USHRT_MAX;

    /* chain it in (at the tail of the list) */
    if (rttgraphhead == NULL) {
	rttgraphhead = prttg;
	rttgraphtail = prttg;
    } else {
	rttgraphtail->next = prttg;
	rttgraphtail = prttg;
    }

    return(prttg);
}


void
rttgraph_read(
    struct ip *pip,		/* the packet */
    tcp_pair *ptp,		/* info I have about this connection */
    void *plast,		/* past byte in the packet */
    void *mod_data)		/* module specific info for this connection */
{
    struct tcphdr *ptcp;
    struct rttgraph_info *prttg = mod_data;
    struct rtt_tcb *prtcb;
    tcb *ptcb;
    double rtt_us;
    u_long rtt_ms;

    /* find the start of the TCP header */
    ptcp = (struct tcphdr *) ((char *)pip + 4*IP_HL(pip));

    /* make sure there we could have a RTT sample */
    if (!ACK_SET(ptcp))
	return;  /* no RTT info */

    /* see which direction it is, if we don't know yet */
    ptcb = ptp2ptcb(ptp,pip,ptcp);
    if (ptcb == prttg->a2b.ptcb)
	prtcb = &prttg->a2b;
    else if (ptcb == prttg->b2a.ptcb)
	prtcb = &prttg->b2a;
    else {
	fprintf(stderr,
		"rttgraph_read: INTERNAL error (can't kind tcb)!!\n");
	exit(1);
    }

    /* grab the RTT */
    rtt_us = prtcb->ptcb->rtt_last;
    if (rtt_us == 0.0)
	return;  /* not a valid sample */

    /* convert to ms buckets */
    rtt_ms = (u_long) (rtt_us / 1000.0);

    if (debug && (rtt_ms == 0))
	printf("rtt_ms is 0, rtt_us was %f\n", rtt_us);

    /* add in the sample RTT */
    AddSample(&prtcb->samples, rtt_ms);
}


static void
DoHist(
    struct samples *psamp)
{
    int i;
    struct hist3d hist3d;
    MFILE *f;
    u_long sum;
    int slice;
    int base_z;
    int slice_size;

    if (psamp->num_samples == 0)
	return;

    printf("Samples: %lu\n", psamp->num_samples);
    printf("Min: %u\n", psamp->min);
    printf("Max: %u\n", psamp->max);

    /* init */
    hist3d.rtt.num_buckets = 0;
    memset(&hist3d.rtt,'\00',sizeof(struct hist));
    MakeBuckets(&hist3d.rtt, psamp->num_samples);
    for (i=0; i < NUM_SLICES; ++i) {
	memset(&hist3d.rtt_diff_slices[i],'\00',sizeof(struct hist));
        MakeBuckets(&hist3d.rtt_diff_slices[i], psamp->num_samples);
    }

    /* calculate the global histogram */
    for (i=0; i < psamp->num_samples; ++i) {
	u_short rtt = psamp->samples[i];

	++hist3d.rtt.buckets[rtt];
	++hist3d.rtt.num_samples;
    }
    

    /* find the slices, same amount of data in each slice */
    sum = 0;
    slice = 0;
    base_z = 0;
    slice_size = psamp->num_samples / NUM_SLICES;
    for (i=0; i < psamp->num_samples; ++i) {
	u_short count = hist3d.rtt.buckets[i];

	sum += count;

	if (sum > slice_size) {
	    hist3d.rtt_diff_slices[slice].z = base_z;
	    ++slice;
	    sum = 0;
	    base_z = i+1;
	}
    }
    for (; slice < NUM_SLICES; ++slice)
	hist3d.rtt_diff_slices[slice].z = ULONG_MAX;


    /* add the slice data */
    for (i=1; i < psamp->num_samples; ++i) {
	u_short rtt = psamp->samples[i];
	u_short prev_rtt = psamp->samples[i-1];

	/* see which slice holds the prev_rtt */ 
	for (slice = NUM_SLICES-1; slice >= 0; --slice) {
	    if (prev_rtt > hist3d.rtt_diff_slices[slice].z)
		break;
	}

	++hist3d.rtt_diff_slices[slice].buckets[rtt];
	++hist3d.rtt_diff_slices[slice].num_samples;
    }


    if ((f = Mfopen("rtt.dat","w")) == NULL) {
	perror("rtt.dat");
	exit (1);
    }
    printf("Total Histogram\n");
    hist3d.rtt.z = -1;
    PlotHist(f,&hist3d.rtt);
    Mfclose(f);

    if ((f = Mfopen("rtt3d.dat","w")) == NULL) {
	perror("rtt.dat");
	exit (1);
    }
    for (i=0; i < NUM_SLICES; ++i) {
	struct hist *phist = &hist3d.rtt_diff_slices[i];
	printf("Slice %d Histogram - base: %lu ms\n", i, phist->z);
	       
	PlotHist(f,phist);
    }

    /* plot the "connections" */
#ifdef BROKEN
    for (i=0; i < psamp->max; i+=10) {
	for (slice=0; slice < NUM_SLICES; ++slice) {
	    struct hist *phist = &hist3d.rtt_diff_slices[slice];
	    u_long count = phist->buckets[i];
	    float percent = (float)count / phist->num_samples;

	    if (phist->num_samples == 0)
		continue;

	    fprintf(f,"%4d %lu %.2f\n", i, phist->z, 100 * percent);
	}
	fprintf(f,"\n");
    }
#endif /* BROKEN */

    Mfclose(f);
}



static void
PlotHist(
    MFILE *f,
    struct hist *phist)
{
    int ms;
    int z = phist->z;

    if (phist->buckets == NULL)
	return;

    printf("  %lu samples\n", phist->num_samples);
    for (ms=0; ms < phist->num_buckets; ++ms) {
	u_long count = phist->buckets[ms];
	float percent;
       
	if (count == 0 || phist->num_samples == 0)
	    continue;

        percent = (float)count / phist->num_samples;
       
	printf("  %4d  %5lu  %5.2f\n", ms, count, 100 * percent);
	if (z == -1)
	    Mfprintf(f,"%4d  %.2f\n", ms, 100 * percent);
	else
	    Mfprintf(f,"%4d  %d %.2f\n", ms, z, 100 * percent);
    }
    Mfprintf(f,"\n");
}


static void
PlotOne(
    struct rttgraph_info *prttg)
{
    tcp_pair *ptp = prttg->ptp;

    printf("%s ==> %s (%s2%s)\n",
	   ptp->a_endpoint, ptp->b_endpoint,
	   ptp->a2b.host_letter, ptp->b2a.host_letter);
    DoHist(&prttg->a2b.samples);

    printf("%s ==> %s (%s2%s)\n",
	   ptp->b_endpoint, ptp->a_endpoint,
	   ptp->b2a.host_letter, ptp->a2b.host_letter);
    DoHist(&prttg->b2a.samples);
}



void
rttgraph_done(void)
{
    struct rttgraph_info *prttg;

    for (prttg=rttgraphhead; prttg; prttg=prttg->next) {
	PlotOne(prttg);
    }
}


void
rttgraph_usage(void)
{
    printf("\t-xrttgraph\tprint info about rttgraph traffic\n");
}


void *
rttgraph_newconn(
    tcp_pair *ptp)
{
    struct rttgraph_info *prttg;

    prttg = MakeRttgraphRec();

    prttg->ptp = ptp;
    prttg->a2b.ptcb = &ptp->a2b;
    prttg->b2a.ptcb = &ptp->b2a;

    return(prttg);
}

#endif /* LOAD_MODULE_RTTGRAPH */
