/*-
 * Public Domain 2014-2016 MongoDB, Inc.
 * Public Domain 2008-2014 WiredTiger, Inc.
 *
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "wtperf.h"

/*
 * Return total insert operations for the populate phase.
 */
uint64_t
sum_pop_ops(CONFIG *cfg)
{
	CONFIG_THREAD *thread;
	uint64_t total;
	u_int i;

	total = 0;

	for (i = 0, thread = cfg->popthreads;
	    thread != NULL && i < cfg->populate_threads; ++i, ++thread)
		total += thread->insert.ops;
	return (total);
}

/*
 * Return total checkpoint operations.
 */
uint64_t
sum_ckpt_ops(CONFIG *cfg)
{
	CONFIG_THREAD *thread;
	uint64_t total;
	u_int i;

	total = 0;

	for (i = 0, thread = cfg->ckptthreads;
	    thread != NULL && i < cfg->checkpoint_threads; ++i, ++thread)
		total += thread->ckpt.ops;
	return (total);
}

/*
 * Return total operations count for the worker threads.
 */
static uint64_t
sum_ops(CONFIG *cfg, size_t field_offset)
{
	CONFIG_THREAD *thread;
	uint64_t total;
	int64_t i, th_cnt;

	total = 0;
	if (cfg->popthreads == NULL) {
		thread = cfg->workers;
		th_cnt = cfg->workers_cnt;
	} else {
		thread = cfg->popthreads;
		th_cnt = cfg->populate_threads;
	}
	for (i = 0; thread != NULL && i < th_cnt; ++i, ++thread)
		total += ((TRACK *)((uint8_t *)thread + field_offset))->ops;

	return (total);
}
uint64_t
sum_insert_ops(CONFIG *cfg)
{
	return (sum_ops(cfg, offsetof(CONFIG_THREAD, insert)));
}
uint64_t
sum_read_ops(CONFIG *cfg)
{
	return (sum_ops(cfg, offsetof(CONFIG_THREAD, read)));
}
uint64_t
sum_truncate_ops(CONFIG *cfg)
{
	return (sum_ops(cfg, offsetof(CONFIG_THREAD, truncate)));
}
uint64_t
sum_update_ops(CONFIG *cfg)
{
	return (sum_ops(cfg, offsetof(CONFIG_THREAD, update)));
}

/*
 * latency_op --
 *	Get average, minimum and maximum latency for this period for a
 * particular operation.
 */
static void
latency_op(CONFIG *cfg,
    size_t field_offset, uint32_t *avgp, uint32_t *minp, uint32_t *maxp)
{
	CONFIG_THREAD *thread;
	TRACK *track;
	uint64_t ops, latency, tmp;
	int64_t i, th_cnt;
	uint32_t max, min;

	ops = latency = 0;
	max = 0;
	min = UINT32_MAX;

	if (cfg->popthreads == NULL) {
		thread = cfg->workers;
		th_cnt = cfg->workers_cnt;
	} else {
		thread = cfg->popthreads;
		th_cnt = cfg->populate_threads;
	}
	for (i = 0; thread != NULL && i < th_cnt; ++i, ++thread) {
		track = (TRACK *)((uint8_t *)thread + field_offset);
		tmp = track->latency_ops;
		ops += tmp - track->last_latency_ops;
		track->last_latency_ops = tmp;
		tmp = track->latency;
		latency += tmp - track->last_latency;
		track->last_latency = tmp;

		if (min > track->min_latency)
			min = track->min_latency;
		track->min_latency = UINT32_MAX;
		if (max < track->max_latency)
			max = track->max_latency;
		track->max_latency = 0;
	}

	if (ops == 0)
		*avgp = *minp = *maxp = 0;
	else {
		*minp = min;
		*maxp = max;
		*avgp = (uint32_t)(latency / ops);
	}
}
void
latency_read(CONFIG *cfg, uint32_t *avgp, uint32_t *minp, uint32_t *maxp)
{
	static uint32_t last_avg = 0, last_max = 0, last_min = 0;

	latency_op(cfg, offsetof(CONFIG_THREAD, read), avgp, minp, maxp);

	/*
	 * If nothing happened, graph the average, minimum and maximum as they
	 * were the last time, it keeps the graphs from having discontinuities.
	 */
	if (*minp == 0) {
		*avgp = last_avg;
		*minp = last_min;
		*maxp = last_max;
	} else {
		last_avg = *avgp;
		last_min = *minp;
		last_max = *maxp;
	}
}
void
latency_insert(CONFIG *cfg, uint32_t *avgp, uint32_t *minp, uint32_t *maxp)
{
	static uint32_t last_avg = 0, last_max = 0, last_min = 0;

	latency_op(cfg, offsetof(CONFIG_THREAD, insert), avgp, minp, maxp);

	/*
	 * If nothing happened, graph the average, minimum and maximum as they
	 * were the last time, it keeps the graphs from having discontinuities.
	 */
	if (*minp == 0) {
		*avgp = last_avg;
		*minp = last_min;
		*maxp = last_max;
	} else {
		last_avg = *avgp;
		last_min = *minp;
		last_max = *maxp;
	}
}
void
latency_update(CONFIG *cfg, uint32_t *avgp, uint32_t *minp, uint32_t *maxp)
{
	static uint32_t last_avg = 0, last_max = 0, last_min = 0;

	latency_op(cfg, offsetof(CONFIG_THREAD, update), avgp, minp, maxp);

	/*
	 * If nothing happened, graph the average, minimum and maximum as they
	 * were the last time, it keeps the graphs from having discontinuities.
	 */
	if (*minp == 0) {
		*avgp = last_avg;
		*minp = last_min;
		*maxp = last_max;
	} else {
		last_avg = *avgp;
		last_min = *minp;
		last_max = *maxp;
	}
}

