/*
    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
*/

/* This statistics collector simply prints the replies received from
   the server.  */

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

#include <httperf.h>
#include <call.h>
#include <conn.h>
#include <event.h>

typedef struct Call_Private_Data
  {
    int done_with_reply_hdr; /* are we done printing reply header? */
    size_t size;	/* number of bytes allocated for "line" buffer */
    size_t len;		/* current length of line buffer */
    char *line;		/* line buffer */
  }
Call_Private_Data;

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

static size_t call_private_data_offset = -1;

static void
flush_print_buf (Call *call, const char *prefix)
{
  Call_Private_Data *priv = CALL_PRIVATE_DATA (call);

  if (priv->len)
    printf ("%s%ld:%.*s\n", prefix, call->id, (int) priv->len, priv->line);
  priv->len = 0;
}

static void
print_buf (Call *call, const char *prefix, const char *buf, int len)
{
  Call_Private_Data *priv = CALL_PRIVATE_DATA (call);
  const char *eol, *end;
  size_t line_len;

  for (end = buf + len; buf < end; buf += line_len)
    {
      line_len = (end - buf);
      eol = strchr (buf, '\n');
      if (eol)
	{
	  /* got a complete line: print it */
	  line_len = eol - buf;
	  printf ("%s%ld:", prefix, call->id);

	  if (priv->len)
	    printf ("%.*s", (int) priv->len, priv->line);
	  priv->len = 0;

	  if (line_len > 0)
	    printf ("%.*s", (int) line_len, buf);
	  putchar ('\n');

	  ++line_len;	/* skip over newline */
	}
      else
	{
	  /* got a partial line: buffer it */
	  if (priv->len + line_len > priv->size)
	    {
	      priv->size = priv->len + line_len;
	      if (priv->line)
		priv->line = realloc (priv->line, priv->size);
	      else
		priv->line = malloc (priv->size);
	      if (!priv->line)
		{
		  fprintf (stderr, "%s.print_buf: Out of memory\n", prog_name);
		  exit (1);
		}
	    }
	  memcpy (priv->line + priv->len, buf, line_len);
	  priv->len += line_len;
	}
    }
}

static void
print_request (Call *call)
{
  size_t hdr_len, h_len, b_len;
  int i, first, end;
  char *hdr;

  first = IE_CONTENT;
  end = IE_CONTENT;

  if ((param.print_request & PRINT_HEADER) != 0)
    first = IE_METHOD;

  if ((param.print_request & PRINT_BODY) != 0)
    end = IE_LEN;

  for (i = first; i < end; ++i)
    {
      hdr = call->req.iov[i].iov_base;
      hdr_len = call->req.iov[i].iov_len;

      if (hdr_len)
	print_buf (call, (i < IE_CONTENT) ? "SH" : "SB", hdr, hdr_len);
    }

  for (h_len = 0, i = IE_METHOD; i < IE_CONTENT; ++i)
    h_len += call->req.iov[i].iov_len;

  for (b_len = 0, i = IE_CONTENT; i < IE_LEN; ++i)
    b_len += call->req.iov[i].iov_len;

  printf ("SS%ld: header %ld content %ld\n",
	  call->id, (long) h_len, (long) b_len);
}

static void
print_reply_hdr (Call *call, const char *buf, int len)
{
  Call_Private_Data *priv = CALL_PRIVATE_DATA (call);
  const char *eoh;

  if (len <= 0 || priv->done_with_reply_hdr)
    return;

  eoh = strstr (buf, "\r\n\r\n");
  if (eoh)
    {
      priv->done_with_reply_hdr = 1;
      eoh += 4;
    }
  else
    {
      /* no CRLFCRLF: non-conforming server */
      eoh = strstr (buf, "\n\n");
      if (eoh)
	{
	  priv->done_with_reply_hdr = 1;
	  eoh += 2;
	}
      else
	eoh = buf + len;
    }
  print_buf (call, "RH", buf, eoh - buf);
}

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

  assert (et == EV_CALL_DESTROYED && object_is_call (obj));
  call = (Call *) obj;
  priv = CALL_PRIVATE_DATA (call);

  if (priv->line)
    free (priv->line);
}

static void
send_raw_data (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call *call;

  assert (et == EV_CALL_SEND_RAW_DATA && object_is_call (obj));
  call = (Call *) obj;

  print_request (call);
}

static void
recv_raw_data (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  struct iovec *iov;
  Call *call;

  assert (et == EV_CALL_RECV_RAW_DATA && object_is_call (obj));
  call = (Call *) obj;
  iov = callarg.vp;

  print_reply_hdr (call, iov->iov_base, iov->iov_len);
}

static void
recv_data (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  struct iovec *iov;
  Call *call;

  assert (et == EV_CALL_RECV_DATA && object_is_call (obj));
  call = (Call *) obj;
  iov = callarg.vp;

  print_buf (call, "RB", iov->iov_base, iov->iov_len);
}

static void
recv_stop (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call *call;

  assert (et == EV_CALL_RECV_STOP && object_is_call (obj));
  call = (Call *) obj;

  flush_print_buf (call, "RB");

  printf ("RS%ld: header %ld content %ld footer %ld\n",
	  call->id, (long) call->reply.header_bytes,
	  (long) call->reply.content_bytes, (long) call->reply.footer_bytes);
}

static void
init (void)
{
  Any_Type arg;

  call_private_data_offset = object_expand (OBJ_CALL,
					    sizeof (Call_Private_Data));
  arg.l = 0;
  if ((param.print_request & (PRINT_HEADER | PRINT_BODY)) != 0)
    event_register_handler (EV_CALL_SEND_RAW_DATA, send_raw_data, arg);
  if ((param.print_reply & PRINT_HEADER) != 0)
    event_register_handler (EV_CALL_RECV_RAW_DATA, recv_raw_data, arg);
  if ((param.print_reply & PRINT_BODY) != 0)
    event_register_handler (EV_CALL_RECV_DATA, recv_data, arg);
  if ((param.print_reply & (PRINT_HEADER | PRINT_BODY)) != 0)
    event_register_handler (EV_CALL_RECV_STOP, recv_stop, arg);
  event_register_handler (EV_CALL_DESTROYED, call_destroyed, arg);
}

Stat_Collector stats_print_reply =
  {
    "Reply printer",
    init,
    no_op,
    no_op,
    no_op
  };


syntax highlighted by Code2HTML, v. 0.9.1