1 /*
2     httperf -- a tool for measuring web server performance
3     Copyright 2000-2007 Hewlett-Packard Company
4 
5     This file is part of httperf, a web server performance measurment
6     tool.
7 
8     This program is free software; you can redistribute it and/or
9     modify it under the terms of the GNU General Public License as
10     published by the Free Software Foundation; either version 2 of the
11     License, or (at your option) any later version.
12 
13     In addition, as a special exception, the copyright holders give
14     permission to link the code of this work with the OpenSSL project's
15     "OpenSSL" library (or with modified versions of it that use the same
16     license as the "OpenSSL" library), and distribute linked combinations
17     including the two.  You must obey the GNU General Public License in
18     all respects for all of the code used other than "OpenSSL".  If you
19     modify this file, you may extend this exception to your version of the
20     file, but you are not obligated to do so.  If you do not wish to do
21     so, delete this exception statement from your version.
22 
23     This program is distributed in the hope that it will be useful,
24     but WITHOUT ANY WARRANTY; without even the implied warranty of
25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
26     General Public License for more details.
27 
28     You should have received a copy of the GNU General Public License
29     along with this program; if not, write to the Free Software
30     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31     02110-1301, USA
32 */
33 
34 /* A session consists of a number of calls.  Workload generators such
35    as wsess and wsesslog determine when and how to issue calls.  This
36    module is responsible for the actual mechanics of issuing the
37    calls.  This includes creating and managing connections as
38    necessary.  Connection management can be controlled by command-line
39    options --max-piped-calls=Np and --max-connections=Nc.  This module
40    creates up to Nc concurrent connections to dispatch calls.  On each
41    connection, up to Np pipelined requests can be issued.  When no
42    more calls can be issued, this module waits until some of the
43    pending calls complete.
44 
45    Note that HTTP/1.1 allows a server to close a connection pretty
46    much any time it feels like.  This means that a session may fail
47    (be closed) while there pipelined calls are pending.  In such a
48    case, this module takes care of creating a new connection and
49    re-issuing the calls that were pending on the failed connection.
50 
51    A session is considered to fail if:
52 
53    (a) any operation exceeds the timeout parameters, or
54 
55    (b) a connection closes on us before we received at least one
56        reply, or
57 
58    (c) param.failure_status is non-zero and the reply status of a call
59        matches this failure status.
60    */
61 
62 #include "config.h"
63 
64 #include <assert.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 
69 #include <generic_types.h>
70 #include <object.h>
71 #include <timer.h>
72 #include <httperf.h>
73 #include <call.h>
74 #include <conn.h>
75 #include <core.h>
76 #include <localevent.h>
77 #include <sess.h>
78 #include <session.h>
79 
80 #define MAX_CONN		 4	/* max # of connections per session */
81 #define MAX_PIPED		32	/* max # of calls that can be piped */
82 
83 #define SESS_PRIVATE_DATA(c)						\
84   ((Sess_Private_Data *) ((char *)(c) + sess_private_data_offset))
85 
86 #define CONN_PRIVATE_DATA(c)						\
87   ((Conn_Private_Data *) ((char *)(c) + conn_private_data_offset))
88 
89 #define CALL_PRIVATE_DATA(c)						\
90   ((Call_Private_Data *) ((char *)(c) + call_private_data_offset))
91 
92 typedef struct Sess_Private_Data
93   {
94     struct Conn_Info
95       {
96 	Conn *conn;		/* connection or NULL */
97 	u_int is_connected : 1;	/* is connection ready for use? */
98 	u_int is_successful : 1; /* got at least one reply on this conn? */
99 
100 	/* Ring-buffer of pending calls: */
101 	u_int num_pending;	/* # of calls pending */
102 	u_int num_sent;		/* # of calls sent so far */
103 	u_int rd;		/* first pending call */
104 	u_int wr;		/* where to insert next call */
105 	Call *call[MAX_PIPED];
106       }
107     conn_info[MAX_CONN];
108   }
109 Sess_Private_Data;
110 
111 typedef struct Conn_Private_Data
112   {
113     Sess *sess;
114     struct Conn_Info *ci;	/* pointer to relevant conn-info */
115   }
116 Conn_Private_Data;
117 
118 typedef struct Call_Private_Data
119   {
120     Sess *sess;
121   }
122 Call_Private_Data;
123 
124 static size_t sess_private_data_offset = -1;
125 static size_t conn_private_data_offset = -1;
126 static size_t call_private_data_offset = -1;
127 static size_t max_qlen;
128 
129 static void
create_conn(Sess * sess,struct Conn_Info * ci)130 create_conn (Sess *sess, struct Conn_Info *ci)
131 {
132   Conn_Private_Data *cpriv;
133 
134   /* No connection yet (or anymore).  Create a new connection.  Note
135      that CI->CONN is NOT reference-counted.  This is again to avoid
136      introducing recursive dependencies (see also comment regarding
137      member CONN in call.h). */
138   ci->conn = conn_new ();
139   if (!ci->conn)
140     {
141       sess_failure (sess);
142       return;
143     }
144   cpriv = CONN_PRIVATE_DATA (ci->conn);
145   cpriv->sess = sess;
146   cpriv->ci = ci;
147 
148   ci->is_connected = 0;
149   ci->is_successful = 0;
150   ci->num_sent = 0;		/* (re-)send all pending calls */
151 
152 #ifdef HAVE_SSL
153   if (param.ssl_reuse && ci->conn->ssl && sess->ssl)
154     {
155       if (DBG > 0)
156 	fprintf (stderr, "create_conn: reusing SSL session %p\n",
157 		 (void *) sess->ssl);
158       SSL_copy_session_id (ci->conn->ssl, sess->ssl);
159     }
160 #endif
161 
162   if (core_connect (ci->conn) < 0)
163     sess_failure (sess);
164 }
165 
166 static void
send_calls(Sess * sess,struct Conn_Info * ci)167 send_calls (Sess *sess, struct Conn_Info *ci)
168 {
169   u_int rd;
170   int i;
171 
172   if (!ci->conn)
173     {
174       create_conn (sess, ci);
175       return;
176     }
177 
178   if (!ci->is_connected)
179     /* wait until connection is connected (or has failed)  */
180     return;
181 
182   rd = (ci->rd + ci->num_sent) % MAX_PIPED;
183 
184   for (i = ci->num_sent; i < ci->num_pending; ++i)
185     {
186       core_send (ci->conn, ci->call[rd]);
187       ++ci->num_sent;
188       rd = (rd + 1) % MAX_PIPED;
189     }
190 }
191 
192 static void
sess_destroyed(Event_Type et,Object * obj,Any_Type regarg,Any_Type callarg)193 sess_destroyed (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
194 {
195   Sess_Private_Data *priv;
196   struct Conn_Info *ci;
197   Sess *sess;
198   int i, j, rd;
199 
200   assert (et == EV_SESS_DESTROYED && object_is_sess (obj));
201   sess = (Sess *) obj;
202   priv = SESS_PRIVATE_DATA (sess);
203 
204   for (i = 0; i < param.max_conns; ++i)
205     {
206       ci = priv->conn_info + i;
207 
208       if (ci->conn)
209 	core_close (ci->conn);
210 
211       rd = ci->rd;
212       for (j = 0; j < ci->num_pending; ++j)
213 	{
214 	  call_dec_ref (ci->call[rd]);
215 	  rd = (rd + 1) % MAX_PIPED;
216 	}
217     }
218 }
219 
220 static void
conn_connected(Event_Type et,Object * obj,Any_Type regarg,Any_Type callarg)221 conn_connected (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
222 {
223   Conn_Private_Data *cpriv;
224   struct Conn_Info *ci;
225   Sess *sess;
226   Conn *conn;
227 
228   assert (et == EV_CONN_CONNECTED && object_is_conn (obj));
229 
230   conn = (Conn *) obj;
231   cpriv = CONN_PRIVATE_DATA (conn);
232   sess = cpriv->sess;
233   ci = cpriv->ci;
234 
235   ci->is_connected = 1;
236 
237 #ifdef HAVE_SSL
238   if (param.ssl_reuse && !sess->ssl && ci->conn->ssl)
239     {
240       sess->ssl = SSL_dup (ci->conn->ssl);
241       if (DBG > 0)
242 	fprintf (stderr, "create_conn: cached SSL session %p as %p\n",
243 		 (void *) ci->conn->ssl, (void *) sess->ssl);
244     }
245 #endif /* HAVE_SSL */
246 
247   send_calls (sess, ci);
248 }
249 
250 static void
conn_failed(Event_Type et,Object * obj,Any_Type regarg,Any_Type callarg)251 conn_failed (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
252 {
253   Conn_Private_Data *cpriv;
254   struct Conn_Info *ci;
255   Conn *conn;
256   Sess *sess;
257 
258   assert (et == EV_CONN_FAILED && object_is_conn (obj));
259   conn = (Conn *) obj;
260   cpriv = CONN_PRIVATE_DATA (conn);
261   sess = cpriv->sess;
262   ci = cpriv->ci;
263 
264   if (ci->is_successful || param.retry_on_failure)
265     /* try to create a new connection so we can issue the remaining
266        calls. */
267     create_conn (sess, ci);
268   else
269     /* The connection failed before we got even one reply, so declare
270        the session as dead... */
271     sess_failure (cpriv->sess);
272 }
273 
274 static void
conn_timeout(Event_Type et,Object * obj,Any_Type regarg,Any_Type callarg)275 conn_timeout (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
276 {
277   Conn_Private_Data *cpriv;
278   Conn *conn;
279 
280   /* doh, this session is dead now... */
281 
282   assert (et == EV_CONN_TIMEOUT && object_is_conn (obj));
283   conn = (Conn *) obj;
284   cpriv = CONN_PRIVATE_DATA (conn);
285 
286   sess_failure (cpriv->sess);
287 }
288 
289 static void
call_done(Event_Type et,Object * obj,Any_Type regarg,Any_Type callarg)290 call_done (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
291 {
292   Conn_Private_Data *cpriv;
293   struct Conn_Info *ci;
294   Sess *sess;
295   Conn *conn;
296   Call *call;
297 
298   assert (et == EV_CALL_RECV_STOP && object_is_call (obj));
299   call = (Call *) obj;
300   conn = call->conn;
301   cpriv = CONN_PRIVATE_DATA (conn);
302   sess = cpriv->sess;
303   ci = cpriv->ci;
304 
305   ci->is_successful = 1;	/* conn has received at least one reply */
306 
307   /* remove the call from the conn_info structure */
308   assert (ci->call[ci->rd] == call && ci->num_pending > 0 && ci->num_sent > 0);
309   ci->call[ci->rd] = 0;
310   ci->rd = (ci->rd + 1) % MAX_PIPED;
311   --ci->num_pending;
312   --ci->num_sent;
313 
314   /* if the reply status matches the failure status, the session has
315      failed */
316   if (param.failure_status && call->reply.status == param.failure_status)
317     {
318       if (param.retry_on_failure)
319 	session_issue_call (sess, call);
320       else
321 	sess_failure (sess);
322     }
323 
324   call_dec_ref (call);
325 
326   if (param.http_version < 0x10001)
327     {
328       /* Rather than waiting for the connection to close on us, we
329 	 close it pro-actively (this is what a pre-1.1 browser would
330 	 do.  */
331       core_close (ci->conn);
332       ci->conn = 0;
333     }
334 }
335 
336 void
session_init(void)337 session_init (void)
338 {
339   Any_Type arg;
340 
341   if (!param.max_conns)
342     param.max_conns = MAX_CONN;
343 
344   if (!param.max_piped)
345     {
346       if (param.http_version >= 0x10001)
347 	param.max_piped = MAX_PIPED;
348       else
349 	/* no pipelining before HTTP/1.1... */
350 	param.max_piped = 1;
351     }
352 
353   if (param.max_conns > MAX_CONN)
354     {
355       fprintf (stderr, "%s.session_init: --max-conns must be <= %u\n",
356 	       prog_name, MAX_CONN);
357       exit (1);
358     }
359   if (param.max_piped > MAX_PIPED)
360     {
361       fprintf (stderr, "%s.session_init: --max-piped-calls must be <= %u\n",
362 	       prog_name, MAX_PIPED);
363       exit (1);
364     }
365 
366   max_qlen = param.max_conns * param.max_piped;
367 
368   sess_private_data_offset = object_expand (OBJ_SESS,
369 					    sizeof (Sess_Private_Data));
370   conn_private_data_offset = object_expand (OBJ_CONN,
371 					    sizeof (Conn_Private_Data));
372   call_private_data_offset = object_expand (OBJ_CALL,
373 					    sizeof (Call_Private_Data));
374 
375   arg.l = 0;
376   event_register_handler (EV_SESS_DESTROYED, sess_destroyed, arg);
377 
378   event_register_handler (EV_CONN_CONNECTED, conn_connected, arg);
379   event_register_handler (EV_CONN_FAILED, conn_failed, arg);
380   event_register_handler (EV_CONN_TIMEOUT, conn_timeout, arg);
381 
382   event_register_handler (EV_CALL_RECV_STOP, call_done, arg);
383 }
384 
385 size_t
session_max_qlen(Sess * sess)386 session_max_qlen (Sess *sess)
387 {
388   return max_qlen;
389 }
390 
391 size_t
session_current_qlen(Sess * sess)392 session_current_qlen (Sess *sess)
393 {
394   Sess_Private_Data *priv;
395   size_t num_pending = 0;
396   int i;
397 
398   priv = SESS_PRIVATE_DATA (sess);
399 
400   for (i = 0; i < param.max_conns; ++i)
401     num_pending += priv->conn_info[i].num_pending;
402 
403   return num_pending;
404 }
405 
406 int
session_issue_call(Sess * sess,Call * call)407 session_issue_call (Sess *sess, Call *call)
408 {
409   Call_Private_Data *cpriv;
410   Sess_Private_Data *priv;
411   struct Conn_Info *ci;
412   int i;
413 
414   priv = SESS_PRIVATE_DATA (sess);
415 
416   cpriv = CALL_PRIVATE_DATA (call);
417   cpriv->sess = sess;
418 
419   for (i = 0; i < param.max_conns; ++i)
420     {
421       ci = priv->conn_info + i;
422       if (ci->num_pending < param.max_piped)
423 	{
424 	  ++ci->num_pending;
425 	  ci->call[ci->wr] = call;
426 	  call_inc_ref (call);
427 	  ci->wr = (ci->wr + 1) % MAX_PIPED;
428 	  send_calls (sess, ci);
429 	  return 0;
430 	}
431     }
432   fprintf (stderr, "%s.session_issue_call: too many calls pending!\n"
433 	   "\tIncrease --max-connections and/or --max-piped-calls.\n",
434 	   prog_name);
435   exit (1);
436 }
437 
438 Sess *
session_get_sess_from_conn(Conn * conn)439 session_get_sess_from_conn (Conn *conn)
440 {
441   assert (object_is_conn (conn));
442   return CONN_PRIVATE_DATA (conn)->sess;
443 }
444 
445 Sess *
session_get_sess_from_call(Call * call)446 session_get_sess_from_call (Call *call)
447 {
448   assert (object_is_call (call));
449   return CALL_PRIVATE_DATA (call)->sess;
450 }
451