/*
 * Scheduler speed test for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2007-2014 by Apple Inc.
 * Copyright 1997-2005 by Easy Software Products.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include <cups/string-private.h>
#include <cups/cups.h>
#include <cups/language.h>
#include <cups/debug-private.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>


/*
 * Local functions...
 */

static int	do_test(const char *server, int port,
		        http_encryption_t encryption, int requests,
			const char *opstring, int verbose);
static void	usage(void) _CUPS_NORETURN;


/*
 * 'main()' - Send multiple IPP requests and report on the average response
 *            time.
 */

int
main(int  argc,				/* I - Number of command-line arguments */
     char *argv[])			/* I - Command-line arguments */
{
  int		i;			/* Looping var */
  char		*server,		/* Server to use */
		*ptr;			/* Pointer to port in server */
  int		port;			/* Port to use */
  http_encryption_t encryption;		/* Encryption to use */
  int		requests;		/* Number of requests to send */
  int		children;		/* Number of children to fork */
  int		good_children;		/* Number of children that exited normally */
  int		pid;			/* Child PID */
  int		status;			/* Child status */
  time_t	start,			/* Start time */
		end;			/* End time */
  double	elapsed;		/* Elapsed time */
  int		verbose;		/* Verbosity */
  const char	*opstring;		/* Operation name */


 /*
  * Parse command-line options...
  */

  requests   = 100;
  children   = 5;
  server     = (char *)cupsServer();
  port       = ippPort();
  encryption = HTTP_ENCRYPT_IF_REQUESTED;
  verbose    = 0;
  opstring   = NULL;

  for (i = 1; i < argc; i ++)
    if (argv[i][0] == '-')
    {
      for (ptr = argv[i] + 1; *ptr; ptr ++)
        switch (*ptr)
	{
	  case 'E' : /* Enable encryption */
	      encryption = HTTP_ENCRYPT_REQUIRED;
	      break;

	  case 'c' : /* Number of children */
	      i ++;
	      if (i >= argc)
		usage();

	      children = atoi(argv[i]);
	      break;

          case 'o' : /* Operation */
	      i ++;
	      if (i >= argc)
		usage();

	      opstring = argv[i];
	      break;

          case 'r' : /* Number of requests */
	      i ++;
	      if (i >= argc)
		usage();

	      requests = atoi(argv[i]);
	      break;

          case 'v' : /* Verbose logging */
              verbose ++;
	      break;

          default :
              usage();
	      break;
        }
    }
    else
    {
      server = argv[i];

      if (server[0] != '/' && (ptr = strrchr(server, ':')) != NULL)
      {
        *ptr++ = '\0';
	port   = atoi(ptr);
      }
    }

 /*
  * Then create child processes to act as clients...
  */

  if (children > 0)
  {
    printf("testspeed: Simulating %d clients with %d requests to %s with "
           "%sencryption...\n", children, requests, server,
	   encryption == HTTP_ENCRYPT_IF_REQUESTED ? "no " : "");
  }

  start = time(NULL);

  if (children < 1)
    return (do_test(server, port, encryption, requests, opstring, verbose));
  else if (children == 1)
    good_children = do_test(server, port, encryption, requests, opstring,
                            verbose) ? 0 : 1;
  else
  {
    char	options[255],		/* Command-line options for child */
		reqstr[255],		/* Requests string for child */
		serverstr[255];		/* Server:port string for child */


    snprintf(reqstr, sizeof(reqstr), "%d", requests);

    if (port == 631 || server[0] == '/')
      strlcpy(serverstr, server, sizeof(serverstr));
    else
      snprintf(serverstr, sizeof(serverstr), "%s:%d", server, port);

    strlcpy(options, "-cr", sizeof(options));

    if (encryption == HTTP_ENCRYPT_REQUIRED)
      strlcat(options, "E", sizeof(options));

    if (verbose)
      strlcat(options, "v", sizeof(options));

    for (i = 0; i < children; i ++)
    {
      fflush(stdout);

      if ((pid = fork()) == 0)
      {
       /*
	* Child goes here...
	*/

        if (opstring)
	  execlp(argv[0], argv[0], options, "0", reqstr, "-o", opstring,
	         serverstr, (char *)NULL);
        else
	  execlp(argv[0], argv[0], options, "0", reqstr, serverstr, (char *)NULL);

	exit(errno);
      }
      else if (pid < 0)
      {
	printf("testspeed: Fork failed: %s\n", strerror(errno));
	break;
      }
      else
	printf("testspeed: Started child %d...\n", pid);
    }

   /*
    * Wait for children to finish...
    */

    puts("testspeed: Waiting for children to finish...");

    for (good_children = 0;;)
    {
      pid = wait(&status);

      if (pid < 0 && errno != EINTR)
	break;

      printf("testspeed: Ended child %d (%d)...\n", pid, status / 256);

      if (!status)
        good_children ++;
    }
  }

 /*
  * Compute the total run time...
  */

  if (good_children > 0)
  {
    end     = time(NULL);
    elapsed = end - start;
    i       = good_children * requests;

    printf("testspeed: %dx%d=%d requests in %.1fs (%.3fs/r, %.1fr/s)\n",
	   good_children, requests, i, elapsed, elapsed / i, i / elapsed);
  }

 /*
  * Exit with no errors...
  */

  return (0);
}


