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