/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>

#define THIS_FILE   "tsx_uas_test.c"


static pjsip_module mod_tsx_user;

static int uac_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
{
    unsigned i;
    pjsip_tx_data *request;
    pjsip_transaction **tsx;
    pj_timestamp t1, t2, elapsed;
    pjsip_via_hdr *via;
    pj_status_t status;

    /* Create the request first. */
    pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
    pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
    pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
    pj_str_t str_contact = str_from;

    status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
                                        &str_target, &str_from, &str_to,
                                        &str_contact, NULL, -1, NULL,
                                        &request);
    if (status != PJ_SUCCESS) {
        app_perror("    error: unable to create request", status);
        return status;
    }

    via = (pjsip_via_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_VIA,
                                              NULL);

    /* Create transaction array */
    tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));

    pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
    mod_tsx_user.id = -1;

    /* Benchmark */
    elapsed.u64 = 0;
    pj_get_timestamp(&t1);
    for (i=0; i<working_set; ++i) {
        status = pjsip_tsx_create_uac(&mod_tsx_user, request, &tsx[i]);
        if (status != PJ_SUCCESS)
            goto on_error;
        /* Reset branch param */
        via->branch_param.slen = 0;
    }
    pj_get_timestamp(&t2);
    pj_sub_timestamp(&t2, &t1);
    pj_add_timestamp(&elapsed, &t2);

    p_elapsed->u64 = elapsed.u64;
    status = PJ_SUCCESS;
    
on_error:
    for (i=0; i<working_set; ++i) {
        if (tsx[i]) {
            pj_timer_heap_t *th;

            pjsip_tsx_terminate(tsx[i], 601);
            tsx[i] = NULL;

            th = pjsip_endpt_get_timer_heap(endpt);
            pj_timer_heap_poll(th, NULL);
        }
    }
    pjsip_tx_data_dec_ref(request);
    flush_events(2000);
    return status;
}



static int uas_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
{
    unsigned i;
    pjsip_tx_data *request;
    pjsip_via_hdr *via;
    pjsip_rx_data rdata;
    pj_sockaddr_in remote;
    pjsip_transaction **tsx;
    pj_timestamp t1, t2, elapsed;
    char branch_buf[80] = PJSIP_RFC3261_BRANCH_ID "0000000000";
    pj_status_t status;

    /* Create the request first. */
    pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
    pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
    pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
    pj_str_t str_contact = str_from;

    status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
                                        &str_target, &str_from, &str_to,
                                        &str_contact, NULL, -1, NULL,
                                        &request);
    if (status != PJ_SUCCESS) {
        app_perror("    error: unable to create request", status);
        return status;
    }

    /* Create  Via */
    via = pjsip_via_hdr_create(request->pool);
    via->sent_by.host = pj_str("192.168.0.7");
    via->sent_by.port = 5061;
    via->transport = pj_str("udp");
    via->rport_param = 1;
    via->recvd_param = pj_str("192.168.0.7");
    pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via);
    

    /* Create "dummy" rdata from the tdata */
    pj_bzero(&rdata, sizeof(pjsip_rx_data));
    rdata.tp_info.pool = request->pool;
    rdata.msg_info.msg = request->msg;
    rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
    rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL);
    rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL);
    rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
    rdata.msg_info.via = via;
    
    pj_sockaddr_in_init(&remote, 0, 0);
    status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, 
                                           &remote, sizeof(pj_sockaddr_in),
                                           NULL, &rdata.tp_info.transport);
    if (status != PJ_SUCCESS) {
        app_perror("    error: unable to get loop transport", status);
        return status;
    }


    /* Create transaction array */
    tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));

    pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
    mod_tsx_user.id = -1;


    /* Benchmark */
    elapsed.u64 = 0;
    pj_get_timestamp(&t1);
    for (i=0; i<working_set; ++i) {
        via->branch_param.ptr = branch_buf;
        via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN + 
                                    pj_ansi_sprintf(branch_buf+PJSIP_RFC3261_BRANCH_LEN,
                                                    "-%d", i);
        status = pjsip_tsx_create_uas(&mod_tsx_user, &rdata, &tsx[i]);
        if (status != PJ_SUCCESS)
            goto on_error;

    }
    pj_get_timestamp(&t2);
    pj_sub_timestamp(&t2, &t1);
    pj_add_timestamp(&elapsed, &t2);

    p_elapsed->u64 = elapsed.u64;
    status = PJ_SUCCESS;
    
