WARNING: The online documentation has moved to https://docs.pjsip.org.

Visit the new documentation at https://docs.pjsip.org:

BLOG | DOCUMENTATION | GITHUB

Home --> Documentations --> PJMEDIA Reference

Samples: SIP Performance Benchmark

pjsip-perf is a complete program to measure the performance of PJSIP or other SIP endpoints. It consists of two parts:

  • the server, to respond incoming requests, and
  • the client, who actively submits requests and measure the performance of the server.

Both server and client part can run simultaneously, to measure the performance when both endpoints are co-located in a single program.

The server accepts both INVITE and non-INVITE requests. The server exports several different types of URL, which would control how the request would be handled by the server:

  • URL with "0" as the user part will be handled statelessly. It should not be used with INVITE method.
  • URL with "1" as the user part will be handled statefully. If the request is an INVITE request, INVITE transaction will be created and 200/OK response will be sent, along with a valid SDP body. However, the SDP is just a static text body, and is not a proper SDP generated by PJMEDIA.
  • URL with "2" as the user part is only meaningful for INVITE requests, as it would be handled call-statefully by the server. For this URL, the server also would generate SDP dynamically and perform a proper SDP negotiation for the incoming call. Also for every call, server will limit the call duration to 10 seconds, on which the call will be terminated if the client doesn't hangup the call.

This file is pjsip-apps/src/samples/pjsip-perf.c

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

 


PJMEDIA small footprint Open Source media stack
Copyright (C) 2006-2008 Teluu Inc.