/*
    httperf -- a tool for measuring web server performance
    Copyright (C) 2000  Hewlett-Packard Company
    Contributed by David Mosberger-Tang <davidm@hpl.hp.com>

    This file is part of httperf, a web server performance measurment
    tool.

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA
*/

/* Similar to wsess but instead of generating fixed bursts, each
   fetched html page is parsed and the embedded objects are fetched in
   a burst.

   This is NOT a high performance workload generator!  Use it only for
   non-performance critical tests.  */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <httperf.h>
#include <call.h>
#include <conn.h>
#include <core.h>
#include <event.h>
#include <rate.h>
#include <session.h>
#include <timer.h>

#define CALL_PRIVATE_DATA(c) \
  ((Call_Private_Data *) ((char *)(c) + call_private_data_offset))
#define SESS_PRIVATE_DATA(c) \
  ((Sess_Private_Data *) ((char *)(c) + sess_private_data_offset))

typedef struct Call_Private_Data
  {
    enum
      {
	P_INITIAL,
	P_HTML,
	P_CMD,		/* we saw a `<' and are scanning for the end of CMD */
	P_DASH_ONE,	/* looking for the first dash of a comment close */
	P_DASH_TWO,	/* looking for the second dash of a comment close */
	P_RANGLE,	/* looking for '>' */
	P_SRC,		/* we're looking for "src" */
	P_DATA,		/* we're looking for "data" */
	P_LQUOTE,	/* we're looking for the left quote of a URI */
	P_NAKED_URI,	/* we're looking for an unquoted URI */
	P_QUOTED_URI	/* we're looking for a quoted URI */
      }
    state;
    int buf_len;
    char buf[1024];
    void *to_free;	/* call queue element to free when done */
  }
Call_Private_Data;

typedef struct Sess_Private_Data
  {
    u_int num_created;		/* # of calls created in this burst */
    u_int num_destroyed;	/* # of calls destroyed in this burst */
    u_int num_reqs_completed;	/* # of user reqs completed */
    Timer *timer;		/* timer for session think time */
    struct uri_list
      {
	struct uri_list *next;
	size_t uri_len;
	char uri[1];		/* really URI_LEN+1 bytes... */
      }
    *uri_list;
  }
Sess_Private_Data;

static size_t sess_private_data_offset;
static size_t call_private_data_offset;

static int num_sessions_generated;
static int num_sessions_destroyed;
static Rate_Generator rg_sess;

static size_t prefix_len;
static char *prefix;

static void
issue_calls (Sess *sess, Sess_Private_Data *priv)
{
  int i, to_create, retval, embedded = 0;
  Call_Private_Data *cpriv;
  struct uri_list *el;
  Call *call;

  /* Mimic browser behavior of fetching html object, then a couple of
     embedded objects: */

  to_create = 1;
  if (priv->num_created > 0)
    {
      to_create = session_max_qlen (sess) - session_current_qlen (sess);
      embedded = 1;
    }

  for (i = 0; i < to_create && (!embedded || priv->uri_list); ++i)
    {
      ++priv->num_created;

      call = call_new ();
      if (!call)
	{
	  sess_failure (sess);
	  return;
	}
      if (embedded)
	{
	  el = priv->uri_list;
	  priv->uri_list = el->next;

	  cpriv = CALL_PRIVATE_DATA (call);
	  cpriv->to_free = el;
	  call_set_uri (call, el->uri, el->uri_len);
	}

      if (verbose > 1)
	printf ("%s: fetching `%s'\n",
		prog_name, (char *)call->req.iov[IE_URI].iov_base);

      retval = session_issue_call (sess, call);
      call_dec_ref (call);
      if (retval < 0)
	return;
    }
}

static void
fetch_uri (Sess *sess, Sess_Private_Data *priv, Call_Private_Data *cpriv,
	   const char *uri, size_t uri_len)
{
  struct uri_list *el;
  int is_relative;
  size_t len;
  char *dst;

  if (strchr (uri, ':'))
    {
      len = strlen (param.server);
      if (strncmp (uri, "http://", 7) == 0
	  && strncmp (uri + 7, param.server, len) == 0
	  && uri[7 + len] == '/')
	{
	  uri += 7 + len;
	  uri_len -= 7 + len;
	}
      else
	{
	  /* Eventually, we may want to create new sessions on the fly,
	     but for now, we simply punt on non-absolute URIs */
	  if (verbose > 1)
	    fprintf (stderr, "%s: ignoring absolute URI `%s'\n",
		     prog_name, uri);
	  return;
	}
    }

  is_relative = (uri[0] != '/');

  /* enqueue the new uri: */
  len = uri_len;
  if (is_relative)
    len += prefix_len;
  el = malloc (sizeof (*el) + len);
  if (!el)
    panic ("%s.fetch_uri: out of memory!\n", prog_name);

  el->uri_len = len;
  dst = el->uri;
  if (is_relative)
    {
      memcpy (el->uri, prefix, prefix_len);
      dst += prefix_len;
    }
  memcpy (dst, uri, uri_len + 1);

  el->next = priv->uri_list;
  priv->uri_list = el;

  issue_calls (sess, priv);
}

