/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the "License").  You may not use this file except
 * in compliance with the License.
 *
 * You can obtain a copy of the license at
 * usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * HEADER in each file and include the License file at
 * usr/src/OPENSOLARIS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your
 * own identifying information: Portions Copyright [yyyy]
 * [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * This program is a micro-benchmark which simulates memory-intensive
 * applications. It
 *
 * - Allocates a chunk of memory and touches al of it.
 * - Walks this memory reading bytes
 * - measures the time it takes to read
 *
 * To get around address prefetch optimizations in the hardware, the program can
 * read memory from addresses separated by certain distance from each other (the
 * distance can be both positive and negative). By using large buffer size you
 * can ensure that data is always outside the cache.
 *
 * The program has many configurable options. Its main value is not what it
 * measures but its flexibility - it is really easy to play with different
 * options and see what effect they have.
 *
 * The program is originally developed by Jonathan Chew <jonathan.chew at sun.com>
 * and Alexander Kolbasov <alexander.kolbasov at sun.com> with many suggestions
 * from Bart Smaalders <bart.smaalders at sun.com>
 */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <errno.h>
#include <thread.h>
#include <signal.h>
#include <strings.h>
#include <libgen.h>
#include <sys/lgrp_user.h>

#pragma ident "$Id: memtest.c,v 1.5 2005/08/20 05:32:58 akolb Exp $"

/*
 * Print usage string and exit.
 */
static void 
usage(char *s)
{
	fprintf(stderr,
	    "Usage: %s [-v] [-N] -s <size> -n iter "
	    "[-d delta] [-l lgrp] [-c count]\n", s);
	fprintf(stderr,
	    "\t-v:\t\tVerbose mode\n"
	    "\t-N:\t\tNormalize results by number of reads\n"
	    "\t-s <size>:\tWorking set size (may specify K,M,G suffix)\n"
	    "\t-n iter:\tNumber of test iterations\n"
	    "\t-d [+|-]delta:\tDistance between subsequent reads\n"
	    "\t-l lgrp:\tRequest strong affinity to specified lgroup\n"
	    "\t-c count:\tNumber of reads\n"
	    "\t-h:\t\tPrint this help\n" );
	exit(1);
}

#define ABS(x) ((x) >= 0 ? (x) : -(x))

/*
 * format a decimal number with k/m/g suffix
 */
static void
format_num(size_t v, size_t *new, char *code)
{
	if (v % (1024 * 1024 * 1024) == 0) {
		*new = v / (1024 * 1024 * 1024);
		*code = 'G';
	} else if (v % (1024 * 1024) == 0) {
		*new = v / (1024 * 1024);
		*code = 'M';
	} else if (v % (1024) == 0) {
		*new = v / (1024);
		*code = 'K';
	} else {
		*new = v;
		*code = ' ';
	}
}

/*
 * parse a decimal number with k/m/g suffix
 */
static size_t
parse_num(char *s)
{
	size_t v = 0;

	for (;;) {
		switch (tolower(*s)) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			v = v * 10 + *s - '0';
			++s;
			continue;

		case 'k':
			v *= 1024;
			return (v);

		case 'm':
			v *= (1024 * 1024);
			return (v);

		case 'g':
			v *= (1024 * 1024 * 1024);
			return (v);

		default:
			return (v);
		}
	}
}

/*
 * create a memry segment with a given pagesize
 */
static void *
create_memory(size_t size, size_t pagesize)
{
	caddr_t p;

	/*
	 * MAP_NORESERVE prevents using alternate page sizes....
	 */
	p = mmap((void *)pagesize, size, PROT_WRITE|PROT_READ,
	    MAP_ALIGN|MAP_PRIVATE|MAP_ANON, -1, 0);

	if (p == MAP_FAILED) {
		char	code;
		size_t	out;

		format_num(pagesize, &out, &code);
		fprintf(stderr, "mmap(%lu%c,", out, code);

		format_num(size, &out, &code);
		fprintf(stderr, " %lu%c, ...)", out, code);

		perror("failed");
		exit(1);
	}

	return (p);
}


/*
 *
 * Program to benchmark memory system performance for
 * accessing large amounts of memory.
 *
 * Usage: test <size in bytes>
 *
 * sizes are expressed as d, dK, dM, or dG
 * where K=kilobytes, M=megabyte, etc.
 */
int
main (int argc, char **argv)
{
	hrtime_t	start, end;
	unsigned int	i;
	unsigned int	iterations = 1;
	size_t		pagesize = getpagesize();
	size_t		size = 1024;
	longlong_t 	j;
	longlong_t	k;
	char		*table;
	volatile int	value;
	int		c;
	int		lgrp = -1;
	int		verbose = 0;
	int		delta = 1;
	int		normalize = 0;
	size_t		count;
	size_t		count_requested = 0;
	double		normalized;

	while ((c = getopt( argc, argv, "Nhvc:d:l:s:n:")) != EOF) {
		switch (c) {
		case 'l':
			lgrp = atoi(optarg);
			break;
		case 'n':
			iterations = parse_num(optarg);
			break;
		case 's':
			size = parse_num(optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case 'd':
			delta = atoi(optarg);
			break;
		case 'c':
			count_requested = parse_num(optarg);
			break;
		case 'N':
			normalize = 1;
			break;
		case 'h':
		default:
			usage(basename(argv[0]));
			break;
		}
	}

	if (ABS(delta) >= size) {
		fprintf(stderr, "delta %llu is larger than size %llu\n",
		    ABS(delta), size);
		exit(1);
	}

	if (lgrp >= 0) {
		if (verbose)
			printf("setting affinity of process %d to lgrp %d\n",
			    getpid(),lgrp);
		lgrp_affinity_set(P_PID,  P_MYID,  lgrp, LGRP_AFF_STRONG);
	}

	count = count_requested ? count_requested : size;

	/*
	 * allocate the memory
	 */

	if (verbose)
		printf("Creating table of %llu bytes\n", size);

	table = create_memory(size, pagesize);

	if (verbose)
		printf("Going to zero table at 0x%p from %lu - 1 down to 0 by %d\n",
		    table, size, delta);

	for (j = size - 1; j >= 0; j--)
		table[j] = (char)0xb;

	if (verbose)
		printf("Going to read table %d times by %d times\n",
		    iterations, count);

	/*
	 * Main loop - this code is the purpose of the program.
	 * go over the table by delta increments reading each value;
	 * count the total time it takes to do the walk.
	 */
	for (i = 0; i < iterations; i++) {
		k = size - 1;
		start = gethrtime();
		for (j = 0; j < count; j++) {
			value = table[k];

			k += delta;
			if (k < 0)
				k = size + k;
			else if (k >= size)
				k = k - size;
		}
		end = gethrtime();
		normalized = (double)(end - start) / count;
		if (verbose) {
			printf("total time: %llu, normalized time: %g\n",
			    end - start, normalized);
		} else if (normalize) {
			printf("%g\n",
			    (double)(end - start) / count);
		} else {
			printf("%llu\n", end - start);
		}
	}
	exit(0);
}
