/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the files COPYING and Copyright.html.  COPYING can be found at the root   *
 * of the source code distribution tree; Copyright.html can be found at the  *
 * root level of an installed copy of the electronic HDF5 document set and   *
 * is linked from the top-level documents page.  It can also be found at     *
 * http://hdf.ncsa.uiuc.edu/HDF5/doc/Copyright.html.  If you do not have     *
 * access to either file, you may request a copy from hdfhelp@ncsa.uiuc.edu. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Programmer:  Robb Matzke <robb@arborea.spizella.com>
 *              Thursday, May 14, 1998
 *
 * Purpose:	Checks the effect of various I/O request sizes and raw data
 *		cache sizes.  Performance depends on the amount of data read
 *		from disk and we use a filter to get that number.
 */

/* See H5private.h for how to include headers */
#undef NDEBUG
#include "hdf5.h"

#ifdef H5_STDC_HEADERS
#   include <assert.h>
#   include <stdio.h>
#   include <stdlib.h>
#   include <string.h>
#endif


#if !defined(H5_HAVE_ATTRIBUTE) || defined __cplusplus
#   undef __attribute__
#   define __attribute__(X) /*void*/
#   define UNUSED /*void*/
#else
#   define UNUSED __attribute__((unused))
#endif

#define FILE_NAME	"chunk.h5"
#define LINESPOINTS	"lines"
#define CH_SIZE		100		/*squared in terms of bytes    	*/
#define DS_SIZE		20		/*squared in terms of chunks	*/
#define FILTER_COUNTER	305
#define READ		0
#define WRITE		1
#define MIN(X,Y)	((X)<(Y)?(X):(Y))
#define MAX(X,Y)	((X)>(Y)?(X):(Y))
#define SQUARE(X)	((X)*(X))

/* The row-major test */
#define RM_CACHE_STRT	25
#define RM_CACHE_END	25
#define RM_CACHE_DELT	5
#define RM_START	0.50
#define RM_END	        5.00
#define RM_DELTA	0.50
#define RM_W0		0.0
#define RM_NRDCC	521

/* Diagonal test */
#define DIAG_CACHE_STRT	25
#define DIAG_CACHE_END	25
#define DIAG_CACHE_DELT	5
#define DIAG_START	0.50
#define DIAG_END	5.00
#define DIAG_DELTA	0.50
/* #define DIAG_W0		0.65 */
/* #define DIAG_NRDCC		521 */

static size_t	nio_g;
static hid_t	fapl_g = -1;

/* Local function prototypes */
static size_t
counter (unsigned UNUSED flags, size_t cd_nelmts,
	 const unsigned *cd_values, size_t nbytes,
	 size_t *buf_size, void **buf);

/* This message derives from H5Z */
const H5Z_class_t H5Z_COUNTER[1] = {{
    H5Z_CLASS_T_VERS,		/* H5Z_class_t version		*/
    FILTER_COUNTER,		/* Filter id number		*/
    1, 1,			/* Encoding and decoding enabled */
    "counter",			/* Filter name for debugging	*/
    NULL,                       /* The "can apply" callback     */
    NULL,                       /* The "set local" callback     */
    counter,			/* The actual filter function	*/
}};