static void
user_think_time_expired (Timer *t, Any_Type arg)
{
  Sess *sess = arg.vp;
  Sess_Private_Data *priv;

  assert (object_is_sess (sess));

  priv = SESS_PRIVATE_DATA (sess);
  priv->timer = 0;

  issue_calls (sess, priv);
}

static void
call_recv_hdr (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call_Private_Data *cpriv;
  Sess_Private_Data *priv;
  struct iovec *line;
  Call *call;
  Sess *sess;
  char *hdr;

  assert (et == EV_CALL_RECV_HDR && object_is_call (obj));
  call = (Call *) obj;
  cpriv = CALL_PRIVATE_DATA (call);
  sess = session_get_sess_from_call (call);
  priv = SESS_PRIVATE_DATA (sess);

  line = callarg.vp;
  hdr = line->iov_base;

  switch (tolower (hdr[0]))
    {
    case 'c':
      if (line->iov_len >= 23
	  && strncasecmp (hdr + 1, "ontent-type: text/html", 22) == 0)
	cpriv->state = P_HTML;
      break;

    case 'l':
      if (line->iov_len > 10
	  && strncasecmp (hdr + 1, "ocation: ", 9) == 0)
	fetch_uri (sess, priv, cpriv, hdr + 10, line->iov_len - 10);
      break;
    }

}

static void
call_recv_data (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call_Private_Data *cpriv;
  Sess_Private_Data *priv;
  const char *cp, *end;
  struct iovec *line;
  Sess *sess;
  Call *call;
  int ch;

  assert (et == EV_CALL_RECV_DATA && object_is_call (obj));
  call = (Call *) obj;
  cpriv = CALL_PRIVATE_DATA (call);
  sess = session_get_sess_from_call (call);
  priv = SESS_PRIVATE_DATA (sess);

  if (cpriv->state == P_INITIAL)
    return;	/* not an html object */

  line = callarg.vp;
  cp = line->iov_base;
  end = cp + line->iov_len;
  while (cp < end)
    {
      ch = *cp++;

      switch (cpriv->state)
	{
	case P_INITIAL:
	  break;

	case P_HTML:
	  cpriv->buf_len = 0;
	  if (ch == '<')
	    cpriv->state = P_CMD;
	  break;

	case P_CMD:
	  if (isspace (ch) || ch == '=')
	    {
	      if (cpriv->buf_len > 0)
		{
		  if (DBG > 3)
		    fprintf (stderr, "found command `%.*s'\n",
			     cpriv->buf_len, cpriv->buf);

		  if (cpriv->buf_len == 3
		      && strcmp (cpriv->buf, "!--") == 0)
		    cpriv->state = P_DASH_ONE;
		  else if (cpriv->buf_len == 5
			   && strncasecmp (cpriv->buf, "frame", 5) == 0)
		    cpriv->state = P_SRC;
		  else if (cpriv->buf_len == 6
			   && strncasecmp (cpriv->buf, "iframe", 6) == 0)
		    cpriv->state = P_SRC;
		  else if (cpriv->buf_len == 6
			   && strncasecmp (cpriv->buf, "data", 6) == 0)
		    cpriv->state = P_DATA;
		  else if (cpriv->buf_len == 3
			   && strncasecmp (cpriv->buf, "img", 3) == 0)
		    cpriv->state = P_SRC;
		  cpriv->buf_len = 0;
		}
	      else
		cpriv->state = P_HTML;
	    }
	  else if (ch == '>')
	    cpriv->state = P_HTML;
	  else if (cpriv->buf_len < sizeof (cpriv->buf))
	    cpriv->buf[cpriv->buf_len++] = ch;
	  break;

	case P_DASH_ONE:
	  if (ch == '-')
	    cpriv->state = P_DASH_TWO;
	  break;

	case P_DASH_TWO:
	  cpriv->state = (ch == '-') ? P_RANGLE : P_DASH_ONE;
	  break;

	case P_RANGLE:
	  if (ch == '>')
	    cpriv->state = P_HTML;
	  break;

	case P_SRC:
	  if (ch == '>')
	    cpriv->state = P_HTML;
	  else
	    {
	      cpriv->buf[cpriv->buf_len++] = ch;
	      if (cpriv->buf_len == 4)
		{
		  if (strncasecmp (cpriv->buf, "src=", 4) == 0)
		    {
		      cpriv->state = P_LQUOTE;
		      cpriv->buf_len = 0;
		    }
		  else
		    {
		      memcpy (cpriv->buf, cpriv->buf + 1, 3);
		      cpriv->buf_len = 3;
		    }
		}
	    }
	  break;

	case P_DATA:
	  if (ch == '>')
	    cpriv->state = P_HTML;
	  else
	    {
	      cpriv->buf[cpriv->buf_len++] = ch;
	      if (cpriv->buf_len == 5)
		{
		  if (strncasecmp (cpriv->buf, "data=", 5) == 0)
		    {
		      cpriv->state = P_LQUOTE;
		      cpriv->buf_len = 0;
		    }
		  else
		    {
		      memcpy (cpriv->buf, cpriv->buf + 1, 4);
		      cpriv->buf_len = 4;
		    }
		}
	    }
	  break;

	case P_LQUOTE:
	  if (ch == '"')
	    cpriv->state = P_QUOTED_URI;
	  else if (!isspace (ch))
	    {
	      cpriv->state = P_NAKED_URI;
	      cpriv->buf[cpriv->buf_len++] = ch;
	    }
	  break;

	case P_NAKED_URI:
	case P_QUOTED_URI:
	  if ((cpriv->state == P_QUOTED_URI && ch == '"')
	      || (cpriv->state == P_NAKED_URI && isspace (ch)))
	    {
	      cpriv->buf[cpriv->buf_len] = '\0';
	      fetch_uri (sess, priv, cpriv, cpriv->buf, cpriv->buf_len);
	      cpriv->state = P_HTML;
	      cpriv->buf_len = 0;
	    }
	  else if (cpriv->buf_len < sizeof (cpriv->buf) - 1)
	    cpriv->buf[cpriv->buf_len++] = ch;
	  break;
	}
    }
}

