blob: 6c1f7290ec338e2079afb2be39ff89c171d179c0 [file] [log] [blame]
Benny Prijono514ca6b2006-07-03 01:30:01 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
21/**
22 * \page page_pjsip_perf_c Samples: SIP Performance Benchmark
23 *
24 * <b>pjsip-perf</b> is a complete program to measure the
25 * performance of PJSIP or other SIP endpoints. It consists of two
26 * parts:
27 * - the server, to respond incoming requests, and
28 * - the client, who actively submits requests and measure the
29 * performance of the server.
30 *
31 * Both server and client part can run simultaneously, to measure the
32 * performance when both endpoints are co-located in a single program.
33 *
34 * The server accepts both INVITE and non-INVITE requests.
35 * The server exports several different types of URL, which would
36 * control how the request would be handled by the server:
37 * - URL with "0" as the user part will be handled statelessly.
38 * It should not be used with INVITE method.
39 * - URL with "1" as the user part will be handled statefully.
40 * If the request is an INVITE request, INVITE transaction will
41 * be created and 200/OK response will be sent, along with a valid
42 * SDP body. However, the SDP is just a static text body, and
43 * is not a proper SDP generated by PJMEDIA.
44 * - URL with "2" as the user part is only meaningful for INVITE
45 * requests, as it would be handled <b>call-statefully</b> by the
46 * server. For this URL, the server also would generate SDP dynamically
47 * and perform a proper SDP negotiation for the incoming call.
48 * Also for every call, server will limit the call duration to
49 * 10 seconds, on which the call will be terminated if the client
50 * doesn't hangup the call.
51 *
52 *
53 *
54 * This file is pjsip-apps/src/samples/pjsip-perf.c
55 *
56 * \includelineno pjsip-perf.c
57 */
58
59/* Include all headers. */
60#include <pjsip.h>
61#include <pjmedia.h>
62#include <pjmedia-codec.h>
63#include <pjsip_ua.h>
64#include <pjsip_simple.h>
65#include <pjlib-util.h>
66#include <pjlib.h>
67#include <stdio.h>
68
Benny Prijono1479b652006-07-03 14:18:17 +000069#if defined(PJ_WIN32) && PJ_WIN32!=0
70# include <windows.h>
71#endif
72
73#define THIS_FILE "pjsip-perf.c"
74#define DEFAULT_COUNT (PJSIP_MAX_TSX_COUNT/2>10000?10000:PJSIP_MAX_TSX_COUNT/2)
Benny Prijonoc3573762006-07-10 21:39:24 +000075#define JOB_WINDOW 1000
Benny Prijono1479b652006-07-03 14:18:17 +000076#define TERMINATE_TSX(x,c)
77
78
79#ifndef CACHING_POOL_SIZE
80# define CACHING_POOL_SIZE (256*1024*1024)
81#endif
Benny Prijono514ca6b2006-07-03 01:30:01 +000082
83
84/* Static message body for INVITE, when stateful processing is
85 * invoked (instead of call-stateful, where SDP is generated
86 * dynamically.
87 */
88static pj_str_t dummy_sdp_str =
89{
90 "v=0\r\n"
91 "o=- 3360842071 3360842071 IN IP4 192.168.0.68\r\n"
92 "s=pjmedia\r\n"
93 "c=IN IP4 192.168.0.68\r\n"
94 "t=0 0\r\n"
95 "m=audio 4000 RTP/AVP 103 102 3 0 8 101\r\n"
96 "a=rtcp:4001 IN IP4 192.168.0.68\r\n"
97 "a=rtpmap:103 speex/16000\r\n"
98 "a=rtpmap:102 speex/8000\r\n"
99 "a=rtpmap:3 GSM/8000\r\n"
100 "a=rtpmap:0 PCMU/8000\r\n"
101 "a=rtpmap:8 PCMA/8000\r\n"
102 "a=sendrecv\r\n"
103 "a=rtpmap:101 telephone-event/8000\r\n"
104 "a=fmtp:101 0-15\r\n",
105 0
106};
107
108static pj_str_t mime_application = { "application", 11};
109static pj_str_t mime_sdp = {"sdp", 3};
110
111
112struct srv_state
113{
114 unsigned stateless_cnt;
115 unsigned stateful_cnt;
116 unsigned call_cnt;
117};
118
119
120struct app
121{
122 pj_caching_pool cp;
123 pj_pool_t *pool;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000124 pj_bool_t use_tcp;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000125 pj_str_t local_addr;
126 int local_port;
127 pjsip_endpoint *sip_endpt;
128 pjmedia_endpt *med_endpt;
129 pj_str_t local_uri;
130 pj_str_t local_contact;
131 unsigned skinfo_cnt;
132 pjmedia_sock_info skinfo[8];
133
134 pj_bool_t thread_quit;
135 unsigned thread_count;
136 pj_thread_t *thread[16];
137
138 pj_bool_t real_sdp;
139 pjmedia_sdp_session *dummy_sdp;
Benny Prijonoc3573762006-07-10 21:39:24 +0000140
141 int log_level;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000142
143 struct {
144 pjsip_method method;
145 pj_str_t dst_uri;
146 pj_bool_t stateless;
147 unsigned timeout;
148 unsigned job_count,
149 job_submitted,
150 job_finished,
151 job_window;
Benny Prijono1479b652006-07-03 14:18:17 +0000152 unsigned stat_max_window;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000153 pj_time_val first_request;
Benny Prijonoc3573762006-07-10 21:39:24 +0000154 pj_time_val requests_sent;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000155 pj_time_val last_completion;
156 unsigned total_responses;
Benny Prijono49f682a2006-07-11 12:25:45 +0000157 unsigned response_codes[800];
Benny Prijono514ca6b2006-07-03 01:30:01 +0000158 } client;
159
160 struct {
161 struct srv_state prev_state;
162 struct srv_state cur_state;
163 } server;
164
165
166} app;
167
168struct call
169{
170 pjsip_inv_session *inv;
171 pj_timer_entry timer;
172};
173
174
175static void app_perror(const char *sender, const char *title,
176 pj_status_t status)
177{
178 char errmsg[PJ_ERR_MSG_SIZE];
179
180 pj_strerror(status, errmsg, sizeof(errmsg));
181 PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
182}
183
184
185/**************************************************************************
186 * STATELESS SERVER
187 */
188static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata);
189
190/* Module to handle incoming requests statelessly.
191 */
192static pjsip_module mod_stateless_server =
193{
194 NULL, NULL, /* prev, next. */
195 { "mod-stateless-server", 20 }, /* Name. */
196 -1, /* Id */
197 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
198 NULL, /* load() */
199 NULL, /* start() */
200 NULL, /* stop() */
201 NULL, /* unload() */
202 &mod_stateless_on_rx_request, /* on_rx_request() */
203 NULL, /* on_rx_response() */
204 NULL, /* on_tx_request. */
205 NULL, /* on_tx_response() */
206 NULL, /* on_tsx_state() */
207};
208
209
210static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
211{
212 const pj_str_t stateless_user = { "0", 1 };
213 pjsip_uri *uri;
214 pjsip_sip_uri *sip_uri;
215
216 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
217
218 /* Only want to receive SIP scheme */
219 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
220 return PJ_FALSE;
221
222 sip_uri = (pjsip_sip_uri*) uri;
223
224 /* Check for matching user part */
225 if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
226 return PJ_FALSE;
227
228 /*
229 * Yes, this is for us.
230 */
231
232 /* Ignore ACK request */
233 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
234 return PJ_TRUE;
235
236 /*
237 * Respond statelessly with 200/OK.
238 */
239 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
240 NULL, NULL);
241 app.server.cur_state.stateless_cnt++;
242 return PJ_TRUE;
243}
244
245
246/**************************************************************************
247 * STATEFUL SERVER
248 */
249static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata);
250
251/* Module to handle incoming requests statefully.
252 */
253static pjsip_module mod_stateful_server =
254{
255 NULL, NULL, /* prev, next. */
256 { "mod-stateful-server", 19 }, /* Name. */
257 -1, /* Id */
258 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
259 NULL, /* load() */
260 NULL, /* start() */
261 NULL, /* stop() */
262 NULL, /* unload() */
263 &mod_stateful_on_rx_request, /* on_rx_request() */
264 NULL, /* on_rx_response() */
265 NULL, /* on_tx_request. */
266 NULL, /* on_tx_response() */
267 NULL, /* on_tsx_state() */
268};
269
270
271static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
272{
273 const pj_str_t stateful_user = { "1", 1 };
274 pjsip_uri *uri;
275 pjsip_sip_uri *sip_uri;
276
277 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
278
279 /* Only want to receive SIP scheme */
280 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
281 return PJ_FALSE;
282
283 sip_uri = (pjsip_sip_uri*) uri;
284
285 /* Check for matching user part */
286 if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
287 return PJ_FALSE;
288
289 /*
290 * Yes, this is for us.
291 * Respond statefully with 200/OK.
292 */
293 switch (rdata->msg_info.msg->line.req.method.id) {
294 case PJSIP_INVITE_METHOD:
295 {
296 pjsip_msg_body *body;
297
298 if (dummy_sdp_str.slen == 0)
299 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
300
301 body = pjsip_msg_body_create(rdata->tp_info.pool,
302 &mime_application, &mime_sdp,
303 &dummy_sdp_str);
304 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
305 200, NULL, NULL, body, NULL);
306 }
307 break;
308 case PJSIP_ACK_METHOD:
309 return PJ_TRUE;
310 default:
311 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
312 200, NULL, NULL, NULL, NULL);
313 break;
314 }
315
316 app.server.cur_state.stateful_cnt++;
317 return PJ_TRUE;
318}
319
320
321/**************************************************************************
322 * CALL SERVER
323 */
324static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
325
326/* Module to handle incoming requests callly.
327 */
328static pjsip_module mod_call_server =
329{
330 NULL, NULL, /* prev, next. */
331 { "mod-call-server", 15 }, /* Name. */
332 -1, /* Id */
333 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
334 NULL, /* load() */
335 NULL, /* start() */
336 NULL, /* stop() */
337 NULL, /* unload() */
338 &mod_call_on_rx_request, /* on_rx_request() */
339 NULL, /* on_rx_response() */
340 NULL, /* on_tx_request. */
341 NULL, /* on_tx_response() */
342 NULL, /* on_tsx_state() */
343};
344
345
346static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
347{
348 const pj_str_t call_user = { "2", 1 };
349 pjsip_uri *uri;
350 pjsip_sip_uri *sip_uri;
351 struct call *call;
352 pjsip_dialog *dlg;
353 pjmedia_sdp_session *sdp;
354 pjsip_tx_data *tdata;
355 pj_status_t status;
356
357 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
358
359 /* Only want to receive SIP scheme */
360 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
361 return PJ_FALSE;
362
363 sip_uri = (pjsip_sip_uri*) uri;
364
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000365 /* Only want to handle INVITE requests. */
Benny Prijono514ca6b2006-07-03 01:30:01 +0000366 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
367 return PJ_FALSE;
368 }
369
370
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000371 /* Check for matching user part. Incoming requests will be handled
372 * call-statefully if:
373 * - user part is "2", or
374 * - user part is not "0" nor "1" and method is INVITE.
375 */
376 if (pj_strcmp(&sip_uri->user, &call_user) == 0 ||
377 sip_uri->user.slen != 1 ||
378 (*sip_uri->user.ptr != '0' && *sip_uri->user.ptr != '1'))
379 {
380 /* Match */
381
382 } else {
383 return PJ_FALSE;
384 }
385
386
Benny Prijono514ca6b2006-07-03 01:30:01 +0000387 /* Verify that we can handle the request. */
388 if (app.real_sdp) {
389 unsigned options = 0;
390 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
391 app.sip_endpt, &tdata);
392 if (status != PJ_SUCCESS) {
393
394 /*
395 * No we can't handle the incoming INVITE request.
396 */
397
398 if (tdata) {
399 pjsip_response_addr res_addr;
400
401 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
402 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
403 NULL, NULL);
404
405 } else {
406
407 /* Respond with 500 (Internal Server Error) */
408 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
409 NULL, NULL);
410 }
411
412 return PJ_TRUE;
413 }
414 }
415
416 /* Create UAS dialog */
417 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
418 &app.local_contact, &dlg);
419 if (status != PJ_SUCCESS) {
420 const pj_str_t reason = pj_str("Unable to create dialog");
421 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
422 500, &reason,
423 NULL, NULL);
424 return PJ_TRUE;
425 }
426
427 /* Alloc call structure. */
428 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
429
430 /* Create SDP from PJMEDIA */
431 if (app.real_sdp) {
432 status = pjmedia_endpt_create_sdp(app.med_endpt, rdata->tp_info.pool,
433 app.skinfo_cnt, app.skinfo,
434 &sdp);
435 } else {
436 sdp = app.dummy_sdp;
437 }
438
439 /* Create UAS invite session */
440 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
441 if (status != PJ_SUCCESS) {
442 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
443 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
444 return PJ_TRUE;
445 }
446
447
448 /* Create 200 response .*/
449 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
450 NULL, NULL, &tdata);
451 if (status != PJ_SUCCESS) {
452 status = pjsip_inv_initial_answer(call->inv, rdata,
453 PJSIP_SC_NOT_ACCEPTABLE,
454 NULL, NULL, &tdata);
455 if (status == PJ_SUCCESS)
456 pjsip_inv_send_msg(call->inv, tdata);
457 else
458 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
459 return PJ_TRUE;
460 }
461
462
463 /* Send the 200 response. */
464 status = pjsip_inv_send_msg(call->inv, tdata);
465 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return PJ_TRUE);
466
467
468 /* Done */
469 app.server.cur_state.call_cnt++;
470
471 return PJ_TRUE;
472}
473
474
475
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000476/**************************************************************************
477 * Default handler when incoming request is not handled by any other
478 * modules.
479 */
480static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata);
481
482/* Module to handle incoming requests statelessly.
483 */
484static pjsip_module mod_responder =
485{
486 NULL, NULL, /* prev, next. */
487 { "mod-responder", 13 }, /* Name. */
488 -1, /* Id */
489 PJSIP_MOD_PRIORITY_APPLICATION+1, /* Priority */
490 NULL, /* load() */
491 NULL, /* start() */
492 NULL, /* stop() */
493 NULL, /* unload() */
494 &mod_responder_on_rx_request, /* on_rx_request() */
495 NULL, /* on_rx_response() */
496 NULL, /* on_tx_request. */
497 NULL, /* on_tx_response() */
498 NULL, /* on_tsx_state() */
499};
500
501
502static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata)
503{
504 const pj_str_t reason = pj_str("Not expecting request at this URI");
505
506 /*
Benny Prijono7db431e2006-07-23 14:38:49 +0000507 * Respond any requests (except ACK!) with 500.
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000508 */
Benny Prijono7db431e2006-07-23 14:38:49 +0000509 if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
510 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, &reason,
511 NULL, NULL);
512 }
513
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000514 return PJ_TRUE;
515}
516
517
Benny Prijono514ca6b2006-07-03 01:30:01 +0000518
519/*****************************************************************************
520 * Below is a simple module to log all incoming and outgoing SIP messages
521 */
522
523
524/* Notification on incoming messages */
525static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
526{
Benny Prijonoc3573762006-07-10 21:39:24 +0000527 PJ_LOG(3,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +0000528 "%.*s\n"
529 "--end msg--",
530 rdata->msg_info.len,
531 pjsip_rx_data_get_info(rdata),
Benny Prijonoc3573762006-07-10 21:39:24 +0000532 rdata->tp_info.transport->type_name,
Benny Prijono514ca6b2006-07-03 01:30:01 +0000533 rdata->pkt_info.src_name,
534 rdata->pkt_info.src_port,
535 (int)rdata->msg_info.len,
536 rdata->msg_info.msg_buf));
537
538 /* Always return false, otherwise messages will not get processed! */
539 return PJ_FALSE;
540}
541
542/* Notification on outgoing messages */
543static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
544{
545
546 /* Important note:
547 * tp_info field is only valid after outgoing messages has passed
548 * transport layer. So don't try to access tp_info when the module
549 * has lower priority than transport layer.
550 */
551
Benny Prijonoc3573762006-07-10 21:39:24 +0000552 PJ_LOG(3,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +0000553 "%.*s\n"
554 "--end msg--",
555 (tdata->buf.cur - tdata->buf.start),
556 pjsip_tx_data_get_info(tdata),
Benny Prijonoc3573762006-07-10 21:39:24 +0000557 tdata->tp_info.transport->type_name,
Benny Prijono514ca6b2006-07-03 01:30:01 +0000558 tdata->tp_info.dst_name,
559 tdata->tp_info.dst_port,
560 (int)(tdata->buf.cur - tdata->buf.start),
561 tdata->buf.start));
562
563 /* Always return success, otherwise message will not get sent! */
564 return PJ_SUCCESS;
565}
566
567/* The module instance. */
568static pjsip_module msg_logger =
569{
570 NULL, NULL, /* prev, next. */
571 { "mod-siprtp-log", 14 }, /* Name. */
572 -1, /* Id */
573 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
574 NULL, /* load() */
575 NULL, /* start() */
576 NULL, /* stop() */
577 NULL, /* unload() */
578 &logger_on_rx_msg, /* on_rx_request() */
579 &logger_on_rx_msg, /* on_rx_response() */
580 &logger_on_tx_msg, /* on_tx_request. */
581 &logger_on_tx_msg, /* on_tx_response() */
582 NULL, /* on_tsx_state() */
583
584};
585
586
587
588/**************************************************************************
589 * Test Client.
590 */
591
592static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata);
593
594static void call_on_media_update( pjsip_inv_session *inv,
595 pj_status_t status);
596static void call_on_state_changed( pjsip_inv_session *inv,
597 pjsip_event *e);
598static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
599
600
601/* Module to handle incoming requests callly.
602 */
603static pjsip_module mod_test =
604{
605 NULL, NULL, /* prev, next. */
606 { "mod-test", 8 }, /* Name. */
607 -1, /* Id */
608 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
609 NULL, /* load() */
610 NULL, /* start() */
611 NULL, /* stop() */
612 NULL, /* unload() */
613 NULL, /* on_rx_request() */
614 &mod_test_on_rx_response, /* on_rx_response() */
615 NULL, /* on_tx_request. */
616 NULL, /* on_tx_response() */
617 NULL, /* on_tsx_state() */
618};
619
620
621static void report_completion(int status_code)
622{
623 app.client.job_finished++;
Benny Prijono49f682a2006-07-11 12:25:45 +0000624 if (status_code >= 200 && status_code < 800)
625 app.client.response_codes[status_code]++;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000626 app.client.total_responses++;
627 pj_gettimeofday(&app.client.last_completion);
628}
629
630
631/* Handler when response is received. */
632static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata)
633{
634 if (pjsip_rdata_get_tsx(rdata) == NULL) {
635 report_completion(rdata->msg_info.msg->line.status.code);
636 }
637
638 return PJ_TRUE;
639}
640
641
642/*
643 * Create app
644 */
645static pj_status_t create_app(void)
646{
647 pj_status_t status;
648
649 status = pj_init();
650 if (status != PJ_SUCCESS) {
651 app_perror(THIS_FILE, "Error initializing pjlib", status);
652 return status;
653 }
654
655 /* init PJLIB-UTIL: */
656 status = pjlib_util_init();
657 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
658
659 /* Must create a pool factory before we can allocate any memory. */
Benny Prijono1479b652006-07-03 14:18:17 +0000660 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy,
661 CACHING_POOL_SIZE);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000662
663 /* Create application pool for misc. */
664 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
665
666 /* Create the endpoint: */
667 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
668 &app.sip_endpt);
669 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
670
671
672 return status;
673}
674
675
676/*
677 * Init SIP stack
678 */
679static pj_status_t init_sip()
680{
681 pj_status_t status;
682
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000683 /* Add UDP/TCP transport. */
Benny Prijono514ca6b2006-07-03 01:30:01 +0000684 {
685 pj_sockaddr_in addr;
686 pjsip_host_port addrname;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000687 const char *transport_type;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000688
Benny Prijonoac623b32006-07-03 15:19:31 +0000689 pj_bzero(&addr, sizeof(addr));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000690 addr.sin_family = PJ_AF_INET;
691 addr.sin_addr.s_addr = 0;
692 addr.sin_port = pj_htons((pj_uint16_t)app.local_port);
693
694 if (app.local_addr.slen) {
695 addrname.host = app.local_addr;
696 addrname.port = 5060;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000697 }
Benny Prijono514ca6b2006-07-03 01:30:01 +0000698 if (app.local_port != 0)
699 addrname.port = app.local_port;
700
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000701 if (app.use_tcp) {
702 pj_sockaddr_in local_addr;
703 pjsip_tpfactory *tpfactory;
704
705 transport_type = "tcp";
Benny Prijono1479b652006-07-03 14:18:17 +0000706 pj_sockaddr_in_init(&local_addr, 0, (pj_uint16_t)app.local_port);
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000707 status = pjsip_tcp_transport_start(app.sip_endpt, &local_addr,
708 app.thread_count, &tpfactory);
709 if (status == PJ_SUCCESS) {
710 app.local_addr = tpfactory->addr_name.host;
711 app.local_port = tpfactory->addr_name.port;
712 }
713 } else {
714 pjsip_transport *tp;
715
716 transport_type = "udp";
717 status = pjsip_udp_transport_start(app.sip_endpt, &addr,
718 (app.local_addr.slen ? &addrname:NULL),
719 app.thread_count, &tp);
720 if (status == PJ_SUCCESS) {
721 app.local_addr = tp->local_name.host;
722 app.local_port = tp->local_name.port;
723 }
724
725 }
Benny Prijono514ca6b2006-07-03 01:30:01 +0000726 if (status != PJ_SUCCESS) {
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000727 app_perror(THIS_FILE, "Unable to start transport", status);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000728 return status;
729 }
730
Benny Prijono514ca6b2006-07-03 01:30:01 +0000731 app.local_uri.ptr = pj_pool_alloc(app.pool, 128);
732 app.local_uri.slen = pj_ansi_sprintf(app.local_uri.ptr,
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000733 "<sip:pjsip-perf@%.*s:%d;transport=%s>",
734 (int)app.local_addr.slen,
735 app.local_addr.ptr,
736 app.local_port,
737 transport_type);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000738
739 app.local_contact = app.local_uri;
740 }
741
742 /*
743 * Init transaction layer.
744 * This will create/initialize transaction hash tables etc.
745 */
746 status = pjsip_tsx_layer_init_module(app.sip_endpt);
747 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
748
749 /* Initialize UA layer. */
750 status = pjsip_ua_init_module( app.sip_endpt, NULL );
751 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
752
753 /* Init invite session module. */
754 {
755 pjsip_inv_callback inv_cb;
756
757 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000758 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000759 inv_cb.on_state_changed = &call_on_state_changed;
760 inv_cb.on_new_session = &call_on_forked;
761 inv_cb.on_media_update = &call_on_media_update;
762
763 /* Initialize invite session module: */
764 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
765 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
766 }
767
768 /* Register our module to receive incoming requests. */
769 status = pjsip_endpt_register_module( app.sip_endpt, &mod_test);
770 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
771
772
773 /* Register stateless server module */
774 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateless_server);
775 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
776
Benny Prijonodcc0cbf2006-07-16 10:40:37 +0000777 /* Register default responder module */
778 status = pjsip_endpt_register_module( app.sip_endpt, &mod_responder);
779 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000780
781 /* Register stateless server module */
782 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateful_server);
783 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
784
785
786 /* Register call server module */
787 status = pjsip_endpt_register_module( app.sip_endpt, &mod_call_server);
788 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
789
790
791 /* Done */
792 return PJ_SUCCESS;
793}
794
795
796/*
797 * Destroy SIP
798 */
799static void destroy_app()
800{
801 unsigned i;
802
803 app.thread_quit = 1;
804 for (i=0; i<app.thread_count; ++i) {
805 if (app.thread[i]) {
806 pj_thread_join(app.thread[i]);
807 pj_thread_destroy(app.thread[i]);
808 app.thread[i] = NULL;
809 }
810 }
811
812 if (app.sip_endpt) {
813 pjsip_endpt_destroy(app.sip_endpt);
814 app.sip_endpt = NULL;
815 }
816
817 if (app.pool) {
818 pj_pool_release(app.pool);
819 app.pool = NULL;
Benny Prijono1ef06df2006-07-09 10:06:44 +0000820 PJ_LOG(3,(THIS_FILE, "Peak memory size: %uMB",
821 app.cp.peak_used_size / 1000000));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000822 pj_caching_pool_destroy(&app.cp);
823 }
824}
825
826
827/*
828 * Init media stack.
829 */
830static pj_status_t init_media()
831{
832 unsigned i;
833 pj_uint16_t rtp_port;
834 pj_status_t status;
835
836
837 /* Initialize media endpoint so that at least error subsystem is properly
838 * initialized.
839 */
840 status = pjmedia_endpt_create(&app.cp.factory,
841 pjsip_endpt_get_ioqueue(app.sip_endpt), 0,
842 &app.med_endpt);
843 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
844
845
846 /* Must register all codecs to be supported */
847 pjmedia_codec_g711_init(app.med_endpt);
848 pjmedia_codec_gsm_init(app.med_endpt);
849 pjmedia_codec_speex_init(app.med_endpt, PJMEDIA_SPEEX_NO_UWB, 3, 3);
850
851
852 /* Init dummy socket addresses */
853 app.skinfo_cnt = 0;
854 for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
855 pjmedia_sock_info *skinfo;
856
857 skinfo = &app.skinfo[i];
858
859 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
860 (pj_uint16_t)rtp_port);
861 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
862 (pj_uint16_t)(rtp_port+1));
863 app.skinfo_cnt++;
864 }
865
866 /* Generate dummy SDP */
867 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
868 status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen,
869 &app.dummy_sdp);
870 if (status != PJ_SUCCESS) {
871 app_perror(THIS_FILE, "Error parsing dummy SDP", status);
872 return status;
873 }
874
875
876 /* Done */
877 return PJ_SUCCESS;
878}
879
880
881/* This is notification from the call about media negotiation
882 * status. This is called for client calls only.
883 */
884static void call_on_media_update( pjsip_inv_session *inv,
885 pj_status_t status)
886{
887 if (status != PJ_SUCCESS) {
888 pjsip_tx_data *tdata;
889 pj_status_t status;
890
891 status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE,
892 NULL, &tdata);
893 if (status == PJ_SUCCESS && tdata)
894 status = pjsip_inv_send_msg(inv, tdata);
895 }
896}
897
898
899/* This is notification from the call when the call state has changed.
900 * This is called for client calls only.
901 */
902static void call_on_state_changed( pjsip_inv_session *inv,
903 pjsip_event *e)
904{
905 PJ_UNUSED_ARG(e);
906
907 /* Bail out if the session has been counted before */
908 if (inv->mod_data[mod_test.id] != NULL)
909 return;
910
911 /* Bail out if this is not an outgoing call */
912 if (inv->role != PJSIP_UAC_ROLE)
913 return;
914
915 if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
916 pjsip_tx_data *tdata;
917 pj_status_t status;
918
919 //report_completion(200);
920 //inv->mod_data[mod_test.id] = (void*)1;
921
922 status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
923 if (status == PJ_SUCCESS && tdata)
924 status = pjsip_inv_send_msg(inv, tdata);
925
926 } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
927 report_completion(inv->cause);
928 inv->mod_data[mod_test.id] = (void*)1;
929 }
930}
931
932
933/* Not implemented for now */
934static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
935{
936 /* Do nothing */
937 PJ_UNUSED_ARG(inv);
938 PJ_UNUSED_ARG(e);
939}
940
941
942/*
943 * Make outgoing call.
944 */
945static pj_status_t make_call(const pj_str_t *dst_uri)
946{
947 struct call *call;
948 pjsip_dialog *dlg;
949 pjmedia_sdp_session *sdp;
950 pjsip_tx_data *tdata;
951 pj_status_t status;
952
953
954 /* Create UAC dialog */
955 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
956 &app.local_uri, /* local URI */
957 &app.local_contact, /* local Contact */
958 dst_uri, /* remote URI */
959 dst_uri, /* remote target */
960 &dlg); /* dialog */
961 if (status != PJ_SUCCESS) {
962 return status;
963 }
964
965 /* Create call */
966 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
967
968 /* Create SDP */
969 if (app.real_sdp) {
970 status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1,
971 app.skinfo, &sdp);
972 if (status != PJ_SUCCESS) {
973 pjsip_dlg_terminate(dlg);
974 return status;
975 }
976 } else
977 sdp = app.dummy_sdp;
978
979 /* Create the INVITE session. */
980 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
981 if (status != PJ_SUCCESS) {
982 pjsip_dlg_terminate(dlg);
983 return status;
984 }
985
986
987 /* Create initial INVITE request.
988 * This INVITE request will contain a perfectly good request and
989 * an SDP body as well.
990 */
991 status = pjsip_inv_invite(call->inv, &tdata);
992 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
993
994
995 /* Send initial INVITE request.
996 * From now on, the invite session's state will be reported to us
997 * via the invite session callbacks.
998 */
999 status = pjsip_inv_send_msg(call->inv, tdata);
1000 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1001
1002
1003 return PJ_SUCCESS;
1004}
1005
1006
1007/*
1008 * Verify that valid SIP url is given.
1009 */
1010static pj_status_t verify_sip_url(const char *c_url)
1011{
1012 pjsip_uri *p;
1013 pj_pool_t *pool;
1014 char *url;
1015 int len = (c_url ? pj_ansi_strlen(c_url) : 0);
1016
1017 if (!len) return -1;
1018
1019 pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
1020 if (!pool) return PJ_ENOMEM;
1021
1022 url = pj_pool_alloc(pool, len+1);
1023 pj_ansi_strcpy(url, c_url);
1024 url[len] = '\0';
1025
1026 p = pjsip_parse_uri(pool, url, len, 0);
1027 if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
1028 p = NULL;
1029
1030 pj_pool_release(pool);
1031 return p ? 0 : -1;
1032}
1033
1034
1035static void usage(void)
1036{
1037 printf(
1038 "Usage:\n"
1039 " pjsip-perf [OPTIONS] -- to start as server\n"
1040 " pjsip-perf [OPTIONS] URL -- to call server (possibly itself)\n"
1041 "\n"
1042 "where:\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001043 " URL The SIP URL to be contacted.\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +00001044 "\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001045 "Client options:\n"
Benny Prijono775a1b22006-07-11 09:53:27 +00001046 " --method=METHOD, -m Set test method (set to INVITE for call benchmark)\n"
1047 " [default: OPTIONS]\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001048 " --count=N, -n Set total number of requests to initiate\n"
1049 " [default=%d]\n"
1050 " --stateless, -s Set to operate in stateless mode\n"
1051 " [default: stateful]\n"
1052 " --timeout=SEC, -t Set client timeout [default=60 sec]\n"
1053 " --window=COUNT, -w Set maximum outstanding job [default: %d]\n"
1054 "\n"
1055 "SDP options (client and server):\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +00001056 " --real-sdp Generate real SDP from pjmedia, and also perform\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001057 " proper SDP negotiation [default: dummy]\n"
1058 "\n"
1059 "Client and Server options:\n"
1060 " --local-port=PORT, -p Set local port [default: 5060]\n"
Benny Prijono775a1b22006-07-11 09:53:27 +00001061 " --use-tcp, -T Use TCP instead of UDP. Note that when started as\n"
1062 " client, you must add ;transport=tcp parameter to URL\n"
1063 " [default: no]\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001064 " --thread-count=N Set number of worker threads [default=1]\n"
1065 "\n"
1066 "Misc options:\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +00001067 " --help, -h Display this screen\n"
Benny Prijono775a1b22006-07-11 09:53:27 +00001068 " --verbose, -v Verbose logging (put more than once for even more)\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +00001069 "\n"
1070 "When started as server, pjsip-perf can be contacted on the following URIs:\n"
Benny Prijonoc3573762006-07-10 21:39:24 +00001071 " - sip:0@server-addr To handle requests statelessly.\n"
1072 " - sip:1@server-addr To handle requests statefully.\n"
1073 " - sip:2@server-addr To handle INVITE call.\n",
Benny Prijono1479b652006-07-03 14:18:17 +00001074 DEFAULT_COUNT, JOB_WINDOW);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001075}
1076
1077
1078static int my_atoi(const char *s)
1079{
1080 pj_str_t ss = pj_str((char*)s);
1081 return pj_strtoul(&ss);
1082}
1083
1084
1085static pj_status_t init_options(int argc, char *argv[])
1086{
1087 enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP };
1088 struct pj_getopt_option long_options[] = {
1089 { "local-port", 1, 0, 'p' },
1090 { "count", 1, 0, 'c' },
1091 { "thread-count", 1, 0, OPT_THREAD_COUNT },
1092 { "method", 1, 0, 'm' },
1093 { "help", 0, 0, 'h' },
1094 { "stateless", 0, 0, 's' },
1095 { "timeout", 1, 0, 't' },
1096 { "real-sdp", 0, 0, OPT_REAL_SDP },
1097 { "verbose", 0, 0, 'v' },
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001098 { "use-tcp", 0, 0, 'T' },
Benny Prijono1479b652006-07-03 14:18:17 +00001099 { "window", 1, 0, 'w' },
Benny Prijono514ca6b2006-07-03 01:30:01 +00001100 { NULL, 0, 0, 0 },
1101 };
1102 int c;
1103 int option_index;
1104
1105 /* Init default application configs */
1106 app.local_port = 5060;
1107 app.thread_count = 1;
1108 app.client.job_count = DEFAULT_COUNT;
1109 app.client.method = pjsip_options_method;
1110 app.client.job_window = c = JOB_WINDOW;
1111 app.client.timeout = 60;
Benny Prijonoc3573762006-07-10 21:39:24 +00001112 app.log_level = 3;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001113
1114
1115 /* Parse options */
1116 pj_optind = 0;
Benny Prijono1479b652006-07-03 14:18:17 +00001117 while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:hsv",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001118 long_options, &option_index))!=-1)
1119 {
1120 switch (c) {
1121 case 'p':
1122 app.local_port = my_atoi(pj_optarg);
1123 if (app.local_port < 0 || app.local_port > 65535) {
1124 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1125 return -1;
1126 }
1127 break;
1128
1129 case 'c':
1130 app.client.job_count = my_atoi(pj_optarg);
1131 if (app.client.job_count < 0) {
1132 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1133 return -1;
1134 }
1135 if (app.client.job_count > PJSIP_MAX_TSX_COUNT)
1136 PJ_LOG(3,(THIS_FILE,
1137 "Warning: --count value (%d) exceeds maximum "
1138 "transaction count (%d)", app.client.job_count,
1139 PJSIP_MAX_TSX_COUNT));
1140 break;
1141
1142 case OPT_THREAD_COUNT:
1143 app.thread_count = my_atoi(pj_optarg);
1144 if (app.thread_count < 1 || app.thread_count > 16) {
1145 PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
1146 return -1;
1147 }
1148 break;
1149
1150 case 'm':
1151 {
1152 pj_str_t temp = pj_str((char*)pj_optarg);
1153 pjsip_method_init_np(&app.client.method, &temp);
1154 }
1155 break;
1156
1157 case 'h':
1158 usage();
1159 return -1;
1160
1161 case 's':
1162 app.client.stateless = PJ_TRUE;
1163 break;
1164
1165 case OPT_REAL_SDP:
1166 app.real_sdp = 1;
1167 break;
1168
1169 case 'v':
Benny Prijonoc3573762006-07-10 21:39:24 +00001170 app.log_level++;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001171 break;
1172
1173 case 't':
1174 app.client.timeout = my_atoi(pj_optarg);
1175 if (app.client.timeout < 0 || app.client.timeout > 600) {
1176 PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
1177 return -1;
1178 }
1179 break;
1180
Benny Prijono1479b652006-07-03 14:18:17 +00001181 case 'w':
1182 app.client.job_window = my_atoi(pj_optarg);
1183 if (app.client.job_window <= 0) {
1184 PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg));
1185 return -1;
1186 }
1187 break;
1188
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001189 case 'T':
1190 app.use_tcp = PJ_TRUE;
1191 break;
1192
Benny Prijono514ca6b2006-07-03 01:30:01 +00001193 default:
1194 PJ_LOG(1,(THIS_FILE,
1195 "Invalid argument. Use --help to see help"));
1196 return -1;
1197 }
1198 }
1199
1200 if (pj_optind != argc) {
1201
1202 if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
1203 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
1204 return -1;
1205 }
1206 app.client.dst_uri = pj_str(argv[pj_optind]);
1207
1208 pj_optind++;
1209
1210 }
1211
1212 if (pj_optind != argc) {
1213 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
1214 return -1;
1215 }
1216
1217 return 0;
1218}
1219
1220
1221/* Send one stateless request */
1222static pj_status_t submit_stateless_job(void)
1223{
1224 pjsip_tx_data *tdata;
1225 pj_status_t status;
1226
1227 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1228 &app.client.dst_uri, &app.local_uri,
1229 &app.client.dst_uri, &app.local_contact,
1230 NULL, -1, NULL, &tdata);
1231 if (status != PJ_SUCCESS) {
1232 app_perror(THIS_FILE, "Error creating request", status);
1233 report_completion(701);
1234 return status;
1235 }
1236
1237 status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
1238 NULL);
1239 if (status != PJ_SUCCESS) {
1240 pjsip_tx_data_dec_ref(tdata);
1241 app_perror(THIS_FILE, "Error sending stateless request", status);
1242 report_completion(701);
1243 return status;
1244 }
1245
1246 return PJ_SUCCESS;
1247}
1248
1249
1250/* This callback is called when client transaction state has changed */
1251static void tsx_completion_cb(void *token, pjsip_event *event)
1252{
1253 pjsip_transaction *tsx;
1254
1255 PJ_UNUSED_ARG(token);
1256
1257 if (event->type != PJSIP_EVENT_TSX_STATE)
1258 return;
1259
1260 tsx = event->body.tsx_state.tsx;
1261
1262 if (tsx->mod_data[mod_test.id] != NULL) {
1263 /* This transaction has been calculated before */
1264 return;
1265 }
1266
1267 if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
1268 report_completion(tsx->status_code);
1269 tsx->mod_data[mod_test.id] = (void*)1;
1270 }
1271 else if (tsx->method.id == PJSIP_INVITE_METHOD &&
1272 tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
1273
1274 report_completion(tsx->status_code);
1275 tsx->mod_data[mod_test.id] = (void*)1;
1276
1277 } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
1278
1279 report_completion(tsx->status_code);
1280 tsx->mod_data[mod_test.id] = (void*)1;
1281
Benny Prijono1479b652006-07-03 14:18:17 +00001282 TERMINATE_TSX(tsx, tsx->status_code);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001283 }
1284}
1285
1286
1287/* Send one stateful request */
1288static pj_status_t submit_job(void)
1289{
1290 pjsip_tx_data *tdata;
1291 pj_status_t status;
1292
1293 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1294 &app.client.dst_uri, &app.local_uri,
1295 &app.client.dst_uri, &app.local_contact,
1296 NULL, -1, NULL, &tdata);
1297 if (status != PJ_SUCCESS) {
1298 app_perror(THIS_FILE, "Error creating request", status);
1299 report_completion(701);
1300 return status;
1301 }
1302
1303 status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL,
1304 &tsx_completion_cb);
1305 if (status != PJ_SUCCESS) {
1306 app_perror(THIS_FILE, "Error sending stateful request", status);
1307 //should have been reported by tsx_completion_cb().
1308 //report_completion(701);
1309 pjsip_tx_data_dec_ref(tdata);
1310 }
1311 return status;
1312}
1313
1314
1315/* Client worker thread */
1316static int client_thread(void *arg)
1317{
Benny Prijono775a1b22006-07-11 09:53:27 +00001318 pj_time_val end_time, last_report, now;
Benny Prijono7db431e2006-07-23 14:38:49 +00001319 unsigned thread_index = (unsigned)(long)arg;
Benny Prijono49f682a2006-07-11 12:25:45 +00001320 unsigned cycle = 0, last_cycle = 0;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001321
1322 pj_thread_sleep(100);
1323
1324 pj_gettimeofday(&end_time);
1325 end_time.sec += app.client.timeout;
1326
Benny Prijono775a1b22006-07-11 09:53:27 +00001327 pj_gettimeofday(&last_report);
1328
Benny Prijono514ca6b2006-07-03 01:30:01 +00001329 if (app.client.first_request.sec == 0) {
1330 pj_gettimeofday(&app.client.first_request);
1331 }
1332
1333 /* Submit all jobs */
Benny Prijono775a1b22006-07-11 09:53:27 +00001334 while (app.client.job_submitted < app.client.job_count && !app.thread_quit){
Benny Prijonoc3573762006-07-10 21:39:24 +00001335 pj_time_val timeout = { 0, 1 };
Benny Prijono514ca6b2006-07-03 01:30:01 +00001336 unsigned i;
1337 int outstanding;
1338 pj_status_t status;
1339
Benny Prijono1479b652006-07-03 14:18:17 +00001340 /* Calculate current outstanding job */
1341 outstanding = app.client.job_submitted - app.client.job_finished;
1342
1343 /* Update stats on max outstanding jobs */
1344 if (outstanding > (int)app.client.stat_max_window)
1345 app.client.stat_max_window = outstanding;
1346
Benny Prijono514ca6b2006-07-03 01:30:01 +00001347 /* Wait if there are more pending jobs than allowed in the
Benny Prijono49f682a2006-07-11 12:25:45 +00001348 * window. But spawn a new job anyway if no events are happening
1349 * after we wait for some time.
Benny Prijono514ca6b2006-07-03 01:30:01 +00001350 */
Benny Prijono49f682a2006-07-11 12:25:45 +00001351 for (i=0; outstanding > (int)app.client.job_window && i<1000; ++i) {
1352 pj_time_val wait = { 0, 500 };
1353 unsigned count = 0;
1354
1355 pjsip_endpt_handle_events2(app.sip_endpt, &wait, &count);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001356 outstanding = app.client.job_submitted - app.client.job_finished;
Benny Prijono49f682a2006-07-11 12:25:45 +00001357
1358 if (count == 0)
1359 break;
1360
1361 ++cycle;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001362 }
1363
Benny Prijono1479b652006-07-03 14:18:17 +00001364
1365 /* Submit one job */
Benny Prijono514ca6b2006-07-03 01:30:01 +00001366 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1367 status = make_call(&app.client.dst_uri);
1368 } else if (app.client.stateless) {
1369 status = submit_stateless_job();
1370 } else {
1371 status = submit_job();
1372 }
1373
1374 ++app.client.job_submitted;
Benny Prijono49f682a2006-07-11 12:25:45 +00001375 ++cycle;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001376
Benny Prijono1479b652006-07-03 14:18:17 +00001377 /* Handle event */
1378 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
1379
Benny Prijono775a1b22006-07-11 09:53:27 +00001380 /* Check for time out, also print report */
Benny Prijono49f682a2006-07-11 12:25:45 +00001381 if (cycle - last_cycle >= 500) {
Benny Prijono1479b652006-07-03 14:18:17 +00001382 pj_gettimeofday(&now);
Benny Prijono775a1b22006-07-11 09:53:27 +00001383 if (PJ_TIME_VAL_GTE(now, end_time)) {
Benny Prijono514ca6b2006-07-03 01:30:01 +00001384 break;
Benny Prijono775a1b22006-07-11 09:53:27 +00001385 }
Benny Prijono49f682a2006-07-11 12:25:45 +00001386 last_cycle = cycle;
Benny Prijono775a1b22006-07-11 09:53:27 +00001387
1388
1389 if (thread_index == 0 && now.sec-last_report.sec >= 2) {
1390 printf("\r%d jobs started, %d completed... ",
1391 app.client.job_submitted, app.client.job_finished);
1392 fflush(stdout);
1393 last_report = now;
1394 }
Benny Prijono514ca6b2006-07-03 01:30:01 +00001395 }
1396 }
1397
Benny Prijonoc3573762006-07-10 21:39:24 +00001398 if (app.client.requests_sent.sec == 0) {
1399 pj_gettimeofday(&app.client.requests_sent);
1400 }
1401
1402
Benny Prijono775a1b22006-07-11 09:53:27 +00001403 if (thread_index == 0) {
1404 printf("\r%d jobs started, %d completed%s\n",
1405 app.client.job_submitted, app.client.job_finished,
1406 (app.client.job_submitted!=app.client.job_finished ?
1407 ", waiting..." : ".") );
1408 fflush(stdout);
1409 }
1410
Benny Prijono514ca6b2006-07-03 01:30:01 +00001411 /* Wait until all jobs completes, or timed out */
Benny Prijonoc3573762006-07-10 21:39:24 +00001412 pj_gettimeofday(&now);
Benny Prijono775a1b22006-07-11 09:53:27 +00001413 while (PJ_TIME_VAL_LT(now, end_time) &&
Benny Prijonoc3573762006-07-10 21:39:24 +00001414 app.client.job_finished < app.client.job_count &&
1415 !app.thread_quit)
1416 {
1417 pj_time_val timeout = { 0, 1 };
Benny Prijono514ca6b2006-07-03 01:30:01 +00001418 unsigned i;
1419
Benny Prijonoc3573762006-07-10 21:39:24 +00001420 for (i=0; i<1000; ++i) {
Benny Prijono775a1b22006-07-11 09:53:27 +00001421 unsigned count;
1422 count = 0;
1423 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
1424 if (count == 0)
1425 break;
1426 }
1427
1428 pj_gettimeofday(&now);
1429 }
1430
Benny Prijono49f682a2006-07-11 12:25:45 +00001431 /* Wait couple of seconds to let jobs completes (e.g. ACKs to be sent) */
Benny Prijono775a1b22006-07-11 09:53:27 +00001432 pj_gettimeofday(&now);
1433 end_time = now;
1434 end_time.sec += 2;
Benny Prijono49f682a2006-07-11 12:25:45 +00001435 while (PJ_TIME_VAL_LT(now, end_time))
Benny Prijono775a1b22006-07-11 09:53:27 +00001436 {
1437 pj_time_val timeout = { 0, 1 };
1438 unsigned i;
1439
1440 for (i=0; i<1000; ++i) {
1441 unsigned count;
1442 count = 0;
1443 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
1444 if (count == 0)
1445 break;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001446 }
1447
1448 pj_gettimeofday(&now);
Benny Prijonoc3573762006-07-10 21:39:24 +00001449 }
Benny Prijono514ca6b2006-07-03 01:30:01 +00001450
1451 return 0;
1452}
1453
1454
1455static const char *good_number(char *buf, pj_int32_t val)
1456{
1457 if (val < 1000) {
1458 pj_ansi_sprintf(buf, "%d", val);
1459 } else if (val < 1000000) {
1460 pj_ansi_sprintf(buf, "%d.%dK",
1461 val / 1000,
1462 (val % 1000) / 100);
1463 } else {
1464 pj_ansi_sprintf(buf, "%d.%02dM",
1465 val / 1000000,
1466 (val % 1000000) / 10000);
1467 }
1468
1469 return buf;
1470}
1471
1472
1473static int server_thread(void *arg)
1474{
Benny Prijonoc3573762006-07-10 21:39:24 +00001475 pj_time_val timeout = { 0, 1 };
Benny Prijono7db431e2006-07-23 14:38:49 +00001476 unsigned thread_index = (unsigned)(long)arg;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001477 pj_time_val last_report, next_report;
1478
1479 pj_gettimeofday(&last_report);
1480 next_report = last_report;
1481 next_report.sec++;
1482
1483 while (!app.thread_quit) {
1484 pj_time_val now;
1485 unsigned i;
1486
1487 for (i=0; i<100; ++i) {
1488 unsigned count = 0;
1489 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
1490 if (count == 0)
1491 break;
1492 }
1493
1494 if (thread_index == 0) {
1495 pj_gettimeofday(&now);
1496
1497 if (PJ_TIME_VAL_GTE(now, next_report)) {
1498 pj_time_val tmp;
1499 unsigned msec;
1500 unsigned stateless, stateful, call;
1501 char str_stateless[32], str_stateful[32], str_call[32];
1502
1503 tmp = now;
1504 PJ_TIME_VAL_SUB(tmp, last_report);
1505 msec = PJ_TIME_VAL_MSEC(tmp);
1506
1507 last_report = now;
1508 next_report = last_report;
1509 next_report.sec++;
1510
1511 stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
1512 stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
1513 call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
1514
1515 good_number(str_stateless, app.server.cur_state.stateless_cnt);
1516 good_number(str_stateful, app.server.cur_state.stateful_cnt);
1517 good_number(str_call, app.server.cur_state.call_cnt);
1518
Benny Prijono1479b652006-07-03 14:18:17 +00001519 printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s) \r",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001520 str_stateless, stateless*1000/msec,
1521 str_stateful, stateful*1000/msec,
1522 str_call, call*1000/msec);
1523 fflush(stdout);
1524
1525 app.server.prev_state = app.server.cur_state;
1526 }
1527 }
1528 }
1529
1530 return 0;
1531}
1532
Benny Prijono1479b652006-07-03 14:18:17 +00001533static void write_report(const char *msg)
1534{
1535 puts(msg);
1536
1537#if defined(PJ_WIN32) && PJ_WIN32!=0
1538 OutputDebugString(msg);
1539 OutputDebugString("\n");
1540#endif
1541}
1542
1543
Benny Prijono514ca6b2006-07-03 01:30:01 +00001544int main(int argc, char *argv[])
1545{
Benny Prijono1479b652006-07-03 14:18:17 +00001546 static char report[1024];
Benny Prijono514ca6b2006-07-03 01:30:01 +00001547
Benny Prijonodcc0cbf2006-07-16 10:40:37 +00001548 printf("PJSIP Performance Measurement Tool v%s\n"
1549 "(c)2006 pjsip.org\n\n",
1550 PJ_VERSION);
Benny Prijono775a1b22006-07-11 09:53:27 +00001551
Benny Prijono514ca6b2006-07-03 01:30:01 +00001552 if (create_app() != 0)
1553 return 1;
1554
1555 if (init_options(argc, argv) != 0)
1556 return 1;
1557
1558 if (init_sip() != 0)
1559 return 1;
1560
1561 if (init_media() != 0)
1562 return 1;
1563
Benny Prijonoc3573762006-07-10 21:39:24 +00001564 pj_log_set_level(app.log_level);
1565
Benny Prijonodcc0cbf2006-07-16 10:40:37 +00001566 if (app.log_level > 4) {
Benny Prijono514ca6b2006-07-03 01:30:01 +00001567 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1568 }
1569
1570
1571 /* Misc infos */
1572 if (app.client.dst_uri.slen != 0) {
Benny Prijonoc3573762006-07-10 21:39:24 +00001573 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1574 if (app.client.stateless) {
1575 PJ_LOG(3,(THIS_FILE,
1576 "Info: --stateless option makes no sense for INVITE,"
1577 " ignored."));
1578 }
Benny Prijono514ca6b2006-07-03 01:30:01 +00001579 }
Benny Prijonoc3573762006-07-10 21:39:24 +00001580
Benny Prijono514ca6b2006-07-03 01:30:01 +00001581 }
1582
Benny Prijono514ca6b2006-07-03 01:30:01 +00001583
1584
1585 if (app.client.dst_uri.slen) {
1586 /* Client mode */
1587 pj_status_t status;
1588 char test_type[64];
Benny Prijonoc3573762006-07-10 21:39:24 +00001589 unsigned msec_req, msec_res;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001590 unsigned i;
1591
1592 /* Get the job name */
1593 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1594 pj_ansi_strcpy(test_type, "INVITE calls");
1595 } else if (app.client.stateless) {
1596 pj_ansi_sprintf(test_type, "stateless %.*s requests",
1597 (int)app.client.method.name.slen,
1598 app.client.method.name.ptr);
1599 } else {
1600 pj_ansi_sprintf(test_type, "stateful %.*s requests",
1601 (int)app.client.method.name.slen,
1602 app.client.method.name.ptr);
1603 }
1604
1605
Benny Prijono775a1b22006-07-11 09:53:27 +00001606 printf("Sending %d %s to '%.*s' with %d maximum outstanding jobs, please wait..\n",
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001607 app.client.job_count, test_type,
Benny Prijonoc3573762006-07-10 21:39:24 +00001608 (int)app.client.dst_uri.slen, app.client.dst_uri.ptr,
Benny Prijono775a1b22006-07-11 09:53:27 +00001609 app.client.job_window);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001610
1611 for (i=0; i<app.thread_count; ++i) {
Benny Prijono7db431e2006-07-23 14:38:49 +00001612 status = pj_thread_create(app.pool, NULL, &client_thread,
1613 (void*)(long)i, 0, 0, &app.thread[i]);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001614 if (status != PJ_SUCCESS) {
1615 app_perror(THIS_FILE, "Unable to create thread", status);
1616 return 1;
1617 }
1618 }
1619
1620 for (i=0; i<app.thread_count; ++i) {
1621 pj_thread_join(app.thread[i]);
1622 app.thread[i] = NULL;
1623 }
1624
1625 if (app.client.last_completion.sec) {
1626 pj_time_val duration;
1627 duration = app.client.last_completion;
1628 PJ_TIME_VAL_SUB(duration, app.client.first_request);
Benny Prijonoc3573762006-07-10 21:39:24 +00001629 msec_res = PJ_TIME_VAL_MSEC(duration);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001630 } else {
Benny Prijonoc3573762006-07-10 21:39:24 +00001631 msec_res = app.client.timeout * 1000;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001632 }
1633
Benny Prijonoc3573762006-07-10 21:39:24 +00001634 if (msec_res == 0) msec_res = 1;
1635
1636 if (app.client.requests_sent.sec) {
1637 pj_time_val duration;
1638 duration = app.client.requests_sent;
1639 PJ_TIME_VAL_SUB(duration, app.client.first_request);
1640 msec_req = PJ_TIME_VAL_MSEC(duration);
1641 } else {
1642 msec_req = app.client.timeout * 1000;
1643 }
1644
1645 if (msec_req == 0) msec_req = 1;
1646
Benny Prijono775a1b22006-07-11 09:53:27 +00001647 if (app.client.job_submitted < app.client.job_count)
1648 puts("\ntimed-out!\n");
1649 else
1650 puts("\ndone.\n");
Benny Prijono514ca6b2006-07-03 01:30:01 +00001651
Benny Prijono1479b652006-07-03 14:18:17 +00001652 pj_ansi_snprintf(
1653 report, sizeof(report),
Benny Prijono775a1b22006-07-11 09:53:27 +00001654 "Total %d %s sent in %d ms at rate of %d/sec\n"
Benny Prijono49f682a2006-07-11 12:25:45 +00001655 "Total %d responses receieved in %d ms at rate of %d/sec:",
Benny Prijonoc3573762006-07-10 21:39:24 +00001656 app.client.job_submitted, test_type, msec_req,
1657 app.client.job_submitted * 1000 / msec_req,
1658 app.client.total_responses, msec_res,
Benny Prijono49f682a2006-07-11 12:25:45 +00001659 app.client.total_responses*1000/msec_res);
1660 write_report(report);
1661
1662 /* Print detailed response code received */
1663 pj_ansi_sprintf(report, "\nDetailed responses received:");
1664 write_report(report);
1665
1666 for (i=0; i<PJ_ARRAY_SIZE(app.client.response_codes); ++i) {
1667 const pj_str_t *reason;
1668
1669 if (app.client.response_codes[i] == 0)
1670 continue;
1671
1672 reason = pjsip_get_status_text(i);
1673 pj_ansi_snprintf( report, sizeof(report),
1674 " - %d responses: %7d (%.*s)",
1675 i, app.client.response_codes[i],
1676 (int)reason->slen, reason->ptr);
1677 write_report(report);
1678 }
1679
1680 /* Total responses and rate */
1681 pj_ansi_snprintf( report, sizeof(report),
1682 " ------\n"
1683 " TOTAL responses: %7d (rate=%d/sec)\n",
1684 app.client.total_responses,
1685 app.client.total_responses*1000/msec_res);
Benny Prijono1479b652006-07-03 14:18:17 +00001686
1687 write_report(report);
1688
1689 pj_ansi_sprintf(report, "Maximum outstanding job: %d",
1690 app.client.stat_max_window);
1691 write_report(report);
1692
Benny Prijono514ca6b2006-07-03 01:30:01 +00001693
1694 } else {
1695 /* Server mode */
1696 char s[10];
1697 pj_status_t status;
1698 unsigned i;
1699
Benny Prijono514ca6b2006-07-03 01:30:01 +00001700 puts("pjsip-perf started in server-mode");
1701
1702 printf("Receiving requests on the following URIs:\n"
Benny Prijono49f682a2006-07-11 12:25:45 +00001703 " sip:0@%.*s:%d%s for stateless handling\n"
1704 " sip:1@%.*s:%d%s for stateful handling\n"
1705 " sip:2@%.*s:%d%s for call handling\n",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001706 (int)app.local_addr.slen,
1707 app.local_addr.ptr,
1708 app.local_port,
Benny Prijono49f682a2006-07-11 12:25:45 +00001709 (app.use_tcp ? ";transport=tcp" : ""),
Benny Prijono514ca6b2006-07-03 01:30:01 +00001710 (int)app.local_addr.slen,
1711 app.local_addr.ptr,
1712 app.local_port,
Benny Prijono49f682a2006-07-11 12:25:45 +00001713 (app.use_tcp ? ";transport=tcp" : ""),
Benny Prijono514ca6b2006-07-03 01:30:01 +00001714 (int)app.local_addr.slen,
1715 app.local_addr.ptr,
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001716 app.local_port,
Benny Prijono49f682a2006-07-11 12:25:45 +00001717 (app.use_tcp ? ";transport=tcp" : ""));
Benny Prijonodcc0cbf2006-07-16 10:40:37 +00001718 printf("INVITE with non-matching user part will be handled call-statefully\n");
Benny Prijono514ca6b2006-07-03 01:30:01 +00001719
1720 for (i=0; i<app.thread_count; ++i) {
Benny Prijono7db431e2006-07-23 14:38:49 +00001721 status = pj_thread_create(app.pool, NULL, &server_thread,
1722 (void*)(long)i, 0, 0, &app.thread[i]);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001723 if (status != PJ_SUCCESS) {
1724 app_perror(THIS_FILE, "Unable to create thread", status);
1725 return 1;
1726 }
1727 }
1728
Benny Prijono49f682a2006-07-11 12:25:45 +00001729 puts("\nPress <ENTER> to quit\n");
Benny Prijono514ca6b2006-07-03 01:30:01 +00001730 fflush(stdout);
1731 fgets(s, sizeof(s), stdin);
1732
1733 app.thread_quit = PJ_TRUE;
1734 for (i=0; i<app.thread_count; ++i) {
1735 pj_thread_join(app.thread[i]);
1736 app.thread[i] = NULL;
1737 }
Benny Prijono49f682a2006-07-11 12:25:45 +00001738
1739 puts("");
Benny Prijono514ca6b2006-07-03 01:30:01 +00001740 }
1741
1742
1743 destroy_app();
1744
1745 return 0;
1746}
1747