/*
 * Raster benchmark program for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2007-2016 by Apple Inc.
 * Copyright 1997-2006 by Easy Software Products.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include <config.h>
#include <cups/raster.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>


/*
 * Constants...
 */

#define TEST_WIDTH	1024
#define TEST_HEIGHT	1024
#define TEST_PAGES	16
#define TEST_PASSES	20


/*
 * Local functions...
 */

static double	compute_median(double *secs);
static double	get_time(void);
static void	read_test(int fd);
static int	run_read_test(void);
static void	write_test(int fd, cups_mode_t mode);


/*
 * 'main()' - Benchmark the raster read/write functions.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  int		i;			/* Looping var */
  int		ras_fd,			/* File descriptor for read process */
		status;			/* Exit status of read process */
  double	start_secs,		/* Start time */
		write_secs,		/* Write time */
		read_secs,		/* Read time */
		pass_secs[TEST_PASSES];	/* Total test times */
  cups_mode_t	mode;			/* Write mode */


 /*
  * See if we have anything on the command-line...
  */

  if (argc > 2 || (argc == 2 && strcmp(argv[1], "-z")))
  {
    puts("Usage: rasterbench [-z]");
    return (1);
  }

  mode = argc > 1 ? CUPS_RASTER_WRITE_COMPRESSED : CUPS_RASTER_WRITE;

 /*
  * Ignore SIGPIPE...
  */

  signal(SIGPIPE, SIG_IGN);

 /*
  * Run the tests several times to get a good average...
  */

  printf("Test read/write speed of %d pages, %dx%d pixels...\n\n",
         TEST_PAGES, TEST_WIDTH, TEST_HEIGHT);
  for (i = 0; i < TEST_PASSES; i ++)
  {
    printf("PASS %2d: ", i + 1);
    fflush(stdout);

    ras_fd     = run_read_test();
    start_secs = get_time();

    write_test(ras_fd, mode);

    write_secs = get_time();
    printf(" %.3f write,", write_secs - start_secs);
    fflush(stdout);

    close(ras_fd);
    wait(&status);

    read_secs    = get_time();
    pass_secs[i] = read_secs - start_secs;
    printf(" %.3f read, %.3f total\n", read_secs - write_secs, pass_secs[i]);
  }

  printf("\nMedian Total Time: %.3f seconds per document\n",
         compute_median(pass_secs));

  return (0);
}


/*
 * 'compute_median()' - Compute the median time for a test.
 */

static double				/* O - Median time in seconds */
compute_median(double *secs)		/* I - Array of time samples */
{
  int		i, j;			/* Looping vars */
  double	temp;			/* Swap variable */


 /*
  * Sort the array into ascending order using a quick bubble sort...
  */

  for (i = 0; i < (TEST_PASSES - 1); i ++)
    for (j = i + 1; j < TEST_PASSES; j ++)
      if (secs[i] > secs[j])
      {
        temp    = secs[i];
	secs[i] = secs[j];
	secs[j] = temp;
      }

 /*
  * Return the average of the middle two samples...
  */

  return (0.5 * (secs[TEST_PASSES / 2 - 1] + secs[TEST_PASSES / 2]));
}


/*
 * 'get_time()' - Get the current time in seconds.
 */

static double				/* O - Time in seconds */
get_time(void)
{
  struct timeval	curtime;	/* Current time */


  gettimeofday(&curtime, NULL);
  return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
}


/*
 * 'read_test()' - Benchmark the raster read functions.
 */

static void
read_test(int fd)			/* I - File descriptor to read from */
{
  unsigned		y;		/* Looping var */
  cups_raster_t		*r;		/* Raster stream */
  cups_page_header2_t	header;		/* Page header */
  unsigned char		buffer[8 * TEST_WIDTH];
					/* Read buffer */


 /*
  * Test read speed...
  */

  if ((r = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL)
  {
    perror("Unable to create raster input stream");
    return;
  }

  while (cupsRasterReadHeader2(r, &header))
  {
    for (y = 0; y < header.cupsHeight; y ++)
      cupsRasterReadPixels(r, buffer, header.cupsBytesPerLine);
  }

  cupsRasterClose(r);
}


/*
 * 'run_read_test()' - Run the read test as a child process via pipes.
 */

static int				/* O - Standard input of child */
run_read_test(void)
{
  int	ras_pipes[2];			/* Raster data pipes */
  int	pid;				/* Child process ID */


  if (pipe(ras_pipes))
    return (-1);

  if ((pid = fork()) < 0)
  {
   /*
    * Fork error - return -1 on error...
    */

    close(ras_pipes[0]);
    close(ras_pipes[1]);

    return (-1);
  }
  else if (pid == 0)
  {
   /*
    * Child comes here - read data from the input pipe...
    */

    close(ras_pipes[1]);
    read_test(ras_pipes[0]);
    exit(0);
  }
  else
  {
   /*
    * Parent comes here - return the output pipe...
    */

    close(ras_pipes[0]);
    return (ras_pipes[1]);
  }
}


/*
 * 'write_test()' - Benchmark the raster write functions.
 */

static void
write_test(int         fd,		/* I - File descriptor to write to */
           cups_mode_t mode)		/* I - Write mode */
{
  unsigned		page, x, y;	/* Looping vars */
  unsigned		count;		/* Number of bytes to set */
  cups_raster_t		*r;		/* Raster stream */
  cups_page_header2_t	header;		/* Page header */
  unsigned char		data[32][8 * TEST_WIDTH];
					/* Raster data to write */


 /*
  * Create a combination of random data and repeated data to simulate
  * text with some whitespace.
  */

  CUPS_SRAND(time(NULL));

  memset(data, 0, sizeof(data));

  for (y = 0; y < 28; y ++)
  {
    for (x = CUPS_RAND() & 127, count = (CUPS_RAND() & 15) + 1;
         x < sizeof(data[0]);
         x ++, count --)
    {
      if (count <= 0)
      {
	x     += (CUPS_RAND() & 15) + 1;
	count = (CUPS_RAND() & 15) + 1;

        if (x >= sizeof(data[0]))
	  break;
      }

      data[y][x] = (unsigned char)CUPS_RAND();
    }
  }

 /*
  * Test write speed...
  */

  if ((r = cupsRasterOpen(fd, mode)) == NULL)
  {
    perror("Unable to create raster output stream");
    return;
  }

  for (page = 0; page < TEST_PAGES; page ++)
  {
    memset(&header, 0, sizeof(header));
    header.cupsWidth        = TEST_WIDTH;
    header.cupsHeight       = TEST_HEIGHT;
    header.cupsBytesPerLine = TEST_WIDTH;

    if (page & 1)
    {
      header.cupsBytesPerLine *= 4;
      header.cupsColorSpace = CUPS_CSPACE_CMYK;
      header.cupsColorOrder = CUPS_ORDER_CHUNKED;
    }
    else
    {
      header.cupsColorSpace = CUPS_CSPACE_K;
      header.cupsColorOrder = CUPS_ORDER_BANDED;
    }

    if (page & 2)
    {
      header.cupsBytesPerLine *= 2;
      header.cupsBitsPerColor = 16;
      header.cupsBitsPerPixel = (page & 1) ? 64 : 16;
    }
    else
    {
      header.cupsBitsPerColor = 8;
      header.cupsBitsPerPixel = (page & 1) ? 32 : 8;
    }

    cupsRasterWriteHeader2(r, &header);

    for (y = 0; y < TEST_HEIGHT; y ++)
      cupsRasterWritePixels(r, data[y & 31], header.cupsBytesPerLine);
  }

  cupsRasterClose(r);
}
