/* $Id$ */ /* * Copyright (C) 2003-2006 Benny Prijono * * 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 #include /* 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 = 0; settings.start_rate = 10; settings.max_capacity = 64; settings.duration = 0; settings.thread_cnt = 1; settings.local_port = 5060; settings.log_level = 3; settings.app_log_level = 3; 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; } /***************************************************************************** * This is a simple module to count and log messages */ static void on_rx_msg(pjsip_rx_data *rdata) { PJ_LOG(5,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" "%s\n" "--end msg--", rdata->msg_info.len, pjsip_rx_data_get_info(rdata), rdata->pkt_info.src_name, rdata->pkt_info.src_port, rdata->msg_info.msg_buf)); } static void on_tx_msg(pjsip_tx_data *tdata) { PJ_LOG(5,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" "%s\n" "--end msg--", (tdata->buf.cur - tdata->buf.start), pjsip_tx_data_get_info(tdata), tdata->tp_info.dst_name, tdata->tp_info.dst_port, tdata->buf.start)); } static pj_bool_t mod_counter_on_rx_request(pjsip_rx_data *rdata) { settings.rx_req++; on_rx_msg(rdata); return PJ_FALSE; } static pj_bool_t mod_counter_on_rx_response(pjsip_rx_data *rdata) { settings.rx_res++; on_rx_msg(rdata); return PJ_FALSE; } static pj_status_t mod_counter_on_tx_request(pjsip_tx_data *tdata) { settings.tx_req++; on_tx_msg(tdata); return PJ_SUCCESS; } static pj_status_t mod_counter_on_tx_response(pjsip_tx_data *tdata) { settings.tx_res++; on_tx_msg(tdata); return PJ_SUCCESS; } static pjsip_module mod_counter = { NULL, NULL, /* prev, next. */ { "mod-counter", 11 }, /* Name. */ -1, /* Id */ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ NULL, /* load() */ NULL, /* start() */ NULL, /* stop() */ NULL, /* unload() */ &mod_counter_on_rx_request, /* on_rx_request() */ &mod_counter_on_rx_response, /* on_rx_response() */ &mod_counter_on_tx_request, /* on_tx_request. */ &mod_counter_on_tx_response, /* on_tx_response() */ NULL, /* on_tsx_state() */ }; /***************************************************************************** * Console application custom logging: */ static FILE *log_file; static void app_log_writer(int level, const char *buffer, int len) { /* Write to both stdout and file. */ if (level <= settings.app_log_level) pj_log_write(level, buffer, len); if (log_file) { fwrite(buffer, len, 1, log_file); fflush(log_file); } } static pj_status_t app_logging_init(void) { /* Redirect log function to ours */ pj_log_set_log_func( &app_log_writer ); /* If output log file is desired, create the file: */ if (settings.log_file) { log_file = fopen(settings.log_file, "wt"); if (log_file == NULL) { PJ_LOG(1,(THIS_FILE, "Unable to open log file %s", settings.log_file)); return -1; } } return PJ_SUCCESS; } void app_logging_shutdown(void) { /* Close logging file, if any: */ if (log_file) { fclose(log_file); log_file = NULL; } } /* Initialize */ static pj_status_t initialize(void) { pj_sockaddr_in addr; int i; pj_status_t status; /* Init logging */ if (app_logging_init() != PJ_SUCCESS) return -1; /* 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; } /* Register message counter module. */ status = pjsip_endpt_register_module(settings.endpt, &mod_counter); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to register module", status); return status; } /* Start worker thread. */ for (i=0; i 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; /* Batch time. */ PJ_TIME_VAL_SUB(elapsed, batch->start_time); msec = PJ_TIME_VAL_MSEC(elapsed); if (msec == 0) msec = 1; /* Session time */ PJ_TIME_VAL_SUB(sess_elapsed, settings.session->start_time); /* Spawn time */ PJ_TIME_VAL_SUB(batch->spawned_time, batch->start_time); if (batch->failed) { PJ_LOG(2,(THIS_FILE, "%02d:%02d:%02d: %d tasks in %d.%ds (%d tasks/sec), " "spawn=time=%d.%d, FAILED=%d", (sess_elapsed.sec / 3600), (sess_elapsed.sec % 3600) / 60, (sess_elapsed.sec % 60), batch->rate, elapsed.sec, elapsed.msec, batch->rate * 1000 / msec, batch->spawned_time.sec, batch->spawned_time.msec, batch->failed)); } else { PJ_LOG(3,(THIS_FILE, "%02d:%02d:%02d: %d tasks in %d.%ds (%d tasks/sec), " "spawn=time=%d.%d", (sess_elapsed.sec / 3600), (sess_elapsed.sec % 3600) / 60, (sess_elapsed.sec % 60), batch->rate, elapsed.sec, elapsed.msec, batch->rate * 1000 / msec, batch->spawned_time.sec, batch->spawned_time.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 elapsed; 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_gettimeofday(&batch->start_time); batch->spawned_time = batch->start_time; pj_list_push_back(&sess->active_list, batch); for (i=0; irate; ++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++; elapsed.sec = elapsed.msec = 0; pjsip_endpt_handle_events(settings.endpt, &elapsed); } pj_gettimeofday(&batch->spawned_time); /// #if 0 elapsed = batch->spawned_time; PJ_TIME_VAL_SUB(elapsed, batch->start_time); PJ_LOG(2,(THIS_FILE, "%d requests sent in %d ms", batch->started, PJ_TIME_VAL_MSEC(elapsed))); #endif 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 = { 0, 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; settings.timer.cb = &spawn_batch; pjsip_endpt_schedule_timer( settings.endpt, &settings.timer, &interval); } /* 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); printf("| Total tx requests: %-7u rx requests: %-7u |\n", settings.tx_req, settings.rx_req); printf("| tx responses: %-7u rx responses: %-7u |\n", settings.tx_res, settings.rx_res); 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 c Clear counters |"); 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; puts("Stopping sessions..."); } else { PJ_LOG(3,(THIS_FILE,"Error: no sessions")); } break; case 'c': /* Clear counters */ settings.rx_req = settings.rx_res = settings.tx_req = settings.tx_res = 0; puts("Counters cleared"); 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; }