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