/* -*- linux-c -*-
 * Statistics Aggregation
 * Copyright (C) 2005-2016 Red Hat Inc.
 * Copyright (C) 2006 Intel Corporation
 *
 * This file is part of systemtap, and is free software.  You can
 * redistribute it and/or modify it under the terms of the GNU General
 * Public License (GPL); either version 2, or (at your option) any
 * later version.
 */
#ifndef _STAT_C_
#define _STAT_C_

/** @file stat.c
 * @brief Statistics Aggregation
 */
/** @addtogroup stat Statistics Aggregation
 * The Statistics aggregations keep per-cpu statistics. You
 * must create all aggregations at probe initialization and it is
 * best to not read them until probe exit. If you must read them
 * while probes are running, the values may be slightly off due
 * to a probe updating the statistics of one cpu while another cpu attempts
 * to read the same data. This will also negatively impact performance.
 *
 * Stats keep track of count, sum, min, max, avg, and variance.  Bit-shift
 * can be optionally specified, scaling the numbers, in order to improve the
 * accuracy of the integer arithmetics.
 *
 * Histograms are optional. If you want a histogram, you must set "type"
 * to HIST_LOG or HIST_LINEAR when you call _stp_stat_init().
 *
 * @{
 */

#include "stat-common.c"


/** Initialize a Stat.
 * Call this during probe initialization to create a Stat.
 *
 * @param type (KEY_HIST_TYPE and associated parameters) 
 *
 * For HIST_LOG, the following additional parametrs are required:
 * @param buckets - An integer specifying the number of buckets.
 *
 * For HIST_LINEAR, the following additional parametrs are required:
 * @param start - An integer. The start of the histogram.
 * @param stop - An integer. The stopping value. Should be > start.
 * @param interval - An integer. The interval.
 *
 * @param stat_ops (STAT_OP_* and associated parameter bit_shift for STAT_OP_VARIANCE)
 */
static Stat _stp_stat_init (int first_arg, ...)
{
	int size, buckets=0, start=0, stop=0, interval=0, bit_shift=0;
	int stat_ops=0, htype=0;
	int arg = first_arg;
	Stat st;
	va_list ap;

	va_start (ap, first_arg);
	do {
		switch (arg) {
		case KEY_HIST_TYPE:
			htype = va_arg(ap, int);
			if (htype == HIST_LINEAR) {
				start = va_arg(ap, int);
				stop = va_arg(ap, int);
				interval = va_arg(ap, int);

				buckets = _stp_stat_calc_buckets(stop, start, interval);
				if (!buckets) {
					va_end (ap);
					return NULL;
				}
			}
			if (htype == HIST_LOG)
				buckets = HIST_LOG_BUCKETS;
                        break;
		case STAT_OP_COUNT:
			stat_ops |= STAT_OP_COUNT;
			break;
		case STAT_OP_SUM:
			stat_ops |= STAT_OP_SUM;
			break;
		case STAT_OP_MIN:
			stat_ops |= STAT_OP_MIN;
			break;
		case STAT_OP_MAX:
			stat_ops |= STAT_OP_MAX;
			break;
		case STAT_OP_AVG:
			stat_ops |= STAT_OP_AVG;
			break;
		case STAT_OP_VARIANCE:
			stat_ops |= STAT_OP_VARIANCE;
			bit_shift = va_arg(ap, int);
			break;
		default:
			_stp_warn ("Unknown argument %d\n", arg);
		}
		arg = va_arg(ap, int);
	} while (arg);
	va_end (ap);

	size = buckets * sizeof(int64_t) + sizeof(stat_data);
	st = _stp_stat_alloc (size);
	if (st == NULL)
		return NULL;

	st->hist.type = htype;
	st->hist.start = start;
	st->hist.stop = stop;
	st->hist.interval = interval;
	st->hist.buckets = buckets;
	st->hist.bit_shift = bit_shift;
	st->hist.stat_ops = stat_ops;
	return st;
}

/** Delete Stat.
 * Call this to free all memory allocated during initialization.
 *
 * @param st Stat
 */
static void _stp_stat_del (Stat st)
{
	if (st)
		_stp_stat_free(st);
}

/** Add to a Stat.
 * Add an int64 to a Stat, and for optimization purposes specify which
 * statistical operators are bound to given Stat.  Set all of stat_op*
 * to 1 if unsure.  Note that @avg() is being evaluated separately based
 * on @sum and @count within the code directly generated by the translator.
 *
 * @param st Stat
 * @param val Value to add
 * @param stat_op_count int
 * @param stat_op_sum int
 * @param stat_op_min int
 * @param stat_op_max int
 * @param stat_op_variance int
 *
 */
