blob: 85ab15e78a6047d23b6355d5c729511c6a752461 [file] [log] [blame]
Benny Prijono514ca6b2006-07-03 01:30:01 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
21/**
22 * \page page_pjsip_perf_c Samples: SIP Performance Benchmark
23 *
24 * <b>pjsip-perf</b> is a complete program to measure the
25 * performance of PJSIP or other SIP endpoints. It consists of two
26 * parts:
27 * - the server, to respond incoming requests, and
28 * - the client, who actively submits requests and measure the
29 * performance of the server.
30 *
31 * Both server and client part can run simultaneously, to measure the
32 * performance when both endpoints are co-located in a single program.
33 *
34 * The server accepts both INVITE and non-INVITE requests.
35 * The server exports several different types of URL, which would
36 * control how the request would be handled by the server:
37 * - URL with "0" as the user part will be handled statelessly.
38 * It should not be used with INVITE method.
39 * - URL with "1" as the user part will be handled statefully.
40 * If the request is an INVITE request, INVITE transaction will
41 * be created and 200/OK response will be sent, along with a valid
42 * SDP body. However, the SDP is just a static text body, and
43 * is not a proper SDP generated by PJMEDIA.
44 * - URL with "2" as the user part is only meaningful for INVITE
45 * requests, as it would be handled <b>call-statefully</b> by the
46 * server. For this URL, the server also would generate SDP dynamically
47 * and perform a proper SDP negotiation for the incoming call.
48 * Also for every call, server will limit the call duration to
49 * 10 seconds, on which the call will be terminated if the client
50 * doesn't hangup the call.
51 *
52 *
53 *
54 * This file is pjsip-apps/src/samples/pjsip-perf.c
55 *
56 * \includelineno pjsip-perf.c
57 */
58
59/* Include all headers. */
60#include <pjsip.h>
61#include <pjmedia.h>
62#include <pjmedia-codec.h>
63#include <pjsip_ua.h>
64#include <pjsip_simple.h>
65#include <pjlib-util.h>
66#include <pjlib.h>
67#include <stdio.h>
68
Benny Prijono1479b652006-07-03 14:18:17 +000069#if defined(PJ_WIN32) && PJ_WIN32!=0
70# include <windows.h>
71#endif
72
73#define THIS_FILE "pjsip-perf.c"
74#define DEFAULT_COUNT (PJSIP_MAX_TSX_COUNT/2>10000?10000:PJSIP_MAX_TSX_COUNT/2)
75#define JOB_WINDOW DEFAULT_COUNT
76#define TERMINATE_TSX(x,c)
77
78
79#ifndef CACHING_POOL_SIZE
80# define CACHING_POOL_SIZE (256*1024*1024)
81#endif
Benny Prijono514ca6b2006-07-03 01:30:01 +000082
83
84/* Static message body for INVITE, when stateful processing is
85 * invoked (instead of call-stateful, where SDP is generated
86 * dynamically.
87 */
88static pj_str_t dummy_sdp_str =
89{
90 "v=0\r\n"
91 "o=- 3360842071 3360842071 IN IP4 192.168.0.68\r\n"
92 "s=pjmedia\r\n"
93 "c=IN IP4 192.168.0.68\r\n"
94 "t=0 0\r\n"
95 "m=audio 4000 RTP/AVP 103 102 3 0 8 101\r\n"
96 "a=rtcp:4001 IN IP4 192.168.0.68\r\n"
97 "a=rtpmap:103 speex/16000\r\n"
98 "a=rtpmap:102 speex/8000\r\n"
99 "a=rtpmap:3 GSM/8000\r\n"
100 "a=rtpmap:0 PCMU/8000\r\n"
101 "a=rtpmap:8 PCMA/8000\r\n"
102 "a=sendrecv\r\n"
103 "a=rtpmap:101 telephone-event/8000\r\n"
104 "a=fmtp:101 0-15\r\n",
105 0
106};
107
108static pj_str_t mime_application = { "application", 11};
109static pj_str_t mime_sdp = {"sdp", 3};
110
111
112struct srv_state
113{
114 unsigned stateless_cnt;
115 unsigned stateful_cnt;
116 unsigned call_cnt;
117};
118
119
120struct app
121{
122 pj_caching_pool cp;
123 pj_pool_t *pool;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000124 pj_bool_t use_tcp;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000125 pj_str_t local_addr;
126 int local_port;
127 pjsip_endpoint *sip_endpt;
128 pjmedia_endpt *med_endpt;
129 pj_str_t local_uri;
130 pj_str_t local_contact;
131 unsigned skinfo_cnt;
132 pjmedia_sock_info skinfo[8];
133
134 pj_bool_t thread_quit;
135 unsigned thread_count;
136 pj_thread_t *thread[16];
137
138 pj_bool_t real_sdp;
139 pjmedia_sdp_session *dummy_sdp;
140 pj_bool_t verbose;
141
142 struct {
143 pjsip_method method;
144 pj_str_t dst_uri;
145 pj_bool_t stateless;
146 unsigned timeout;
147 unsigned job_count,
148 job_submitted,
149 job_finished,
150 job_window;
Benny Prijono1479b652006-07-03 14:18:17 +0000151 unsigned stat_max_window;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000152 pj_time_val first_request;
153 pj_time_val last_completion;
154 unsigned total_responses;
155 unsigned status_class[7];
156 } client;
157
158 struct {
159 struct srv_state prev_state;
160 struct srv_state cur_state;
161 } server;
162
163
164} app;
165
166struct call
167{
168 pjsip_inv_session *inv;
169 pj_timer_entry timer;
170};
171
172
173static void app_perror(const char *sender, const char *title,
174 pj_status_t status)
175{
176 char errmsg[PJ_ERR_MSG_SIZE];
177
178 pj_strerror(status, errmsg, sizeof(errmsg));
179 PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
180}
181
182
183/**************************************************************************
184 * STATELESS SERVER
185 */
186static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata);
187
188/* Module to handle incoming requests statelessly.
189 */
190static pjsip_module mod_stateless_server =
191{
192 NULL, NULL, /* prev, next. */
193 { "mod-stateless-server", 20 }, /* Name. */
194 -1, /* Id */
195 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
196 NULL, /* load() */
197 NULL, /* start() */
198 NULL, /* stop() */
199 NULL, /* unload() */
200 &mod_stateless_on_rx_request, /* on_rx_request() */
201 NULL, /* on_rx_response() */
202 NULL, /* on_tx_request. */
203 NULL, /* on_tx_response() */
204 NULL, /* on_tsx_state() */
205};
206
207
208static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
209{
210 const pj_str_t stateless_user = { "0", 1 };
211 pjsip_uri *uri;
212 pjsip_sip_uri *sip_uri;
213
214 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
215
216 /* Only want to receive SIP scheme */
217 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
218 return PJ_FALSE;
219
220 sip_uri = (pjsip_sip_uri*) uri;
221
222 /* Check for matching user part */
223 if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
224 return PJ_FALSE;
225
226 /*
227 * Yes, this is for us.
228 */
229
230 /* Ignore ACK request */
231 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
232 return PJ_TRUE;
233
234 /*
235 * Respond statelessly with 200/OK.
236 */
237 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
238 NULL, NULL);
239 app.server.cur_state.stateless_cnt++;
240 return PJ_TRUE;
241}
242
243
244/**************************************************************************
245 * STATEFUL SERVER
246 */
247static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata);
248
249/* Module to handle incoming requests statefully.
250 */
251static pjsip_module mod_stateful_server =
252{
253 NULL, NULL, /* prev, next. */
254 { "mod-stateful-server", 19 }, /* Name. */
255 -1, /* Id */
256 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
257 NULL, /* load() */
258 NULL, /* start() */
259 NULL, /* stop() */
260 NULL, /* unload() */
261 &mod_stateful_on_rx_request, /* on_rx_request() */
262 NULL, /* on_rx_response() */
263 NULL, /* on_tx_request. */
264 NULL, /* on_tx_response() */
265 NULL, /* on_tsx_state() */
266};
267
268
269static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
270{
271 const pj_str_t stateful_user = { "1", 1 };
272 pjsip_uri *uri;
273 pjsip_sip_uri *sip_uri;
274
275 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
276
277 /* Only want to receive SIP scheme */
278 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
279 return PJ_FALSE;
280
281 sip_uri = (pjsip_sip_uri*) uri;
282
283 /* Check for matching user part */
284 if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
285 return PJ_FALSE;
286
287 /*
288 * Yes, this is for us.
289 * Respond statefully with 200/OK.
290 */
291 switch (rdata->msg_info.msg->line.req.method.id) {
292 case PJSIP_INVITE_METHOD:
293 {
294 pjsip_msg_body *body;
295
296 if (dummy_sdp_str.slen == 0)
297 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
298
299 body = pjsip_msg_body_create(rdata->tp_info.pool,
300 &mime_application, &mime_sdp,
301 &dummy_sdp_str);
302 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
303 200, NULL, NULL, body, NULL);
304 }
305 break;
306 case PJSIP_ACK_METHOD:
307 return PJ_TRUE;
308 default:
309 pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
310 200, NULL, NULL, NULL, NULL);
311 break;
312 }
313
314 app.server.cur_state.stateful_cnt++;
315 return PJ_TRUE;
316}
317
318
319/**************************************************************************
320 * CALL SERVER
321 */
322static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
323
324/* Module to handle incoming requests callly.
325 */
326static pjsip_module mod_call_server =
327{
328 NULL, NULL, /* prev, next. */
329 { "mod-call-server", 15 }, /* Name. */
330 -1, /* Id */
331 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
332 NULL, /* load() */
333 NULL, /* start() */
334 NULL, /* stop() */
335 NULL, /* unload() */
336 &mod_call_on_rx_request, /* on_rx_request() */
337 NULL, /* on_rx_response() */
338 NULL, /* on_tx_request. */
339 NULL, /* on_tx_response() */
340 NULL, /* on_tsx_state() */
341};
342
343
344static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
345{
346 const pj_str_t call_user = { "2", 1 };
347 pjsip_uri *uri;
348 pjsip_sip_uri *sip_uri;
349 struct call *call;
350 pjsip_dialog *dlg;
351 pjmedia_sdp_session *sdp;
352 pjsip_tx_data *tdata;
353 pj_status_t status;
354
355 uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
356
357 /* Only want to receive SIP scheme */
358 if (!PJSIP_URI_SCHEME_IS_SIP(uri))
359 return PJ_FALSE;
360
361 sip_uri = (pjsip_sip_uri*) uri;
362
363 /* Check for matching user part */
364 if (pj_strcmp(&sip_uri->user, &call_user)!=0)
365 return PJ_FALSE;
366
367 /* Only want to handle INVITE requests (for now). */
368 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
369 return PJ_FALSE;
370 }
371
372
373 /* Verify that we can handle the request. */
374 if (app.real_sdp) {
375 unsigned options = 0;
376 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
377 app.sip_endpt, &tdata);
378 if (status != PJ_SUCCESS) {
379
380 /*
381 * No we can't handle the incoming INVITE request.
382 */
383
384 if (tdata) {
385 pjsip_response_addr res_addr;
386
387 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
388 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
389 NULL, NULL);
390
391 } else {
392
393 /* Respond with 500 (Internal Server Error) */
394 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
395 NULL, NULL);
396 }
397
398 return PJ_TRUE;
399 }
400 }
401
402 /* Create UAS dialog */
403 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
404 &app.local_contact, &dlg);
405 if (status != PJ_SUCCESS) {
406 const pj_str_t reason = pj_str("Unable to create dialog");
407 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
408 500, &reason,
409 NULL, NULL);
410 return PJ_TRUE;
411 }
412
413 /* Alloc call structure. */
414 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
415
416 /* Create SDP from PJMEDIA */
417 if (app.real_sdp) {
418 status = pjmedia_endpt_create_sdp(app.med_endpt, rdata->tp_info.pool,
419 app.skinfo_cnt, app.skinfo,
420 &sdp);
421 } else {
422 sdp = app.dummy_sdp;
423 }
424
425 /* Create UAS invite session */
426 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
427 if (status != PJ_SUCCESS) {
428 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
429 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
430 return PJ_TRUE;
431 }
432
433
434 /* Create 200 response .*/
435 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
436 NULL, NULL, &tdata);
437 if (status != PJ_SUCCESS) {
438 status = pjsip_inv_initial_answer(call->inv, rdata,
439 PJSIP_SC_NOT_ACCEPTABLE,
440 NULL, NULL, &tdata);
441 if (status == PJ_SUCCESS)
442 pjsip_inv_send_msg(call->inv, tdata);
443 else
444 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
445 return PJ_TRUE;
446 }
447
448
449 /* Send the 200 response. */
450 status = pjsip_inv_send_msg(call->inv, tdata);
451 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return PJ_TRUE);
452
453
454 /* Done */
455 app.server.cur_state.call_cnt++;
456
457 return PJ_TRUE;
458}
459
460
461
462
463/*****************************************************************************
464 * Below is a simple module to log all incoming and outgoing SIP messages
465 */
466
467
468/* Notification on incoming messages */
469static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
470{
471 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
472 "%.*s\n"
473 "--end msg--",
474 rdata->msg_info.len,
475 pjsip_rx_data_get_info(rdata),
476 rdata->pkt_info.src_name,
477 rdata->pkt_info.src_port,
478 (int)rdata->msg_info.len,
479 rdata->msg_info.msg_buf));
480
481 /* Always return false, otherwise messages will not get processed! */
482 return PJ_FALSE;
483}
484
485/* Notification on outgoing messages */
486static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
487{
488
489 /* Important note:
490 * tp_info field is only valid after outgoing messages has passed
491 * transport layer. So don't try to access tp_info when the module
492 * has lower priority than transport layer.
493 */
494
495 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
496 "%.*s\n"
497 "--end msg--",
498 (tdata->buf.cur - tdata->buf.start),
499 pjsip_tx_data_get_info(tdata),
500 tdata->tp_info.dst_name,
501 tdata->tp_info.dst_port,
502 (int)(tdata->buf.cur - tdata->buf.start),
503 tdata->buf.start));
504
505 /* Always return success, otherwise message will not get sent! */
506 return PJ_SUCCESS;
507}
508
509/* The module instance. */
510static pjsip_module msg_logger =
511{
512 NULL, NULL, /* prev, next. */
513 { "mod-siprtp-log", 14 }, /* Name. */
514 -1, /* Id */
515 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
516 NULL, /* load() */
517 NULL, /* start() */
518 NULL, /* stop() */
519 NULL, /* unload() */
520 &logger_on_rx_msg, /* on_rx_request() */
521 &logger_on_rx_msg, /* on_rx_response() */
522 &logger_on_tx_msg, /* on_tx_request. */
523 &logger_on_tx_msg, /* on_tx_response() */
524 NULL, /* on_tsx_state() */
525
526};
527
528
529
530/**************************************************************************
531 * Test Client.
532 */
533
534static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata);
535
536static void call_on_media_update( pjsip_inv_session *inv,
537 pj_status_t status);
538static void call_on_state_changed( pjsip_inv_session *inv,
539 pjsip_event *e);
540static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
541
542
543/* Module to handle incoming requests callly.
544 */
545static pjsip_module mod_test =
546{
547 NULL, NULL, /* prev, next. */
548 { "mod-test", 8 }, /* Name. */
549 -1, /* Id */
550 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
551 NULL, /* load() */
552 NULL, /* start() */
553 NULL, /* stop() */
554 NULL, /* unload() */
555 NULL, /* on_rx_request() */
556 &mod_test_on_rx_response, /* on_rx_response() */
557 NULL, /* on_tx_request. */
558 NULL, /* on_tx_response() */
559 NULL, /* on_tsx_state() */
560};
561
562
563static void report_completion(int status_code)
564{
565 app.client.job_finished++;
566 app.client.status_class[status_code/100]++;
567 app.client.total_responses++;
568 pj_gettimeofday(&app.client.last_completion);
569}
570
571
572/* Handler when response is received. */
573static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata)
574{
575 if (pjsip_rdata_get_tsx(rdata) == NULL) {
576 report_completion(rdata->msg_info.msg->line.status.code);
577 }
578
579 return PJ_TRUE;
580}
581
582
583/*
584 * Create app
585 */
586static pj_status_t create_app(void)
587{
588 pj_status_t status;
589
590 status = pj_init();
591 if (status != PJ_SUCCESS) {
592 app_perror(THIS_FILE, "Error initializing pjlib", status);
593 return status;
594 }
595
596 /* init PJLIB-UTIL: */
597 status = pjlib_util_init();
598 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
599
600 /* Must create a pool factory before we can allocate any memory. */
Benny Prijono1479b652006-07-03 14:18:17 +0000601 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy,
602 CACHING_POOL_SIZE);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000603
604 /* Create application pool for misc. */
605 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
606
607 /* Create the endpoint: */
608 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
609 &app.sip_endpt);
610 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
611
612
613 return status;
614}
615
616
617/*
618 * Init SIP stack
619 */
620static pj_status_t init_sip()
621{
622 pj_status_t status;
623
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000624 /* Add UDP/TCP transport. */
Benny Prijono514ca6b2006-07-03 01:30:01 +0000625 {
626 pj_sockaddr_in addr;
627 pjsip_host_port addrname;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000628 const char *transport_type;
Benny Prijono514ca6b2006-07-03 01:30:01 +0000629
Benny Prijonoac623b32006-07-03 15:19:31 +0000630 pj_bzero(&addr, sizeof(addr));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000631 addr.sin_family = PJ_AF_INET;
632 addr.sin_addr.s_addr = 0;
633 addr.sin_port = pj_htons((pj_uint16_t)app.local_port);
634
635 if (app.local_addr.slen) {
636 addrname.host = app.local_addr;
637 addrname.port = 5060;
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000638 }
Benny Prijono514ca6b2006-07-03 01:30:01 +0000639 if (app.local_port != 0)
640 addrname.port = app.local_port;
641
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000642 if (app.use_tcp) {
643 pj_sockaddr_in local_addr;
644 pjsip_tpfactory *tpfactory;
645
646 transport_type = "tcp";
Benny Prijono1479b652006-07-03 14:18:17 +0000647 pj_sockaddr_in_init(&local_addr, 0, (pj_uint16_t)app.local_port);
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000648 status = pjsip_tcp_transport_start(app.sip_endpt, &local_addr,
649 app.thread_count, &tpfactory);
650 if (status == PJ_SUCCESS) {
651 app.local_addr = tpfactory->addr_name.host;
652 app.local_port = tpfactory->addr_name.port;
653 }
654 } else {
655 pjsip_transport *tp;
656
657 transport_type = "udp";
658 status = pjsip_udp_transport_start(app.sip_endpt, &addr,
659 (app.local_addr.slen ? &addrname:NULL),
660 app.thread_count, &tp);
661 if (status == PJ_SUCCESS) {
662 app.local_addr = tp->local_name.host;
663 app.local_port = tp->local_name.port;
664 }
665
666 }
Benny Prijono514ca6b2006-07-03 01:30:01 +0000667 if (status != PJ_SUCCESS) {
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000668 app_perror(THIS_FILE, "Unable to start transport", status);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000669 return status;
670 }
671
Benny Prijono514ca6b2006-07-03 01:30:01 +0000672 app.local_uri.ptr = pj_pool_alloc(app.pool, 128);
673 app.local_uri.slen = pj_ansi_sprintf(app.local_uri.ptr,
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000674 "<sip:pjsip-perf@%.*s:%d;transport=%s>",
675 (int)app.local_addr.slen,
676 app.local_addr.ptr,
677 app.local_port,
678 transport_type);
Benny Prijono514ca6b2006-07-03 01:30:01 +0000679
680 app.local_contact = app.local_uri;
681 }
682
683 /*
684 * Init transaction layer.
685 * This will create/initialize transaction hash tables etc.
686 */
687 status = pjsip_tsx_layer_init_module(app.sip_endpt);
688 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
689
690 /* Initialize UA layer. */
691 status = pjsip_ua_init_module( app.sip_endpt, NULL );
692 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
693
694 /* Init invite session module. */
695 {
696 pjsip_inv_callback inv_cb;
697
698 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000699 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000700 inv_cb.on_state_changed = &call_on_state_changed;
701 inv_cb.on_new_session = &call_on_forked;
702 inv_cb.on_media_update = &call_on_media_update;
703
704 /* Initialize invite session module: */
705 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
706 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
707 }
708
709 /* Register our module to receive incoming requests. */
710 status = pjsip_endpt_register_module( app.sip_endpt, &mod_test);
711 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
712
713
714 /* Register stateless server module */
715 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateless_server);
716 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
717
718
719 /* Register stateless server module */
720 status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateful_server);
721 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
722
723
724 /* Register call server module */
725 status = pjsip_endpt_register_module( app.sip_endpt, &mod_call_server);
726 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
727
728
729 /* Done */
730 return PJ_SUCCESS;
731}
732
733
734/*
735 * Destroy SIP
736 */
737static void destroy_app()
738{
739 unsigned i;
740
741 app.thread_quit = 1;
742 for (i=0; i<app.thread_count; ++i) {
743 if (app.thread[i]) {
744 pj_thread_join(app.thread[i]);
745 pj_thread_destroy(app.thread[i]);
746 app.thread[i] = NULL;
747 }
748 }
749
750 if (app.sip_endpt) {
751 pjsip_endpt_destroy(app.sip_endpt);
752 app.sip_endpt = NULL;
753 }
754
755 if (app.pool) {
756 pj_pool_release(app.pool);
757 app.pool = NULL;
Benny Prijono1ef06df2006-07-09 10:06:44 +0000758 PJ_LOG(3,(THIS_FILE, "Peak memory size: %uMB",
759 app.cp.peak_used_size / 1000000));
Benny Prijono514ca6b2006-07-03 01:30:01 +0000760 pj_caching_pool_destroy(&app.cp);
761 }
762}
763
764
765/*
766 * Init media stack.
767 */
768static pj_status_t init_media()
769{
770 unsigned i;
771 pj_uint16_t rtp_port;
772 pj_status_t status;
773
774
775 /* Initialize media endpoint so that at least error subsystem is properly
776 * initialized.
777 */
778 status = pjmedia_endpt_create(&app.cp.factory,
779 pjsip_endpt_get_ioqueue(app.sip_endpt), 0,
780 &app.med_endpt);
781 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
782
783
784 /* Must register all codecs to be supported */
785 pjmedia_codec_g711_init(app.med_endpt);
786 pjmedia_codec_gsm_init(app.med_endpt);
787 pjmedia_codec_speex_init(app.med_endpt, PJMEDIA_SPEEX_NO_UWB, 3, 3);
788
789
790 /* Init dummy socket addresses */
791 app.skinfo_cnt = 0;
792 for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
793 pjmedia_sock_info *skinfo;
794
795 skinfo = &app.skinfo[i];
796
797 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
798 (pj_uint16_t)rtp_port);
799 pj_sockaddr_in_init(&skinfo->rtp_addr_name, &app.local_addr,
800 (pj_uint16_t)(rtp_port+1));
801 app.skinfo_cnt++;
802 }
803
804 /* Generate dummy SDP */
805 dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
806 status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen,
807 &app.dummy_sdp);
808 if (status != PJ_SUCCESS) {
809 app_perror(THIS_FILE, "Error parsing dummy SDP", status);
810 return status;
811 }
812
813
814 /* Done */
815 return PJ_SUCCESS;
816}
817
818
819/* This is notification from the call about media negotiation
820 * status. This is called for client calls only.
821 */
822static void call_on_media_update( pjsip_inv_session *inv,
823 pj_status_t status)
824{
825 if (status != PJ_SUCCESS) {
826 pjsip_tx_data *tdata;
827 pj_status_t status;
828
829 status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE,
830 NULL, &tdata);
831 if (status == PJ_SUCCESS && tdata)
832 status = pjsip_inv_send_msg(inv, tdata);
833 }
834}
835
836
837/* This is notification from the call when the call state has changed.
838 * This is called for client calls only.
839 */
840static void call_on_state_changed( pjsip_inv_session *inv,
841 pjsip_event *e)
842{
843 PJ_UNUSED_ARG(e);
844
845 /* Bail out if the session has been counted before */
846 if (inv->mod_data[mod_test.id] != NULL)
847 return;
848
849 /* Bail out if this is not an outgoing call */
850 if (inv->role != PJSIP_UAC_ROLE)
851 return;
852
853 if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
854 pjsip_tx_data *tdata;
855 pj_status_t status;
856
857 //report_completion(200);
858 //inv->mod_data[mod_test.id] = (void*)1;
859
860 status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
861 if (status == PJ_SUCCESS && tdata)
862 status = pjsip_inv_send_msg(inv, tdata);
863
864 } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
865 report_completion(inv->cause);
866 inv->mod_data[mod_test.id] = (void*)1;
867 }
868}
869
870
871/* Not implemented for now */
872static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
873{
874 /* Do nothing */
875 PJ_UNUSED_ARG(inv);
876 PJ_UNUSED_ARG(e);
877}
878
879
880/*
881 * Make outgoing call.
882 */
883static pj_status_t make_call(const pj_str_t *dst_uri)
884{
885 struct call *call;
886 pjsip_dialog *dlg;
887 pjmedia_sdp_session *sdp;
888 pjsip_tx_data *tdata;
889 pj_status_t status;
890
891
892 /* Create UAC dialog */
893 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
894 &app.local_uri, /* local URI */
895 &app.local_contact, /* local Contact */
896 dst_uri, /* remote URI */
897 dst_uri, /* remote target */
898 &dlg); /* dialog */
899 if (status != PJ_SUCCESS) {
900 return status;
901 }
902
903 /* Create call */
904 call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
905
906 /* Create SDP */
907 if (app.real_sdp) {
908 status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1,
909 app.skinfo, &sdp);
910 if (status != PJ_SUCCESS) {
911 pjsip_dlg_terminate(dlg);
912 return status;
913 }
914 } else
915 sdp = app.dummy_sdp;
916
917 /* Create the INVITE session. */
918 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
919 if (status != PJ_SUCCESS) {
920 pjsip_dlg_terminate(dlg);
921 return status;
922 }
923
924
925 /* Create initial INVITE request.
926 * This INVITE request will contain a perfectly good request and
927 * an SDP body as well.
928 */
929 status = pjsip_inv_invite(call->inv, &tdata);
930 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
931
932
933 /* Send initial INVITE request.
934 * From now on, the invite session's state will be reported to us
935 * via the invite session callbacks.
936 */
937 status = pjsip_inv_send_msg(call->inv, tdata);
938 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
939
940
941 return PJ_SUCCESS;
942}
943
944
945/*
946 * Verify that valid SIP url is given.
947 */
948static pj_status_t verify_sip_url(const char *c_url)
949{
950 pjsip_uri *p;
951 pj_pool_t *pool;
952 char *url;
953 int len = (c_url ? pj_ansi_strlen(c_url) : 0);
954
955 if (!len) return -1;
956
957 pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
958 if (!pool) return PJ_ENOMEM;
959
960 url = pj_pool_alloc(pool, len+1);
961 pj_ansi_strcpy(url, c_url);
962 url[len] = '\0';
963
964 p = pjsip_parse_uri(pool, url, len, 0);
965 if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
966 p = NULL;
967
968 pj_pool_release(pool);
969 return p ? 0 : -1;
970}
971
972
973static void usage(void)
974{
975 printf(
976 "Usage:\n"
977 " pjsip-perf [OPTIONS] -- to start as server\n"
978 " pjsip-perf [OPTIONS] URL -- to call server (possibly itself)\n"
979 "\n"
980 "where:\n"
981 " URL The URL to be contacted\n"
982 "\n"
983 "and OPTIONS are:\n"
984 " --count=N, -n Set number of requests to initiate\n"
985 " (client only, default=%d)\n"
986 " --method=METHOD, -m Set the test method (default: OPTIONS)\n"
987 " --local-port=PORT, -p Set local port [default: 5060]\n"
Benny Prijonoc03a3c52006-07-03 02:31:10 +0000988 " --use-tcp, -T Use TCP instead of UDP (default: no)\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +0000989 " --thread-count=N Set number of worker threads (default=1)\n"
990 " --stateless, -s Set client to operate in stateless mode\n"
991 " (default: stateful)\n"
Benny Prijono1479b652006-07-03 14:18:17 +0000992 " --window=COUNT, -w Set maximum outstanding job in client (default: %d)\n"
Benny Prijono514ca6b2006-07-03 01:30:01 +0000993 " --real-sdp Generate real SDP from pjmedia, and also perform\n"
994 " proper SDP negotiation (default: dummy)\n"
995 " --timeout=SEC, -t Set client timeout (default=60 sec)\n"
996 " --help, -h Display this screen\n"
997 " --verbose, -v Display verbose logging\n"
998 "\n"
999 "When started as server, pjsip-perf can be contacted on the following URIs:\n"
1000 " - sip:0@server-addr To handle requests statelessly (non-INVITE only)\n"
1001 " - sip:1@server-addr To handle requests statefully (INVITE and non-INVITE)\n"
1002 " - sip:2@server-addr To handle INVITE call (INVITE only)\n",
Benny Prijono1479b652006-07-03 14:18:17 +00001003 DEFAULT_COUNT, JOB_WINDOW);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001004}
1005
1006
1007static int my_atoi(const char *s)
1008{
1009 pj_str_t ss = pj_str((char*)s);
1010 return pj_strtoul(&ss);
1011}
1012
1013
1014static pj_status_t init_options(int argc, char *argv[])
1015{
1016 enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP };
1017 struct pj_getopt_option long_options[] = {
1018 { "local-port", 1, 0, 'p' },
1019 { "count", 1, 0, 'c' },
1020 { "thread-count", 1, 0, OPT_THREAD_COUNT },
1021 { "method", 1, 0, 'm' },
1022 { "help", 0, 0, 'h' },
1023 { "stateless", 0, 0, 's' },
1024 { "timeout", 1, 0, 't' },
1025 { "real-sdp", 0, 0, OPT_REAL_SDP },
1026 { "verbose", 0, 0, 'v' },
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001027 { "use-tcp", 0, 0, 'T' },
Benny Prijono1479b652006-07-03 14:18:17 +00001028 { "window", 1, 0, 'w' },
Benny Prijono514ca6b2006-07-03 01:30:01 +00001029 { NULL, 0, 0, 0 },
1030 };
1031 int c;
1032 int option_index;
1033
1034 /* Init default application configs */
1035 app.local_port = 5060;
1036 app.thread_count = 1;
1037 app.client.job_count = DEFAULT_COUNT;
1038 app.client.method = pjsip_options_method;
1039 app.client.job_window = c = JOB_WINDOW;
1040 app.client.timeout = 60;
1041
1042
1043 /* Parse options */
1044 pj_optind = 0;
Benny Prijono1479b652006-07-03 14:18:17 +00001045 while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:hsv",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001046 long_options, &option_index))!=-1)
1047 {
1048 switch (c) {
1049 case 'p':
1050 app.local_port = my_atoi(pj_optarg);
1051 if (app.local_port < 0 || app.local_port > 65535) {
1052 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1053 return -1;
1054 }
1055 break;
1056
1057 case 'c':
1058 app.client.job_count = my_atoi(pj_optarg);
1059 if (app.client.job_count < 0) {
1060 PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
1061 return -1;
1062 }
1063 if (app.client.job_count > PJSIP_MAX_TSX_COUNT)
1064 PJ_LOG(3,(THIS_FILE,
1065 "Warning: --count value (%d) exceeds maximum "
1066 "transaction count (%d)", app.client.job_count,
1067 PJSIP_MAX_TSX_COUNT));
1068 break;
1069
1070 case OPT_THREAD_COUNT:
1071 app.thread_count = my_atoi(pj_optarg);
1072 if (app.thread_count < 1 || app.thread_count > 16) {
1073 PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
1074 return -1;
1075 }
1076 break;
1077
1078 case 'm':
1079 {
1080 pj_str_t temp = pj_str((char*)pj_optarg);
1081 pjsip_method_init_np(&app.client.method, &temp);
1082 }
1083 break;
1084
1085 case 'h':
1086 usage();
1087 return -1;
1088
1089 case 's':
1090 app.client.stateless = PJ_TRUE;
1091 break;
1092
1093 case OPT_REAL_SDP:
1094 app.real_sdp = 1;
1095 break;
1096
1097 case 'v':
1098 app.verbose = PJ_TRUE;
1099 break;
1100
1101 case 't':
1102 app.client.timeout = my_atoi(pj_optarg);
1103 if (app.client.timeout < 0 || app.client.timeout > 600) {
1104 PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
1105 return -1;
1106 }
1107 break;
1108
Benny Prijono1479b652006-07-03 14:18:17 +00001109 case 'w':
1110 app.client.job_window = my_atoi(pj_optarg);
1111 if (app.client.job_window <= 0) {
1112 PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg));
1113 return -1;
1114 }
1115 break;
1116
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001117 case 'T':
1118 app.use_tcp = PJ_TRUE;
1119 break;
1120
Benny Prijono514ca6b2006-07-03 01:30:01 +00001121 default:
1122 PJ_LOG(1,(THIS_FILE,
1123 "Invalid argument. Use --help to see help"));
1124 return -1;
1125 }
1126 }
1127
1128 if (pj_optind != argc) {
1129
1130 if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
1131 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
1132 return -1;
1133 }
1134 app.client.dst_uri = pj_str(argv[pj_optind]);
1135
1136 pj_optind++;
1137
1138 }
1139
1140 if (pj_optind != argc) {
1141 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
1142 return -1;
1143 }
1144
1145 return 0;
1146}
1147
1148
1149/* Send one stateless request */
1150static pj_status_t submit_stateless_job(void)
1151{
1152 pjsip_tx_data *tdata;
1153 pj_status_t status;
1154
1155 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1156 &app.client.dst_uri, &app.local_uri,
1157 &app.client.dst_uri, &app.local_contact,
1158 NULL, -1, NULL, &tdata);
1159 if (status != PJ_SUCCESS) {
1160 app_perror(THIS_FILE, "Error creating request", status);
1161 report_completion(701);
1162 return status;
1163 }
1164
1165 status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
1166 NULL);
1167 if (status != PJ_SUCCESS) {
1168 pjsip_tx_data_dec_ref(tdata);
1169 app_perror(THIS_FILE, "Error sending stateless request", status);
1170 report_completion(701);
1171 return status;
1172 }
1173
1174 return PJ_SUCCESS;
1175}
1176
1177
1178/* This callback is called when client transaction state has changed */
1179static void tsx_completion_cb(void *token, pjsip_event *event)
1180{
1181 pjsip_transaction *tsx;
1182
1183 PJ_UNUSED_ARG(token);
1184
1185 if (event->type != PJSIP_EVENT_TSX_STATE)
1186 return;
1187
1188 tsx = event->body.tsx_state.tsx;
1189
1190 if (tsx->mod_data[mod_test.id] != NULL) {
1191 /* This transaction has been calculated before */
1192 return;
1193 }
1194
1195 if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
1196 report_completion(tsx->status_code);
1197 tsx->mod_data[mod_test.id] = (void*)1;
1198 }
1199 else if (tsx->method.id == PJSIP_INVITE_METHOD &&
1200 tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
1201
1202 report_completion(tsx->status_code);
1203 tsx->mod_data[mod_test.id] = (void*)1;
1204
1205 } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
1206
1207 report_completion(tsx->status_code);
1208 tsx->mod_data[mod_test.id] = (void*)1;
1209
Benny Prijono1479b652006-07-03 14:18:17 +00001210 TERMINATE_TSX(tsx, tsx->status_code);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001211 }
1212}
1213
1214
1215/* Send one stateful request */
1216static pj_status_t submit_job(void)
1217{
1218 pjsip_tx_data *tdata;
1219 pj_status_t status;
1220
1221 status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
1222 &app.client.dst_uri, &app.local_uri,
1223 &app.client.dst_uri, &app.local_contact,
1224 NULL, -1, NULL, &tdata);
1225 if (status != PJ_SUCCESS) {
1226 app_perror(THIS_FILE, "Error creating request", status);
1227 report_completion(701);
1228 return status;
1229 }
1230
1231 status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL,
1232 &tsx_completion_cb);
1233 if (status != PJ_SUCCESS) {
1234 app_perror(THIS_FILE, "Error sending stateful request", status);
1235 //should have been reported by tsx_completion_cb().
1236 //report_completion(701);
1237 pjsip_tx_data_dec_ref(tdata);
1238 }
1239 return status;
1240}
1241
1242
1243/* Client worker thread */
1244static int client_thread(void *arg)
1245{
Benny Prijono1479b652006-07-03 14:18:17 +00001246 unsigned last_timeout_check = 0;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001247 pj_time_val end_time, now;
1248
1249 PJ_UNUSED_ARG(arg);
1250
1251 pj_thread_sleep(100);
1252
1253 pj_gettimeofday(&end_time);
1254 end_time.sec += app.client.timeout;
1255
1256 if (app.client.first_request.sec == 0) {
1257 pj_gettimeofday(&app.client.first_request);
1258 }
1259
1260 /* Submit all jobs */
1261 while (app.client.job_submitted < app.client.job_count && !app.thread_quit) {
1262 pj_time_val timeout = { 0, 0 };
1263 unsigned i;
1264 int outstanding;
1265 pj_status_t status;
1266
Benny Prijono1479b652006-07-03 14:18:17 +00001267 /* Calculate current outstanding job */
1268 outstanding = app.client.job_submitted - app.client.job_finished;
1269
1270 /* Update stats on max outstanding jobs */
1271 if (outstanding > (int)app.client.stat_max_window)
1272 app.client.stat_max_window = outstanding;
1273
Benny Prijono514ca6b2006-07-03 01:30:01 +00001274 /* Wait if there are more pending jobs than allowed in the
1275 * window.
1276 */
Benny Prijono1479b652006-07-03 14:18:17 +00001277 for (i=0; outstanding > (int)app.client.job_window && i<100; ++i) {
Benny Prijono514ca6b2006-07-03 01:30:01 +00001278 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
1279 outstanding = app.client.job_submitted - app.client.job_finished;
1280 }
1281
Benny Prijono1479b652006-07-03 14:18:17 +00001282
1283 /* Submit one job */
Benny Prijono514ca6b2006-07-03 01:30:01 +00001284 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1285 status = make_call(&app.client.dst_uri);
1286 } else if (app.client.stateless) {
1287 status = submit_stateless_job();
1288 } else {
1289 status = submit_job();
1290 }
1291
1292 ++app.client.job_submitted;
1293
Benny Prijono1479b652006-07-03 14:18:17 +00001294 /* Handle event */
1295 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
1296
1297 /* Check for time out */
1298 if (app.client.job_submitted - last_timeout_check >= 2000) {
1299 pj_gettimeofday(&now);
1300 if (PJ_TIME_VAL_GTE(now, end_time))
Benny Prijono514ca6b2006-07-03 01:30:01 +00001301 break;
Benny Prijono1479b652006-07-03 14:18:17 +00001302 last_timeout_check = app.client.job_submitted;
Benny Prijono514ca6b2006-07-03 01:30:01 +00001303 }
1304 }
1305
1306 /* Wait until all jobs completes, or timed out */
1307 do {
1308 pj_time_val timeout = { 0, 0 };
1309 unsigned i;
1310
Benny Prijono1479b652006-07-03 14:18:17 +00001311 for (i=0; i<2000; ++i) {
1312 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001313 }
1314
1315 pj_gettimeofday(&now);
1316
1317 } while (now.sec < end_time.sec &&
1318 app.client.job_finished < app.client.job_count &&
1319 !app.thread_quit);
1320
1321 return 0;
1322}
1323
1324
1325static const char *good_number(char *buf, pj_int32_t val)
1326{
1327 if (val < 1000) {
1328 pj_ansi_sprintf(buf, "%d", val);
1329 } else if (val < 1000000) {
1330 pj_ansi_sprintf(buf, "%d.%dK",
1331 val / 1000,
1332 (val % 1000) / 100);
1333 } else {
1334 pj_ansi_sprintf(buf, "%d.%02dM",
1335 val / 1000000,
1336 (val % 1000000) / 10000);
1337 }
1338
1339 return buf;
1340}
1341
1342
1343static int server_thread(void *arg)
1344{
1345 pj_time_val timeout = { 0, 0 };
1346 unsigned thread_index = (unsigned)arg;
1347 pj_time_val last_report, next_report;
1348
1349 pj_gettimeofday(&last_report);
1350 next_report = last_report;
1351 next_report.sec++;
1352
1353 while (!app.thread_quit) {
1354 pj_time_val now;
1355 unsigned i;
1356
1357 for (i=0; i<100; ++i) {
1358 unsigned count = 0;
1359 pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
1360 if (count == 0)
1361 break;
1362 }
1363
1364 if (thread_index == 0) {
1365 pj_gettimeofday(&now);
1366
1367 if (PJ_TIME_VAL_GTE(now, next_report)) {
1368 pj_time_val tmp;
1369 unsigned msec;
1370 unsigned stateless, stateful, call;
1371 char str_stateless[32], str_stateful[32], str_call[32];
1372
1373 tmp = now;
1374 PJ_TIME_VAL_SUB(tmp, last_report);
1375 msec = PJ_TIME_VAL_MSEC(tmp);
1376
1377 last_report = now;
1378 next_report = last_report;
1379 next_report.sec++;
1380
1381 stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
1382 stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
1383 call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
1384
1385 good_number(str_stateless, app.server.cur_state.stateless_cnt);
1386 good_number(str_stateful, app.server.cur_state.stateful_cnt);
1387 good_number(str_call, app.server.cur_state.call_cnt);
1388
Benny Prijono1479b652006-07-03 14:18:17 +00001389 printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s) \r",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001390 str_stateless, stateless*1000/msec,
1391 str_stateful, stateful*1000/msec,
1392 str_call, call*1000/msec);
1393 fflush(stdout);
1394
1395 app.server.prev_state = app.server.cur_state;
1396 }
1397 }
1398 }
1399
1400 return 0;
1401}
1402
Benny Prijono1479b652006-07-03 14:18:17 +00001403static void write_report(const char *msg)
1404{
1405 puts(msg);
1406
1407#if defined(PJ_WIN32) && PJ_WIN32!=0
1408 OutputDebugString(msg);
1409 OutputDebugString("\n");
1410#endif
1411}
1412
1413
Benny Prijono514ca6b2006-07-03 01:30:01 +00001414int main(int argc, char *argv[])
1415{
Benny Prijono1479b652006-07-03 14:18:17 +00001416 static char report[1024];
Benny Prijono514ca6b2006-07-03 01:30:01 +00001417
1418 if (create_app() != 0)
1419 return 1;
1420
1421 if (init_options(argc, argv) != 0)
1422 return 1;
1423
1424 if (init_sip() != 0)
1425 return 1;
1426
1427 if (init_media() != 0)
1428 return 1;
1429
1430 if (app.verbose) {
1431 pj_log_set_level(4);
1432 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001433 } else {
1434 pj_log_set_level(3);
Benny Prijono514ca6b2006-07-03 01:30:01 +00001435 }
1436
1437
1438 /* Misc infos */
1439 if (app.client.dst_uri.slen != 0) {
1440 if (app.client.method.id == PJSIP_INVITE_METHOD &&
1441 app.client.stateless)
1442 {
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001443 PJ_LOG(3,(THIS_FILE,
1444 "Info: --stateless option makes no sense for INVITE, "
1445 "ignored."));
Benny Prijono514ca6b2006-07-03 01:30:01 +00001446 }
1447 }
1448
1449 if (app.real_sdp) {
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001450 PJ_LOG(3,(THIS_FILE,
1451 "Info: client/server using real SDP from PJMEDIA"));
Benny Prijono514ca6b2006-07-03 01:30:01 +00001452 } else {
1453 PJ_LOG(3,(THIS_FILE, "Info: client/server using dummy SDP"));
1454 }
1455
1456
1457 if (app.client.dst_uri.slen) {
1458 /* Client mode */
1459 pj_status_t status;
1460 char test_type[64];
1461 unsigned msec;
1462 unsigned i;
1463
1464 /* Get the job name */
1465 if (app.client.method.id == PJSIP_INVITE_METHOD) {
1466 pj_ansi_strcpy(test_type, "INVITE calls");
1467 } else if (app.client.stateless) {
1468 pj_ansi_sprintf(test_type, "stateless %.*s requests",
1469 (int)app.client.method.name.slen,
1470 app.client.method.name.ptr);
1471 } else {
1472 pj_ansi_sprintf(test_type, "stateful %.*s requests",
1473 (int)app.client.method.name.slen,
1474 app.client.method.name.ptr);
1475 }
1476
1477
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001478 PJ_LOG(3,(THIS_FILE, "Sending %d %s to %.*s, please wait..",
1479 app.client.job_count, test_type,
1480 (int)app.client.dst_uri.slen, app.client.dst_uri.ptr));
Benny Prijono514ca6b2006-07-03 01:30:01 +00001481
1482 for (i=0; i<app.thread_count; ++i) {
1483 status = pj_thread_create(app.pool, NULL, &client_thread, NULL,
1484 0, 0, &app.thread[i]);
1485 if (status != PJ_SUCCESS) {
1486 app_perror(THIS_FILE, "Unable to create thread", status);
1487 return 1;
1488 }
1489 }
1490
1491 for (i=0; i<app.thread_count; ++i) {
1492 pj_thread_join(app.thread[i]);
1493 app.thread[i] = NULL;
1494 }
1495
1496 if (app.client.last_completion.sec) {
1497 pj_time_val duration;
1498 duration = app.client.last_completion;
1499 PJ_TIME_VAL_SUB(duration, app.client.first_request);
1500 msec = PJ_TIME_VAL_MSEC(duration);
1501 } else {
1502 msec = app.client.timeout * 1000;
1503 }
1504
1505 if (msec == 0) msec = 1;
1506
Benny Prijono1479b652006-07-03 14:18:17 +00001507 pj_ansi_snprintf(
1508 report, sizeof(report),
1509 "Total %d %s sent, %d responses received in %d ms:\n"
1510 " - 2xx responses: %7d (rate=%d/sec)\n"
1511 " - 3xx responses: %7d (rate=%d/sec)\n"
1512 " - 4xx responses: %7d (rate=%d/sec)\n"
1513 " - 5xx responses: %7d (rate=%d/sec)\n"
1514 " - 6xx responses: %7d (rate=%d/sec)\n"
1515 " - 7xx responses: %7d (rate=%d/sec)\n"
1516 " ----------------\n"
1517 " TOTAL responses: %7d (rate=%d/sec)\n",
1518 app.client.job_submitted, test_type,
1519 app.client.total_responses, msec,
1520 app.client.status_class[2], app.client.status_class[2]*1000/msec,
1521 app.client.status_class[3], app.client.status_class[3]*1000/msec,
1522 app.client.status_class[4], app.client.status_class[4]*1000/msec,
1523 app.client.status_class[5], app.client.status_class[5]*1000/msec,
1524 app.client.status_class[6], app.client.status_class[6]*1000/msec,
1525 app.client.status_class[7], app.client.status_class[7]*1000/msec,
1526 app.client.total_responses, app.client.total_responses*1000/msec);
1527
1528 write_report(report);
1529
1530 pj_ansi_sprintf(report, "Maximum outstanding job: %d",
1531 app.client.stat_max_window);
1532 write_report(report);
1533
Benny Prijono514ca6b2006-07-03 01:30:01 +00001534
1535 } else {
1536 /* Server mode */
1537 char s[10];
1538 pj_status_t status;
1539 unsigned i;
1540
1541 puts("");
1542 puts("pjsip-perf started in server-mode");
1543
1544 printf("Receiving requests on the following URIs:\n"
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001545 " sip:0@%.*s:%d;transport=%s for stateless handling (non-INVITE only)\n"
1546 " sip:1@%.*s:%d;transport=%s for stateful handling (INVITE and non-INVITE)\n"
1547 " sip:2@%.*s:%d;transport=%s for call handling (INVITE only)\n",
Benny Prijono514ca6b2006-07-03 01:30:01 +00001548 (int)app.local_addr.slen,
1549 app.local_addr.ptr,
1550 app.local_port,
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001551 (app.use_tcp ? "tcp" : "udp"),
Benny Prijono514ca6b2006-07-03 01:30:01 +00001552 (int)app.local_addr.slen,
1553 app.local_addr.ptr,
1554 app.local_port,
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001555 (app.use_tcp ? "tcp" : "udp"),
Benny Prijono514ca6b2006-07-03 01:30:01 +00001556 (int)app.local_addr.slen,
1557 app.local_addr.ptr,
Benny Prijonoc03a3c52006-07-03 02:31:10 +00001558 app.local_port,
1559 (app.use_tcp ? "tcp" : "udp"));
Benny Prijono514ca6b2006-07-03 01:30:01 +00001560
1561 for (i=0; i<app.thread_count; ++i) {
1562 status = pj_thread_create(app.pool, NULL, &server_thread, (void*)i,
1563 0, 0, &app.thread[i]);
1564 if (status != PJ_SUCCESS) {
1565 app_perror(THIS_FILE, "Unable to create thread", status);
1566 return 1;
1567 }
1568 }
1569
1570 puts("Press <ENTER> to quit");
1571 fflush(stdout);
1572 fgets(s, sizeof(s), stdin);
1573
1574 app.thread_quit = PJ_TRUE;
1575 for (i=0; i<app.thread_count; ++i) {
1576 pj_thread_join(app.thread[i]);
1577 app.thread[i] = NULL;
1578 }
1579 }
1580
1581
1582 destroy_app();
1583
1584 return 0;
1585}
1586