/*
 * sum_latency --
 *	Sum latency for a set of threads.
 */
static void
sum_latency(CONFIG *cfg, size_t field_offset, TRACK *total)
{
	CONFIG_THREAD *thread;
	TRACK *trk;
	int64_t i;
	u_int j;

	memset(total, 0, sizeof(*total));

	for (i = 0, thread = cfg->workers;
	    thread != NULL && i < cfg->workers_cnt; ++i, ++thread) {
		trk = (TRACK *)((uint8_t *)thread + field_offset);

		for (j = 0; j < ELEMENTS(trk->us); ++j) {
			total->ops += trk->us[j];
			total->us[j] += trk->us[j];
		}
		for (j = 0; j < ELEMENTS(trk->ms); ++j) {
			total->ops += trk->ms[j];
			total->ms[j] += trk->ms[j];
		}
		for (j = 0; j < ELEMENTS(trk->sec); ++j) {
			total->ops += trk->sec[j];
			total->sec[j] += trk->sec[j];
		}
	}
}
static void
sum_insert_latency(CONFIG *cfg, TRACK *total)
{
	sum_latency(cfg, offsetof(CONFIG_THREAD, insert), total);
}
static void
sum_read_latency(CONFIG *cfg, TRACK *total)
{
	sum_latency(cfg, offsetof(CONFIG_THREAD, read), total);
}
static void
sum_update_latency(CONFIG *cfg, TRACK *total)
{
	sum_latency(cfg, offsetof(CONFIG_THREAD, update), total);
}

static void
latency_print_single(CONFIG *cfg, TRACK *total, const char *name)
{
	FILE *fp;
	u_int i;
	uint64_t cumops;
	char path[1024];

	snprintf(path, sizeof(path), "%s/latency.%s", cfg->monitor_dir, name);
	if ((fp = fopen(path, "w")) == NULL) {
		lprintf(cfg, errno, 0, "%s", path);
		return;
	}

	fprintf(fp,
	    "#usecs,operations,cumulative-operations,total-operations\n");
	cumops = 0;
	for (i = 0; i < ELEMENTS(total->us); ++i) {
		if (total->us[i] == 0)
			continue;
		cumops += total->us[i];
		fprintf(fp,
		    "%u,%" PRIu32 ",%" PRIu64 ",%" PRIu64 "\n",
		    (i + 1), total->us[i], cumops, total->ops);
	}
	for (i = 1; i < ELEMENTS(total->ms); ++i) {
		if (total->ms[i] == 0)
			continue;
		cumops += total->ms[i];
		fprintf(fp,
		    "%llu,%" PRIu32 ",%" PRIu64 ",%" PRIu64 "\n",
		    ms_to_us(i + 1), total->ms[i], cumops, total->ops);
	}
	for (i = 1; i < ELEMENTS(total->sec); ++i) {
		if (total->sec[i] == 0)
			continue;
		cumops += total->sec[i];
		fprintf(fp,
		    "%llu,%" PRIu32 ",%" PRIu64 ",%" PRIu64 "\n",
		    sec_to_us(i + 1), total->sec[i], cumops, total->ops);
	}

	(void)fclose(fp);
}

void
latency_print(CONFIG *cfg)
{
	TRACK total;

	sum_insert_latency(cfg, &total);
	latency_print_single(cfg, &total, "insert");
	sum_read_latency(cfg, &total);
	latency_print_single(cfg, &total, "read");
	sum_update_latency(cfg, &total);
	latency_print_single(cfg, &total, "update");
}
