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

00001 /* $Id: pjsip-perf.c 3664 2011-07-19 03:42:28Z nanang $ */
00002 /* 
00003  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
00004  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
00019  */
00020 
00021 
00060 /* Include all headers. */
00061 #include <pjsip.h>
00062 #include <pjmedia.h>
00063 #include <pjmedia-codec.h>
00064 #include <pjsip_ua.h>
00065 #include <pjsip_simple.h>
00066 #include <pjlib-util.h>
00067 #include <pjlib.h>
00068 #include <stdio.h>
00069 
00070 #if defined(PJ_WIN32) && PJ_WIN32!=0
00071 #  include <windows.h>
00072 #endif
00073 
00074 #define THIS_FILE           "pjsip-perf.c"
00075 #define DEFAULT_COUNT       (pjsip_cfg()->tsx.max_count/2>10000?10000:pjsip_cfg()->tsx.max_count/2)
00076 #define JOB_WINDOW          1000
00077 #define TERMINATE_TSX(x,c)
00078 
00079 
00080 #ifndef CACHING_POOL_SIZE
00081 #   define CACHING_POOL_SIZE   (256*1024*1024)
00082 #endif
00083 
00084 
00085 /* Static message body for INVITE, when stateful processing is
00086  * invoked (instead of call-stateful, where SDP is generated
00087  * dynamically.
00088  */
00089 static pj_str_t dummy_sdp_str = 
00090 {
00091     "v=0\r\n"
00092     "o=- 3360842071 3360842071 IN IP4 192.168.0.68\r\n"
00093     "s=pjmedia\r\n"
00094     "c=IN IP4 192.168.0.68\r\n"
00095     "t=0 0\r\n"
00096     "m=audio 4000 RTP/AVP 0 8 3 103 102 101\r\n"
00097     "a=rtcp:4001 IN IP4 192.168.0.68\r\n"
00098     "a=rtpmap:103 speex/16000\r\n"
00099     "a=rtpmap:102 speex/8000\r\n"
00100     "a=rtpmap:3 GSM/8000\r\n"
00101     "a=rtpmap:0 PCMU/8000\r\n"
00102     "a=rtpmap:8 PCMA/8000\r\n"
00103     "a=sendrecv\r\n"
00104     "a=rtpmap:101 telephone-event/8000\r\n"
00105     "a=fmtp:101 0-15\r\n",
00106     0
00107 };
00108 
00109 static pj_str_t mime_application = { "application", 11};
00110 static pj_str_t mime_sdp = {"sdp", 3};
00111 
00112 
00113 struct srv_state
00114 {
00115     unsigned        stateless_cnt;
00116     unsigned        stateful_cnt;
00117     unsigned        call_cnt;
00118 };
00119 
00120 
00121 struct app
00122 {
00123     pj_caching_pool      cp;
00124     pj_pool_t           *pool;
00125     pj_bool_t            use_tcp;
00126     pj_str_t             local_addr;
00127     int                  local_port;
00128     pjsip_endpoint      *sip_endpt;
00129     pjmedia_endpt       *med_endpt;
00130     pj_str_t             local_uri;
00131     pj_str_t             local_contact;
00132     unsigned             skinfo_cnt;
00133     pjmedia_sock_info    skinfo[8];
00134 
00135     pj_bool_t            thread_quit;
00136     unsigned             thread_count;
00137     pj_thread_t         *thread[16];
00138 
00139     pj_bool_t            real_sdp;
00140     pjmedia_sdp_session *dummy_sdp;
00141 
00142     int                  log_level;
00143 
00144     struct {
00145         pjsip_method         method;
00146         pj_str_t             dst_uri;
00147         pj_bool_t            stateless;
00148         unsigned             timeout;
00149         unsigned             job_count,
00150                              job_submitted, 
00151                              job_finished,
00152                              job_window;
00153         unsigned             stat_max_window;
00154         pj_time_val          first_request;
00155         pj_time_val          requests_sent;
00156         pj_time_val          last_completion;
00157         unsigned             total_responses;
00158         unsigned             response_codes[800];
00159     } client;
00160 
00161     struct {
00162         pj_bool_t send_trying;
00163         pj_bool_t send_ringing;
00164         unsigned delay;
00165         struct srv_state prev_state;
00166         struct srv_state cur_state;
00167     } server;
00168 
00169 
00170 } app;
00171 
00172 struct call
00173 {
00174     pjsip_inv_session   *inv;
00175     pj_timer_entry       ans_timer;
00176 };
00177 
00178 
00179 static void app_perror(const char *sender, const char *title, 
00180                        pj_status_t status)
00181 {
00182     char errmsg[PJ_ERR_MSG_SIZE];
00183 
00184     pj_strerror(status, errmsg, sizeof(errmsg));
00185     PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
00186 }
00187 
00188 
00189 /**************************************************************************
00190  * STATELESS SERVER
00191  */
00192 static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata);
00193 
00194 /* Module to handle incoming requests statelessly.
00195  */
00196 static pjsip_module mod_stateless_server =
00197 {
00198     NULL, NULL,                     /* prev, next.              */
00199     { "mod-stateless-server", 20 }, /* Name.                    */
00200     -1,                             /* Id                       */
00201     PJSIP_MOD_PRIORITY_APPLICATION, /* Priority                 */
00202     NULL,                           /* load()                   */
00203     NULL,                           /* start()                  */
00204     NULL,                           /* stop()                   */
00205     NULL,                           /* unload()                 */
00206     &mod_stateless_on_rx_request,   /* on_rx_request()          */
00207     NULL,                           /* on_rx_response()         */
00208     NULL,                           /* on_tx_request.           */
00209     NULL,                           /* on_tx_response()         */
00210     NULL,                           /* on_tsx_state()           */
00211 };
00212 
00213 
00214 static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
00215 {
00216     const pj_str_t stateless_user = { "0", 1 };
00217     pjsip_uri *uri;
00218     pjsip_sip_uri *sip_uri;
00219 
00220     uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
00221 
00222     /* Only want to receive SIP scheme */
00223     if (!PJSIP_URI_SCHEME_IS_SIP(uri))
00224         return PJ_FALSE;
00225 
00226     sip_uri = (pjsip_sip_uri*) uri;
00227 
00228     /* Check for matching user part */
00229     if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
00230         return PJ_FALSE;
00231 
00232     /*
00233      * Yes, this is for us.
00234      */
00235 
00236     /* Ignore ACK request */
00237     if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
00238         return PJ_TRUE;
00239 
00240     /*
00241      * Respond statelessly with 200/OK.
00242      */
00243     pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
00244                                   NULL, NULL);
00245     app.server.cur_state.stateless_cnt++;
00246     return PJ_TRUE;
00247 }
00248 
00249 
00250 /**************************************************************************
00251  * STATEFUL SERVER
00252  */
00253 static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata);
00254 
00255 /* Module to handle incoming requests statefully.
00256  */
00257 static pjsip_module mod_stateful_server =
00258 {
00259     NULL, NULL,                     /* prev, next.              */
00260     { "mod-stateful-server", 19 },  /* Name.                    */
00261     -1,                             /* Id                       */
00262     PJSIP_MOD_PRIORITY_APPLICATION, /* Priority                 */
00263     NULL,                           /* load()                   */
00264     NULL,                           /* start()                  */
00265     NULL,                           /* stop()                   */
00266     NULL,                           /* unload()                 */
00267     &mod_stateful_on_rx_request,   /* on_rx_request()           */
00268     NULL,                           /* on_rx_response()         */
00269     NULL,                           /* on_tx_request.           */
00270     NULL,                           /* on_tx_response()         */
00271     NULL,                           /* on_tsx_state()           */
00272 };
00273 
00274 
00275 static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
00276 {
00277     const pj_str_t stateful_user = { "1", 1 };
00278     pjsip_uri *uri;
00279     pjsip_sip_uri *sip_uri;
00280 
00281     uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
00282 
00283     /* Only want to receive SIP scheme */
00284     if (!PJSIP_URI_SCHEME_IS_SIP(uri))
00285         return PJ_FALSE;
00286 
00287     sip_uri = (pjsip_sip_uri*) uri;
00288 
00289     /* Check for matching user part */
00290     if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
00291         return PJ_FALSE;
00292 
00293     /*
00294      * Yes, this is for us.
00295      * Respond statefully with 200/OK.
00296      */
00297     switch (rdata->msg_info.msg->line.req.method.id) {
00298     case PJSIP_INVITE_METHOD:
00299         {
00300             pjsip_msg_body *body;
00301 
00302             if (dummy_sdp_str.slen == 0)
00303                 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
00304 
00305             body = pjsip_msg_body_create(rdata->tp_info.pool, 
00306                                          &mime_application, &mime_sdp, 
00307                                          &dummy_sdp_str);
00308             pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
00309                                 200, NULL, NULL, body, NULL);
00310         }
00311         break;
00312     case PJSIP_ACK_METHOD:
00313         return PJ_TRUE;
00314     default:
00315         pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
00316                             200, NULL, NULL, NULL, NULL);
00317         break;
00318     }
00319 
00320     app.server.cur_state.stateful_cnt++;
00321     return PJ_TRUE;
00322 }
00323 
00324 
00325 /**************************************************************************
00326  * CALL SERVER
00327  */
00328 static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
00329 
00330 /* Module to handle incoming requests callly.
00331  */
00332 static pjsip_module mod_call_server =
00333 {
00334     NULL, NULL,                     /* prev, next.              */
00335     { "mod-call-server", 15 },      /* Name.                    */
00336     -1,                             /* Id                       */
00337     PJSIP_MOD_PRIORITY_APPLICATION, /* Priority                 */
00338     NULL,                           /* load()                   */
00339     NULL,                           /* start()                  */
00340     NULL,                           /* stop()                   */
00341     NULL,                           /* unload()                 */
00342     &mod_call_on_rx_request,        /* on_rx_request()          */
00343     NULL,                           /* on_rx_response()         */
00344     NULL,                           /* on_tx_request.           */
00345     NULL,                           /* on_tx_response()         */
00346     NULL,                           /* on_tsx_state()           */
00347 };
00348 
00349 
00350 static pj_status_t send_response(pjsip_inv_session *inv, 
00351                                  pjsip_rx_data *rdata,
00352                                  int code,
00353                                  pj_bool_t *has_initial)
00354 {
00355     pjsip_tx_data *tdata;
00356     pj_status_t status;
00357 
00358     if (*has_initial) {
00359         status = pjsip_inv_answer(inv, code, NULL, NULL, &tdata);
00360     } else {
00361         status = pjsip_inv_initial_answer(inv, rdata, code, 
00362                                           NULL, NULL, &tdata);
00363     }
00364 
00365     if (status != PJ_SUCCESS) {
00366         if (*has_initial) {
00367             status = pjsip_inv_answer(inv, PJSIP_SC_NOT_ACCEPTABLE, 
00368                                       NULL, NULL, &tdata);
00369         } else {
00370             status = pjsip_inv_initial_answer(inv, rdata, 
00371                                               PJSIP_SC_NOT_ACCEPTABLE,
00372                                               NULL, NULL, &tdata);
00373         }
00374 
00375         if (status == PJ_SUCCESS) {
00376             *has_initial = PJ_TRUE;
00377             pjsip_inv_send_msg(inv, tdata); 
00378         } else {
00379             pjsip_inv_terminate(inv, 500, PJ_FALSE);
00380             return -1;
00381         }
00382     } else {
00383         *has_initial = PJ_TRUE;
00384 
00385         status = pjsip_inv_send_msg(inv, tdata); 
00386         if (status != PJ_SUCCESS) {
00387             pjsip_tx_data_dec_ref(tdata);
00388             return status;
00389         }
00390     }
00391 
00392     return status;
00393 }
00394 
00395 static void answer_timer_cb(pj_timer_heap_t *h, pj_timer_entry *entry)
00396 {
00397     struct call *call = entry->user_data;
00398     pj_bool_t has_initial = PJ_TRUE;
00399 
00400     PJ_UNUSED_ARG(h);
00401 
00402     entry->id = 0;
00403     send_response(call->inv, NULL, 200, &has_initial);
00404 }
00405 
00406 static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
00407 {
00408     const pj_str_t call_user = { "2", 1 };
00409     pjsip_uri *uri;
00410     pjsip_sip_uri *sip_uri;
00411     struct call *call;
00412     pjsip_dialog *dlg;
00413     pjmedia_sdp_session *sdp;
00414     pjsip_tx_data *tdata;
00415     pj_bool_t has_initial = PJ_FALSE;
00416     pj_status_t status;
00417 
00418     uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
00419 
00420     /* Only want to receive SIP scheme */
00421     if (!PJSIP_URI_SCHEME_IS_SIP(uri))
00422         return PJ_FALSE;
00423 
00424     sip_uri = (pjsip_sip_uri*) uri;
00425 
00426     /* Only want to handle INVITE requests. */
00427     if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
00428         return PJ_FALSE;
00429     }
00430 
00431 
00432     /* Check for matching user part. Incoming requests will be handled 
00433      * call-statefully if:
00434      *  - user part is "2", or
00435      *  - user part is not "0" nor "1" and method is INVITE.
00436      */
00437     if (pj_strcmp(&sip_uri->user, &call_user) == 0 ||
00438         sip_uri->user.slen != 1 ||
00439         (*sip_uri->user.ptr != '0' && *sip_uri->user.ptr != '1'))
00440     {
00441         /* Match */
00442 
00443     } else {
00444         return PJ_FALSE;
00445     }
00446 
00447 
00448     /* Verify that we can handle the request. */
00449     if (app.real_sdp) {
00450         unsigned options = 0;
00451         status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
00452                                           app.sip_endpt, &tdata);
00453         if (status != PJ_SUCCESS) {
00454 
00455             /*
00456              * No we can't handle the incoming INVITE request.
00457              */
00458 
00459             if (tdata) {
00460                 pjsip_response_addr res_addr;
00461 
00462                 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
00463                 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata, 
00464                                           NULL, NULL);
00465 
00466             } else {
00467 
00468                 /* Respond with 500 (Internal Server Error) */
00469                 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
00470                                               NULL, NULL);
00471             }
00472 
00473             return PJ_TRUE;
00474         } 
00475     }
00476 
00477     /* Create UAS dialog */
00478     status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
00479                                    &app.local_contact, &dlg);
00480     if (status != PJ_SUCCESS) {
00481         const pj_str_t reason = pj_str("Unable to create dialog");
00482         pjsip_endpt_respond_stateless( app.sip_endpt, rdata, 
00483                                        500, &reason,
00484                                        NULL, NULL);
00485         return PJ_TRUE;
00486     }
00487 
00488     /* Alloc call structure. */
00489     call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
00490 
00491     /* Create SDP from PJMEDIA */
00492     if (app.real_sdp) {
00493         status = pjmedia_endpt_create_sdp(app.med_endpt, rdata->tp_info.pool, 
00494                                           app.skinfo_cnt, app.skinfo, 
00495                                           &sdp);
00496     } else {
00497         sdp = app.dummy_sdp;
00498     }
00499 
00500     /* Create UAS invite session */
00501     status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
00502     if (status != PJ_SUCCESS) {
00503         pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
00504         pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
00505         return PJ_TRUE;
00506     }
00507     
00508     /* Send 100/Trying if needed */
00509     if (app.server.send_trying) {
00510         status = send_response(call->inv, rdata, 100, &has_initial);
00511         if (status != PJ_SUCCESS)
00512             return PJ_TRUE;
00513     }
00514 
00515     /* Send 180/Ringing if needed */
00516     if (app.server.send_ringing) {
00517         status = send_response(call->inv, rdata, 180, &has_initial);
00518         if (status != PJ_SUCCESS)
00519             return PJ_TRUE;
00520     }
00521 
00522     /* Simulate call processing delay */
00523     if (app.server.delay) {
00524         pj_time_val delay;
00525 
00526         call->ans_timer.id = 1;
00527         call->ans_timer.user_data = call;
00528         call->ans_timer.cb = &answer_timer_cb;
00529         
00530         delay.sec = 0;
00531         delay.msec = app.server.delay;
00532         pj_time_val_normalize(&delay);
00533 
00534         pjsip_endpt_schedule_timer(app.sip_endpt, &call->ans_timer, &delay);
00535 
00536     } else {
00537         /* Send the 200 response immediately . */  
00538         status = send_response(call->inv, rdata, 200, &has_initial);
00539         PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return PJ_TRUE);
00540     }
00541 
00542     /* Done */
00543     app.server.cur_state.call_cnt++;
00544 
00545     return PJ_TRUE;
00546 }
00547 
00548 
00549 
00550 /**************************************************************************
00551  * Default handler when incoming request is not handled by any other
00552  * modules.
00553  */
00554 static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata);
00555 
00556 /* Module to handle incoming requests statelessly.
00557  */
00558 static pjsip_module mod_responder =
00559 {
00560     NULL, NULL,                     /* prev, next.              */
00561     { "mod-responder", 13 },        /* Name.                    */
00562     -1,                             /* Id                       */
00563     PJSIP_MOD_PRIORITY_APPLICATION+1, /* Priority               */
00564     NULL,                           /* load()                   */
00565     NULL,                           /* start()                  */
00566     NULL,                           /* stop()                   */
00567     NULL,                           /* unload()                 */
00568     &mod_responder_on_rx_request,   /* on_rx_request()          */
00569     NULL,                           /* on_rx_response()         */
00570     NULL,                           /* on_tx_request.           */
00571     NULL,                           /* on_tx_response()         */
00572     NULL,                           /* on_tsx_state()           */
00573 };
00574 
00575 
00576 static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata)
00577 {
00578     const pj_str_t reason = pj_str("Not expecting request at this URI");
00579 
00580     /*
00581      * Respond any requests (except ACK!) with 500.
00582      */
00583     if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
00584         pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, &reason,
00585                                       NULL, NULL);
00586     }
00587 
00588     return PJ_TRUE;
00589 }
00590 
00591 
00592 
00593 /*****************************************************************************
00594  * Below is a simple module to log all incoming and outgoing SIP messages
00595  */
00596 
00597 
00598 /* Notification on incoming messages */
00599 static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
00600 {
00601     PJ_LOG(3,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
00602                          "%.*s\n"
00603                          "--end msg--",
00604                          rdata->msg_info.len,
00605                          pjsip_rx_data_get_info(rdata),
00606                          rdata->tp_info.transport->type_name,
00607                          rdata->pkt_info.src_name,
00608                          rdata->pkt_info.src_port,
00609                          (int)rdata->msg_info.len,
00610                          rdata->msg_info.msg_buf));
00611     
00612     /* Always return false, otherwise messages will not get processed! */
00613     return PJ_FALSE;
00614 }
00615 
00616 /* Notification on outgoing messages */
00617 static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
00618 {
00619     
00620     /* Important note:
00621      *  tp_info field is only valid after outgoing messages has passed
00622      *  transport layer. So don't try to access tp_info when the module
00623      *  has lower priority than transport layer.
00624      */
00625 
00626     PJ_LOG(3,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
00627                          "%.*s\n"
00628                          "--end msg--",
00629                          (tdata->buf.cur - tdata->buf.start),
00630                          pjsip_tx_data_get_info(tdata),
00631                          tdata->tp_info.transport->type_name,
00632                          tdata->tp_info.dst_name,
00633                          tdata->tp_info.dst_port,
00634                          (int)(tdata->buf.cur - tdata->buf.start),
00635                          tdata->buf.start));
00636 
00637     /* Always return success, otherwise message will not get sent! */
00638     return PJ_SUCCESS;
00639 }
00640 
00641 /* The module instance. */
00642 static pjsip_module msg_logger = 
00643 {
00644     NULL, NULL,                         /* prev, next.          */
00645     { "mod-siprtp-log", 14 },           /* Name.                */
00646     -1,                                 /* Id                   */
00647     PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority            */
00648     NULL,                               /* load()               */
00649     NULL,                               /* start()              */
00650     NULL,                               /* stop()               */
00651     NULL,                               /* unload()             */
00652     &logger_on_rx_msg,                  /* on_rx_request()      */
00653     &logger_on_rx_msg,                  /* on_rx_response()     */
00654     &logger_on_tx_msg,                  /* on_tx_request.       */
00655     &logger_on_tx_msg,                  /* on_tx_response()     */
00656     NULL,                               /* on_tsx_state()       */
00657 
00658 };
00659 
00660 
00661 
00662 /**************************************************************************
00663  * Test Client.
00664  */
00665 
00666 static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata);
00667 
00668 static void call_on_media_update( pjsip_inv_session *inv,
00669                                   pj_status_t status);
00670 static void call_on_state_changed( pjsip_inv_session *inv, 
00671                                    pjsip_event *e);
00672 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
00673 
00674 
00675 /* Module to handle incoming requests callly.
00676  */
00677 static pjsip_module mod_test =
00678 {
00679     NULL, NULL,                     /* prev, next.              */
00680     { "mod-test", 8 },              /* Name.                    */
00681     -1,                             /* Id                       */
00682     PJSIP_MOD_PRIORITY_APPLICATION, /* Priority                 */
00683     NULL,                           /* load()                   */
00684     NULL,                           /* start()                  */
00685     NULL,                           /* stop()                   */
00686     NULL,                           /* unload()                 */
00687     NULL,                           /* on_rx_request()          */
00688     &mod_test_on_rx_response,       /* on_rx_response()         */
00689     NULL,                           /* on_tx_request.           */
00690     NULL,                           /* on_tx_response()         */
00691     NULL,                           /* on_tsx_state()           */
00692 };
00693 
00694 
00695 static void report_completion(int status_code)
00696 {
00697     app.client.job_finished++;
00698     if (status_code >= 200 && status_code < 800)
00699         app.client.response_codes[status_code]++;
00700     app.client.total_responses++;
00701     pj_gettimeofday(&app.client.last_completion);
00702 }
00703 
00704 
00705 /* Handler when response is received. */
00706 static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata)
00707 {
00708     if (pjsip_rdata_get_tsx(rdata) == NULL) {
00709         report_completion(rdata->msg_info.msg->line.status.code);
00710     }
00711 
00712     return PJ_TRUE;
00713 }
00714 
00715 
00716 /*
00717  * Create app
00718  */
00719 static pj_status_t create_app(void)
00720 {
00721     pj_status_t status;
00722 
00723     status = pj_init();
00724     if (status != PJ_SUCCESS) {
00725         app_perror(THIS_FILE, "Error initializing pjlib", status);
00726         return status;
00727     }
00728 
00729     /* init PJLIB-UTIL: */
00730     status = pjlib_util_init();
00731     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00732 
00733     /* Must create a pool factory before we can allocate any memory. */
00734     pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 
00735                          CACHING_POOL_SIZE);
00736 
00737     /* Create application pool for misc. */
00738     app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
00739 
00740     /* Create the endpoint: */
00741     status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr, 
00742                                 &app.sip_endpt);
00743     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00744 
00745 
00746     return status;
00747 }
00748 
00749 
00750 /*
00751  * Init SIP stack
00752  */
00753 static pj_status_t init_sip()
00754 {
00755     pj_status_t status = -1;
00756 
00757     /* Add UDP/TCP transport. */
00758     {
00759         pj_sockaddr_in addr;
00760         pjsip_host_port addrname;
00761         const char *transport_type = NULL;
00762 
00763         pj_bzero(&addr, sizeof(addr));
00764         addr.sin_family = pj_AF_INET();
00765         addr.sin_addr.s_addr = 0;
00766         addr.sin_port = pj_htons((pj_uint16_t)app.local_port);
00767 
00768         if (app.local_addr.slen) {
00769             addrname.host = app.local_addr;
00770             addrname.port = 5060;
00771         } 
00772         if (app.local_port != 0)
00773             addrname.port = app.local_port;
00774 
00775         if (0) {
00776 #if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0
00777         } else if (app.use_tcp) {
00778             pj_sockaddr_in local_addr;
00779             pjsip_tpfactory *tpfactory;
00780             
00781             transport_type = "tcp";
00782             pj_sockaddr_in_init(&local_addr, 0, (pj_uint16_t)app.local_port);
00783             status = pjsip_tcp_transport_start(app.sip_endpt, &local_addr,
00784                                                app.thread_count, &tpfactory);
00785             if (status == PJ_SUCCESS) {
00786                 app.local_addr = tpfactory->addr_name.host;
00787                 app.local_port = tpfactory->addr_name.port;
00788             }
00789 #endif
00790         } else {
00791             pjsip_transport *tp;
00792 
00793             transport_type = "udp";
00794             status = pjsip_udp_transport_start(app.sip_endpt, &addr, 
00795                                                (app.local_addr.slen ? &addrname:NULL),
00796                                                app.thread_count, &tp);
00797             if (status == PJ_SUCCESS) {
00798                 app.local_addr = tp->local_name.host;
00799                 app.local_port = tp->local_name.port;
00800             }
00801 
00802         }
00803         if (status != PJ_SUCCESS) {
00804             app_perror(THIS_FILE, "Unable to start transport", status);
00805             return status;
00806         }
00807 
00808         app.local_uri.ptr = pj_pool_alloc(app.pool, 128);
00809         app.local_uri.slen = pj_ansi_sprintf(app.local_uri.ptr, 
00810                                              "<sip:pjsip-perf@%.*s:%d;transport=%s>",
00811                                              (int)app.local_addr.slen,
00812                                              app.local_addr.ptr,
00813                                              app.local_port,
00814                                              transport_type);
00815 
00816         app.local_contact = app.local_uri;
00817     }
00818 
00819     /* 
00820      * Init transaction layer.
00821      * This will create/initialize transaction hash tables etc.
00822      */
00823     status = pjsip_tsx_layer_init_module(app.sip_endpt);
00824     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00825 
00826     /*  Initialize UA layer. */
00827     status = pjsip_ua_init_module( app.sip_endpt, NULL );
00828     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00829 
00830     /* Initialize 100rel support */
00831     status = pjsip_100rel_init_module(app.sip_endpt);
00832     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00833 
00834     /*  Init invite session module. */
00835     {
00836         pjsip_inv_callback inv_cb;
00837 
00838         /* Init the callback for INVITE session: */
00839         pj_bzero(&inv_cb, sizeof(inv_cb));
00840         inv_cb.on_state_changed = &call_on_state_changed;
00841         inv_cb.on_new_session = &call_on_forked;
00842         inv_cb.on_media_update = &call_on_media_update;
00843 
00844         /* Initialize invite session module:  */
00845         status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
00846         PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00847     }
00848 
00849     /* Register our module to receive incoming requests. */
00850     status = pjsip_endpt_register_module( app.sip_endpt, &mod_test);
00851     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00852 
00853 
00854     /* Register stateless server module */
00855     status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateless_server);
00856     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00857 
00858     /* Register default responder module */
00859     status = pjsip_endpt_register_module( app.sip_endpt, &mod_responder);
00860     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00861 
00862     /* Register stateless server module */
00863     status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateful_server);
00864     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00865 
00866 
00867     /* Register call server module */
00868     status = pjsip_endpt_register_module( app.sip_endpt, &mod_call_server);
00869     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00870 
00871 
00872     /* Done */
00873     return PJ_SUCCESS;
00874 }
00875 
00876 
00877 /*
00878  * Destroy SIP
00879  */
00880 static void destroy_app()
00881 {
00882     unsigned i;
00883 
00884     app.thread_quit = 1;
00885     for (i=0; i<app.thread_count; ++i) {
00886         if (app.thread[i]) {
00887             pj_thread_join(app.thread[i]);
00888             pj_thread_destroy(app.thread[i]);
00889             app.thread[i] = NULL;
00890         }
00891     }
00892 
00893     if (app.sip_endpt) {
00894         pjsip_endpt_destroy(app.sip_endpt);
00895         app.sip_endpt = NULL;
00896     }
00897 
00898     if (app.pool) {
00899         pj_pool_release(app.pool);
00900         app.pool = NULL;
00901         PJ_LOG(3,(THIS_FILE, "Peak memory size: %uMB",
00902                              app.cp.peak_used_size / 1000000));
00903         pj_caching_pool_destroy(&app.cp);
00904     }
00905 
00906     /* Shutdown PJLIB */
00907     pj_shutdown();
00908 }
00909 
00910 
00911 /*
00912  * Init media stack.
00913  */
00914 static pj_status_t init_media()
00915 {
00916     unsigned    i;
00917     pj_uint16_t rtp_port;
00918     pj_status_t status;
00919 
00920 
00921     /* Initialize media endpoint so that at least error subsystem is properly
00922      * initialized.
00923      */
00924     status = pjmedia_endpt_create(&app.cp.factory, 
00925                                   pjsip_endpt_get_ioqueue(app.sip_endpt), 0, 
00926                                   &app.med_endpt);
00927     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00928 
00929 
00930     /* Must register all codecs to be supported */
00931     pjmedia_codec_register_audio_codecs(app.med_endpt, NULL);
00932 
00933     /* Init dummy socket addresses */
00934     app.skinfo_cnt = 0;
00935     for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
00936         pjmedia_sock_info *skinfo;
00937 
00938         skinfo = &app.skinfo[i];
00939         
00940         pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
00941                             (pj_uint16_t)rtp_port);
00942         pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
00943                             (pj_uint16_t)(rtp_port+1));
00944         app.skinfo_cnt++;
00945     }
00946 
00947     /* Generate dummy SDP */
00948     dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
00949     status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen, 
00950                                &app.dummy_sdp);
00951     if (status != PJ_SUCCESS) {
00952         app_perror(THIS_FILE, "Error parsing dummy SDP", status);
00953         return status;
00954     }
00955 
00956 
00957     /* Done */
00958     return PJ_SUCCESS;
00959 }
00960 
00961 
00962 /* This is notification from the call about media negotiation
00963  * status. This is called for client calls only.
00964  */
00965 static void call_on_media_update( pjsip_inv_session *inv,
00966                                   pj_status_t status)
00967 {
00968     if (status != PJ_SUCCESS) {
00969         pjsip_tx_data *tdata;
00970         pj_status_t status;
00971 
00972         status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE, 
00973                                        NULL, &tdata);
00974         if (status == PJ_SUCCESS && tdata)
00975             status = pjsip_inv_send_msg(inv, tdata);
00976     }
00977 }
00978 
00979 
00980 /* This is notification from the call when the call state has changed.
00981  * This is called for client calls only.
00982  */
00983 static void call_on_state_changed( pjsip_inv_session *inv, 
00984                                    pjsip_event *e)
00985 {
00986     PJ_UNUSED_ARG(e);
00987 
00988     /* Bail out if the session has been counted before */
00989     if (inv->mod_data[mod_test.id] != NULL)
00990         return;
00991 
00992     /* Bail out if this is not an outgoing call */
00993     if (inv->role != PJSIP_UAC_ROLE)
00994         return;
00995 
00996     if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
00997         pjsip_tx_data *tdata;
00998         pj_status_t status;
00999 
01000         //report_completion(200);
01001         //inv->mod_data[mod_test.id] = (void*)1;
01002 
01003         status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
01004         if (status == PJ_SUCCESS && tdata)
01005             status = pjsip_inv_send_msg(inv, tdata);
01006 
01007     } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
01008         report_completion(inv->cause);
01009         inv->mod_data[mod_test.id] = (void*)1;
01010     }
01011 }
01012 
01013 
01014 /* Not implemented for now */
01015 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
01016 {
01017     /* Do nothing */
01018     PJ_UNUSED_ARG(inv);
01019     PJ_UNUSED_ARG(e);
01020 }
01021 
01022 
01023 /*
01024  * Make outgoing call.
01025  */
01026 static pj_status_t make_call(const pj_str_t *dst_uri)
01027 {
01028     struct call *call;
01029     pjsip_dialog *dlg;
01030     pjmedia_sdp_session *sdp;
01031     pjsip_tx_data *tdata;
01032     pj_status_t status;
01033 
01034 
01035     /* Create UAC dialog */
01036     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
01037                                    &app.local_uri,      /* local URI        */
01038                                    &app.local_contact,  /* local Contact    */
01039                                    dst_uri,             /* remote URI       */
01040                                    dst_uri,             /* remote target    */
01041                                    &dlg);               /* dialog           */
01042     if (status != PJ_SUCCESS) {
01043         return status;
01044     }
01045 
01046     /* Create call */
01047     call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
01048 
01049     /* Create SDP */
01050     if (app.real_sdp) {
01051         status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1, 
01052                                           app.skinfo, &sdp);
01053         if (status != PJ_SUCCESS) {
01054             pjsip_dlg_terminate(dlg);
01055             return status;
01056         }
01057     } else
01058         sdp = app.dummy_sdp;
01059 
01060     /* Create the INVITE session. */
01061     status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
01062     if (status != PJ_SUCCESS) {
01063         pjsip_dlg_terminate(dlg);
01064         return status;
01065     }
01066 
01067 
01068     /* Create initial INVITE request.
01069      * This INVITE request will contain a perfectly good request and 
01070      * an SDP body as well.
01071      */
01072     status = pjsip_inv_invite(call->inv, &tdata);
01073     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
01074 
01075 
01076     /* Send initial INVITE request. 
01077      * From now on, the invite session's state will be reported to us
01078      * via the invite session callbacks.
01079      */
01080     status = pjsip_inv_send_msg(call->inv, tdata);
01081     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
01082 
01083 
01084     return PJ_SUCCESS;
01085 }
01086 
01087 
01088 /*
01089  * Verify that valid SIP url is given.
01090  */
01091 static pj_status_t verify_sip_url(const char *c_url)
01092 {
01093     pjsip_uri *p;
01094     pj_pool_t *pool;
01095     char *url;
01096     int len = (c_url ? pj_ansi_strlen(c_url) : 0);
01097 
01098     if (!len) return -1;
01099 
01100     pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
01101     if (!pool) return PJ_ENOMEM;
01102 
01103     url = pj_pool_alloc(pool, len+1);
01104     pj_ansi_strcpy(url, c_url);
01105     url[len] = '\0';
01106 
01107     p = pjsip_parse_uri(pool, url, len, 0);
01108     if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
01109         p = NULL;
01110 
01111     pj_pool_release(pool);
01112     return p ? 0 : -1;
01113 }
01114 
01115 
01116 static void usage(void)
01117 {
01118     printf(
01119         "Usage:\n"
01120         "   pjsip-perf [OPTIONS]        -- to start as server\n"
01121         "   pjsip-perf [OPTIONS] URL    -- to call server (possibly itself)\n"
01122         "\n"
01123         "where:\n"
01124         "   URL                     The SIP URL to be contacted.\n"
01125         "\n"
01126         "Client options:\n"
01127         "   --method=METHOD, -m     Set test method (set to INVITE for call benchmark)\n"
01128         "                           [default: OPTIONS]\n"
01129         "   --count=N, -n           Set total number of requests to initiate\n"
01130         "                           [default=%d]\n"
01131         "   --stateless, -s         Set to operate in stateless mode\n"
01132         "                           [default: stateful]\n"
01133         "   --timeout=SEC, -t       Set client timeout [default=60 sec]\n"
01134         "   --window=COUNT, -w      Set maximum outstanding job [default: %d]\n"
01135         "\n"
01136         "SDP options (client and server):\n"
01137         "   --real-sdp              Generate real SDP from pjmedia, and also perform\n"
01138         "                           proper SDP negotiation [default: dummy]\n"
01139         "\n"
01140         "Client and Server options:\n"
01141         "   --local-port=PORT, -p   Set local port [default: 5060]\n"
01142         "   --use-tcp, -T           Use TCP instead of UDP. Note that when started as\n"
01143         "                           client, you must add ;transport=tcp parameter to URL\n"
01144         "                           [default: no]\n"
01145         "   --thread-count=N        Set number of worker threads [default=1]\n"
01146         "   --trying                Send 100/Trying response (server, default no)\n"
01147         "   --ringing               Send 180/Ringing response (server, default no)\n"
01148         "   --delay=MS, -d          Delay answering call by MS (server, default no)\n"
01149         "\n"
01150         "Misc options:\n"
01151         "   --help, -h              Display this screen\n"
01152         "   --verbose, -v           Verbose logging (put more than once for even more)\n"
01153         "\n"
01154         "When started as server, pjsip-perf can be contacted on the following URIs:\n"
01155         "   - sip:0@server-addr     To handle requests statelessly.\n"
01156         "   - sip:1@server-addr     To handle requests statefully.\n"
01157         "   - sip:2@server-addr     To handle INVITE call.\n",
01158         DEFAULT_COUNT, JOB_WINDOW);
01159 }
01160 
01161 
01162 static int my_atoi(const char *s)
01163 {
01164     pj_str_t ss = pj_str((char*)s);
01165     return pj_strtoul(&ss);
01166 }
01167 
01168 
01169 static pj_status_t init_options(int argc, char *argv[])
01170 {
01171     enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP, OPT_TRYING, OPT_RINGING };
01172     struct pj_getopt_option long_options[] = {
01173         { "local-port",     1, 0, 'p' },
01174         { "count",          1, 0, 'c' },
01175         { "thread-count",   1, 0, OPT_THREAD_COUNT },
01176         { "method",         1, 0, 'm' },
01177         { "help",           0, 0, 'h' },
01178         { "stateless",      0, 0, 's' },
01179         { "timeout",        1, 0, 't' },
01180         { "real-sdp",       0, 0, OPT_REAL_SDP },
01181         { "verbose",        0, 0, 'v' },
01182         { "use-tcp",        0, 0, 'T' },
01183         { "window",         1, 0, 'w' },
01184         { "delay",          1, 0, 'd' },
01185         { "trying",         0, 0, OPT_TRYING},
01186         { "ringing",        0, 0, OPT_RINGING},
01187         { NULL, 0, 0, 0 },
01188     };
01189     int c;
01190     int option_index;
01191 
01192     /* Init default application configs */
01193     app.local_port = 5060;
01194     app.thread_count = 1;
01195     app.client.job_count = DEFAULT_COUNT;
01196     app.client.method = *pjsip_get_options_method();
01197     app.client.job_window = c = JOB_WINDOW;
01198     app.client.timeout = 60;
01199     app.log_level = 3;
01200 
01201 
01202     /* Parse options */
01203     pj_optind = 0;
01204     while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:d:hsv", 
01205                             long_options, &option_index))!=-1) 
01206     {
01207         switch (c) {
01208         case 'p':
01209             app.local_port = my_atoi(pj_optarg);
01210             if (app.local_port < 0 || app.local_port > 65535) {
01211                 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
01212                 return -1;
01213             }
01214             break;
01215 
01216         case 'c':
01217             app.client.job_count = my_atoi(pj_optarg);
01218             if (app.client.job_count < 0) {
01219                 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
01220                 return -1;
01221             }
01222             if (app.client.job_count > pjsip_cfg()->tsx.max_count)
01223                 PJ_LOG(3,(THIS_FILE, 
01224                           "Warning: --count value (%d) exceeds maximum "
01225                           "transaction count (%d)", app.client.job_count,
01226                           pjsip_cfg()->tsx.max_count));
01227             break;
01228 
01229         case OPT_THREAD_COUNT:
01230             app.thread_count = my_atoi(pj_optarg);
01231             if (app.thread_count < 1 || app.thread_count > 16) {
01232                 PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
01233                 return -1;
01234             }
01235             break;
01236 
01237         case 'm':
01238             {
01239                 pj_str_t temp = pj_str((char*)pj_optarg);
01240                 pjsip_method_init_np(&app.client.method, &temp);
01241             }
01242             break;
01243 
01244         case 'h':
01245             usage();
01246             return -1;
01247 
01248         case 's':
01249             app.client.stateless = PJ_TRUE;
01250             break;
01251 
01252         case OPT_REAL_SDP:
01253             app.real_sdp = 1;
01254             break;
01255 
01256         case 'v':
01257             app.log_level++;
01258             break;
01259 
01260         case 't':
01261             app.client.timeout = my_atoi(pj_optarg);
01262             if (app.client.timeout < 0 || app.client.timeout > 600) {
01263                 PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
01264                 return -1;
01265             }
01266             break;
01267 
01268         case 'w':
01269             app.client.job_window = my_atoi(pj_optarg);
01270             if (app.client.job_window <= 0) {
01271                 PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg));
01272                 return -1;
01273             }
01274             break;
01275 
01276         case 'T':
01277             app.use_tcp = PJ_TRUE;
01278             break;
01279 
01280         case 'd':
01281             app.server.delay = my_atoi(pj_optarg);
01282             if (app.server.delay > 3600) {
01283                 PJ_LOG(3,(THIS_FILE, "I think --delay %s is too long", 
01284                           pj_optarg));
01285                 return -1;
01286             }
01287             break;
01288 
01289         case OPT_TRYING:
01290             app.server.send_trying = 1;
01291             break;
01292 
01293         case OPT_RINGING:
01294             app.server.send_ringing = 1;
01295             break;
01296 
01297         default:
01298             PJ_LOG(1,(THIS_FILE, 
01299                       "Invalid argument. Use --help to see help"));
01300             return -1;
01301         }
01302     }
01303 
01304     if (pj_optind != argc) {
01305 
01306         if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
01307             PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
01308             return -1;
01309         }
01310         app.client.dst_uri = pj_str(argv[pj_optind]);
01311         
01312         pj_optind++;
01313 
01314     }
01315 
01316     if (pj_optind != argc) {
01317         PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
01318         return -1;
01319     }
01320 
01321     return 0;
01322 }
01323 
01324 
01325 /* Send one stateless request */
01326 static pj_status_t submit_stateless_job(void)
01327 {
01328     pjsip_tx_data *tdata;
01329     pj_status_t status;
01330 
01331     status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method, 
01332                                         &app.client.dst_uri, &app.local_uri,
01333                                         &app.client.dst_uri, &app.local_contact,
01334                                         NULL, -1, NULL, &tdata);
01335     if (status != PJ_SUCCESS) {
01336         app_perror(THIS_FILE, "Error creating request", status);
01337         report_completion(701);
01338         return status;
01339     }
01340 
01341     status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
01342                                                 NULL);
01343     if (status != PJ_SUCCESS) {
01344         pjsip_tx_data_dec_ref(tdata);
01345         app_perror(THIS_FILE, "Error sending stateless request", status);
01346         report_completion(701);
01347         return status;
01348     }
01349 
01350     return PJ_SUCCESS;
01351 }
01352 
01353 
01354 /* This callback is called when client transaction state has changed */
01355 static void tsx_completion_cb(void *token, pjsip_event *event)
01356 {
01357     pjsip_transaction *tsx;
01358 
01359     PJ_UNUSED_ARG(token);
01360 
01361     if (event->type != PJSIP_EVENT_TSX_STATE)
01362         return;
01363 
01364     tsx = event->body.tsx_state.tsx;
01365 
01366     if (tsx->mod_data[mod_test.id] != NULL) {
01367         /* This transaction has been calculated before */
01368         return;
01369     }
01370 
01371     if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
01372         report_completion(tsx->status_code);
01373         tsx->mod_data[mod_test.id] = (void*)1;
01374     }
01375     else if (tsx->method.id == PJSIP_INVITE_METHOD &&
01376              tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
01377 
01378         report_completion(tsx->status_code);
01379         tsx->mod_data[mod_test.id] = (void*)1;
01380         
01381     } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
01382 
01383         report_completion(tsx->status_code);
01384         tsx->mod_data[mod_test.id] = (void*)1;
01385 
01386         TERMINATE_TSX(tsx, tsx->status_code);
01387     }
01388 }
01389 
01390 
01391 /* Send one stateful request */
01392 static pj_status_t submit_job(void)
01393 {
01394     pjsip_tx_data *tdata;
01395     pj_status_t status;
01396 
01397     status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method, 
01398                                         &app.client.dst_uri, &app.local_uri,
01399                                         &app.client.dst_uri, &app.local_contact,
01400                                         NULL, -1, NULL, &tdata);
01401     if (status != PJ_SUCCESS) {
01402         app_perror(THIS_FILE, "Error creating request", status);
01403         report_completion(701);
01404         return status;
01405     }
01406 
01407     status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL, 
01408                                       &tsx_completion_cb);
01409     if (status != PJ_SUCCESS) {
01410         app_perror(THIS_FILE, "Error sending stateful request", status);
01411         //should have been reported by tsx_completion_cb().
01412         //report_completion(701);
01413         //No longer necessary (r777)
01414         //pjsip_tx_data_dec_ref(tdata);
01415     }
01416     return status;
01417 }
01418 
01419 
01420 /* Client worker thread */
01421 static int client_thread(void *arg)
01422 {
01423     pj_time_val end_time, last_report, now;
01424     unsigned thread_index = (unsigned)(long)arg;
01425     unsigned cycle = 0, last_cycle = 0;
01426 
01427     pj_thread_sleep(100);
01428 
01429     pj_gettimeofday(&end_time);
01430     end_time.sec += app.client.timeout;
01431 
01432     pj_gettimeofday(&last_report);
01433 
01434     if (app.client.first_request.sec == 0) {
01435         pj_gettimeofday(&app.client.first_request);
01436     }
01437 
01438     /* Submit all jobs */
01439     while (app.client.job_submitted < app.client.job_count && !app.thread_quit){
01440         pj_time_val timeout = { 0, 1 };
01441         unsigned i;
01442         int outstanding;
01443         pj_status_t status;
01444 
01445         /* Calculate current outstanding job */
01446         outstanding = app.client.job_submitted - app.client.job_finished;
01447 
01448         /* Update stats on max outstanding jobs */
01449         if (outstanding > (int)app.client.stat_max_window)
01450             app.client.stat_max_window = outstanding;
01451 
01452         /* Wait if there are more pending jobs than allowed in the
01453          * window. But spawn a new job anyway if no events are happening
01454          * after we wait for some time.
01455          */
01456         for (i=0; outstanding > (int)app.client.job_window && i<1000; ++i) {
01457             pj_time_val wait = { 0, 500 };
01458             unsigned count = 0;
01459 
01460             pjsip_endpt_handle_events2(app.sip_endpt, &wait, &count);
01461             outstanding = app.client.job_submitted - app.client.job_finished;
01462 
01463             if (count == 0)
01464                 break;
01465 
01466             ++cycle;
01467         }
01468 
01469 
01470         /* Submit one job */
01471         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01472             status = make_call(&app.client.dst_uri);
01473         } else if (app.client.stateless) {
01474             status = submit_stateless_job();
01475         } else {
01476             status = submit_job();
01477         }
01478 
01479         ++app.client.job_submitted;
01480         ++cycle;
01481 
01482         /* Handle event */
01483         pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
01484 
01485         /* Check for time out, also print report */
01486         if (cycle - last_cycle >= 500) {
01487             pj_gettimeofday(&now);
01488             if (PJ_TIME_VAL_GTE(now, end_time)) {
01489                 break;
01490             }
01491             last_cycle = cycle;
01492 
01493             
01494             if (thread_index == 0 && now.sec-last_report.sec >= 2) {
01495                 printf("\r%d jobs started, %d completed...   ",
01496                        app.client.job_submitted, app.client.job_finished);
01497                 fflush(stdout);
01498                 last_report = now;
01499             }
01500         }
01501     }
01502 
01503     if (app.client.requests_sent.sec == 0) {
01504         pj_gettimeofday(&app.client.requests_sent);
01505     }
01506 
01507 
01508     if (thread_index == 0) {
01509         printf("\r%d jobs started, %d completed%s\n",
01510                app.client.job_submitted, app.client.job_finished,
01511                (app.client.job_submitted!=app.client.job_finished ? 
01512                 ", waiting..." : ".") );
01513         fflush(stdout);
01514     }
01515 
01516     /* Wait until all jobs completes, or timed out */
01517     pj_gettimeofday(&now);
01518     while (PJ_TIME_VAL_LT(now, end_time) && 
01519            app.client.job_finished < app.client.job_count && 
01520            !app.thread_quit) 
01521     {
01522         pj_time_val timeout = { 0, 1 };
01523         unsigned i;
01524 
01525         for (i=0; i<1000; ++i) {
01526             unsigned count;
01527             count = 0;
01528             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01529             if (count == 0)
01530                 break;
01531         }
01532 
01533         pj_gettimeofday(&now);
01534     }
01535 
01536     /* Wait couple of seconds to let jobs completes (e.g. ACKs to be sent)  */
01537     pj_gettimeofday(&now);
01538     end_time = now;
01539     end_time.sec += 2;
01540     while (PJ_TIME_VAL_LT(now, end_time)) 
01541     {
01542         pj_time_val timeout = { 0, 1 };
01543         unsigned i;
01544 
01545         for (i=0; i<1000; ++i) {
01546             unsigned count;
01547             count = 0;
01548             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01549             if (count == 0)
01550                 break;
01551         }
01552 
01553         pj_gettimeofday(&now);
01554     }
01555 
01556     return 0;
01557 }
01558 
01559 
01560 static const char *good_number(char *buf, pj_int32_t val)
01561 {
01562     if (val < 1000) {
01563         pj_ansi_sprintf(buf, "%d", val);
01564     } else if (val < 1000000) {
01565         pj_ansi_sprintf(buf, "%d.%dK", 
01566                         val / 1000,
01567                         (val % 1000) / 100);
01568     } else {
01569         pj_ansi_sprintf(buf, "%d.%02dM", 
01570                         val / 1000000,
01571                         (val % 1000000) / 10000);
01572     }
01573 
01574     return buf;
01575 }
01576 
01577 
01578 static int server_thread(void *arg)
01579 {
01580     pj_time_val timeout = { 0, 1 };
01581     unsigned thread_index = (unsigned)(long)arg;
01582     pj_time_val last_report, next_report;
01583 
01584     pj_gettimeofday(&last_report);
01585     next_report = last_report;
01586     next_report.sec++;
01587 
01588     while (!app.thread_quit) {
01589         pj_time_val now;
01590         unsigned i;
01591 
01592         for (i=0; i<100; ++i) {
01593             unsigned count = 0;
01594             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01595             if (count == 0)
01596                 break;
01597         }
01598 
01599         if (thread_index == 0) {
01600             pj_gettimeofday(&now);
01601 
01602             if (PJ_TIME_VAL_GTE(now, next_report)) {
01603                 pj_time_val tmp;
01604                 unsigned msec;
01605                 unsigned stateless, stateful, call;
01606                 char str_stateless[32], str_stateful[32], str_call[32];
01607 
01608                 tmp = now;
01609                 PJ_TIME_VAL_SUB(tmp, last_report);
01610                 msec = PJ_TIME_VAL_MSEC(tmp);
01611 
01612                 last_report = now;
01613                 next_report = last_report;
01614                 next_report.sec++;
01615 
01616                 stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
01617                 stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
01618                 call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
01619 
01620                 good_number(str_stateless, app.server.cur_state.stateless_cnt);
01621                 good_number(str_stateful, app.server.cur_state.stateful_cnt);
01622                 good_number(str_call, app.server.cur_state.call_cnt);
01623 
01624                 printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s)       \r",
01625                        str_stateless, stateless*1000/msec,
01626                        str_stateful, stateful*1000/msec,
01627                        str_call, call*1000/msec);
01628                 fflush(stdout);
01629 
01630                 app.server.prev_state = app.server.cur_state;
01631             }
01632         }
01633     }
01634 
01635     return 0;
01636 }
01637 
01638 static void write_report(const char *msg)
01639 {
01640     puts(msg);
01641 
01642 #if defined(PJ_WIN32) && PJ_WIN32!=0
01643     OutputDebugString(msg);
01644     OutputDebugString("\n");
01645 #endif
01646 }
01647 
01648 
01649 int main(int argc, char *argv[])
01650 {
01651     static char report[1024];
01652 
01653     printf("PJSIP Performance Measurement Tool v%s\n"
01654            "(c)2006 pjsip.org\n\n",
01655            PJ_VERSION);
01656 
01657     if (create_app() != 0)
01658         return 1;
01659 
01660     if (init_options(argc, argv) != 0)
01661         return 1;
01662 
01663     if (init_sip() != 0)
01664         return 1;
01665 
01666     if (init_media() != 0)
01667         return 1;
01668 
01669     pj_log_set_level(app.log_level);
01670 
01671     if (app.log_level > 4) {
01672         pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
01673     }
01674 
01675 
01676     /* Misc infos */
01677     if (app.client.dst_uri.slen != 0) {
01678         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01679             if (app.client.stateless) {
01680                 PJ_LOG(3,(THIS_FILE, 
01681                           "Info: --stateless option makes no sense for INVITE,"
01682                           " ignored."));
01683             }
01684         }
01685 
01686     }
01687 
01688 
01689 
01690     if (app.client.dst_uri.slen) {
01691         /* Client mode */
01692         pj_status_t status;
01693         char test_type[64];
01694         unsigned msec_req, msec_res;
01695         unsigned i;
01696 
01697         /* Get the job name */
01698         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01699             pj_ansi_strcpy(test_type, "INVITE calls");
01700         } else if (app.client.stateless) {
01701             pj_ansi_sprintf(test_type, "stateless %.*s requests",
01702                             (int)app.client.method.name.slen,
01703                             app.client.method.name.ptr);
01704         } else {
01705             pj_ansi_sprintf(test_type, "stateful %.*s requests",
01706                             (int)app.client.method.name.slen,
01707                             app.client.method.name.ptr);
01708         }
01709         
01710 
01711         printf("Sending %d %s to '%.*s' with %d maximum outstanding jobs, please wait..\n", 
01712                   app.client.job_count, test_type,
01713                   (int)app.client.dst_uri.slen, app.client.dst_uri.ptr,
01714                   app.client.job_window);
01715 
01716         for (i=0; i<app.thread_count; ++i) {
01717             status = pj_thread_create(app.pool, NULL, &client_thread, 
01718                                       (void*)(long)i, 0, 0, &app.thread[i]);
01719             if (status != PJ_SUCCESS) {
01720                 app_perror(THIS_FILE, "Unable to create thread", status);
01721                 return 1;
01722             }
01723         }
01724 
01725         for (i=0; i<app.thread_count; ++i) {
01726             pj_thread_join(app.thread[i]);
01727             app.thread[i] = NULL;
01728         }
01729 
01730         if (app.client.last_completion.sec) {
01731             pj_time_val duration;
01732             duration = app.client.last_completion;
01733             PJ_TIME_VAL_SUB(duration, app.client.first_request);
01734             msec_res = PJ_TIME_VAL_MSEC(duration);
01735         } else {
01736             msec_res = app.client.timeout * 1000;
01737         }
01738 
01739         if (msec_res == 0) msec_res = 1;
01740 
01741         if (app.client.requests_sent.sec) {
01742             pj_time_val duration;
01743             duration = app.client.requests_sent;
01744             PJ_TIME_VAL_SUB(duration, app.client.first_request);
01745             msec_req = PJ_TIME_VAL_MSEC(duration);
01746         } else {
01747             msec_req = app.client.timeout * 1000;
01748         }
01749 
01750         if (msec_req == 0) msec_req = 1;
01751 
01752         if (app.client.job_submitted < app.client.job_count)
01753             puts("\ntimed-out!\n");
01754         else
01755             puts("\ndone.\n");
01756 
01757         pj_ansi_snprintf(
01758             report, sizeof(report),
01759             "Total %d %s sent in %d ms at rate of %d/sec\n"
01760             "Total %d responses receieved in %d ms at rate of %d/sec:",
01761             app.client.job_submitted, test_type, msec_req, 
01762             app.client.job_submitted * 1000 / msec_req,
01763             app.client.total_responses, msec_res,
01764             app.client.total_responses*1000/msec_res);
01765         write_report(report);
01766 
01767         /* Print detailed response code received */
01768         pj_ansi_sprintf(report, "\nDetailed responses received:");
01769         write_report(report);
01770 
01771         for (i=0; i<PJ_ARRAY_SIZE(app.client.response_codes); ++i) {
01772             const pj_str_t *reason;
01773 
01774             if (app.client.response_codes[i] == 0)
01775                 continue;
01776 
01777             reason = pjsip_get_status_text(i);
01778             pj_ansi_snprintf( report, sizeof(report),
01779                               " - %d responses:  %7d     (%.*s)",
01780                               i, app.client.response_codes[i],
01781                               (int)reason->slen, reason->ptr);
01782             write_report(report);
01783         }
01784 
01785         /* Total responses and rate */
01786         pj_ansi_snprintf( report, sizeof(report),
01787             "                    ------\n"
01788             " TOTAL responses:  %7d (rate=%d/sec)\n",
01789             app.client.total_responses, 
01790             app.client.total_responses*1000/msec_res);
01791 
01792         write_report(report);
01793 
01794         pj_ansi_sprintf(report, "Maximum outstanding job: %d", 
01795                         app.client.stat_max_window);
01796         write_report(report);
01797 
01798 
01799     } else {
01800         /* Server mode */
01801         char s[10], *unused;
01802         pj_status_t status;
01803         unsigned i;
01804 
01805         puts("pjsip-perf started in server-mode");
01806 
01807         printf("Receiving requests on the following URIs:\n"
01808                "  sip:0@%.*s:%d%s    for stateless handling\n"
01809                "  sip:1@%.*s:%d%s    for stateful handling\n"
01810                "  sip:2@%.*s:%d%s    for call handling\n",
01811                (int)app.local_addr.slen,
01812                app.local_addr.ptr,
01813                app.local_port,
01814                (app.use_tcp ? ";transport=tcp" : ""),
01815                (int)app.local_addr.slen,
01816                app.local_addr.ptr,
01817                app.local_port,
01818                (app.use_tcp ? ";transport=tcp" : ""),
01819                (int)app.local_addr.slen,
01820                app.local_addr.ptr,
01821                app.local_port,
01822                (app.use_tcp ? ";transport=tcp" : ""));
01823         printf("INVITE with non-matching user part will be handled call-statefully\n");
01824 
01825         for (i=0; i<app.thread_count; ++i) {
01826             status = pj_thread_create(app.pool, NULL, &server_thread, 
01827                                       (void*)(long)i, 0, 0, &app.thread[i]);
01828             if (status != PJ_SUCCESS) {
01829                 app_perror(THIS_FILE, "Unable to create thread", status);
01830                 return 1;
01831             }
01832         }
01833 
01834         puts("\nPress <ENTER> to quit\n");
01835         fflush(stdout);
01836         unused = fgets(s, sizeof(s), stdin);
01837         PJ_UNUSED_ARG(unused);
01838 
01839         app.thread_quit = PJ_TRUE;
01840         for (i=0; i<app.thread_count; ++i) {
01841             pj_thread_join(app.thread[i]);
01842             app.thread[i] = NULL;
01843         }
01844 
01845         puts("");
01846     }
01847 
01848 
01849     destroy_app();
01850 
01851     return 0;
01852 }
01853 

 


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