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:

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:

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

1 /* $Id: pjsip-perf.c 5535 2017-01-19 10:31:38Z riza $ */
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 
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 
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 
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 
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 
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 
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 
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 
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 */
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 */
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 
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. */
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  */
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. */
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  */
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  */
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  */
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  */
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  */
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 */
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  */
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  */
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 
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 
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 
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 */
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 */
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 */
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 */
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 
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 
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 
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 
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 
pj_uint16_t sin_family
PJMEDIA main header file.
struct pj_timer_heap_t pj_timer_heap_t
pj_ssize_t slen
#define PJ_ASSERT_RETURN(expr, retval)
pj_in_addr sin_addr
char * ptr
PJ_BEGIN_DECL typedef int pj_int32_t
void pj_pool_release(pj_pool_t *pool)
int pj_bool_t
struct pj_thread_t pj_thread_t
pj_uint16_t sin_port
#define PJ_TIME_VAL_SUB(t1, t2)
struct pjmedia_endpt pjmedia_endpt
Definition: types.h:132
unsigned long pj_strtoul(const pj_str_t *str)
#define pj_AF_INET()
#define PJ_TIME_VAL_GTE(t1, t2)
#define PJ_TIME_VAL_MSEC(t)
#define PJ_LOG(level, arg)
int pj_status_t
pj_uint16_t pj_htons(pj_uint16_t hostshort)
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_pool_factory_policy pj_pool_factory_default_policy
void * pj_pool_alloc(pj_pool_t *pool, pj_size_t size)
Definition: pjsip-perf.c:113
pj_status_t pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *cp, pj_uint16_t port)
pj_timer_heap_callback * cb
Include all codecs API in PJMEDIA-CODEC.
pj_status_t pjmedia_sdp_parse(pj_pool_t *pool, char *buf, pj_size_t len, pjmedia_sdp_session **p_sdp)
void pj_time_val_normalize(pj_time_val *t)
const pj_str_t * pj_gethostname(void)
pj_status_t pjmedia_endpt_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, unsigned worker_cnt, pjmedia_endpt **p_endpt)
Definition: endpoint.h:105
void * user_data
pj_str_t pj_str(char *str)
void pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity)
long pj_ssize_t
PJ_BEGIN_DECL pj_status_t pjlib_util_init(void)
void pj_bzero(void *dst, pj_size_t size)
pj_str_t pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize)
pj_status_t pj_gettimeofday(pj_time_val *tv)
Definition: sdp.h:604
int pj_strcmp(const pj_str_t *str1, const pj_str_t *str2)
pj_status_t pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, const pjmedia_audio_codec_config *c)
#define PJ_ARRAY_SIZE(a)
pj_uint32_t s_addr
typedef int(PJ_THREAD_FUNC pj_thread_proc)(void *)
#define PJ_ERR_MSG_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)
#define PJ_ASSERT_ON_FAIL(expr, exec_on_fail)
int id
pj_status_t pj_thread_join(pj_thread_t *thread)
#define PJ_ENOMEM
pj_status_t pj_thread_destroy(pj_thread_t *thread)
pj_sockaddr_in ipv4
void pj_shutdown(void)
pj_sockaddr rtp_addr_name
Definition: transport.h:279
#define PJ_TIME_VAL_LT(t1, t2)
pj_status_t pj_thread_sleep(unsigned msec)
int pj_stricmp2(const pj_str_t *str1, const char *str2)
void * pj_pool_zalloc(pj_pool_t *pool, pj_size_t size)
unsigned short pj_uint16_t
pj_status_t pj_init(void)
size_t pj_size_t
void pj_log_set_level(int level)
#define PJ_UNUSED_ARG(arg)
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)
void pj_caching_pool_destroy(pj_caching_pool *ch_pool)
Definition: transport.h:269

 


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