on_error:
    for (i=0; i<working_set; ++i) {
        if (tsx[i]) {
            pj_timer_heap_t *th;

            pjsip_tsx_terminate(tsx[i], 601);
            tsx[i] = NULL;

            th = pjsip_endpt_get_timer_heap(endpt);
            pj_timer_heap_poll(th, NULL);

        }
    }
    pjsip_tx_data_dec_ref(request);
    flush_events(2000);
    return status;
}



int tsx_bench(void)
{
    enum { WORKING_SET=10000, REPEAT = 4 };
    unsigned i, speed;
    pj_timestamp usec[REPEAT], min, freq;
    char desc[250];
    int status;

    status = pj_get_timestamp_freq(&freq);
    if (status != PJ_SUCCESS)
        return status;


    /*
     * Benchmark UAC
     */
    PJ_LOG(3,(THIS_FILE, "   benchmarking UAC transaction creation:"));
    for (i=0; i<REPEAT; ++i) {
        PJ_LOG(3,(THIS_FILE, "    test %d of %d..",
                  i+1, REPEAT));
        PJ_LOG(3,(THIS_FILE, "    number of current tsx: %d",
                  pjsip_tsx_layer_get_tsx_count()));
        status = uac_tsx_bench(WORKING_SET, &usec[i]);
        if (status != PJ_SUCCESS)
            return status;
    }

    min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
    for (i=0; i<REPEAT; ++i) {
        if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
    }

    
    /* Report time */
    pj_ansi_sprintf(desc, "Time to create %d UAC transactions, in miliseconds",
                          WORKING_SET);
    report_ival("create-uac-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);


    /* Write speed */
    speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
    PJ_LOG(3,(THIS_FILE, "    UAC created at %d tsx/sec", speed));

    pj_ansi_sprintf(desc, "Number of UAC transactions that potentially can be created per second "
                          "with <tt>pjsip_tsx_create_uac()</tt>, based on the time "
                          "to create %d simultaneous transactions above.",
                          WORKING_SET);

    report_ival("create-uac-tsx-per-sec", 
                speed, "tsx/sec", desc);



    /*
     * Benchmark UAS
     */
    PJ_LOG(3,(THIS_FILE, "   benchmarking UAS transaction creation:"));
    for (i=0; i<REPEAT; ++i) {
        PJ_LOG(3,(THIS_FILE, "    test %d of %d..",
                  i+1, REPEAT));
        PJ_LOG(3,(THIS_FILE, "    number of current tsx: %d",
                  pjsip_tsx_layer_get_tsx_count()));
        status = uas_tsx_bench(WORKING_SET, &usec[i]);
        if (status != PJ_SUCCESS)
            return status;
    }

    min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
    for (i=0; i<REPEAT; ++i) {
        if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
    }

    
    /* Report time */
    pj_ansi_sprintf(desc, "Time to create %d UAS transactions, in miliseconds",
                          WORKING_SET);
    report_ival("create-uas-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);


    /* Write speed */
    speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
    PJ_LOG(3,(THIS_FILE, "    UAS created at %d tsx/sec", speed));

    pj_ansi_sprintf(desc, "Number of UAS transactions that potentially can be created per second "
                          "with <tt>pjsip_tsx_create_uas()</tt>, based on the time "
                          "to create %d simultaneous transactions above.",
                          WORKING_SET);

    report_ival("create-uas-tsx-per-sec", 
                speed, "tsx/sec", desc);

    return PJ_SUCCESS;
}

