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