static inline void _stp_stat_add (Stat st, int64_t val, int stat_op_count,
                                  int stat_op_sum, int stat_op_min,
				  int stat_op_max, int stat_op_variance)
{
	stat_data *sd = _stp_stat_per_cpu_ptr (st, STAT_GET_CPU());
	STAT_LOCK(sd);
	__stp_stat_add (&st->hist, sd, val, stat_op_count, stat_op_sum,
	                stat_op_min, stat_op_max, stat_op_variance);
	STAT_UNLOCK(sd);
	STAT_PUT_CPU();
}

static void _stp_stat_clear_data (Stat st, stat_data *sd)
{
        int j;
        sd->count = sd->sum = sd->min = sd->max = 0;
        sd->avg_s = sd->variance = sd->variance_s = 0;

        if (st->hist.type != HIST_NONE) {
                for (j = 0; j < st->hist.buckets; j++)
                        sd->histogram[j] = 0;
        }
}

/** Get Stats.
 * Gets the aggregated Stats for all CPUs.
 *
 * @param st Stat
 * @param clear Set if you want the data cleared after the read. Useful
 * for polling.
 * @returns A pointer to a stat.
 */
static stat_data *_stp_stat_get (Stat st, int clear)
{
	int i, j;
	int64_t S1, S2;
	stat_data *agg = _stp_stat_get_agg(st);
	stat_data *sd;
	STAT_LOCK(agg);
	_stp_stat_clear_data (st, agg);
	S1 = S2 = 0;

	for_each_possible_cpu(i) {
		stat_data *sd = _stp_stat_per_cpu_ptr (st, i);
		STAT_LOCK(sd);
		if (sd->count) {
			agg->shift = sd->shift;
			if (agg->count == 0) {
				agg->min = sd->min;
				agg->max = sd->max;
			}
			agg->count += sd->count;
			agg->sum += sd->sum;
			if (sd->max > agg->max)
				agg->max = sd->max;
			if (sd->min < agg->min)
				agg->min = sd->min;
			if (st->hist.type != HIST_NONE) {
				for (j = 0; j < st->hist.buckets; j++)
					agg->histogram[j] += sd->histogram[j];
			}
		}
		STAT_UNLOCK(sd);
	}

	agg->avg_s = _stp_div64(NULL, agg->sum << agg->shift, agg->count);

	/*
	 * For aggregating variance over available CPUs, the Total Variance
	 * formula is being used.  This formula is mentioned in following
	 * paper: Niranjan Kamat, Arnab Nandi: A Closer Look at Variance
	 * Implementations In Modern Database Systems: SIGMOD Record 2015.
	 * Available at: http://web.cse.ohio-state.edu/~kamatn/variance.pdf
	 */
	for_each_possible_cpu(i) {
		sd = _stp_stat_per_cpu_ptr (st, i);
		STAT_LOCK(sd);
		if (sd->count) {
			S1 += sd->count * (sd->avg_s - agg->avg_s) * (sd->avg_s - agg->avg_s);
			S2 += (sd->count - 1) * sd->variance_s;
		}
		if (clear)
			_stp_stat_clear_data (st, sd);
		STAT_UNLOCK(sd);
	}

	agg->variance_s = _stp_div64(NULL, (S1 + S2), (agg->count - 1));
	agg->variance = agg->variance_s >> (2 * agg->shift);

	/*
	 * Originally this function returned the aggregate still
	 * locked and it was the caller's responsibility to unlock the
	 * aggregate. However the translator generated code that called
	 * this function wasn't unlocking it...
	 *
	 * But, the translator generates its own locks for global
	 * variables (like stats), so we don't need to return the
	 * aggregate still locked.
	 *
	 * It is possible we could even skip locking the aggregate in
	 * this function, but to be a bit paranoid lets keep the
	 * locking.
	 */
	STAT_UNLOCK(agg);
	return agg;
}


/** Clear Stats.
 * Clears the Stats.
 *
 * @param st Stat
 */
static void _stp_stat_clear (Stat st)
{
	int i;

	for_each_possible_cpu(i) {
		stat_data *sd = _stp_stat_per_cpu_ptr (st, i);
		STAT_LOCK(sd);
		_stp_stat_clear_data (st, sd);
		STAT_UNLOCK(sd);
	}
}
/** @} */
#endif /* _STAT_C_ */
