pjsip logo pjsip.org
Open source SIP stack and media stack for presence, im/instant messaging, and multimedia communication

HOME

SIP/media Features
High Performance SIP
Small Footprint SIP
Symbian Port

FAQ

Documentation

Licensing

Download

Development (Trac)

Projects using pjsip

Mailing List

Open Source Links


About: PJLIB, PJLIB-UTIL, PJSIP, and PJMEDIA are created by: Benny Prijono
<bennylp@pjsip.org>


 

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 2408 2009-01-01 22:08:21Z bennylp $ */
00002 /* 
00003  * Copyright (C) 2008-2009 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 #if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
00932     pjmedia_codec_g711_init(app.med_endpt);
00933 #endif
00934 #if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
00935     pjmedia_codec_gsm_init(app.med_endpt);
00936 #endif
00937 #if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
00938     pjmedia_codec_speex_init(app.med_endpt, PJMEDIA_SPEEX_NO_UWB, 3, 3);
00939 #endif
00940 #if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
00941     pjmedia_codec_g722_init(app.med_endpt);
00942 #endif
00943 
00944     /* Init dummy socket addresses */
00945     app.skinfo_cnt = 0;
00946     for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
00947         pjmedia_sock_info *skinfo;
00948 
00949         skinfo = &app.skinfo[i];
00950         
00951         pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
00952                             (pj_uint16_t)rtp_port);
00953         pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
00954                             (pj_uint16_t)(rtp_port+1));
00955         app.skinfo_cnt++;
00956     }
00957 
00958     /* Generate dummy SDP */
00959     dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
00960     status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen, 
00961                                &app.dummy_sdp);
00962     if (status != PJ_SUCCESS) {
00963         app_perror(THIS_FILE, "Error parsing dummy SDP", status);
00964         return status;
00965     }
00966 
00967 
00968     /* Done */
00969     return PJ_SUCCESS;
00970 }
00971 
00972 
00973 /* This is notification from the call about media negotiation
00974  * status. This is called for client calls only.
00975  */
00976 static void call_on_media_update( pjsip_inv_session *inv,
00977                                   pj_status_t status)
00978 {
00979     if (status != PJ_SUCCESS) {
00980         pjsip_tx_data *tdata;
00981         pj_status_t status;
00982 
00983         status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE, 
00984                                        NULL, &tdata);
00985         if (status == PJ_SUCCESS && tdata)
00986             status = pjsip_inv_send_msg(inv, tdata);
00987     }
00988 }
00989 
00990 
00991 /* This is notification from the call when the call state has changed.
00992  * This is called for client calls only.
00993  */
00994 static void call_on_state_changed( pjsip_inv_session *inv, 
00995                                    pjsip_event *e)
00996 {
00997     PJ_UNUSED_ARG(e);
00998 
00999     /* Bail out if the session has been counted before */
01000     if (inv->mod_data[mod_test.id] != NULL)
01001         return;
01002 
01003     /* Bail out if this is not an outgoing call */
01004     if (inv->role != PJSIP_UAC_ROLE)
01005         return;
01006 
01007     if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
01008         pjsip_tx_data *tdata;
01009         pj_status_t status;
01010 
01011         //report_completion(200);
01012         //inv->mod_data[mod_test.id] = (void*)1;
01013 
01014         status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
01015         if (status == PJ_SUCCESS && tdata)
01016             status = pjsip_inv_send_msg(inv, tdata);
01017 
01018     } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
01019         report_completion(inv->cause);
01020         inv->mod_data[mod_test.id] = (void*)1;
01021     }
01022 }
01023 
01024 
01025 /* Not implemented for now */
01026 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
01027 {
01028     /* Do nothing */
01029     PJ_UNUSED_ARG(inv);
01030     PJ_UNUSED_ARG(e);
01031 }
01032 
01033 
01034 /*
01035  * Make outgoing call.
01036  */
01037 static pj_status_t make_call(const pj_str_t *dst_uri)
01038 {
01039     struct call *call;
01040     pjsip_dialog *dlg;
01041     pjmedia_sdp_session *sdp;
01042     pjsip_tx_data *tdata;
01043     pj_status_t status;
01044 
01045 
01046     /* Create UAC dialog */
01047     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
01048                                    &app.local_uri,      /* local URI        */
01049                                    &app.local_contact,  /* local Contact    */
01050                                    dst_uri,             /* remote URI       */
01051                                    dst_uri,             /* remote target    */
01052                                    &dlg);               /* dialog           */
01053     if (status != PJ_SUCCESS) {
01054         return status;
01055     }
01056 
01057     /* Create call */
01058     call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
01059 
01060     /* Create SDP */
01061     if (app.real_sdp) {
01062         status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1, 
01063                                           app.skinfo, &sdp);
01064         if (status != PJ_SUCCESS) {
01065             pjsip_dlg_terminate(dlg);
01066             return status;
01067         }
01068     } else
01069         sdp = app.dummy_sdp;
01070 
01071     /* Create the INVITE session. */
01072     status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
01073     if (status != PJ_SUCCESS) {
01074         pjsip_dlg_terminate(dlg);
01075         return status;
01076     }
01077 
01078 
01079     /* Create initial INVITE request.
01080      * This INVITE request will contain a perfectly good request and 
01081      * an SDP body as well.
01082      */
01083     status = pjsip_inv_invite(call->inv, &tdata);
01084     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
01085 
01086 
01087     /* Send initial INVITE request. 
01088      * From now on, the invite session's state will be reported to us
01089      * via the invite session callbacks.
01090      */
01091     status = pjsip_inv_send_msg(call->inv, tdata);
01092     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
01093 
01094 
01095     return PJ_SUCCESS;
01096 }
01097 
01098 
01099 /*
01100  * Verify that valid SIP url is given.
01101  */
01102 static pj_status_t verify_sip_url(const char *c_url)
01103 {
01104     pjsip_uri *p;
01105     pj_pool_t *pool;
01106     char *url;
01107     int len = (c_url ? pj_ansi_strlen(c_url) : 0);
01108 
01109     if (!len) return -1;
01110 
01111     pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
01112     if (!pool) return PJ_ENOMEM;
01113 
01114     url = pj_pool_alloc(pool, len+1);
01115     pj_ansi_strcpy(url, c_url);
01116     url[len] = '\0';
01117 
01118     p = pjsip_parse_uri(pool, url, len, 0);
01119     if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
01120         p = NULL;
01121 
01122     pj_pool_release(pool);
01123     return p ? 0 : -1;
01124 }
01125 
01126 
01127 static void usage(void)
01128 {
01129     printf(
01130         "Usage:\n"
01131         "   pjsip-perf [OPTIONS]        -- to start as server\n"
01132         "   pjsip-perf [OPTIONS] URL    -- to call server (possibly itself)\n"
01133         "\n"
01134         "where:\n"
01135         "   URL                     The SIP URL to be contacted.\n"
01136         "\n"
01137         "Client options:\n"
01138         "   --method=METHOD, -m     Set test method (set to INVITE for call benchmark)\n"
01139         "                           [default: OPTIONS]\n"
01140         "   --count=N, -n           Set total number of requests to initiate\n"
01141         "                           [default=%d]\n"
01142         "   --stateless, -s         Set to operate in stateless mode\n"
01143         "                           [default: stateful]\n"
01144         "   --timeout=SEC, -t       Set client timeout [default=60 sec]\n"
01145         "   --window=COUNT, -w      Set maximum outstanding job [default: %d]\n"
01146         "\n"
01147         "SDP options (client and server):\n"
01148         "   --real-sdp              Generate real SDP from pjmedia, and also perform\n"
01149         "                           proper SDP negotiation [default: dummy]\n"
01150         "\n"
01151         "Client and Server options:\n"
01152         "   --local-port=PORT, -p   Set local port [default: 5060]\n"
01153         "   --use-tcp, -T           Use TCP instead of UDP. Note that when started as\n"
01154         "                           client, you must add ;transport=tcp parameter to URL\n"
01155         "                           [default: no]\n"
01156         "   --thread-count=N        Set number of worker threads [default=1]\n"
01157         "   --trying                Send 100/Trying response (server, default no)\n"
01158         "   --ringing               Send 180/Ringing response (server, default no)\n"
01159         "   --delay=MS, -d          Delay answering call by MS (server, default no)\n"
01160         "\n"
01161         "Misc options:\n"
01162         "   --help, -h              Display this screen\n"
01163         "   --verbose, -v           Verbose logging (put more than once for even more)\n"
01164         "\n"
01165         "When started as server, pjsip-perf can be contacted on the following URIs:\n"
01166         "   - sip:0@server-addr     To handle requests statelessly.\n"
01167         "   - sip:1@server-addr     To handle requests statefully.\n"
01168         "   - sip:2@server-addr     To handle INVITE call.\n",
01169         DEFAULT_COUNT, JOB_WINDOW);
01170 }
01171 
01172 
01173 static int my_atoi(const char *s)
01174 {
01175     pj_str_t ss = pj_str((char*)s);
01176     return pj_strtoul(&ss);
01177 }
01178 
01179 
01180 static pj_status_t init_options(int argc, char *argv[])
01181 {
01182     enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP, OPT_TRYING, OPT_RINGING };
01183     struct pj_getopt_option long_options[] = {
01184         { "local-port",     1, 0, 'p' },
01185         { "count",          1, 0, 'c' },
01186         { "thread-count",   1, 0, OPT_THREAD_COUNT },
01187         { "method",         1, 0, 'm' },
01188         { "help",           0, 0, 'h' },
01189         { "stateless",      0, 0, 's' },
01190         { "timeout",        1, 0, 't' },
01191         { "real-sdp",       0, 0, OPT_REAL_SDP },
01192         { "verbose",        0, 0, 'v' },
01193         { "use-tcp",        0, 0, 'T' },
01194         { "window",         1, 0, 'w' },
01195         { "delay",          1, 0, 'd' },
01196         { "trying",         0, 0, OPT_TRYING},
01197         { "ringing",        0, 0, OPT_RINGING},
01198         { NULL, 0, 0, 0 },
01199     };
01200     int c;
01201     int option_index;
01202 
01203     /* Init default application configs */
01204     app.local_port = 5060;
01205     app.thread_count = 1;
01206     app.client.job_count = DEFAULT_COUNT;
01207     app.client.method = *pjsip_get_options_method();
01208     app.client.job_window = c = JOB_WINDOW;
01209     app.client.timeout = 60;
01210     app.log_level = 3;
01211 
01212 
01213     /* Parse options */
01214     pj_optind = 0;
01215     while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:d:hsv", 
01216                             long_options, &option_index))!=-1) 
01217     {
01218         switch (c) {
01219         case 'p':
01220             app.local_port = my_atoi(pj_optarg);
01221             if (app.local_port < 0 || app.local_port > 65535) {
01222                 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
01223                 return -1;
01224             }
01225             break;
01226 
01227         case 'c':
01228             app.client.job_count = my_atoi(pj_optarg);
01229             if (app.client.job_count < 0) {
01230                 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
01231                 return -1;
01232             }
01233             if (app.client.job_count > pjsip_cfg()->tsx.max_count)
01234                 PJ_LOG(3,(THIS_FILE, 
01235                           "Warning: --count value (%d) exceeds maximum "
01236                           "transaction count (%d)", app.client.job_count,
01237                           pjsip_cfg()->tsx.max_count));
01238             break;
01239 
01240         case OPT_THREAD_COUNT:
01241             app.thread_count = my_atoi(pj_optarg);
01242             if (app.thread_count < 1 || app.thread_count > 16) {
01243                 PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
01244                 return -1;
01245             }
01246             break;
01247 
01248         case 'm':
01249             {
01250                 pj_str_t temp = pj_str((char*)pj_optarg);
01251                 pjsip_method_init_np(&app.client.method, &temp);
01252             }
01253             break;
01254 
01255         case 'h':
01256             usage();
01257             return -1;
01258 
01259         case 's':
01260             app.client.stateless = PJ_TRUE;
01261             break;
01262 
01263         case OPT_REAL_SDP:
01264             app.real_sdp = 1;
01265             break;
01266 
01267         case 'v':
01268             app.log_level++;
01269             break;
01270 
01271         case 't':
01272             app.client.timeout = my_atoi(pj_optarg);
01273             if (app.client.timeout < 0 || app.client.timeout > 600) {
01274                 PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
01275                 return -1;
01276             }
01277             break;
01278 
01279         case 'w':
01280             app.client.job_window = my_atoi(pj_optarg);
01281             if (app.client.job_window <= 0) {
01282                 PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg));
01283                 return -1;
01284             }
01285             break;
01286 
01287         case 'T':
01288             app.use_tcp = PJ_TRUE;
01289             break;
01290 
01291         case 'd':
01292             app.server.delay = my_atoi(pj_optarg);
01293             if (app.server.delay > 3600) {
01294                 PJ_LOG(3,(THIS_FILE, "I think --delay %s is too long", 
01295                           pj_optarg));
01296                 return -1;
01297             }
01298             break;
01299 
01300         case OPT_TRYING:
01301             app.server.send_trying = 1;
01302             break;
01303 
01304         case OPT_RINGING:
01305             app.server.send_ringing = 1;
01306             break;
01307 
01308         default:
01309             PJ_LOG(1,(THIS_FILE, 
01310                       "Invalid argument. Use --help to see help"));
01311             return -1;
01312         }
01313     }
01314 
01315     if (pj_optind != argc) {
01316 
01317         if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
01318             PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
01319             return -1;
01320         }
01321         app.client.dst_uri = pj_str(argv[pj_optind]);
01322         
01323         pj_optind++;
01324 
01325     }
01326 
01327     if (pj_optind != argc) {
01328         PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
01329         return -1;
01330     }
01331 
01332     return 0;
01333 }
01334 
01335 
01336 /* Send one stateless request */
01337 static pj_status_t submit_stateless_job(void)
01338 {
01339     pjsip_tx_data *tdata;
01340     pj_status_t status;
01341 
01342     status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method, 
01343                                         &app.client.dst_uri, &app.local_uri,
01344                                         &app.client.dst_uri, &app.local_contact,
01345                                         NULL, -1, NULL, &tdata);
01346     if (status != PJ_SUCCESS) {
01347         app_perror(THIS_FILE, "Error creating request", status);
01348         report_completion(701);
01349         return status;
01350     }
01351 
01352     status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
01353                                                 NULL);
01354     if (status != PJ_SUCCESS) {
01355         pjsip_tx_data_dec_ref(tdata);
01356         app_perror(THIS_FILE, "Error sending stateless request", status);
01357         report_completion(701);
01358         return status;
01359     }
01360 
01361     return PJ_SUCCESS;
01362 }
01363 
01364 
01365 /* This callback is called when client transaction state has changed */
01366 static void tsx_completion_cb(void *token, pjsip_event *event)
01367 {
01368     pjsip_transaction *tsx;
01369 
01370     PJ_UNUSED_ARG(token);
01371 
01372     if (event->type != PJSIP_EVENT_TSX_STATE)
01373         return;
01374 
01375     tsx = event->body.tsx_state.tsx;
01376 
01377     if (tsx->mod_data[mod_test.id] != NULL) {
01378         /* This transaction has been calculated before */
01379         return;
01380     }
01381 
01382     if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
01383         report_completion(tsx->status_code);
01384         tsx->mod_data[mod_test.id] = (void*)1;
01385     }
01386     else if (tsx->method.id == PJSIP_INVITE_METHOD &&
01387              tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
01388 
01389         report_completion(tsx->status_code);
01390         tsx->mod_data[mod_test.id] = (void*)1;
01391         
01392     } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
01393 
01394         report_completion(tsx->status_code);
01395         tsx->mod_data[mod_test.id] = (void*)1;
01396 
01397         TERMINATE_TSX(tsx, tsx->status_code);
01398     }
01399 }
01400 
01401 
01402 /* Send one stateful request */
01403 static pj_status_t submit_job(void)
01404 {
01405     pjsip_tx_data *tdata;
01406     pj_status_t status;
01407 
01408     status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method, 
01409                                         &app.client.dst_uri, &app.local_uri,
01410                                         &app.client.dst_uri, &app.local_contact,
01411                                         NULL, -1, NULL, &tdata);
01412     if (status != PJ_SUCCESS) {
01413         app_perror(THIS_FILE, "Error creating request", status);
01414         report_completion(701);
01415         return status;
01416     }
01417 
01418     status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL, 
01419                                       &tsx_completion_cb);
01420     if (status != PJ_SUCCESS) {
01421         app_perror(THIS_FILE, "Error sending stateful request", status);
01422         //should have been reported by tsx_completion_cb().
01423         //report_completion(701);
01424         //No longer necessary (r777)
01425         //pjsip_tx_data_dec_ref(tdata);
01426     }
01427     return status;
01428 }
01429 
01430 
01431 /* Client worker thread */
01432 static int client_thread(void *arg)
01433 {
01434     pj_time_val end_time, last_report, now;
01435     unsigned thread_index = (unsigned)(long)arg;
01436     unsigned cycle = 0, last_cycle = 0;
01437 
01438     pj_thread_sleep(100);
01439 
01440     pj_gettimeofday(&end_time);
01441     end_time.sec += app.client.timeout;
01442 
01443     pj_gettimeofday(&last_report);
01444 
01445     if (app.client.first_request.sec == 0) {
01446         pj_gettimeofday(&app.client.first_request);
01447     }
01448 
01449     /* Submit all jobs */
01450     while (app.client.job_submitted < app.client.job_count && !app.thread_quit){
01451         pj_time_val timeout = { 0, 1 };
01452         unsigned i;
01453         int outstanding;
01454         pj_status_t status;
01455 
01456         /* Calculate current outstanding job */
01457         outstanding = app.client.job_submitted - app.client.job_finished;
01458 
01459         /* Update stats on max outstanding jobs */
01460         if (outstanding > (int)app.client.stat_max_window)
01461             app.client.stat_max_window = outstanding;
01462 
01463         /* Wait if there are more pending jobs than allowed in the
01464          * window. But spawn a new job anyway if no events are happening
01465          * after we wait for some time.
01466          */
01467         for (i=0; outstanding > (int)app.client.job_window && i<1000; ++i) {
01468             pj_time_val wait = { 0, 500 };
01469             unsigned count = 0;
01470 
01471             pjsip_endpt_handle_events2(app.sip_endpt, &wait, &count);
01472             outstanding = app.client.job_submitted - app.client.job_finished;
01473 
01474             if (count == 0)
01475                 break;
01476 
01477             ++cycle;
01478         }
01479 
01480 
01481         /* Submit one job */
01482         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01483             status = make_call(&app.client.dst_uri);
01484         } else if (app.client.stateless) {
01485             status = submit_stateless_job();
01486         } else {
01487             status = submit_job();
01488         }
01489 
01490         ++app.client.job_submitted;
01491         ++cycle;
01492 
01493         /* Handle event */
01494         pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
01495 
01496         /* Check for time out, also print report */
01497         if (cycle - last_cycle >= 500) {
01498             pj_gettimeofday(&now);
01499             if (PJ_TIME_VAL_GTE(now, end_time)) {
01500                 break;
01501             }
01502             last_cycle = cycle;
01503 
01504             
01505             if (thread_index == 0 && now.sec-last_report.sec >= 2) {
01506                 printf("\r%d jobs started, %d completed...   ",
01507                        app.client.job_submitted, app.client.job_finished);
01508                 fflush(stdout);
01509                 last_report = now;
01510             }
01511         }
01512     }
01513 
01514     if (app.client.requests_sent.sec == 0) {
01515         pj_gettimeofday(&app.client.requests_sent);
01516     }
01517 
01518 
01519     if (thread_index == 0) {
01520         printf("\r%d jobs started, %d completed%s\n",
01521                app.client.job_submitted, app.client.job_finished,
01522                (app.client.job_submitted!=app.client.job_finished ? 
01523                 ", waiting..." : ".") );
01524         fflush(stdout);
01525     }
01526 
01527     /* Wait until all jobs completes, or timed out */
01528     pj_gettimeofday(&now);
01529     while (PJ_TIME_VAL_LT(now, end_time) && 
01530            app.client.job_finished < app.client.job_count && 
01531            !app.thread_quit) 
01532     {
01533         pj_time_val timeout = { 0, 1 };
01534         unsigned i;
01535 
01536         for (i=0; i<1000; ++i) {
01537             unsigned count;
01538             count = 0;
01539             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01540             if (count == 0)
01541                 break;
01542         }
01543 
01544         pj_gettimeofday(&now);
01545     }
01546 
01547     /* Wait couple of seconds to let jobs completes (e.g. ACKs to be sent)  */
01548     pj_gettimeofday(&now);
01549     end_time = now;
01550     end_time.sec += 2;
01551     while (PJ_TIME_VAL_LT(now, end_time)) 
01552     {
01553         pj_time_val timeout = { 0, 1 };
01554         unsigned i;
01555 
01556         for (i=0; i<1000; ++i) {
01557             unsigned count;
01558             count = 0;
01559             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01560             if (count == 0)
01561                 break;
01562         }
01563 
01564         pj_gettimeofday(&now);
01565     }
01566 
01567     return 0;
01568 }
01569 
01570 
01571 static const char *good_number(char *buf, pj_int32_t val)
01572 {
01573     if (val < 1000) {
01574         pj_ansi_sprintf(buf, "%d", val);
01575     } else if (val < 1000000) {
01576         pj_ansi_sprintf(buf, "%d.%dK", 
01577                         val / 1000,
01578                         (val % 1000) / 100);
01579     } else {
01580         pj_ansi_sprintf(buf, "%d.%02dM", 
01581                         val / 1000000,
01582                         (val % 1000000) / 10000);
01583     }
01584 
01585     return buf;
01586 }
01587 
01588 
01589 static int server_thread(void *arg)
01590 {
01591     pj_time_val timeout = { 0, 1 };
01592     unsigned thread_index = (unsigned)(long)arg;
01593     pj_time_val last_report, next_report;
01594 
01595     pj_gettimeofday(&last_report);
01596     next_report = last_report;
01597     next_report.sec++;
01598 
01599     while (!app.thread_quit) {
01600         pj_time_val now;
01601         unsigned i;
01602 
01603         for (i=0; i<100; ++i) {
01604             unsigned count = 0;
01605             pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
01606             if (count == 0)
01607                 break;
01608         }
01609 
01610         if (thread_index == 0) {
01611             pj_gettimeofday(&now);
01612 
01613             if (PJ_TIME_VAL_GTE(now, next_report)) {
01614                 pj_time_val tmp;
01615                 unsigned msec;
01616                 unsigned stateless, stateful, call;
01617                 char str_stateless[32], str_stateful[32], str_call[32];
01618 
01619                 tmp = now;
01620                 PJ_TIME_VAL_SUB(tmp, last_report);
01621                 msec = PJ_TIME_VAL_MSEC(tmp);
01622 
01623                 last_report = now;
01624                 next_report = last_report;
01625                 next_report.sec++;
01626 
01627                 stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
01628                 stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
01629                 call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
01630 
01631                 good_number(str_stateless, app.server.cur_state.stateless_cnt);
01632                 good_number(str_stateful, app.server.cur_state.stateful_cnt);
01633                 good_number(str_call, app.server.cur_state.call_cnt);
01634 
01635                 printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s)       \r",
01636                        str_stateless, stateless*1000/msec,
01637                        str_stateful, stateful*1000/msec,
01638                        str_call, call*1000/msec);
01639                 fflush(stdout);
01640 
01641                 app.server.prev_state = app.server.cur_state;
01642             }
01643         }
01644     }
01645 
01646     return 0;
01647 }
01648 
01649 static void write_report(const char *msg)
01650 {
01651     puts(msg);
01652 
01653 #if defined(PJ_WIN32) && PJ_WIN32!=0
01654     OutputDebugString(msg);
01655     OutputDebugString("\n");
01656 #endif
01657 }
01658 
01659 
01660 int main(int argc, char *argv[])
01661 {
01662     static char report[1024];
01663 
01664     printf("PJSIP Performance Measurement Tool v%s\n"
01665            "(c)2006 pjsip.org\n\n",
01666            PJ_VERSION);
01667 
01668     if (create_app() != 0)
01669         return 1;
01670 
01671     if (init_options(argc, argv) != 0)
01672         return 1;
01673 
01674     if (init_sip() != 0)
01675         return 1;
01676 
01677     if (init_media() != 0)
01678         return 1;
01679 
01680     pj_log_set_level(app.log_level);
01681 
01682     if (app.log_level > 4) {
01683         pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
01684     }
01685 
01686 
01687     /* Misc infos */
01688     if (app.client.dst_uri.slen != 0) {
01689         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01690             if (app.client.stateless) {
01691                 PJ_LOG(3,(THIS_FILE, 
01692                           "Info: --stateless option makes no sense for INVITE,"
01693                           " ignored."));
01694             }
01695         }
01696 
01697     }
01698 
01699 
01700 
01701     if (app.client.dst_uri.slen) {
01702         /* Client mode */
01703         pj_status_t status;
01704         char test_type[64];
01705         unsigned msec_req, msec_res;
01706         unsigned i;
01707 
01708         /* Get the job name */
01709         if (app.client.method.id == PJSIP_INVITE_METHOD) {
01710             pj_ansi_strcpy(test_type, "INVITE calls");
01711         } else if (app.client.stateless) {
01712             pj_ansi_sprintf(test_type, "stateless %.*s requests",
01713                             (int)app.client.method.name.slen,
01714                             app.client.method.name.ptr);
01715         } else {
01716             pj_ansi_sprintf(test_type, "stateful %.*s requests",
01717                             (int)app.client.method.name.slen,
01718                             app.client.method.name.ptr);
01719         }
01720         
01721 
01722         printf("Sending %d %s to '%.*s' with %d maximum outstanding jobs, please wait..\n", 
01723                   app.client.job_count, test_type,
01724                   (int)app.client.dst_uri.slen, app.client.dst_uri.ptr,
01725                   app.client.job_window);
01726 
01727         for (i=0; i<app.thread_count; ++i) {
01728             status = pj_thread_create(app.pool, NULL, &client_thread, 
01729                                       (void*)(long)i, 0, 0, &app.thread[i]);
01730             if (status != PJ_SUCCESS) {
01731                 app_perror(THIS_FILE, "Unable to create thread", status);
01732                 return 1;
01733             }
01734         }
01735 
01736         for (i=0; i<app.thread_count; ++i) {
01737             pj_thread_join(app.thread[i]);
01738             app.thread[i] = NULL;
01739         }
01740 
01741         if (app.client.last_completion.sec) {
01742             pj_time_val duration;
01743             duration = app.client.last_completion;
01744             PJ_TIME_VAL_SUB(duration, app.client.first_request);
01745             msec_res = PJ_TIME_VAL_MSEC(duration);
01746         } else {
01747             msec_res = app.client.timeout * 1000;
01748         }
01749 
01750         if (msec_res == 0) msec_res = 1;
01751 
01752         if (app.client.requests_sent.sec) {
01753             pj_time_val duration;
01754             duration = app.client.requests_sent;
01755             PJ_TIME_VAL_SUB(duration, app.client.first_request);
01756             msec_req = PJ_TIME_VAL_MSEC(duration);
01757         } else {
01758             msec_req = app.client.timeout * 1000;
01759         }
01760 
01761         if (msec_req == 0) msec_req = 1;
01762 
01763         if (app.client.job_submitted < app.client.job_count)
01764             puts("\ntimed-out!\n");
01765         else
01766             puts("\ndone.\n");
01767 
01768         pj_ansi_snprintf(
01769             report, sizeof(report),
01770             "Total %d %s sent in %d ms at rate of %d/sec\n"
01771             "Total %d responses receieved in %d ms at rate of %d/sec:",
01772             app.client.job_submitted, test_type, msec_req, 
01773             app.client.job_submitted * 1000 / msec_req,
01774             app.client.total_responses, msec_res,
01775             app.client.total_responses*1000/msec_res);
01776         write_report(report);
01777 
01778         /* Print detailed response code received */
01779         pj_ansi_sprintf(report, "\nDetailed responses received:");
01780         write_report(report);
01781 
01782         for (i=0; i<PJ_ARRAY_SIZE(app.client.response_codes); ++i) {
01783             const pj_str_t *reason;
01784 
01785             if (app.client.response_codes[i] == 0)
01786                 continue;
01787 
01788             reason = pjsip_get_status_text(i);
01789             pj_ansi_snprintf( report, sizeof(report),
01790                               " - %d responses:  %7d     (%.*s)",
01791                               i, app.client.response_codes[i],
01792                               (int)reason->slen, reason->ptr);
01793             write_report(report);
01794         }
01795 
01796         /* Total responses and rate */
01797         pj_ansi_snprintf( report, sizeof(report),
01798             "                    ------\n"
01799             " TOTAL responses:  %7d (rate=%d/sec)\n",
01800             app.client.total_responses, 
01801             app.client.total_responses*1000/msec_res);
01802 
01803         write_report(report);
01804 
01805         pj_ansi_sprintf(report, "Maximum outstanding job: %d", 
01806                         app.client.stat_max_window);
01807         write_report(report);
01808 
01809 
01810     } else {
01811         /* Server mode */
01812         char s[10], *unused;
01813         pj_status_t status;
01814         unsigned i;
01815 
01816         puts("pjsip-perf started in server-mode");
01817 
01818         printf("Receiving requests on the following URIs:\n"
01819                "  sip:0@%.*s:%d%s    for stateless handling\n"
01820                "  sip:1@%.*s:%d%s    for stateful handling\n"
01821                "  sip:2@%.*s:%d%s    for call handling\n",
01822                (int)app.local_addr.slen,
01823                app.local_addr.ptr,
01824                app.local_port,
01825                (app.use_tcp ? ";transport=tcp" : ""),
01826                (int)app.local_addr.slen,
01827                app.local_addr.ptr,
01828                app.local_port,
01829                (app.use_tcp ? ";transport=tcp" : ""),
01830                (int)app.local_addr.slen,
01831                app.local_addr.ptr,
01832                app.local_port,
01833                (app.use_tcp ? ";transport=tcp" : ""));
01834         printf("INVITE with non-matching user part will be handled call-statefully\n");
01835 
01836         for (i=0; i<app.thread_count; ++i) {
01837             status = pj_thread_create(app.pool, NULL, &server_thread, 
01838                                       (void*)(long)i, 0, 0, &app.thread[i]);
01839             if (status != PJ_SUCCESS) {
01840                 app_perror(THIS_FILE, "Unable to create thread", status);
01841                 return 1;
01842             }
01843         }
01844 
01845         puts("\nPress <ENTER> to quit\n");
01846         fflush(stdout);
01847         unused = fgets(s, sizeof(s), stdin);
01848         PJ_UNUSED_ARG(unused);
01849 
01850         app.thread_quit = PJ_TRUE;
01851         for (i=0; i<app.thread_count; ++i) {
01852             pj_thread_join(app.thread[i]);
01853             app.thread[i] = NULL;
01854         }
01855 
01856         puts("");
01857     }
01858 
01859 
01860     destroy_app();
01861 
01862     return 0;
01863 }
01864 

 


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