/*
httperf -- a tool for measuring web server performance
Copyright (C) 2000 Hewlett-Packard Company
Contributed by Richard Carter <carter@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
*/
/* Creates sessions at the fixed rate PARAM.RATE. The session descriptions
are read in from a configuration file.
There is currently no tool that translates from standard log
formats to the format accepted by this module.
An example input file follows:
#
# This file specifies the potentially-bursty uri sequence for a number of
# user sessions. The format rules of this file are as follows:
#
# Comment lines start with a '#' as the first character. # anywhere else
# is considered part of the uri.
#
# Lines with only whitespace delimit session definitions (multiple blank
# lines do not generate "null" sessions).
#
# Lines otherwise specify a uri-sequence (1 uri per line). If the
# first character of the line is whitespace (e.g. space or tab), the
# uri is considered to be part of a burst that is sent out after the
# previous non-burst uri.
#
# session 1 definition (this is a comment)
/foo.html
/pict1.gif
/pict2.gif
/foo2.html
/pict3.gif
/pict4.gif
#session 2 definition
/foo3.html
/foo4.html
/pict5.gif
Any comment on this module contact carter@hpl.hp.com. */
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <httperf.h>
#include <conn.h>
#include <core.h>
#include <event.h>
#include <rate.h>
#include <session.h>
#include <timer.h>
/* Maximum number of sessions that can be defined in the configuration
file. */
#define MAX_SESSION_TEMPLATES 1000
#define TRUE (1)
#define FALSE (0)
#define SESS_PRIVATE_DATA(c) \
((Sess_Private_Data *) ((char *)(c) + sess_private_data_offset))
typedef struct req REQ;
struct req
{
REQ *next;
int method;
char *uri;
int uri_len;
char *contents;
int contents_len;
char extra_hdrs[50]; /* plenty for "Content-length: 1234567890" */
int extra_hdrs_len;
};
typedef struct burst BURST;
struct burst
{
BURST *next;
int num_reqs;
Time user_think_time;
REQ *req_list;
};
typedef struct Sess_Private_Data Sess_Private_Data;
struct Sess_Private_Data
{
u_int num_calls_in_this_burst; /* # of calls created for this burst */
u_int num_calls_target; /* total # of calls desired */
u_int num_calls_destroyed; /* # of calls destroyed so far */
Timer *timer; /* timer for session think time */
int total_num_reqs; /* total number of requests in this session */
BURST *current_burst; /* the current burst we're working on */
REQ *current_req; /* the current request we're working on */
};
/* Methods allowed for a request: */
enum
{
HM_DELETE, HM_GET, HM_HEAD, HM_OPTIONS, HM_POST, HM_PUT, HM_TRACE,
HM_LEN
};
static const char *call_method_name[] =
{
"DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE"
};
static size_t sess_private_data_offset;
static int num_sessions_generated;
static int num_sessions_destroyed;
static Rate_Generator rg_sess;
/* This is an array rather than a list because we may want different
httperf clients to start at different places in the sequence of
sessions. */
static int num_templates;
static int next_session_template;
static Sess_Private_Data session_templates[MAX_SESSION_TEMPLATES] =
{
{ 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.wsesslog.num_sessions)
core_exit ();
}
static void
issue_calls (Sess *sess, Sess_Private_Data *priv)
{
int i, to_create, retval, n;
const char *method_str;
Call *call;
REQ *req;
/* Mimic browser behavior of fetching html object, then a couple of
embedded objects: */
to_create = 1;
if (priv->num_calls_in_this_burst > 0)
to_create = priv->current_burst->num_reqs - priv->num_calls_in_this_burst;
n = session_max_qlen (sess) - session_current_qlen (sess);
if (n < to_create)
to_create = n;
priv->num_calls_in_this_burst += to_create;
for (i = 0; i < to_create; ++i)
{
call = call_new ();
if (!call)
{
sess_failure (sess);
return;
}
/* fill in the new call: */
req = priv->current_req;
if (req == NULL)
panic ("%s: internal error, requests ran past end of burst\n",
prog_name);
method_str = call_method_name[req->method];
call_set_method (call, method_str, strlen (method_str));
call_set_uri (call, req->uri, req->uri_len);
if (req->contents_len > 0)
{
/* add "Content-length:" header and contents, if necessary: */
call_append_request_header (call, req->extra_hdrs,
req->extra_hdrs_len);
call_set_contents (call, req->contents, req->contents_len);
}
priv->current_req = req->next;
if (DBG > 0)
fprintf (stderr, "%s: accessing URI `%s'\n", prog_name, req->uri);
retval = session_issue_call (sess, call);
call_dec_ref (call);
if (retval < 0)
return;
}
}
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);
}
/* Create a new session and fill in our private information. */
static int
sess_create (Any_Type arg)
{
Sess_Private_Data *priv, *template;
Sess *sess;
if (num_sessions_generated++ >= param.wsesslog.num_sessions)
return -1;
sess = sess_new ();
template = &session_templates[next_session_template];
if (++next_session_template >= num_templates)
next_session_template = 0;
priv = SESS_PRIVATE_DATA (sess);
priv->current_burst = template->current_burst;
priv->current_req = priv->current_burst->req_list;
priv->total_num_reqs = template->total_num_reqs;
priv->num_calls_target = priv->current_burst->num_reqs;
if (DBG > 0)
fprintf (stderr, "Starting session, first burst_len = %d\n",
priv->num_calls_target);
issue_calls (sess, SESS_PRIVATE_DATA (sess));
return 0;
}
static void
prepare_for_next_burst (Sess *sess, Sess_Private_Data *priv)
{
Time think_time;
Any_Type arg;
if (priv->current_burst != NULL)
{
think_time = priv->current_burst->user_think_time;
/* advance to next burst: */
priv->current_burst = priv->current_burst->next;
if (priv->current_burst != NULL)
{
priv->current_req = priv->current_burst->req_list;
priv->num_calls_in_this_burst = 0;
priv->num_calls_target += priv->current_burst->num_reqs;
assert (!priv->timer);
arg.vp = sess;
priv->timer = timer_schedule (user_think_time_expired,
arg, think_time);
}
}
}
static void
call_destroyed (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
Sess_Private_Data *priv;
Sess *sess;
Call *call;
assert (et == EV_CALL_DESTROYED && object_is_call (obj));
call = (Call *) obj;
sess = session_get_sess_from_call (call);
priv = SESS_PRIVATE_DATA (sess);
++priv->num_calls_destroyed;
if (priv->num_calls_destroyed >= priv->total_num_reqs)
/* we're done with this session */
sess_dec_ref (sess);
else if (priv->num_calls_in_this_burst < priv->current_burst->num_reqs)
issue_calls (sess, priv);
else if (priv->num_calls_destroyed >= priv->num_calls_target)
prepare_for_next_burst (sess, priv);
}
/* Allocates memory for a REQ and assigns values to data members.
This is used during configuration file parsing only. */
static REQ*
new_request (char *uristr)
{
REQ *retptr;
retptr = (REQ *) malloc (sizeof (*retptr));
if (retptr == NULL || uristr == NULL)
panic ("%s: ran out of memory while parsing %s\n",
prog_name, param.wsesslog.file);
memset (retptr, 0, sizeof (*retptr));
retptr->uri = uristr;
retptr->uri_len = strlen (uristr);
retptr->method = HM_GET;
return retptr;
}
/* Like new_request except this is for burst descriptors. */
static BURST*
new_burst (REQ *r)
{
BURST *retptr;
retptr = (BURST *) malloc (sizeof (*retptr));
if (retptr == NULL)
panic ("%s: ran out of memory while parsing %s\n",
prog_name, param.wsesslog.file);
memset (retptr, 0, sizeof (*retptr));
retptr->user_think_time = param.wsesslog.think_time;
retptr->req_list = r;
return retptr;
}
/* Read in session-defining configuration file and create in-memory
data structures from which to assign uri_s to calls. */
static void
parse_config (void)
{
FILE *fp;
int lineno, i, reqnum;
Sess_Private_Data *sptr;
char line[10000]; /* some uri's get pretty long */
char uri[10000]; /* some uri's get pretty long */
char method_str[1000];
char this_arg[10000];
char contents[10000];
double think_time;
int bytes_read;
REQ *reqptr;
BURST *bptr, *current_burst = 0;
char *from, *to, *parsed_so_far;
int ch;
int single_quoted, double_quoted, escaped, done;
fp = fopen (param.wsesslog.file, "r");
if (fp == NULL)
panic ("%s: can't open %s\n", prog_name, param.wsesslog.file);
num_templates = 0;
sptr = &session_templates[0];
for (lineno = 1; fgets (line, sizeof (line), fp); lineno++)
{
if (line[0] == '#')
continue; /* skip over comment lines */
if (sscanf (line,"%s%n", uri, &bytes_read) != 1)
{
/* must be a session-delimiting blank line */
if (sptr->current_req != NULL)
sptr++; /* advance to next session */
continue;
}
/* looks like a request-specifying line */
reqptr = new_request (strdup (uri));
if (sptr->current_req == NULL)
{
num_templates++;
if (num_templates > MAX_SESSION_TEMPLATES)
panic ("%s: too many sessions (%d) specified in %s\n",
prog_name, num_templates, param.wsesslog.file);
current_burst = sptr->current_burst = new_burst (reqptr);
}
else
{
if (!isspace (line[0]))
/* this uri starts a new burst */
current_burst = (current_burst->next = new_burst (reqptr));
else
sptr->current_req->next = reqptr;
}
/* do some common steps for all new requests */
current_burst->num_reqs++;
sptr->total_num_reqs++;
sptr->current_req = reqptr;
/* parse rest of line to specify additional parameters of this
request and burst */
parsed_so_far = line + bytes_read;
while (sscanf (parsed_so_far, " %s%n", this_arg, &bytes_read) == 1)
{
if (sscanf (this_arg, "method=%s", method_str) == 1)
{
for (i = 0; i < HM_LEN; i++)
{
if (!strncmp (method_str,call_method_name[i],
strlen (call_method_name[i])))
{
sptr->current_req->method = i;
break;
}
}
if (i == HM_LEN)
panic ("%s: did not recognize method '%s' in %s\n",
prog_name, method_str, param.wsesslog.file);
}
else if (sscanf (this_arg, "think=%lf", &think_time) == 1)
current_burst->user_think_time = think_time;
else if (sscanf (this_arg, "contents=%s", contents) == 1)
{
/* this is tricky since contents might be a quoted
string with embedded spaces or escaped quotes. We
should parse this carefully from parsed_so_far */
from = strchr (parsed_so_far, '=') + 1;
to = contents;
single_quoted = FALSE;
double_quoted = FALSE;
escaped = FALSE;
done = FALSE;
while ((ch = *from++) != '\0' && !done)
{
if (escaped == TRUE)
{
switch (ch)
{
case 'n':
*to++ = '\n';
break;
case 'r':
*to++ = '\r';
break;
case 't':
*to++ = '\t';
break;
case '\n':
*to++ = '\n';
/* this allows an escaped newline to
continue the parsing to the next line. */
if (fgets(line,sizeof(line),fp) == NULL)
{
lineno++;
panic ("%s: premature EOF seen in '%s'\n",
prog_name, param.wsesslog.file);
}
parsed_so_far = from = line;
break;
default:
*to++ = ch;
break;
}
escaped = FALSE;
}
else if (ch == '"' && double_quoted)
{
double_quoted = FALSE;
}
else if (ch == '\'' && single_quoted)
{
single_quoted = FALSE;
}
else
{
switch (ch)
{
case '\t':
case '\n':
case ' ':
if (single_quoted == FALSE &&
double_quoted == FALSE)
done = TRUE; /* we are done */
else
*to++ = ch;
break;
case '\\': /* backslash */
escaped = TRUE;
break;
case '"': /* double quote */
if (single_quoted)
*to++ = ch;
else
double_quoted = TRUE;
break;
case '\'': /* single quote */
if (double_quoted)
*to++ = ch;
else
single_quoted = TRUE;
break;
default:
*to++ = ch;
break;
}
}
}
*to = '\0';
from--; /* back up 'from' to '\0' or white-space */
bytes_read = from - parsed_so_far;
if ((sptr->current_req->contents_len = strlen (contents)) != 0)
{
sptr->current_req->contents = strdup (contents);
sprintf (sptr->current_req->extra_hdrs,
"Content-length: %d\r\n",
sptr->current_req->contents_len);
sptr->current_req->extra_hdrs_len =
strlen (sptr->current_req->extra_hdrs);
}
}
else
{
/* do not recognize this arg */
panic ("%s: did not recognize arg '%s' in %s\n",
prog_name, this_arg, param.wsesslog.file);
}
parsed_so_far += bytes_read;
}
}
fclose (fp);
if (DBG > 3)
{
fprintf (stderr,"%s: session list follows:\n\n", prog_name);
for (i = 0; i < num_templates; i++)
{
sptr = &session_templates[i];
fprintf (stderr, "#session %d (total_reqs=%d):\n",
i, sptr->total_num_reqs);
for (bptr = sptr->current_burst; bptr; bptr = bptr->next)
{
for (reqptr = bptr->req_list, reqnum = 0;
reqptr;
reqptr = reqptr->next, reqnum++)
{
if (reqnum >= bptr->num_reqs)
panic ("%s: internal error detected in parsing %s\n",
prog_name, param.wsesslog.file);
if (reqnum > 0)
fprintf (stderr, "\t");
fprintf (stderr, "%s", reqptr->uri);
if (reqnum == 0
&& bptr->user_think_time != param.wsesslog.think_time)
fprintf (stderr, " think=%0.2f",
(double) bptr->user_think_time);
if (reqptr->method != HM_GET)
fprintf (stderr," method=%s",
call_method_name[reqptr->method]);
if (reqptr->contents != NULL)
fprintf (stderr, " contents='%s'", reqptr->contents);
fprintf (stderr, "\n");
}
}
fprintf (stderr, "\n");
}
}
}
static void
init (void)
{
Any_Type arg;
parse_config ();
sess_private_data_offset = object_expand (OBJ_SESS,
sizeof (Sess_Private_Data));
rg_sess.rate = ¶m.rate;
rg_sess.tick = sess_create;
rg_sess.arg.l = 0;
arg.l = 0;
event_register_handler (EV_SESS_DESTROYED, sess_destroyed, arg);
event_register_handler (EV_CALL_DESTROYED, call_destroyed, arg);
/* This must come last so the session event handlers are executed
before this module's handlers. */
session_init ();
}
static void
start (void)
{
rate_generator_start (&rg_sess, EV_SESS_DESTROYED);
}
Load_Generator wsesslog =
{
"creates log-based session workload",
init,
start,
no_op
};
syntax highlighted by Code2HTML, v. 0.9.1