/*-------------------------------------------------------------------------
 * Function:	counter
 *
 * Purpose:	Count number of bytes but don't do anything.
 *
 * Return:	Success:	src_nbytes-1
 *
 *		Failure:	never fails
 *
 * Programmer:	Robb Matzke
 *              Thursday, May 14, 1998
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static size_t
counter (unsigned UNUSED flags, size_t UNUSED cd_nelmts,
	 const unsigned UNUSED *cd_values, size_t nbytes,
	 size_t UNUSED *buf_size, void UNUSED **buf)
{
    nio_g += nbytes;
    return nbytes;
}


/*-------------------------------------------------------------------------
 * Function:	create_dataset
 *
 * Purpose:	Creates a square dataset with square chunks, registers a
 *		stupid compress/uncompress pair for counting I/O, and
 *		initializes the dataset.  The chunk size is in bytes, the
 *		dataset size is in terms of chunks.
 *
 * Return:	void
 *
 * Programmer:	Robb Matzke
 *              Thursday, May 14, 1998
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static void
create_dataset (void)
{
    hid_t	file, space, dcpl, dset;
    hsize_t	size[2];
    signed char	*buf;

    /* The file */
    file = H5Fcreate (FILE_NAME, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_g);

    /* The data space */
    size[0] = size[1] = DS_SIZE * CH_SIZE;
    space = H5Screate_simple (2, size, size);

    /* The storage layout and compression */
    dcpl = H5Pcreate (H5P_DATASET_CREATE);
    size[0] = size[1] = CH_SIZE;
    H5Pset_chunk (dcpl, 2, size);
    H5Zregister (H5Z_COUNTER);
    H5Pset_filter (dcpl, FILTER_COUNTER, 0, 0, NULL);

    /* The dataset */
    dset = H5Dcreate (file, "dset", H5T_NATIVE_SCHAR, space, dcpl);
    assert (dset>=0);

    /* The data */
    buf = calloc (1, SQUARE (DS_SIZE*CH_SIZE));
    H5Dwrite (dset, H5T_NATIVE_SCHAR, H5S_ALL, H5S_ALL, H5P_DEFAULT, buf);
    free (buf);

    /* Close */
    H5Dclose (dset);
    H5Sclose (space);
    H5Pclose (dcpl);
    H5Fclose (file);
}


/*-------------------------------------------------------------------------
 * Function:	test_rowmaj
 *
 * Purpose:	Reads the entire dataset using the specified size-squared
 *		I/O requests in row major order.
 *
 * Return:	Efficiency: data requested divided by data actually read.
 *
 * Programmer:	Robb Matzke
 *              Thursday, May 14, 1998
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static double
test_rowmaj (int op, size_t cache_size, size_t io_size)
{
    hid_t	file, dset, mem_space, file_space;
    signed char	*buf = calloc (1, (size_t)(SQUARE(io_size)));
    hsize_t	i, j, hs_size[2];
    hsize_t	hs_offset[2];
    int		mdc_nelmts;
    size_t	rdcc_nelmts;
    double	w0;

    H5Pget_cache (fapl_g, &mdc_nelmts, &rdcc_nelmts, NULL, &w0);
#ifdef RM_W0
    w0 = RM_W0;
#endif
#ifdef RM_NRDCC
    rdcc_nelmts = RM_NRDCC;
#endif
    H5Pset_cache (fapl_g, mdc_nelmts, rdcc_nelmts,
		  cache_size*SQUARE (CH_SIZE), w0);
    file = H5Fopen (FILE_NAME, H5F_ACC_RDWR, fapl_g);
    dset = H5Dopen (file, "dset");
    file_space = H5Dget_space (dset);
    nio_g = 0;

    for (i=0; i<CH_SIZE*DS_SIZE; i+=io_size) {
#if 0
	fprintf (stderr, "%5d\b\b\b\b\b", (int)i);
	fflush (stderr);
#endif
	for (j=0; j<CH_SIZE*DS_SIZE; j+=io_size) {
	    hs_offset[0] = i;
	    hs_size[0] = MIN (io_size, CH_SIZE*DS_SIZE-i);
	    hs_offset[1] = j;
	    hs_size[1] = MIN (io_size, CH_SIZE*DS_SIZE-j);
	    mem_space = H5Screate_simple (2, hs_size, hs_size);
	    H5Sselect_hyperslab (file_space, H5S_SELECT_SET, hs_offset,
				 NULL, hs_size, NULL);

	    if (READ==op) {
		H5Dread (dset, H5T_NATIVE_SCHAR, mem_space, file_space,
			 H5P_DEFAULT, buf);
	    } else {
		H5Dwrite (dset, H5T_NATIVE_SCHAR, mem_space, file_space,
			  H5P_DEFAULT, buf);
	    }
	    H5Sclose (mem_space);
	}
    }

    free (buf);
    H5Sclose (file_space);
    H5Dclose (dset);
    H5Fclose (file);

    return (double)SQUARE(CH_SIZE*DS_SIZE)/(double)nio_g;
}


/*-------------------------------------------------------------------------
 * Function:	test_diag
 *
 * Purpose:	Reads windows diagonally across the dataset.  Each window is
 *		offset from the previous window by OFFSET in the x and y
 *		directions.  The reading ends after the (k,k) value is read
 *		where k is the maximum index in the dataset.
 *
 * Return:	Efficiency.
 *
 * Programmer:	Robb Matzke
 *              Friday, May 15, 1998
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static double
test_diag (int op, size_t cache_size, size_t io_size, size_t offset)
{
    hid_t	file, dset, mem_space, file_space;
    hsize_t	i, hs_size[2];
    hsize_t	nio = 0;
    hsize_t	hs_offset[2];
    signed char	*buf = calloc (1, (size_t)(SQUARE (io_size)));
    int		mdc_nelmts;
    size_t	rdcc_nelmts;
    double	w0;

    H5Pget_cache (fapl_g, &mdc_nelmts, &rdcc_nelmts, NULL, &w0);
#ifdef DIAG_W0
    w0 = DIAG_W0;
#endif
#ifdef DIAG_NRDCC
    rdcc_nelmts = DIAG_NRDCC;
#endif
    H5Pset_cache (fapl_g, mdc_nelmts, rdcc_nelmts,
		  cache_size*SQUARE (CH_SIZE), w0);
    file = H5Fopen (FILE_NAME, H5F_ACC_RDWR, fapl_g);
    dset = H5Dopen (file, "dset");
    file_space = H5Dget_space (dset);
    nio_g = 0;

    for (i=0, hs_size[0]=io_size; hs_size[0]==io_size; i+=offset) {
	hs_offset[0] = hs_offset[1] = i;
	hs_size[0] = hs_size[1] = MIN (io_size, CH_SIZE*DS_SIZE-i);
	mem_space = H5Screate_simple (2, hs_size, hs_size);
	H5Sselect_hyperslab (file_space, H5S_SELECT_SET, hs_offset, NULL,
			     hs_size, NULL);
	if (READ==op) {
	    H5Dread (dset, H5T_NATIVE_SCHAR, mem_space, file_space,
		     H5P_DEFAULT, buf);
	} else {
	    H5Dwrite (dset, H5T_NATIVE_SCHAR, mem_space, file_space,
		      H5P_DEFAULT, buf);
	}
	H5Sclose (mem_space);
	nio += hs_size[0]*hs_size[1];
	if (i>0) nio -= SQUARE (io_size-offset);
    }

    free (buf);
    H5Sclose (file_space);
    H5Dclose (dset);
    H5Fclose (file);

    /*
     * The extra cast in the following statement is a bug workaround for the
     * Win32 version 5.0 compiler.
     * 1998-11-06 ptl
     */
    return (double)(hssize_t)nio/(hssize_t)nio_g;
}