/*
 * 'do_test()' - Run a test on a specific host...
 */

static int				/* O - Exit status */
do_test(const char        *server,	/* I - Server to use */
        int               port,		/* I - Port number to use */
        http_encryption_t encryption,	/* I - Encryption to use */
	int               requests,	/* I - Number of requests to send */
	const char        *opstring,	/* I - Operation string */
	int               verbose)	/* I - Verbose output? */
{
  int		i;			/* Looping var */
  http_t	*http;			/* Connection to server */
  ipp_t		*request;		/* IPP Request */
  struct timeval start,			/* Start time */
		end;			/* End time */
  double	reqtime,		/* Time for this request */
		elapsed;		/* Elapsed time */
  int		op;			/* Current operation */
  static ipp_op_t ops[5] =		/* Operations to test... */
		{
		  IPP_PRINT_JOB,
		  CUPS_GET_DEFAULT,
		  CUPS_GET_PRINTERS,
		  CUPS_GET_CLASSES,
		  IPP_GET_JOBS
		};


 /*
  * Connect to the server...
  */

  if ((http = httpConnectEncrypt(server, port, encryption)) == NULL)
  {
    printf("testspeed(%d): unable to connect to server - %s\n", (int)getpid(),
           strerror(errno));
    return (1);
  }

 /*
  * Do multiple requests...
  */

  for (elapsed = 0.0, i = 0; i < requests; i ++)
  {
   /*
    * Build a request which requires the following attributes:
    *
    *    attributes-charset
    *    attributes-natural-language
    *
    * In addition, IPP_GET_JOBS needs a printer-uri attribute.
    */

    if (opstring)
      op = ippOpValue(opstring);
    else
      op = ops[i % (int)(sizeof(ops) / sizeof(ops[0]))];

    request = ippNewRequest(op);

    gettimeofday(&start, NULL);

    if (verbose)
      printf("testspeed(%d): %.6f %s ", (int)getpid(), elapsed,
	     ippOpString(op));

    switch (op)
    {
      case IPP_GET_JOBS :
	  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                       NULL, "ipp://localhost/printers/");

      default :
	  ippDelete(cupsDoRequest(http, request, "/"));
          break;

      case IPP_PRINT_JOB :
	  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                       NULL, "ipp://localhost/printers/test");
	  ippDelete(cupsDoFileRequest(http, request, "/printers/test",
	                              "../data/testprint.ps"));
          break;
    }

    gettimeofday(&end, NULL);

    reqtime = (end.tv_sec - start.tv_sec) +
              0.000001 * (end.tv_usec - start.tv_usec);
    elapsed += reqtime;

    switch (cupsLastError())
    {
      case IPP_OK :
      case IPP_NOT_FOUND :
          if (verbose)
	  {
	    printf("succeeded: %s (%.6f)\n", cupsLastErrorString(), reqtime);
	    fflush(stdout);
	  }
          break;

      default :
          if (!verbose)
	    printf("testspeed(%d): %s ", (int)getpid(),
	           ippOpString(ops[i & 3]));

	  printf("failed: %s\n", cupsLastErrorString());
          httpClose(http);
	  return (1);
    }
  }

  httpClose(http);

  printf("testspeed(%d): %d requests in %.1fs (%.3fs/r, %.1fr/s)\n",
         (int)getpid(), i, elapsed, elapsed / i, i / elapsed);

  return (0);
}


/*
 * 'usage()' - Show program usage...
 */

static void
usage(void)
{
  puts("Usage: testspeed [-c children] [-h] [-o operation] [-r requests] [-v] "
       "[-E] hostname[:port]");
  exit(0);
}
