BLOG | DOCUMENTATION | TRAC

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 /* $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.