/*-------------------------------------------------------------------------
 * Function:	main
 *
 * Purpose:	See file prologue.
 *
 * Return:	Success:
 *
 *		Failure:
 *
 * Programmer:	Robb Matzke
 *              Thursday, May 14, 1998
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
main (void)
{
    size_t	io_size;
    double	effic, io_percent;
    FILE	*f, *d;
    size_t	cache_size;
    double	w0;

    /*
     * Create a global file access property list.
     */
    fapl_g = H5Pcreate (H5P_FILE_ACCESS);
    H5Pget_cache (fapl_g, NULL, NULL, NULL, &w0);

    /* Create the file */
    create_dataset ();
    f = fopen ("x-gnuplot", "w");

    printf("Test      %8s %8s %8s\n", "CacheSz", "ChunkSz",  "Effic");
    printf("--------- -------- -------- --------\n");

#if 1
    /*
     * Test row-major reading of the dataset with various sizes of request
     * windows.
     */
    if (RM_CACHE_STRT==RM_CACHE_END) {
	fprintf (f, "set yrange [0:1.2]\n");
	fprintf (f, "set ytics 0, 0.1, 1\n");
	fprintf (f, "set xlabel \"%s\"\n",
		 "Request size as a fraction of chunk size");
	fprintf (f, "set ylabel \"Efficiency\"\n");
	fprintf (f, "set title \"Cache %d chunks, w0=%g, "
		 "Size=(total=%d, chunk=%d)\"\n",
		 RM_CACHE_STRT, w0, DS_SIZE*CH_SIZE, CH_SIZE);
    } else {
	fprintf (f, "set autoscale\n");
	fprintf (f, "set hidden3d\n");
    }

    fprintf (f, "set terminal postscript\nset output \"x-rowmaj-rd.ps\"\n");
    fprintf (f, "%s \"x-rowmaj-rd.dat\" title \"RowMaj-Read\" with %s\n",
	     RM_CACHE_STRT==RM_CACHE_END?"plot":"splot",
	     LINESPOINTS);
    fprintf (f, "set terminal x11\nreplot\n");
    d = fopen ("x-rowmaj-rd.dat", "w");
    for (cache_size=RM_CACHE_STRT;
	 cache_size<=RM_CACHE_END;
	 cache_size+=RM_CACHE_DELT) {
	for (io_percent=RM_START; io_percent<=RM_END; io_percent+=RM_DELTA) {
	    io_size = MAX (1, (size_t)(CH_SIZE*io_percent));
	    printf ("Rowmaj-rd %8d %8.2f", (int)cache_size, io_percent);
	    fflush (stdout);
	    effic = test_rowmaj (READ, cache_size, io_size);
	    printf (" %8.2f\n", effic);
	    if (RM_CACHE_STRT==RM_CACHE_END) {
		fprintf (d, "%g %g\n", io_percent, effic);
	    } else {
		fprintf (d, "%g\n", effic);
	    }
	}
	fprintf (d, "\n");
    }
    fclose (d);
    fprintf (f, "pause -1\n");