static void
call_destroyed (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call_Private_Data *cpriv;
  Sess_Private_Data *priv;
  Any_Type arg;
  Sess *sess;
  Call *call;

  assert (et == EV_CALL_DESTROYED && object_is_call (obj));

  call = (Call *) obj;
  cpriv = CALL_PRIVATE_DATA (call);
  sess = session_get_sess_from_call (call);
  priv = SESS_PRIVATE_DATA (sess);

  if (cpriv->to_free)
    {
      free (cpriv->to_free);
      cpriv->to_free = 0;
    }
  ++priv->num_destroyed;

  if (sess->failed)
    return;

  if (priv->uri_list)
    /* there are some queued URI's which we may be able to issue now */
    issue_calls (sess, priv);
  else if (priv->num_destroyed >= priv->num_created)
    {
      /* we're done with this burst */
      if (++priv->num_reqs_completed >= param.wsesspage.num_reqs)
	/* we're done with this session */
	sess_dec_ref (sess);
      else 
	{
	  /* schedule the user-think-time timer */
	  priv->num_created = 0;
	  assert (!priv->timer);
	  arg.vp = sess;
	  priv->timer = timer_schedule (user_think_time_expired, arg,
					param.wsesspage.think_time);
	}
    }
}

/* Create a new session.  */
static int
sess_create (Any_Type arg)
{
  Sess_Private_Data *priv;
  Sess *sess;

  if (num_sessions_generated++ >= param.wsesspage.num_sessions)
    return -1;

  sess = sess_new ();
  if (!sess)
    return 1;

  priv = SESS_PRIVATE_DATA (sess);

  issue_calls (sess, SESS_PRIVATE_DATA (sess));
  return 0;
}

static void
sess_destroyed (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Sess_Private_Data *priv;
  Sess *sess;

  assert (et == EV_SESS_DESTROYED && object_is_sess (obj));
  sess = (Sess *) obj;

  priv = SESS_PRIVATE_DATA (sess);
  if (priv->timer)
    {
      timer_cancel (priv->timer);
      priv->timer = 0;
    }

  if (++num_sessions_destroyed >= param.wsesspage.num_sessions)
    core_exit ();
}

static void
init (void)
{
  const char *slash;
  Any_Type arg;

  slash = strrchr (param.uri, '/');
  if (slash)
    prefix_len = (slash + 1) - param.uri;
  else
    panic ("%s: URI specified with --uri must be absolute", prog_name);

  prefix = strdup (param.uri);
  prefix[prefix_len] = '\0';

  session_init ();

  call_private_data_offset = object_expand (OBJ_CALL,
					    sizeof (Call_Private_Data));
  sess_private_data_offset = object_expand (OBJ_SESS,
					    sizeof (Sess_Private_Data));
  rg_sess.rate = &param.rate;
  rg_sess.tick = sess_create;
  rg_sess.arg.l = 0;

  arg.l = 0;
  event_register_handler (EV_CALL_RECV_HDR, call_recv_hdr, arg);
  event_register_handler (EV_CALL_RECV_DATA, call_recv_data, arg);
  event_register_handler (EV_SESS_DESTROYED, sess_destroyed, arg);
  event_register_handler (EV_CALL_DESTROYED, call_destroyed, arg);
}

static void
start (void)
{
  rate_generator_start (&rg_sess, EV_SESS_DESTROYED);
}

Load_Generator wsesspage =
  {
    "creates sessions that fetch html pages and embedded objects",
    init,
    start,
    no_op
  };


syntax highlighted by Code2HTML, v. 0.9.1