
/*
 * $Id: StatHist.c,v 1.12 1998/07/22 20:36:54 wessels Exp $
 *
 * DEBUG: section 62    Generic Histogram
 * AUTHOR: Duane Wessels
 *
 * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from the
 *  Internet community.  Development is led by Duane Wessels of the
 *  National Laboratory for Applied Network Research and funded by the
 *  National Science Foundation.  Squid is Copyrighted (C) 1998 by
 *  Duane Wessels and the University of California San Diego.  Please
 *  see the COPYRIGHT file for full details.  Squid incorporates
 *  software developed and/or copyrighted by other sources.  Please see
 *  the CREDITS file for full details.
 *
 *  This program 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.
 *  
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

/*
 * Important restrictions on val_in and val_out functions:
 * 
 *   - val_in:  ascending, defined on [0, oo), val_in(0) == 0;
 *   - val_out: x == val_out(val_in(x)) where val_in(x) is defined
 *
 *  In practice, the requirements are less strict, 
 *  but then it gets hard to define them without math notation.
 *  val_in is applied after offseting the value but before scaling
 *  See log and linear based histograms for examples
 */

#include "squid.h"

/* Local functions */
static void statHistInit(StatHist * H, int capacity, hbase_f val_in, hbase_f val_out, double min, double max);
static int statHistBin(const StatHist * H, double v);
static double statHistVal(const StatHist * H, int bin);
static StatHistBinDumper statHistBinDumper;

/* low level init, higher level functions has less params */
static void
statHistInit(StatHist * H, int capacity, hbase_f val_in, hbase_f val_out, double min, double max)
{
    assert(H);
    assert(capacity > 0);
    assert(val_in && val_out);
    /* check before we divide to get scale */
    assert(val_in(max - min) > 0);
    H->bins = xcalloc(capacity, sizeof(int));
    H->min = min;
    H->max = max;
    H->capacity = capacity;
    H->scale = capacity / val_in(max - min);
    H->val_in = val_in;
    H->val_out = val_out;
    /* check that functions are valid */
    /* a min value should go into bin[0] */
    assert(statHistBin(H, min) == 0);
    /* a max value should go into the last bin */
    assert(statHistBin(H, max) == H->capacity - 1);
    /* it is hard to test val_out, here is a crude test */
    assert(((int) (0.99 + statHistVal(H, 0) - min)) == 0);
}

void
statHistClean(StatHist * H)
{
    xfree(H->bins);
    H->bins = NULL;
}

/* assumes that somebody already called init for Dest */
void
statHistCopy(StatHist * Dest, const StatHist * Orig)
{
    assert(Dest && Orig);
    assert(Dest->bins);
    /* better be safe than sorry */
    assert(Dest->capacity == Orig->capacity);
    assert(Dest->min == Orig->min && Dest->max == Orig->max);
    assert(Dest->scale == Orig->scale);
    assert(Dest->val_in == Orig->val_in && Dest->val_out == Orig->val_out);
    /* actual copy */
    xmemcpy(Dest->bins, Orig->bins, Dest->capacity * sizeof(*Dest->bins));
}

/*
 * same as statHistCopy but will do nothing if capacities do not match; the
 * latter happens, for example, when #peers changes during reconfiguration;
 * if it happens too often we should think about more general solution..
 */
void
statHistSafeCopy(StatHist * Dest, const StatHist * Orig)
{
    assert(Dest && Orig);
    assert(Dest->bins);
    if (Dest->capacity == Orig->capacity)
	statHistCopy(Dest, Orig);
}

void
statHistCount(StatHist * H, double val)
{
    const int bin = statHistBin(H, val);
    assert(H->bins);		/* make sure it got initialized */
    assert(0 <= bin && bin < H->capacity);
    H->bins[bin]++;
}

static int
statHistBin(const StatHist * H, double v)
{
    int bin;
    v -= H->min;		/* offset */
    if (v < 0.0)		/* too small */
	return 0;
    bin = (int) (H->scale * H->val_in(v) + 0.5);
    if (bin < 0)		/* should not happen */
	bin = 0;
    if (bin >= H->capacity)	/* too big */
	bin = H->capacity - 1;
    return bin;
}

static double
statHistVal(const StatHist * H, int bin)
{
    return H->val_out(bin / H->scale) + H->min;
}

double
statHistDeltaMedian(const StatHist * A, const StatHist * B)
{
    int i;
    int s1 = 0;
    int h = 0;
    int a = 0;
    int b = 0;
    int I = 0;
    int J = A->capacity;
    int K;
    double f;
    int *D = xcalloc(A->capacity, sizeof(int));
    assert(A->capacity == B->capacity);
    for (i = 0; i < A->capacity; i++) {
	D[i] = B->bins[i] - A->bins[i];
	assert(D[i] >= 0);
    }
    for (i = 0; i < A->capacity; i++)
	s1 += D[i];
    h = s1 >> 1;
    for (i = 0; i < A->capacity; i++) {
	J = i;
	b += D[J];
	if (a <= h && h <= b)
	    break;
	I = i;
	a += D[I];
    }
    xfree(D);
    if (s1 == 0)
	return 0.0;
    if (a > h)
	return 0.0;
    if (a >= b)
	return 0.0;
    if (I >= J)
	return 0.0;
    f = (h - a) / (b - a);
    K = (int) (f * (double) (J - I) + I);
    return statHistVal(A, K);
}

static void
statHistBinDumper(StoreEntry * sentry, int idx, double val, double size, int count)
{
    if (count)
	storeAppendPrintf(sentry, "\t%3d/%f\t%d\t%f\n",
	    idx, val, count, count / size);
}

void
statHistDump(const StatHist * H, StoreEntry * sentry, StatHistBinDumper * bd)
{
    int i;
    double left_border = H->min;
    if (!bd)
	bd = statHistBinDumper;
    for (i = 0; i < H->capacity; i++) {
	const double right_border = statHistVal(H, i + 1);
	assert(right_border - left_border > 0.0);
	bd(sentry, i, left_border, right_border - left_border, H->bins[i]);
	left_border = right_border;
    }
}

/* log based histogram */
static double
Log(double x)
{
    return log(x + 1);
}
static double
Exp(double x)
{
    return exp(x) - 1;
}
void
statHistLogInit(StatHist * H, int capacity, double min, double max)
{
    statHistInit(H, capacity, &Log, &Exp, min, max);
}

/* linear histogram for enums */
/* we want to be have [-1,last_enum+1] range to track out of range enums */
static double
Null(double x)
{
    return x;
}

void
statHistEnumInit(StatHist * H, int last_enum)
{
    statHistInit(H, last_enum + 3, &Null, &Null, -1, last_enum + 1 + 1);
}

void
statHistEnumDumper(StoreEntry * sentry, int idx, double val, double size, int count)
{
    if (count)
	storeAppendPrintf(sentry, "%2d\t %5d\t %5d\n",
	    idx, (int) val, count);
}

void
statHistIntInit(StatHist * H, int n)
{
    statHistInit(H, n, &Null, &Null, 0, n - 1);
}

void
statHistIntDumper(StoreEntry * sentry, int idx, double val, double size, int count)
{
    if (count)
	storeAppendPrintf(sentry, "%9d\t%9d\n", (int) val, count);
}
