summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/pjsip-perf
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/pjsip-perf')
-rw-r--r--pjsip-apps/src/pjsip-perf/handler_call.c412
-rw-r--r--pjsip-apps/src/pjsip-perf/handler_options.c148
-rw-r--r--pjsip-apps/src/pjsip-perf/main.c668
-rw-r--r--pjsip-apps/src/pjsip-perf/pjsip_perf.h164
4 files changed, 1392 insertions, 0 deletions
diff --git a/pjsip-apps/src/pjsip-perf/handler_call.c b/pjsip-apps/src/pjsip-perf/handler_call.c
new file mode 100644
index 00000000..469d72c8
--- /dev/null
+++ b/pjsip-apps/src/pjsip-perf/handler_call.c
@@ -0,0 +1,412 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "pjsip_perf.h"
+
+/*
+ * This file handles call generation and incoming calls.
+ */
+#define THIS_FILE "handler_call.c"
+
+/*
+ * Dummy SDP.
+ */
+static pjmedia_sdp_session *local_sdp;
+
+
+#define TIMER_ID 1234
+
+/* Call data, to be attached to invite session. */
+struct call_data
+{
+ pjsip_inv_session *inv;
+ pj_bool_t confirmed;
+ pj_timer_entry bye_timer;
+ void *test_data;
+ void (*completion_cb)(void*,pj_bool_t);
+};
+
+
+/****************************************************************************
+ *
+ * INCOMING CALL HANDLER
+ *
+ ****************************************************************************
+ */
+
+
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_call =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-perf-call", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_call_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+
+/*
+ * Handle incoming requests.
+ * Because this module is registered to the INVITE module too, this
+ * callback may be called for requests inside a dialog.
+ */
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv;
+ pjsip_tx_data *response;
+ struct call_data *call_data;
+ unsigned options;
+ pj_status_t status;
+
+
+ /* Don't want to handle anything but INVITE */
+ if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
+ return PJ_FALSE;
+
+ /* Don't want to handle request that's already associated with
+ * existing dialog or transaction.
+ */
+ if (pjsip_rdata_get_dlg(rdata) || pjsip_rdata_get_tsx(rdata))
+ return PJ_FALSE;
+
+
+ /* Verify that we can handle the request. */
+ options = 0;
+ status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+ settings.endpt, &response);
+ if (status != PJ_SUCCESS) {
+
+ /*
+ * No we can't handle the incoming INVITE request.
+ */
+
+ if (response) {
+ pjsip_response_addr res_addr;
+
+ pjsip_get_response_addr(response->pool, rdata, &res_addr);
+ pjsip_endpt_send_response(settings.endpt, &res_addr, response,
+ NULL, NULL);
+
+ } else {
+
+ /* Respond with 500 (Internal Server Error) */
+ pjsip_endpt_respond_stateless(settings.endpt, rdata, 500, NULL,
+ NULL, NULL);
+ }
+
+ return PJ_TRUE;
+ }
+
+ /*
+ * Yes we can handle the incoming INVITE request.
+ */
+
+ /* Create dialog. */
+ status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_respond(dlg, rdata, 500, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Create invite session: */
+ status = pjsip_inv_create_uas( dlg, rdata, local_sdp, 0, &inv);
+ if (status != PJ_SUCCESS) {
+
+ pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+ // TODO: Need to delete dialog
+ return PJ_TRUE;
+ }
+
+ /* Create and associate call data. */
+ call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data));
+ call_data->inv = inv;
+ call_data->bye_timer.user_data = call_data;
+ inv->mod_data[mod_call.id] = call_data;
+
+ /* Answer with 200 straight away. */
+ status = pjsip_inv_initial_answer(inv, rdata, 200,
+ NULL, NULL, &response);
+ if (status != PJ_SUCCESS) {
+
+ app_perror(THIS_FILE, "Unable to create 200 response", status);
+
+ pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+ // TODO: Need to delete dialog
+
+ } else {
+ status = pjsip_inv_send_msg(inv, response, NULL);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Unable to send 100 response", status);
+ }
+
+
+ return PJ_TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * OUTGOING CALL GENERATOR
+ *
+ ****************************************************************************
+ */
+
+/**
+ * Make outgoing call.
+ */
+pj_status_t call_spawn_test( const pj_str_t *target,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ unsigned cred_cnt,
+ const pjsip_cred_info cred[],
+ const pjsip_route_hdr *route_set,
+ void *test_data,
+ void (*completion_cb)(void*,pj_bool_t))
+{
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv;
+ pjsip_tx_data *tdata;
+ struct call_data *call_data;
+ pj_status_t status;
+
+ /* Create outgoing dialog: */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ from, NULL,
+ to, target,
+ &dlg);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Dialog creation failed", status);
+ return status;
+ }
+
+ /* Create the INVITE session: */
+ status = pjsip_inv_create_uac( dlg, local_sdp, 0, &inv);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Invite session creation failed", status);
+ goto on_error;
+ }
+
+
+ /* Set dialog Route-Set: */
+ if (route_set)
+ pjsip_dlg_set_route_set(dlg, route_set);
+
+
+ /* Set credentials: */
+ pjsip_auth_clt_set_credentials( &dlg->auth_sess, cred_cnt, cred);
+
+
+ /* Create initial INVITE: */
+ status = pjsip_inv_invite(inv, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create initial INVITE request",
+ status);
+ goto on_error;
+ }
+
+
+ /* Create and associate our call data */
+ call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data));
+ call_data->inv = inv;
+ call_data->test_data = test_data;
+ call_data->bye_timer.user_data = call_data;
+ call_data->completion_cb = completion_cb;
+
+ inv->mod_data[mod_call.id] = call_data;
+
+
+ /* Send initial INVITE: */
+ status = pjsip_inv_send_msg(inv, tdata, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror( THIS_FILE, "Unable to send initial INVITE request",
+ status);
+ goto on_error;
+ }
+
+
+ return PJ_SUCCESS;
+
+
+on_error:
+ PJ_TODO(DESTROY_DIALOG_ON_FAIL);
+ return status;
+}
+
+
+/* Timer callback to send BYE. */
+static void bye_callback( pj_timer_heap_t *ht, pj_timer_entry *e)
+{
+ struct call_data *call_data = e->user_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ e->id = 0;
+
+ status = pjsip_inv_end_session(call_data->inv, PJSIP_SC_REQUEST_TIMEOUT,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create BYE", status);
+ return;
+ }
+
+ status = pjsip_inv_send_msg(call_data->inv, tdata, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to send BYE", status);
+ return;
+ }
+
+}
+
+/*
+ * This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e)
+{
+ struct call_data *call_data;
+
+ call_data = inv->mod_data[mod_call.id];
+ if (call_data == NULL)
+ return;
+
+ /* Once call has been confirmed, schedule timer to terminate the call. */
+ if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
+
+ pj_time_val interval;
+
+ call_data->confirmed = PJ_TRUE;
+
+ /* For UAC, schedule time to send BYE.
+ * For UAS, schedule time to disconnect INVITE, just in case BYE
+ * is not received.
+ */
+ if (inv->role == PJSIP_ROLE_UAC)
+ interval.sec = settings.duration, interval.msec = 0;
+ else
+ interval.sec = settings.duration+5, interval.msec = 0;
+
+ call_data->bye_timer.id = TIMER_ID;
+ call_data->bye_timer.cb = &bye_callback;
+ pjsip_endpt_schedule_timer(settings.endpt, &call_data->bye_timer,
+ &interval);
+
+ }
+ /* If call has been terminated, cancel our timer, if any.
+ * And call tester's callback.
+ */
+ else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+ /* Cancel timer, if any. */
+ if (call_data->bye_timer.id == TIMER_ID) {
+ call_data->bye_timer.id = 0;
+ pjsip_endpt_cancel_timer(settings.endpt, &call_data->bye_timer);
+ }
+
+ /* Detach call data from the invite session. */
+ inv->mod_data[mod_call.id] = NULL;
+
+ /* Call tester callback. */
+ if (call_data->completion_cb) {
+ (*call_data->completion_cb)(call_data->test_data,
+ call_data->confirmed);
+ }
+ }
+}
+
+
+/*
+ * This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void call_on_forked( pjsip_inv_session *inv, pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+
+ PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+
+
+/****************************************************************************
+ *
+ * INITIALIZATION
+ *
+ ****************************************************************************
+ */
+
+pj_status_t call_handler_init(void)
+{
+ pjsip_inv_callback inv_cb;
+ pjmedia_sock_info skinfo;
+ pj_status_t status;
+
+ /* Register incoming call handler. */
+ status = pjsip_endpt_register_module(settings.endpt, &mod_call);
+ if (status != PJ_SUCCESS) {
+ app_perror( THIS_FILE, "Unable to register call handler",
+ status);
+ return status;
+ }
+
+ /* Invite session callback: */
+ pj_memset(&inv_cb, 0, sizeof(inv_cb));
+ inv_cb.on_state_changed = &call_on_state_changed;
+ inv_cb.on_new_session = &call_on_forked;
+
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(settings.endpt, &mod_call, &inv_cb);
+ if (status != PJ_SUCCESS) {
+ app_perror( THIS_FILE, "Unable to initialize INVITE session module",
+ status);
+ return status;
+ }
+
+ /* Create dummy SDP. */
+ pj_memset(&skinfo, 0, sizeof(skinfo));
+ pj_sockaddr_in_init(&skinfo.rtp_addr_name, pj_gethostname(), 4000);
+ pj_sockaddr_in_init(&skinfo.rtcp_addr_name, pj_gethostname(), 4001);
+
+ status = pjmedia_endpt_create_sdp( settings.med_endpt, settings.pool,
+ 1, &skinfo, &local_sdp);
+ if (status != PJ_SUCCESS) {
+ app_perror( THIS_FILE, "Unable to generate local SDP",
+ status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip-apps/src/pjsip-perf/handler_options.c b/pjsip-apps/src/pjsip-perf/handler_options.c
new file mode 100644
index 00000000..0f643d7b
--- /dev/null
+++ b/pjsip-apps/src/pjsip-perf/handler_options.c
@@ -0,0 +1,148 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "pjsip_perf.h"
+
+
+/*
+ * This file handles OPTIONS generator and incoming OPTIONS requests.
+ */
+#define THIS_FILE "handler_options.c"
+
+
+/****************************************************************************
+ *
+ * INCOMING OPTIONS HANDLER
+ *
+ ****************************************************************************
+ */
+
+
+static pj_bool_t mod_options_on_rx_request(pjsip_rx_data *rdata);
+
+
+/* The module instance. */
+static pjsip_module mod_perf_options =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-perf-options", 16 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_options_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+static pj_bool_t mod_options_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ if (msg->line.req.method.id == PJSIP_OPTIONS_METHOD) {
+
+ if (settings.stateless) {
+ pjsip_endpt_respond_stateless( settings.endpt, rdata, 200, NULL,
+ NULL, NULL);
+ } else {
+
+ pjsip_endpt_respond( settings.endpt, NULL, rdata, 200, NULL,
+ NULL, NULL, NULL);
+ }
+
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+
+/****************************************************************************
+ *
+ * OUTGOING OPTIONS GENERATOR.
+ *
+ ****************************************************************************
+ */
+
+struct callback_data
+{
+ void *test_data;
+ void (*completion_cb)(void*,pj_bool_t);
+};
+
+static void options_callback(void *token, const pjsip_event *e)
+{
+ struct callback_data *cb_data = token;
+
+ if (e->type == PJSIP_EVENT_TSX_STATE) {
+ (*cb_data->completion_cb)(cb_data->test_data,
+ e->body.tsx_state.tsx->status_code/100==2);
+ }
+}
+
+pj_status_t options_spawn_test(const pj_str_t *target,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ unsigned cred_cnt,
+ const pjsip_cred_info cred[],
+ const pjsip_route_hdr *route_set,
+ void *test_data,
+ void (*completion_cb)(void*,pj_bool_t))
+{
+ pj_status_t status;
+ struct callback_data *cb_data;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_endpt_create_request( settings.endpt,
+ &pjsip_options_method,
+ target,
+ from,
+ to,
+ NULL, NULL, -1, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create request", status);
+ return status;
+ }
+
+ cb_data = pj_pool_alloc(tdata->pool, sizeof(struct callback_data));
+ cb_data->test_data = test_data;
+ cb_data->completion_cb = completion_cb;
+
+ status = pjsip_endpt_send_request( settings.endpt, tdata, -1,
+ cb_data, &options_callback);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to send request", status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+pj_status_t options_handler_init(void)
+{
+ return pjsip_endpt_register_module(settings.endpt, &mod_perf_options);
+}
+
+
diff --git a/pjsip-apps/src/pjsip-perf/main.c b/pjsip-apps/src/pjsip-perf/main.c
new file mode 100644
index 00000000..91817e2c
--- /dev/null
+++ b/pjsip-apps/src/pjsip-perf/main.c
@@ -0,0 +1,668 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "pjsip_perf.h"
+#include <pjsua-lib/getopt.h>
+#include <stdlib.h> /* atoi */
+
+#define THIS_FILE "main.c"
+
+pjsip_perf_settings settings;
+
+/* Show error message. */
+void app_perror(const char *sender, const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+/* Init default settings. */
+static void init_settings(void)
+{
+ pj_status_t status;
+
+ settings.stateless = 1;
+ settings.start_rate = 10;
+ settings.max_capacity = 64;
+ settings.duration = 0;
+ settings.thread_cnt = 1;
+ settings.local_port = 5060;
+
+ pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD);
+
+ pj_init();
+
+ /* Create caching pool. */
+ pj_caching_pool_init(&settings.cp, &pj_pool_factory_default_policy,
+ 4 * 1024 * 1024);
+
+ /* Create application pool. */
+ settings.pool = pj_pool_create(&settings.cp.factory, "pjsip-perf", 1024,
+ 1024, NULL);
+
+ /* Create endpoint. */
+ status = pjsip_endpt_create(&settings.cp.factory, NULL, &settings.endpt);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create endpoint", status);
+ return;
+ }
+
+}
+
+/* Poll function. */
+static int PJ_THREAD_FUNC poll_pjsip(void *arg)
+{
+ pj_status_t last_err = 0;
+
+ PJ_UNUSED_ARG(arg);
+
+ do {
+ pj_time_val timeout = { 0, 10 };
+ pj_status_t status;
+
+ status = pjsip_endpt_handle_events (settings.endpt, &timeout);
+ if (status != last_err) {
+ last_err = status;
+ app_perror(THIS_FILE, "handle_events() returned error", status);
+ }
+ } while (!settings.quit_flag);
+
+ return 0;
+}
+
+/* Initialize */
+static pj_status_t initialize(void)
+{
+ pj_sockaddr_in addr;
+ int i;
+ pj_status_t status;
+
+ /* Create UDP transport. */
+ pj_memset(&addr, 0, sizeof(addr));
+ addr.sin_family = PJ_AF_INET;
+ addr.sin_port = pj_htons((pj_uint16_t)settings.local_port);
+ status = pjsip_udp_transport_start(settings.endpt, &addr, NULL,
+ settings.thread_cnt, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to start UDP transport", status);
+ return status;
+ }
+
+
+ /* Initialize transaction layer: */
+ status = pjsip_tsx_layer_init_module(settings.endpt);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Transaction layer initialization error",
+ status);
+ return status;
+ }
+
+ /* Initialize UA layer module: */
+ pjsip_ua_init_module( settings.endpt, NULL );
+
+ /* Init core SIMPLE module : */
+ pjsip_evsub_init_module(settings.endpt);
+
+ /* Init presence module: */
+ pjsip_pres_init_module( settings.endpt, pjsip_evsub_instance());
+
+ /* Init xfer/REFER module */
+ pjsip_xfer_init_module( settings.endpt );
+
+ /* Init multimedia endpoint. */
+ status = pjmedia_endpt_create(&settings.cp.factory, &settings.med_endpt);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create media endpoint",
+ status);
+ return status;
+ }
+
+ /* Init OPTIONS test handler */
+ status = options_handler_init();
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create OPTIONS handler", status);
+ return status;
+ }
+
+ /* Init call test handler */
+ status = call_handler_init();
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to initialize call handler", status);
+ return status;
+ }
+
+
+ /* Start worker thread. */
+ for (i=0; i<settings.thread_cnt; ++i) {
+ status = pj_thread_create(settings.pool, "pjsip-perf", &poll_pjsip,
+ NULL, 0, 0, &settings.thread[i]);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create thread", status);
+ return status;
+ }
+ }
+
+ pj_log_set_level(3);
+ return PJ_SUCCESS;
+}
+
+
+/* Shutdown */
+static void shutdown(void)
+{
+ int i;
+
+ /* Signal and wait worker thread to quit. */
+ settings.quit_flag = 1;
+
+ for (i=0; i<settings.thread_cnt; ++i) {
+ pj_thread_join(settings.thread[i]);
+ pj_thread_destroy(settings.thread[i]);
+ }
+
+ pjsip_endpt_destroy(settings.endpt);
+ pj_caching_pool_destroy(&settings.cp);
+}
+
+
+/* Verify that valid SIP url is given. */
+pj_status_t verify_sip_url(const char *c_url)
+{
+ pjsip_uri *p;
+ pj_pool_t *pool;
+ char *url;
+ int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+ if (!len) return -1;
+
+ pool = pj_pool_create(&settings.cp.factory, "check%p", 1024, 0, NULL);
+ if (!pool) return -1;
+
+ url = pj_pool_alloc(pool, len+1);
+ pj_ansi_strcpy(url, c_url);
+
+ p = pjsip_parse_uri(pool, url, len, 0);
+ if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+ p = NULL;
+
+ pj_pool_release(pool);
+ return p ? 0 : -1;
+}
+
+
+
+/* Usage */
+static void usage(void)
+{
+ puts("Usage:");
+ puts(" pjsip-perf [options] [target]");
+ puts("where");
+ puts(" target Optional default target URL");
+ puts("");
+ puts("General options:");
+ puts(" --help Display this help screen");
+ puts(" --version Display version info");
+ puts("");
+ puts("SIP options:");
+ puts(" --local-port=N SIP local port");
+ puts(" --stateless Handle incoming request statelessly if possible");
+ puts(" --thread-cnt=N Number of worker threads (default=1)");
+ puts("");
+ puts("Rate control:");
+ puts(" --start-rate=N Start rate in tasks per seconds (default=1)");
+ puts("");
+ puts("Capacity control:");
+ puts(" --max-capacity=N Maximum outstanding sessions (default=64)");
+ puts("");
+ puts("Duration control:");
+ puts(" --duration=secs Sessions duration (default=0)");
+ puts("");
+}
+
+
+/* Read options. */
+static pj_status_t parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_LOCAL_PORT,
+ OPT_STATELESS,
+ OPT_THREAD_CNT,
+ OPT_START_RATE,
+ OPT_MAX_CAPACITY,
+ OPT_DURATION
+ };
+ struct option long_opts[] = {
+ { "help", 0, 0, OPT_HELP},
+ { "version", 0, 0, OPT_VERSION},
+ { "local-port", 1, 0, OPT_LOCAL_PORT},
+ { "stateless", 0, 0, OPT_STATELESS},
+ { "thread-cnt", 1, 0, OPT_THREAD_CNT},
+ { "start-rate", 1, 0, OPT_START_RATE},
+ { "max-capacity", 1, 0, OPT_MAX_CAPACITY},
+ { "duration", 1, 0, OPT_DURATION},
+ { NULL, 0, 0, 0}
+ };
+ int c, option_index;
+
+ optind = 0;
+ while ((c=getopt_long(argc, argv, "", long_opts, &option_index)) != -1) {
+ switch (c) {
+
+ case OPT_HELP:
+ usage();
+ return PJ_EINVAL;
+
+ case OPT_VERSION:
+ pj_dump_config();
+ return PJ_EINVAL;
+
+ case OPT_LOCAL_PORT:
+ settings.local_port = atoi(optarg);
+ if (settings.local_port < 1 || settings.local_port > 65535) {
+ PJ_LOG(1,(THIS_FILE,"Invalid --local-port %s", optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
+ case OPT_STATELESS:
+ settings.stateless = 1;
+ break;
+
+ case OPT_THREAD_CNT:
+ settings.thread_cnt = atoi(optarg);
+ if (settings.thread_cnt < 1 ||
+ settings.thread_cnt > PJ_ARRAY_SIZE(settings.thread))
+ {
+ PJ_LOG(1,(THIS_FILE,"Invalid --thread-cnt %s", optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
+ case OPT_START_RATE:
+ settings.start_rate = atoi(optarg);
+ if (settings.start_rate < 1 || settings.start_rate > 1000000) {
+ PJ_LOG(1,(THIS_FILE,"Invalid --start-rate %s", optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
+ case OPT_MAX_CAPACITY:
+ settings.max_capacity = atoi(optarg);
+ if (settings.max_capacity < 1 || settings.max_capacity > 65000) {
+ PJ_LOG(1,(THIS_FILE,
+ "Invalid --max-capacity %s (range=1-65000)",
+ optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
+ case OPT_DURATION:
+ settings.duration = atoi(optarg);
+ if (settings.duration < 0 || settings.duration > 1000000) {
+ PJ_LOG(1,(THIS_FILE,"Invalid --duration %s", optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
+ }
+ }
+
+ if (optind != argc) {
+ if (verify_sip_url(argv[optind]) != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, "Invalid SIP URL %s", argv[optind]));
+ return PJ_EINVAL;
+ }
+
+ settings.target = pj_str(argv[optind]);
+ ++optind;
+ }
+
+ if (optind != argc) {
+ printf("Error: unknown options %s\n", argv[optind]);
+ return PJ_EINVAL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static void spawn_batch( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry );
+
+/* Completion callback. */
+static void completion_cb(void *token, pj_bool_t success)
+{
+ batch *batch = token;
+
+ if (success)
+ batch->success++;
+ else
+ batch->failed++;
+
+ if (batch->success+batch->failed == batch->rate) {
+ pj_time_val elapsed, sess_elapsed;
+ unsigned msec;
+
+ pj_gettimeofday(&batch->end_time);
+ elapsed = sess_elapsed = batch->end_time;
+
+ PJ_TIME_VAL_SUB(elapsed, batch->start_time);
+ PJ_TIME_VAL_SUB(sess_elapsed, settings.session->start_time);
+ msec = PJ_TIME_VAL_MSEC(elapsed);
+ if (msec == 0) msec = 1;
+
+ PJ_LOG(3,(THIS_FILE, "%02d:%02d:%02d: %d tasks in %d.%ds (%d tasks/sec)",
+ (sess_elapsed.sec / 3600),
+ (sess_elapsed.sec % 3600) / 60,
+ (sess_elapsed.sec % 60),
+ batch->rate,
+ elapsed.sec, elapsed.msec,
+ batch->rate * 1000 / msec));
+
+ if (!settings.session->stopping) {
+ pj_time_val interval;
+
+ if (msec >= 1000)
+ interval.sec = interval.msec = 0;
+ else
+ interval.sec = 0, interval.msec = 1000-msec;
+
+ settings.timer.cb = &spawn_batch;
+ pjsip_endpt_schedule_timer( settings.endpt, &settings.timer, &interval);
+ } else {
+ PJ_LOG(3,(THIS_FILE, "%.*s test session completed",
+ (int)settings.session->method.name.slen,
+ settings.session->method.name.ptr));
+ pj_pool_release(settings.session->pool);
+ settings.session = NULL;
+ }
+ }
+}
+
+/* Spawn new batch. */
+static void spawn_batch( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry )
+{
+ session *sess = settings.session;
+ batch *batch;
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_cred_info cred_info[1];
+ pj_time_val now, spawn_time, sess_time;
+
+ unsigned i;
+
+ if (!pj_list_empty(&sess->free_list)) {
+ batch = sess->free_list.next;
+ pj_list_erase(batch);
+ } else {
+ batch = pj_pool_alloc(sess->pool, sizeof(struct batch));
+ }
+
+ pj_gettimeofday(&batch->start_time);
+ batch->rate = settings.cur_rate;
+ batch->started = 0;
+ batch->success = 0;
+ batch->failed = 0;
+
+ pj_list_push_back(&sess->active_list, batch);
+
+ for (i=0; i<batch->rate; ++i) {
+ pj_str_t from = { "sip:user@127.0.0.1", 18};
+
+ if (sess->method.id == PJSIP_OPTIONS_METHOD) {
+ status = options_spawn_test(&settings.target, &from,
+ &settings.target,
+ 0, cred_info, NULL, batch,
+ &completion_cb);
+ } else if (sess->method.id == PJSIP_INVITE_METHOD) {
+ status = call_spawn_test( &settings.target, &from,
+ &settings.target,
+ 0, cred_info, NULL, batch,
+ &completion_cb);
+ }
+ if (status != PJ_SUCCESS)
+ break;
+
+ batch->started++;
+ }
+
+ pj_gettimeofday(&now);
+ spawn_time = sess_time = now;
+ PJ_TIME_VAL_SUB(spawn_time, batch->start_time);
+ PJ_TIME_VAL_SUB(sess_time, sess->start_time);
+
+ sess->total_created += batch->started;
+
+ batch = sess->active_list.next;
+ sess->outstanding = 0;
+ while (batch != &sess->active_list) {
+ sess->outstanding += (batch->started - batch->success - batch->failed);
+
+ if (batch->started == batch->success + batch->failed) {
+ struct batch *next = batch->next;
+ pj_list_erase(batch);
+ pj_list_push_back(&sess->free_list, batch);
+ batch = next;
+ } else {
+ batch = batch->next;
+ }
+ }
+}
+
+
+/* Start new session */
+static void start_session(pj_bool_t auto_repeat)
+{
+ pj_time_val interval = { 1, 0 };
+ pj_pool_t *pool;
+ session *sess;
+
+ pool = pjsip_endpt_create_pool(settings.endpt, "session", 4000, 4000);
+ if (!pool) {
+ app_perror(THIS_FILE, "Unable to create pool", PJ_ENOMEM);
+ return;
+ }
+
+ sess = pj_pool_zalloc(pool, sizeof(session));
+ sess->pool = pool;
+ sess->stopping = auto_repeat ? 0 : 1;
+ sess->method = settings.method;
+
+ pj_list_init(&sess->active_list);
+ pj_list_init(&sess->free_list);
+ pj_gettimeofday(&sess->start_time);
+
+ settings.session = sess;
+
+ spawn_batch(NULL, NULL);
+}
+
+
+/* Dump state */
+static void dump(pj_bool_t detail)
+{
+ pjsip_endpt_dump(settings.endpt, detail);
+ pjsip_tsx_layer_dump(detail);
+ pjsip_ua_dump(detail);
+}
+
+
+/* help screen */
+static void help_screen(void)
+{
+ puts ("+============================================================================+");
+ printf("| Current mode: %-10s Current rate: %-5d Call Capacity: %-7d |\n",
+ settings.method.name.ptr, settings.cur_rate, settings.max_capacity);
+ printf("| Call Duration: %-7d |\n",
+ settings.duration);
+
+ puts ("+--------------------------------------+-------------------------------------+");
+ puts ("| Test Settings | Misc Commands: |");
+ puts ("| | |");
+ puts ("| m Change mode | |");
+ puts ("| + - Increment/decrement rate by 10 | d Dump status |");
+ puts ("| * / Increment/decrement rate by 100 | d1 Dump detailed (e.g. tables) |");
+ puts ("+--------------------------------------+-------------------------------------+");
+ puts ("| Test Commands |");
+ puts ("| |");
+ puts ("| s Start single test batch |");
+ puts ("| sc Start continuous test x Stop continuous tests |");
+ puts ("+----------------------------------------------------------------------------+");
+ puts ("| q: Quit |");
+ puts ("+============================================================================+");
+ puts ("");
+
+}
+
+/*
+ * Input simple string
+ */
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
+{
+ char *p;
+
+ printf("%s (empty to cancel): ", title); fflush(stdout);
+ fgets(buf, len, stdin);
+
+ /* Remove trailing newlines. */
+ for (p=buf; ; ++p) {
+ if (*p=='\r' || *p=='\n') *p='\0';
+ else if (!*p) break;
+ }
+
+ if (!*buf)
+ return PJ_FALSE;
+
+ return PJ_TRUE;
+}
+
+/* Main input loop */
+static void test_main(void)
+{
+ char menuin[10];
+ char input[80];
+
+ settings.cur_rate = settings.start_rate;
+
+ help_screen();
+
+ for (;;) {
+ printf(">>>> "); fflush(stdout);
+
+ fgets(menuin, sizeof(menuin), stdin);
+
+ switch (menuin[0]) {
+ case 's':
+ if (settings.session != NULL) {
+ PJ_LOG(3,(THIS_FILE,"Error: another session is in progress"));
+ } else if (settings.target.slen == 0) {
+ PJ_LOG(3,(THIS_FILE,"Error: target URL is not configured"));
+ } else {
+ start_session(menuin[1]=='c');
+ }
+ break;
+
+ case 'x':
+ if (settings.session) {
+ settings.session->stopping = 1;
+ } else {
+ PJ_LOG(3,(THIS_FILE,"Error: no sessions"));
+ }
+ break;
+
+ case 'm':
+ if (!simple_input("Change method [OPTIONS,INVITE]", input, sizeof(input)))
+ continue;
+
+ if (pj_ansi_stricmp(input, "OPTIONS")==0)
+ pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD);
+ else if (pj_ansi_stricmp(input, "INVITE")==0)
+ pjsip_method_set(&settings.method, PJSIP_INVITE_METHOD);
+ else {
+ puts("Error: invalid method");
+ }
+ break;
+
+ case 'd':
+ dump(menuin[1]=='1');
+ break;
+
+ case '+':
+ settings.cur_rate += 10;
+ PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+ break;
+
+ case '-':
+ if (settings.cur_rate > 10) {
+ settings.cur_rate -= 10;
+ PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+ }
+ break;
+
+ case '*':
+ settings.cur_rate += 100;
+ PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+ break;
+
+ case '/':
+ if (settings.cur_rate > 100) {
+ settings.cur_rate -= 100;
+ PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+ }
+ break;
+
+ case 'q':
+ return;
+
+ default:
+ help_screen();
+ break;
+
+ }
+ }
+}
+
+
+/* main() */
+int main(int argc, char *argv[])
+{
+ pj_status_t status;
+
+ init_settings();
+
+ status = parse_options(argc, argv);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ status = initialize();
+ if (status != PJ_SUCCESS)
+ return 1;
+
+
+ test_main();
+
+ shutdown();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/pjsip-perf/pjsip_perf.h b/pjsip-apps/src/pjsip-perf/pjsip_perf.h
new file mode 100644
index 00000000..103ea205
--- /dev/null
+++ b/pjsip-apps/src/pjsip-perf/pjsip_perf.h
@@ -0,0 +1,164 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJSIP_PERF_H__
+#define __PJSIP_PERF_H__
+
+#include <pjsua-lib/pjsua.h>
+
+
+PJ_BEGIN_DECL
+
+
+typedef struct batch batch;
+typedef struct session session;
+
+/**
+ * A test batch.
+ */
+struct batch
+{
+ PJ_DECL_LIST_MEMBER(struct batch);
+
+ unsigned rate;
+ unsigned started;
+ unsigned success;
+ unsigned failed;
+ pj_time_val start_time;
+ pj_time_val end_time;
+};
+
+/**
+ * Test session.
+ */
+struct session
+{
+ pj_pool_t *pool;
+ pj_time_val start_time;
+ pj_bool_t stopping;
+ pjsip_method method;
+ struct batch active_list;
+ struct batch free_list;
+
+ unsigned outstanding;
+ unsigned total_created;
+};
+
+
+/**
+ * Request parameter.
+ */
+struct request_param
+{
+ pj_str_t dst;
+ pj_str_t src;
+ pjsip_cred_info cred;
+};
+
+
+typedef struct request_param request_param;
+
+
+void app_perror(const char *sender, const char *title, pj_status_t status);
+
+/* OPTIONS test */
+pj_status_t options_handler_init(void);
+pj_status_t options_spawn_test(const pj_str_t *target,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ unsigned cred_cnt,
+ const pjsip_cred_info cred[],
+ const pjsip_route_hdr *route_set,
+ void *test_data,
+ void (*completion_cb)(void*,pj_bool_t));
+
+/* CALL test */
+pj_status_t call_handler_init(void);
+pj_status_t call_spawn_test( const pj_str_t *target,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ unsigned cred_cnt,
+ const pjsip_cred_info cred[],
+ const pjsip_route_hdr *route_set,
+ void *test_data,
+ void (*completion_cb)(void*,pj_bool_t));
+
+
+
+/**
+ * Global settings
+ */
+struct pjsip_perf_settings
+{
+ /* Global */
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+
+ /* Network: */
+ int local_port;
+
+ /* Threads. */
+ pj_bool_t quit_flag;
+ int thread_cnt;
+ pj_thread_t *thread[16];
+
+ /* Outgoing request method: */
+ pjsip_method method;
+
+ /* Default target: */
+ pj_str_t target;
+
+ /* Media: */
+ pjmedia_endpt *med_endpt;
+ pjmedia_conf *mconf;
+
+ /* Handling incoming requests: */
+ pj_bool_t stateless;
+
+ /* Rate control. */
+ pj_uint32_t start_rate;
+ pj_uint32_t cur_rate;
+
+ /* Capacity control. */
+ pj_uint32_t max_capacity;
+
+ /* Duration control: */
+ pj_uint32_t duration;
+
+ /* Test control: */
+ session *session;
+ pj_timer_entry timer;
+};
+
+
+typedef struct pjsip_perf_settings pjsip_perf_settings;
+
+extern pjsip_perf_settings settings;
+
+
+
+PJ_END_DECL
+
+
+#endif /* __PJSIP_PERF_H__ */
+
+
+
+