#endif

#if 1
    /*
     * Test row-major writing of the dataset with various sizes of request
     * windows.
     */
    if (RM_CACHE_STRT==RM_CACHE_END) {
	fprintf (f, "set yrange [0:1.2]\n");
	fprintf (f, "set ytics 0, 0.1, 1\n");
	fprintf (f, "set xlabel \"%s\"\n",
		 "Request size as a fraction of chunk size");
	fprintf (f, "set ylabel \"Efficiency\"\n");
	fprintf (f, "set title \"Cache %d chunks,w0=%g, "
		 "Size=(total=%d, chunk=%d)\"\n",
		 RM_CACHE_STRT, w0, DS_SIZE*CH_SIZE, CH_SIZE);
    } else {
	fprintf (f, "set autoscale\n");
	fprintf (f, "set hidden3d\n");
    }

    fprintf (f, "set terminal postscript\nset output \"x-rowmaj-wr.ps\"\n");
    fprintf (f, "%s \"x-rowmaj-wr.dat\" title \"RowMaj-Write\" with %s\n",
	     RM_CACHE_STRT==RM_CACHE_END?"plot":"splot",
	     LINESPOINTS);
    fprintf (f, "set terminal x11\nreplot\n");
    d = fopen ("x-rowmaj-wr.dat", "w");
    for (cache_size=RM_CACHE_STRT;
	 cache_size<=RM_CACHE_END;
	 cache_size+=RM_CACHE_DELT) {
	for (io_percent=RM_START; io_percent<=RM_END; io_percent+=RM_DELTA) {
	    io_size = MAX (1, (size_t)(CH_SIZE*io_percent));
	    printf ("Rowmaj-wr %8d %8.2f", (int)cache_size, io_percent);
	    fflush (stdout);
	    effic = test_rowmaj (WRITE, cache_size, io_size);
	    printf (" %8.2f\n", effic);
	    if (RM_CACHE_STRT==RM_CACHE_END) {
		fprintf (d, "%g %g\n", io_percent, effic);
	    } else {
		fprintf (d, "%g\n", effic);
	    }
	}
	fprintf (d, "\n");
    }
    fclose (d);
    fprintf (f, "pause -1\n");
#endif

