blob: 011f6e4929f870383d39e79db2b20c525076e076 [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
69#define THIS_FILE "pjsip-perf.c"
70#define DEFAULT_COUNT (PJSIP_MAX_TSX_COUNT/2>10000?10000:PJSIP_MAX_TSX_COUNT/2)
71#define JOB_WINDOW DEFAULT_COUNT
72
73
74/* Static message body for INVITE, when stateful processing is
75 * invoked (instead of call-stateful, where SDP is generated
76 * dynamically.
77 */
78static pj_str_t dummy_sdp_str =
79{
80 "v=0\r\n"
81 "o=- 3360842071 3360842071 IN IP4 192.168.0.68\r\n"
82 "s=pjmedia\r\n"
83 "c=IN IP4 192.168.0.68\r\n"
84 "t=0 0\r\n"
85 "m=audio 4000 RTP/AVP 103 102 3 0 8 101\r\n"
86 "a=rtcp:4001 IN IP4 192.168.0.68\r\n"
87 "a=rtpmap:103 speex/16000\r\n"
88 "a=rtpmap:102 speex/8000\r\n"
89 "a=rtpmap:3 GSM/8000\r\n"
90 "a=rtpmap:0 PCMU/8000\r\n"
91 "a=rtpmap:8 PCMA/8000\r\n"
92 "a=sendrecv\r\n"
93 "a=rtpmap:101 telephone-event/8000\r\n"
94 "a=fmtp:101 0-15\r\n",
95 0
96};
97
98static pj_str_t mime_application = { "application", 11};
99static pj_str_t mime_sdp = {"sdp", 3};
100
101
102struct srv_state
103{
104 unsigned stateless_cnt;
105 unsigned stateful_cnt;
106 unsigned call_cnt;
107};
108
109
110struct app
111{
112 pj_caching_pool cp;
113 pj_pool_t *pool;
114 pj_str_t local_addr;
115 int local_port;
116 pjsip_endpoint *sip_endpt;
117 pjmedia_endpt *med_endpt;
118 pj_str_t local_uri;
119 pj_str_t local_contact;
120 unsigned skinfo_cnt;
121 pjmedia_sock_info skinfo[8];
122
123 pj_bool_t thread_quit;
124 unsigned thread_count;
125 pj_thread_t *thread[16];
126
127 pj_bool_t real_sdp;
128 pjmedia_sdp_session *dummy_sdp;
129 pj_bool_t verbose;
130
131 struct {
132 pjsip_method method;
133 pj_str_t dst_uri;
134 pj_bool_t stateless;
135 unsigned timeout;
136 unsigned job_count,
137 job_submitted,
138 job_finished,
139 job_window;
140 pj_time_val first_request;
141 pj_time_val last_completion;
142 unsigned total_responses;
143 unsigned status_class[7];
144 } client;
145
146 struct {
147 struct srv_state prev_state;
148 struct srv_state cur_state;
149 } server;
150
151
152} app;
153
154struct call
155{
156 pjsip_inv_session *inv;
157 pj_timer_entry timer;
158};
159
160
161static void app_perror(const char *sender, const char *title,
162 pj_status_t status)
163{
164 char errmsg[PJ_ERR_MSG_SIZE];
165
166 pj_strerror(status, errmsg, sizeof(errmsg));
167 PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
168}
169
170
171/**************************************************************************
172 * STATELESS SERVER
173 */
174static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata);
175
176/* Module to handle incoming requests statelessly.
177 */
178static pjsip_module mod_stateless_server =
179{
180 NULL, NULL, /* prev, next. */
181 { "mod-stateless-server", 20 }, /* Name. */
182 -1, /* Id */
183 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
184 NULL, /* load() */
185 NULL, /* start() */
186 NULL, /* stop() */
187 NULL, /* unload() */
188 &mod_stateless_on_rx_request, /* on_rx_request() */
189 NULL, /* on_rx_response() */
190 NULL, /* on_tx_request. */
191 NULL, /* on_tx_response() */
192 NULL, /* on_tsx_state() */
193};
194
195
196static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
197{
198 const pj_str_t stateless_user = { "0", 1 };
199 pjsip_uri *uri;
200 pjsip_sip_uri *sip_uri;
201
202 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
203
204 /* Only want to receive SIP scheme */
205 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
206 return PJ_FALSE;
207
208 sip_uri = (pjsip_sip_uri*) uri;
209
210 /* Check for matching user part */
211 if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
212 return PJ_FALSE;
213
214 /*
215 * Yes, this is for us.
216 */
217
218 /* Ignore ACK request */
219 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
220 return PJ_TRUE;
221
222 /*
223 * Respond statelessly with 200/OK.
224 */
225 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
226 NULL, NULL);
227 app.server.cur_state.stateless_cnt++;
228 return PJ_TRUE;
229}
230
231
232/**************************************************************************
233 * STATEFUL SERVER
234 */
235static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata);
236
237/* Module to handle incoming requests statefully.
238 */
239static pjsip_module mod_stateful_server =
240{
241 NULL, NULL, /* prev, next. */
242 { "mod-stateful-server", 19 }, /* Name. */
243 -1, /* Id */
244 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
245 NULL, /* load() */
246 NULL, /* start() */
247 NULL, /* stop() */
248 NULL, /* unload() */
249 &mod_stateful_on_rx_request, /* on_rx_request() */
250 NULL, /* on_rx_response() */
251 NULL, /* on_tx_request. */
252 NULL, /* on_tx_response() */
253 NULL, /* on_tsx_state() */
254};
255
256
257static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
258{
259 const pj_str_t stateful_user = { "1", 1 };
260 pjsip_uri *uri;
261 pjsip_sip_uri *sip_uri;
262
263 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
264
265 /* Only want to receive SIP scheme */
266 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
267 return PJ_FALSE;
268
269 sip_uri = (pjsip_sip_uri*) uri;
270
271 /* Check for matching user part */
272 if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
273 return PJ_FALSE;
274
275 /*
276 * Yes, this is for us.
277 * Respond statefully with 200/OK.
278 */
279 switch (rdata->msg_info.msg->line.req.method.id) {
280 case PJSIP_INVITE_METHOD:
281 {
282 pjsip_msg_body *body;
283
284 if (dummy_sdp_str.slen == 0)
285 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
286
287 body = pjsip_msg_body_create(rdata->tp_info.pool,
288 &mime_application, &mime_sdp,
289 &dummy_sdp_str);
290 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
291 200, NULL, NULL, body, NULL);
292 }
293 break;
294 case PJSIP_ACK_METHOD:
295 return PJ_TRUE;
296 default:
297 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
298 200, NULL, NULL, NULL, NULL);
299 break;
300 }
301
302 app.server.cur_state.stateful_cnt++;
303 return PJ_TRUE;
304}
305
306
307/**************************************************************************
308 * CALL SERVER
309 */
310static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
311
312/* Module to handle incoming requests callly.
313 */
314static pjsip_module mod_call_server =
315{
316 NULL, NULL, /* prev, next. */
317 { "mod-call-server", 15 }, /* Name. */
318 -1, /* Id */
319 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
320 NULL, /* load() */
321 NULL, /* start() */
322 NULL, /* stop() */
323 NULL, /* unload() */
324 &mod_call_on_rx_request, /* on_rx_request() */
325 NULL, /* on_rx_response() */
326 NULL, /* on_tx_request. */
327 NULL, /* on_tx_response() */
328 NULL, /* on_tsx_state() */
329};
330
331
332static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
333{
334 const pj_str_t call_user = { "2", 1 };
335 pjsip_uri *uri;
336 pjsip_sip_uri *sip_uri;
337 struct call *call;
338 pjsip_dialog *dlg;
339 pjmedia_sdp_session *sdp;
340 pjsip_tx_data *tdata;
341 pj_status_t status;
342
343 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
344
345 /* Only want to receive SIP scheme */
346 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
347 return PJ_FALSE;
348
349 sip_uri = (pjsip_sip_uri*) uri;
350
351 /* Check for matching user part */
352 if (pj_strcmp(&sip_uri->user, &call_user)!=0)
353 return PJ_FALSE;
354
355 /* Only want to handle INVITE requests (for now). */
356 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
357 return PJ_FALSE;
358 }
359
360
361 /* Verify that we can handle the request. */
362 if (app.real_sdp) {
363 unsigned options = 0;
364 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
365 app.sip_endpt, &tdata);
366 if (status != PJ_SUCCESS) {
367
368 /*
369 * No we can't handle the incoming INVITE request.
370 */
371
372 if (tdata) {
373 pjsip_response_addr res_addr;
374
375 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
376 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
377 NULL, NULL);
378
379 } else {
380
381 /* Respond with 500 (Internal Server Error) */
382 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
383 NULL, NULL);
384 }
385
386 return PJ_TRUE;
387 }
388 }
389
390 /* Create UAS dialog */
391 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
392 &app.local_contact, &dlg);
393 if (status != PJ_SUCCESS) {
394 const pj_str_t reason = pj_str("Unable to create dialog");
395 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
396 500, &reason,
397 NULL, NULL);
398 return PJ_TRUE;
399 }
400
401 /* Alloc call structure. */
402 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
403
404 /* Create SDP from PJMEDIA */
405 if (app.real_sdp) {
406 status = pjmedia_endpt_create_sdp(app.med_endpt, rdata->tp_info.pool,
407 app.skinfo_cnt, app.skinfo,
408 &sdp);
409 } else {
410 sdp = app.dummy_sdp;
411 }
412
413 /* Create UAS invite session */
414 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
415 if (status != PJ_SUCCESS) {
416 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
417 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
418 return PJ_TRUE;
419 }
420
421
422 /* Create 200 response .*/
423 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
424 NULL, NULL, &tdata);
425 if (status != PJ_SUCCESS) {
426 status = pjsip_inv_initial_answer(call->inv, rdata,
427 PJSIP_SC_NOT_ACCEPTABLE,
428 NULL, NULL, &tdata);
429 if (status == PJ_SUCCESS)
430 pjsip_inv_send_msg(call->inv, tdata);
431 else
432 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
433 return PJ_TRUE;
434 }
435
436
437 /* Send the 200 response. */
438 status = pjsip_inv_send_msg(call->inv, tdata);
439 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return PJ_TRUE);
440
441
442 /* Done */
443 app.server.cur_state.call_cnt++;
444
445 return PJ_TRUE;
446}
447
448
449
450
451/*****************************************************************************
452 * Below is a simple module to log all incoming and outgoing SIP messages
453 */
454
455
456/* Notification on incoming messages */
457static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
458{
459 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
460 "%.*s\n"
461 "--end msg--",
462 rdata->msg_info.len,
463 pjsip_rx_data_get_info(rdata),
464 rdata->pkt_info.src_name,
465 rdata->pkt_info.src_port,
466 (int)rdata->msg_info.len,
467 rdata->msg_info.msg_buf));
468
469 /* Always return false, otherwise messages will not get processed! */
470 return PJ_FALSE;
471}
472
473/* Notification on outgoing messages */
474static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
475{
476
477 /* Important note:
478 * tp_info field is only valid after outgoing messages has passed
479 * transport layer. So don't try to access tp_info when the module
480 * has lower priority than transport layer.
481 */
482
483 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
484 "%.*s\n"
485 "--end msg--",
486 (tdata->buf.cur - tdata->buf.start),
487 pjsip_tx_data_get_info(tdata),
488 tdata->tp_info.dst_name,
489 tdata->tp_info.dst_port,
490 (int)(tdata->buf.cur - tdata->buf.start),
491 tdata->buf.start));
492
493 /* Always return success, otherwise message will not get sent! */
494 return PJ_SUCCESS;
495}
496
497/* The module instance. */
498static pjsip_module msg_logger =
499{
500 NULL, NULL, /* prev, next. */
501 { "mod-siprtp-log", 14 }, /* Name. */
502 -1, /* Id */
503 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
504 NULL, /* load() */
505 NULL, /* start() */
506 NULL, /* stop() */
507 NULL, /* unload() */
508 &logger_on_rx_msg, /* on_rx_request() */
509 &logger_on_rx_msg, /* on_rx_response() */
510 &logger_on_tx_msg, /* on_tx_request. */
511 &logger_on_tx_msg, /* on_tx_response() */
512 NULL, /* on_tsx_state() */
513
514};
515
516
517
518/**************************************************************************
519 * Test Client.
520 */
521
522static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata);
523
524static void call_on_media_update( pjsip_inv_session *inv,
525 pj_status_t status);
526static void call_on_state_changed( pjsip_inv_session *inv,
527 pjsip_event *e);
528static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
529
530
531/* Module to handle incoming requests callly.
532 */
533static pjsip_module mod_test =
534{
535 NULL, NULL, /* prev, next. */
536 { "mod-test", 8 }, /* Name. */
537 -1, /* Id */
538 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
539 NULL, /* load() */
540 NULL, /* start() */
541 NULL, /* stop() */
542 NULL, /* unload() */
543 NULL, /* on_rx_request() */
544 &mod_test_on_rx_response, /* on_rx_response() */
545 NULL, /* on_tx_request. */
546 NULL, /* on_tx_response() */
547 NULL, /* on_tsx_state() */
548};
549
550
551static void report_completion(int status_code)
552{
553 app.client.job_finished++;
554 app.client.status_class[status_code/100]++;
555 app.client.total_responses++;
556 pj_gettimeofday(&app.client.last_completion);
557}
558
559
560/* Handler when response is received. */
561static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata)
562{
563 if (pjsip_rdata_get_tsx(rdata) == NULL) {
564 report_completion(rdata->msg_info.msg->line.status.code);
565 }
566
567 return PJ_TRUE;
568}
569
570
571/*
572 * Create app
573 */
574static pj_status_t create_app(void)
575{
576 pj_status_t status;
577
578 status = pj_init();
579 if (status != PJ_SUCCESS) {
580 app_perror(THIS_FILE, "Error initializing pjlib", status);
581 return status;
582 }
583
584 /* init PJLIB-UTIL: */
585 status = pjlib_util_init();
586 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
587
588 /* Must create a pool factory before we can allocate any memory. */
589 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
590
591 /* Create application pool for misc. */
592 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
593
594 /* Create the endpoint: */
595 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
596 &app.sip_endpt);
597 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
598
599
600 return status;
601}
602
603
604/*
605 * Init SIP stack
606 */
607static pj_status_t init_sip()
608{
609 pj_status_t status;
610
611 /* Add UDP transport. */
612 {
613 pj_sockaddr_in addr;
614 pjsip_host_port addrname;
615 pjsip_transport *tp;
616
617 pj_memset(&addr, 0, sizeof(addr));
618 addr.sin_family = PJ_AF_INET;
619 addr.sin_addr.s_addr = 0;
620 addr.sin_port = pj_htons((pj_uint16_t)app.local_port);
621
622 if (app.local_addr.slen) {
623 addrname.host = app.local_addr;
624 addrname.port = 5060;
625 }
626 if (app.local_port != 0)
627 addrname.port = app.local_port;
628
629 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
630 (app.local_addr.slen ? &addrname:NULL),
631 1, &tp);
632 if (status != PJ_SUCCESS) {
633 app_perror(THIS_FILE, "Unable to start UDP transport", status);
634 return status;
635 }
636
637 app.local_addr = tp->local_name.host;
638 app.local_port = tp->local_name.port;
639
640 app.local_uri.ptr = pj_pool_alloc(app.pool, 128);
641 app.local_uri.slen = pj_ansi_sprintf(app.local_uri.ptr,
642 "<sip:pjsip-perf@%.*s:%d>",
643 (int)tp->local_name.host.slen,
644 tp->local_name.host.ptr,
645 tp->local_name.port);
646
647 app.local_contact = app.local_uri;
648 }
649
650 /*
651 * Init transaction layer.
652 * This will create/initialize transaction hash tables etc.
653 */
654 status = pjsip_tsx_layer_init_module(app.sip_endpt);
655 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
656
657 /* Initialize UA layer. */
658 status = pjsip_ua_init_module( app.sip_endpt, NULL );
659 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
660
661 /* Init invite session module. */
662 {
663 pjsip_inv_callback inv_cb;
664
665 /* Init the callback for INVITE session: */
666 pj_memset(&inv_cb, 0, sizeof(inv_cb));
667 inv_cb.on_state_changed = &call_on_state_changed;
668 inv_cb.on_new_session = &call_on_forked;
669 inv_cb.on_media_update = &call_on_media_update;
670
671 /* Initialize invite session module: */
672 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
673 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
674 }
675
676 /* Register our module to receive incoming requests. */
677 status = pjsip_endpt_register_module( app.sip_endpt, &mod_test);
678 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
679
680
681 /* Register stateless server module */
682 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateless_server);
683 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
684
685
686 /* Register stateless server module */
687 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateful_server);
688 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
689
690
691 /* Register call server module */
692 status = pjsip_endpt_register_module( app.sip_endpt, &mod_call_server);
693 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
694
695
696 /* Done */
697 return PJ_SUCCESS;
698}
699
700
701/*
702 * Destroy SIP
703 */
704static void destroy_app()
705{
706 unsigned i;
707
708 app.thread_quit = 1;
709 for (i=0; i<app.thread_count; ++i) {
710 if (app.thread[i]) {
711 pj_thread_join(app.thread[i]);
712 pj_thread_destroy(app.thread[i]);
713 app.thread[i] = NULL;
714 }
715 }
716
717 if (app.sip_endpt) {
718 pjsip_endpt_destroy(app.sip_endpt);
719 app.sip_endpt = NULL;
720 }
721
722 if (app.pool) {
723 pj_pool_release(app.pool);
724 app.pool = NULL;
725 pj_caching_pool_destroy(&app.cp);
726 }
727}
728
729
730/*
731 * Init media stack.
732 */
733static pj_status_t init_media()
734{
735 unsigned i;
736 pj_uint16_t rtp_port;
737 pj_status_t status;
738
739
740 /* Initialize media endpoint so that at least error subsystem is properly
741 * initialized.
742 */
743 status = pjmedia_endpt_create(&app.cp.factory,
744 pjsip_endpt_get_ioqueue(app.sip_endpt), 0,
745 &app.med_endpt);
746 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
747
748
749 /* Must register all codecs to be supported */
750 pjmedia_codec_g711_init(app.med_endpt);
751 pjmedia_codec_gsm_init(app.med_endpt);
752 pjmedia_codec_speex_init(app.med_endpt, PJMEDIA_SPEEX_NO_UWB, 3, 3);
753
754
755 /* Init dummy socket addresses */
756 app.skinfo_cnt = 0;
757 for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
758 pjmedia_sock_info *skinfo;
759
760 skinfo = &app.skinfo[i];
761
762 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
763 (pj_uint16_t)rtp_port);
764 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
765 (pj_uint16_t)(rtp_port+1));
766 app.skinfo_cnt++;
767 }
768
769 /* Generate dummy SDP */
770 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
771 status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen,
772 &app.dummy_sdp);
773 if (status != PJ_SUCCESS) {
774 app_perror(THIS_FILE, "Error parsing dummy SDP", status);
775 return status;
776 }
777
778
779 /* Done */
780 return PJ_SUCCESS;
781}
782
783
784/* This is notification from the call about media negotiation
785 * status. This is called for client calls only.
786 */
787static void call_on_media_update( pjsip_inv_session *inv,
788 pj_status_t status)
789{
790 if (status != PJ_SUCCESS) {
791 pjsip_tx_data *tdata;
792 pj_status_t status;
793
794 status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE,
795 NULL, &tdata);
796 if (status == PJ_SUCCESS && tdata)
797 status = pjsip_inv_send_msg(inv, tdata);
798 }
799}
800
801
802/* This is notification from the call when the call state has changed.
803 * This is called for client calls only.
804 */
805static void call_on_state_changed( pjsip_inv_session *inv,
806 pjsip_event *e)
807{
808 PJ_UNUSED_ARG(e);
809
810 /* Bail out if the session has been counted before */
811 if (inv->mod_data[mod_test.id] != NULL)
812 return;
813
814 /* Bail out if this is not an outgoing call */
815 if (inv->role != PJSIP_UAC_ROLE)
816 return;
817
818 if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
819 pjsip_tx_data *tdata;
820 pj_status_t status;
821
822 //report_completion(200);
823 //inv->mod_data[mod_test.id] = (void*)1;
824
825 status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
826 if (status == PJ_SUCCESS && tdata)
827 status = pjsip_inv_send_msg(inv, tdata);
828
829 } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
830 report_completion(inv->cause);
831 inv->mod_data[mod_test.id] = (void*)1;
832 }
833}
834
835
836/* Not implemented for now */
837static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
838{
839 /* Do nothing */
840 PJ_UNUSED_ARG(inv);
841 PJ_UNUSED_ARG(e);
842}
843
844
845/*
846 * Make outgoing call.
847 */
848static pj_status_t make_call(const pj_str_t *dst_uri)
849{
850 struct call *call;
851 pjsip_dialog *dlg;
852 pjmedia_sdp_session *sdp;
853 pjsip_tx_data *tdata;
854 pj_status_t status;
855
856
857 /* Create UAC dialog */
858 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
859 &app.local_uri, /* local URI */
860 &app.local_contact, /* local Contact */
861 dst_uri, /* remote URI */
862 dst_uri, /* remote target */
863 &dlg); /* dialog */
864 if (status != PJ_SUCCESS) {
865 return status;
866 }
867
868 /* Create call */
869 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
870
871 /* Create SDP */
872 if (app.real_sdp) {
873 status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1,
874 app.skinfo, &sdp);
875 if (status != PJ_SUCCESS) {
876 pjsip_dlg_terminate(dlg);
877 return status;
878 }
879 } else
880 sdp = app.dummy_sdp;
881
882 /* Create the INVITE session. */
883 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
884 if (status != PJ_SUCCESS) {
885 pjsip_dlg_terminate(dlg);
886 return status;
887 }
888
889
890 /* Create initial INVITE request.
891 * This INVITE request will contain a perfectly good request and
892 * an SDP body as well.
893 */
894 status = pjsip_inv_invite(call->inv, &tdata);
895 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
896
897
898 /* Send initial INVITE request.
899 * From now on, the invite session's state will be reported to us
900 * via the invite session callbacks.
901 */
902 status = pjsip_inv_send_msg(call->inv, tdata);
903 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
904
905
906 return PJ_SUCCESS;
907}
908
909
910/*
911 * Verify that valid SIP url is given.
912 */
913static pj_status_t verify_sip_url(const char *c_url)
914{
915 pjsip_uri *p;
916 pj_pool_t *pool;
917 char *url;
918 int len = (c_url ? pj_ansi_strlen(c_url) : 0);
919
920 if (!len) return -1;
921
922 pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
923 if (!pool) return PJ_ENOMEM;
924
925 url = pj_pool_alloc(pool, len+1);
926 pj_ansi_strcpy(url, c_url);
927 url[len] = '\0';
928
929 p = pjsip_parse_uri(pool, url, len, 0);
930 if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
931 p = NULL;
932
933 pj_pool_release(pool);
934 return p ? 0 : -1;
935}
936
937
938static void usage(void)
939{
940 printf(
941 "Usage:\n"
942 " pjsip-perf [OPTIONS] -- to start as server\n"
943 " pjsip-perf [OPTIONS] URL -- to call server (possibly itself)\n"
944 "\n"
945 "where:\n"
946 " URL The URL to be contacted\n"
947 "\n"
948 "and OPTIONS are:\n"
949 " --count=N, -n Set number of requests to initiate\n"
950 " (client only, default=%d)\n"
951 " --method=METHOD, -m Set the test method (default: OPTIONS)\n"
952 " --local-port=PORT, -p Set local port [default: 5060]\n"
953 " --thread-count=N Set number of worker threads (default=1)\n"
954 " --stateless, -s Set client to operate in stateless mode\n"
955 " (default: stateful)\n"
956 " --real-sdp Generate real SDP from pjmedia, and also perform\n"
957 " proper SDP negotiation (default: dummy)\n"
958 " --timeout=SEC, -t Set client timeout (default=60 sec)\n"
959 " --help, -h Display this screen\n"
960 " --verbose, -v Display verbose logging\n"
961 "\n"
962 "When started as server, pjsip-perf can be contacted on the following URIs:\n"
963 " - sip:0@server-addr To handle requests statelessly (non-INVITE only)\n"
964 " - sip:1@server-addr To handle requests statefully (INVITE and non-INVITE)\n"
965 " - sip:2@server-addr To handle INVITE call (INVITE only)\n",
966 DEFAULT_COUNT);
967}
968
969
970static int my_atoi(const char *s)
971{
972 pj_str_t ss = pj_str((char*)s);
973 return pj_strtoul(&ss);
974}
975
976
977static pj_status_t init_options(int argc, char *argv[])
978{
979 enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP };
980 struct pj_getopt_option long_options[] = {
981 { "local-port", 1, 0, 'p' },
982 { "count", 1, 0, 'c' },
983 { "thread-count", 1, 0, OPT_THREAD_COUNT },
984 { "method", 1, 0, 'm' },
985 { "help", 0, 0, 'h' },
986 { "stateless", 0, 0, 's' },
987 { "timeout", 1, 0, 't' },
988 { "real-sdp", 0, 0, OPT_REAL_SDP },
989 { "verbose", 0, 0, 'v' },
990 { NULL, 0, 0, 0 },
991 };
992 int c;
993 int option_index;
994
995 /* Init default application configs */
996 app.local_port = 5060;
997 app.thread_count = 1;
998 app.client.job_count = DEFAULT_COUNT;
999 app.client.method = pjsip_options_method;
1000 app.client.job_window = c = JOB_WINDOW;
1001 app.client.timeout = 60;
1002
1003
1004 /* Parse options */
1005 pj_optind = 0;
1006 while((c=pj_getopt_long(argc,argv, "p:c:m:t:hsv",
1007 long_options, &option_index))!=-1)
1008 {
1009 switch (c) {
1010 case 'p':
1011 app.local_port = my_atoi(pj_optarg);
1012 if (app.local_port < 0 || app.local_port > 65535) {
1013 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1014 return -1;
1015 }
1016 break;
1017
1018 case 'c':
1019 app.client.job_count = my_atoi(pj_optarg);
1020 if (app.client.job_count < 0) {
1021 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1022 return -1;
1023 }
1024 if (app.client.job_count > PJSIP_MAX_TSX_COUNT)
1025 PJ_LOG(3,(THIS_FILE,
1026 "Warning: --count value (%d) exceeds maximum "
1027 "transaction count (%d)", app.client.job_count,
1028 PJSIP_MAX_TSX_COUNT));
1029 break;
1030
1031 case OPT_THREAD_COUNT:
1032 app.thread_count = my_atoi(pj_optarg);
1033 if (app.thread_count < 1 || app.thread_count > 16) {
1034 PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
1035 return -1;
1036 }
1037 break;
1038
1039 case 'm':
1040 {
1041 pj_str_t temp = pj_str((char*)pj_optarg);
1042 pjsip_method_init_np(&app.client.method, &temp);
1043 }
1044 break;
1045
1046 case 'h':
1047 usage();
1048 return -1;
1049
1050 case 's':
1051 app.client.stateless = PJ_TRUE;
1052 break;
1053
1054 case OPT_REAL_SDP:
1055 app.real_sdp = 1;
1056 break;
1057
1058 case 'v':
1059 app.verbose = PJ_TRUE;
1060 break;
1061
1062 case 't':
1063 app.client.timeout = my_atoi(pj_optarg);
1064 if (app.client.timeout < 0 || app.client.timeout > 600) {
1065 PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
1066 return -1;
1067 }
1068 break;
1069
1070 default:
1071 PJ_LOG(1,(THIS_FILE,
1072 "Invalid argument. Use --help to see help"));
1073 return -1;
1074 }
1075 }
1076
1077 if (pj_optind != argc) {
1078
1079 if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
1080 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
1081 return -1;
1082 }
1083 app.client.dst_uri = pj_str(argv[pj_optind]);
1084
1085 pj_optind++;
1086
1087 }
1088
1089 if (pj_optind != argc) {
1090 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
1091 return -1;
1092 }
1093
1094 return 0;
1095}
1096
1097
1098/* Send one stateless request */
1099static pj_status_t submit_stateless_job(void)
1100{
1101 pjsip_tx_data *tdata;
1102 pj_status_t status;
1103
1104 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1105 &app.client.dst_uri, &app.local_uri,
1106 &app.client.dst_uri, &app.local_contact,
1107 NULL, -1, NULL, &tdata);
1108 if (status != PJ_SUCCESS) {
1109 app_perror(THIS_FILE, "Error creating request", status);
1110 report_completion(701);
1111 return status;
1112 }
1113
1114 status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
1115 NULL);
1116 if (status != PJ_SUCCESS) {
1117 pjsip_tx_data_dec_ref(tdata);
1118 app_perror(THIS_FILE, "Error sending stateless request", status);
1119 report_completion(701);
1120 return status;
1121 }
1122
1123 return PJ_SUCCESS;
1124}
1125
1126
1127/* This callback is called when client transaction state has changed */
1128static void tsx_completion_cb(void *token, pjsip_event *event)
1129{
1130 pjsip_transaction *tsx;
1131
1132 PJ_UNUSED_ARG(token);
1133
1134 if (event->type != PJSIP_EVENT_TSX_STATE)
1135 return;
1136
1137 tsx = event->body.tsx_state.tsx;
1138
1139 if (tsx->mod_data[mod_test.id] != NULL) {
1140 /* This transaction has been calculated before */
1141 return;
1142 }
1143
1144 if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
1145 report_completion(tsx->status_code);
1146 tsx->mod_data[mod_test.id] = (void*)1;
1147 }
1148 else if (tsx->method.id == PJSIP_INVITE_METHOD &&
1149 tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
1150
1151 report_completion(tsx->status_code);
1152 tsx->mod_data[mod_test.id] = (void*)1;
1153
1154 } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
1155
1156 report_completion(tsx->status_code);
1157 tsx->mod_data[mod_test.id] = (void*)1;
1158
1159 pjsip_tsx_terminate(tsx, tsx->status_code);
1160 }
1161}
1162
1163
1164/* Send one stateful request */
1165static pj_status_t submit_job(void)
1166{
1167 pjsip_tx_data *tdata;
1168 pj_status_t status;
1169
1170 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1171 &app.client.dst_uri, &app.local_uri,
1172 &app.client.dst_uri, &app.local_contact,
1173 NULL, -1, NULL, &tdata);
1174 if (status != PJ_SUCCESS) {
1175 app_perror(THIS_FILE, "Error creating request", status);
1176 report_completion(701);
1177 return status;
1178 }
1179
1180 status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL,
1181 &tsx_completion_cb);
1182 if (status != PJ_SUCCESS) {
1183 app_perror(THIS_FILE, "Error sending stateful request", status);
1184 //should have been reported by tsx_completion_cb().
1185 //report_completion(701);
1186 pjsip_tx_data_dec_ref(tdata);
1187 }
1188 return status;
1189}
1190
1191
1192/* Client worker thread */
1193static int client_thread(void *arg)
1194{
1195 pj_time_val end_time, now;
1196
1197 PJ_UNUSED_ARG(arg);
1198
1199 pj_thread_sleep(100);
1200
1201 pj_gettimeofday(&end_time);
1202 end_time.sec += app.client.timeout;
1203
1204 if (app.client.first_request.sec == 0) {
1205 pj_gettimeofday(&app.client.first_request);
1206 }
1207
1208 /* Submit all jobs */
1209 while (app.client.job_submitted < app.client.job_count && !app.thread_quit) {
1210 pj_time_val timeout = { 0, 0 };
1211 unsigned i;
1212 int outstanding;
1213 pj_status_t status;
1214
1215 /* Wait if there are more pending jobs than allowed in the
1216 * window.
1217 */
1218 outstanding = app.client.job_submitted - app.client.job_finished;
1219 while (outstanding >= (int)app.client.job_window) {
1220 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
1221 outstanding = app.client.job_submitted - app.client.job_finished;
1222 }
1223
1224 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1225 status = make_call(&app.client.dst_uri);
1226 } else if (app.client.stateless) {
1227 status = submit_stateless_job();
1228 } else {
1229 status = submit_job();
1230 }
1231
1232 ++app.client.job_submitted;
1233
1234 for (i=0; i<2; ++i) {
1235 unsigned cnt=0;
1236 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &cnt);
1237 if (cnt==0)
1238 break;
1239 }
1240 }
1241
1242 /* Wait until all jobs completes, or timed out */
1243 do {
1244 pj_time_val timeout = { 0, 0 };
1245 unsigned i;
1246
1247 for (i=0; i<2; ++i) {
1248 unsigned cnt=0;
1249 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &cnt);
1250 if (cnt==0)
1251 break;
1252 }
1253
1254 pj_gettimeofday(&now);
1255
1256 } while (now.sec < end_time.sec &&
1257 app.client.job_finished < app.client.job_count &&
1258 !app.thread_quit);
1259
1260 return 0;
1261}
1262
1263
1264static const char *good_number(char *buf, pj_int32_t val)
1265{
1266 if (val < 1000) {
1267 pj_ansi_sprintf(buf, "%d", val);
1268 } else if (val < 1000000) {
1269 pj_ansi_sprintf(buf, "%d.%dK",
1270 val / 1000,
1271 (val % 1000) / 100);
1272 } else {
1273 pj_ansi_sprintf(buf, "%d.%02dM",
1274 val / 1000000,
1275 (val % 1000000) / 10000);
1276 }
1277
1278 return buf;
1279}
1280
1281
1282static int server_thread(void *arg)
1283{
1284 pj_time_val timeout = { 0, 0 };
1285 unsigned thread_index = (unsigned)arg;
1286 pj_time_val last_report, next_report;
1287
1288 pj_gettimeofday(&last_report);
1289 next_report = last_report;
1290 next_report.sec++;
1291
1292 while (!app.thread_quit) {
1293 pj_time_val now;
1294 unsigned i;
1295
1296 for (i=0; i<100; ++i) {
1297 unsigned count = 0;
1298 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
1299 if (count == 0)
1300 break;
1301 }
1302
1303 if (thread_index == 0) {
1304 pj_gettimeofday(&now);
1305
1306 if (PJ_TIME_VAL_GTE(now, next_report)) {
1307 pj_time_val tmp;
1308 unsigned msec;
1309 unsigned stateless, stateful, call;
1310 char str_stateless[32], str_stateful[32], str_call[32];
1311
1312 tmp = now;
1313 PJ_TIME_VAL_SUB(tmp, last_report);
1314 msec = PJ_TIME_VAL_MSEC(tmp);
1315
1316 last_report = now;
1317 next_report = last_report;
1318 next_report.sec++;
1319
1320 stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
1321 stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
1322 call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
1323
1324 good_number(str_stateless, app.server.cur_state.stateless_cnt);
1325 good_number(str_stateful, app.server.cur_state.stateful_cnt);
1326 good_number(str_call, app.server.cur_state.call_cnt);
1327
1328 printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s)\r",
1329 str_stateless, stateless*1000/msec,
1330 str_stateful, stateful*1000/msec,
1331 str_call, call*1000/msec);
1332 fflush(stdout);
1333
1334 app.server.prev_state = app.server.cur_state;
1335 }
1336 }
1337 }
1338
1339 return 0;
1340}
1341
1342int main(int argc, char *argv[])
1343{
1344 pj_log_set_level(3);
1345
1346 if (create_app() != 0)
1347 return 1;
1348
1349 if (init_options(argc, argv) != 0)
1350 return 1;
1351
1352 if (init_sip() != 0)
1353 return 1;
1354
1355 if (init_media() != 0)
1356 return 1;
1357
1358 if (app.verbose) {
1359 pj_log_set_level(4);
1360 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1361 }
1362
1363
1364 /* Misc infos */
1365 if (app.client.dst_uri.slen != 0) {
1366 if (app.client.method.id == PJSIP_INVITE_METHOD &&
1367 app.client.stateless)
1368 {
1369 PJ_LOG(3,(THIS_FILE, "Info: --stateless option makes no sense for INVITE, ignored."));
1370 }
1371 }
1372
1373 if (app.real_sdp) {
1374 PJ_LOG(3,(THIS_FILE, "Info: client/server using real SDP from PJMEDIA"));
1375 } else {
1376 PJ_LOG(3,(THIS_FILE, "Info: client/server using dummy SDP"));
1377 }
1378
1379
1380 if (app.client.dst_uri.slen) {
1381 /* Client mode */
1382 pj_status_t status;
1383 char test_type[64];
1384 unsigned msec;
1385 unsigned i;
1386
1387 /* Get the job name */
1388 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1389 pj_ansi_strcpy(test_type, "INVITE calls");
1390 } else if (app.client.stateless) {
1391 pj_ansi_sprintf(test_type, "stateless %.*s requests",
1392 (int)app.client.method.name.slen,
1393 app.client.method.name.ptr);
1394 } else {
1395 pj_ansi_sprintf(test_type, "stateful %.*s requests",
1396 (int)app.client.method.name.slen,
1397 app.client.method.name.ptr);
1398 }
1399
1400
1401 PJ_LOG(3,(THIS_FILE, "Starting %d %s, please wait..",
1402 app.client.job_count, test_type));
1403
1404 for (i=0; i<app.thread_count; ++i) {
1405 status = pj_thread_create(app.pool, NULL, &client_thread, NULL,
1406 0, 0, &app.thread[i]);
1407 if (status != PJ_SUCCESS) {
1408 app_perror(THIS_FILE, "Unable to create thread", status);
1409 return 1;
1410 }
1411 }
1412
1413 for (i=0; i<app.thread_count; ++i) {
1414 pj_thread_join(app.thread[i]);
1415 app.thread[i] = NULL;
1416 }
1417
1418 if (app.client.last_completion.sec) {
1419 pj_time_val duration;
1420 duration = app.client.last_completion;
1421 PJ_TIME_VAL_SUB(duration, app.client.first_request);
1422 msec = PJ_TIME_VAL_MSEC(duration);
1423 } else {
1424 msec = app.client.timeout * 1000;
1425 }
1426
1427 if (msec == 0) msec = 1;
1428
1429 printf("Total %d %s sent, %d responses received in %d msec:\n"
1430 " - 2xx responses: %7d (rate=%d/sec)\n"
1431 " - 3xx responses: %7d (rate=%d/sec)\n"
1432 " - 4xx responses: %7d (rate=%d/sec)\n"
1433 " - 5xx responses: %7d (rate=%d/sec)\n"
1434 " - 6xx responses: %7d (rate=%d/sec)\n"
1435 " - 7xx responses: %7d (rate=%d/sec)\n"
1436 " ----------------\n"
1437 " TOTAL responses: %7d (rate=%d/sec)\n",
1438 app.client.job_submitted, test_type,
1439 app.client.total_responses, msec,
1440 app.client.status_class[2], app.client.status_class[2]*1000/msec,
1441 app.client.status_class[3], app.client.status_class[3]*1000/msec,
1442 app.client.status_class[4], app.client.status_class[4]*1000/msec,
1443 app.client.status_class[5], app.client.status_class[5]*1000/msec,
1444 app.client.status_class[6], app.client.status_class[6]*1000/msec,
1445 app.client.status_class[7], app.client.status_class[7]*1000/msec,
1446 app.client.total_responses, app.client.total_responses*1000/msec);
1447
1448 } else {
1449 /* Server mode */
1450 char s[10];
1451 pj_status_t status;
1452 unsigned i;
1453
1454 puts("");
1455 puts("pjsip-perf started in server-mode");
1456
1457 printf("Receiving requests on the following URIs:\n"
1458 " sip:0@%.*s:%d for stateless handling (non-INVITE only)\n"
1459 " sip:1@%.*s:%d for stateful handling (INVITE and non-INVITE)\n"
1460 " sip:2@%.*s:%d for call handling (INVITE only)\n",
1461 (int)app.local_addr.slen,
1462 app.local_addr.ptr,
1463 app.local_port,
1464 (int)app.local_addr.slen,
1465 app.local_addr.ptr,
1466 app.local_port,
1467 (int)app.local_addr.slen,
1468 app.local_addr.ptr,
1469 app.local_port);
1470
1471 for (i=0; i<app.thread_count; ++i) {
1472 status = pj_thread_create(app.pool, NULL, &server_thread, (void*)i,
1473 0, 0, &app.thread[i]);
1474 if (status != PJ_SUCCESS) {
1475 app_perror(THIS_FILE, "Unable to create thread", status);
1476 return 1;
1477 }
1478 }
1479
1480 puts("Press <ENTER> to quit");
1481 fflush(stdout);
1482 fgets(s, sizeof(s), stdin);
1483
1484 app.thread_quit = PJ_TRUE;
1485 for (i=0; i<app.thread_count; ++i) {
1486 pj_thread_join(app.thread[i]);
1487 app.thread[i] = NULL;
1488 }
1489 }
1490
1491
1492 destroy_app();
1493
1494 return 0;
1495}
1496