#if 1
    /*
     * Test diagonal read
     */
    if (DIAG_CACHE_STRT==DIAG_CACHE_END) {
	fprintf (f, "set yrange [0:1.2]\n");
	fprintf (f, "set ytics 0, 0.1, 1\n");
	fprintf (f, "set xlabel \"%s\"\n",
		 "Request size as a fraction of chunk size");
	fprintf (f, "set ylabel \"Efficiency\"\n");
	fprintf (f, "set title \"Cache %d chunks,w0=%g, "
		 "Size=(total=%d, chunk=%d)\"\n",
		 DIAG_CACHE_STRT, w0, DS_SIZE*CH_SIZE, CH_SIZE);
    } else {
	fprintf (f, "set autoscale\n");
	fprintf (f, "set hidden3d\n");
    }
    fprintf (f, "set terminal postscript\nset output \"x-diag-rd.ps\"\n");
    fprintf (f, "%s \"x-diag-rd.dat\" title \"Diag-Read\" with %s\n",
	     DIAG_CACHE_STRT==DIAG_CACHE_END?"plot":"splot", LINESPOINTS);
    fprintf (f, "set terminal x11\nreplot\n");
    d = fopen ("x-diag-rd.dat", "w");
    for (cache_size=DIAG_CACHE_STRT;
	 cache_size<=DIAG_CACHE_END;
	 cache_size+=DIAG_CACHE_DELT) {
	for (io_percent=DIAG_START;
	     io_percent<=DIAG_END;
	     io_percent+=DIAG_DELTA) {
	    io_size = MAX (1, (size_t)(CH_SIZE*io_percent));
	    printf ("Diag-rd   %8d %8.2f", (int)cache_size, io_percent);
	    fflush (stdout);
	    effic = test_diag (READ, cache_size, io_size, MAX (1, io_size/2));
	    printf (" %8.2f\n", effic);
	    if (DIAG_CACHE_STRT==DIAG_CACHE_END) {
		fprintf (d, "%g %g\n", io_percent, effic);
	    } else {
		fprintf (d, "%g\n", effic);
	    }
	}
	fprintf (d, "\n");
    }
    fclose (d);
    fprintf (f, "pause -1\n");
#endif

#if 1
    /*
     * Test diagonal write
     */
    if (DIAG_CACHE_STRT==DIAG_CACHE_END) {
	fprintf (f, "set yrange [0:1.2]\n");
	fprintf (f, "set ytics 0, 0.1, 1\n");
	fprintf (f, "set xlabel \"%s\"\n",
		 "Request size as a fraction of chunk size");
	fprintf (f, "set ylabel \"Efficiency\"\n");
	fprintf (f, "set title \"Cache %d chunks, w0=%g, "
		 "Size=(total=%d, chunk=%d)\"\n",
		 DIAG_CACHE_STRT, w0, DS_SIZE*CH_SIZE, CH_SIZE);
    } else {
	fprintf (f, "set autoscale\n");
	fprintf (f, "set hidden3d\n");
    }
    fprintf (f, "set terminal postscript\nset output \"x-diag-wr.ps\"\n");
    fprintf (f, "%s \"x-diag-wr.dat\" title \"Diag-Write\" with %s\n",
	     DIAG_CACHE_STRT==DIAG_CACHE_END?"plot":"splot", LINESPOINTS);
    fprintf (f, "set terminal x11\nreplot\n");
    d = fopen ("x-diag-wr.dat", "w");
    for (cache_size=DIAG_CACHE_STRT;
	 cache_size<=DIAG_CACHE_END;
	 cache_size+=DIAG_CACHE_DELT) {
	for (io_percent=DIAG_START;
	     io_percent<=DIAG_END;
	     io_percent+=DIAG_DELTA) {
	    io_size = MAX (1, (size_t)(CH_SIZE*io_percent));
	    printf ("Diag-wr   %8d %8.2f", (int)cache_size, io_percent);
	    fflush (stdout);
	    effic = test_diag (WRITE, cache_size, io_size, MAX (1, io_size/2));
	    printf (" %8.2f\n", effic);
	    if (DIAG_CACHE_STRT==DIAG_CACHE_END) {
		fprintf (d, "%g %g\n", io_percent, effic);
	    } else {
		fprintf (d, "%g\n", effic);
	    }
	}
	fprintf (d, "\n");
    }
    fclose (d);
    fprintf (f, "pause -1\n");
#endif


    H5Pclose (fapl_g);
    fclose (f);
    return 0;
}