<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
<!-- Include general documentation entities -->
<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
%docentities;
]>

<chapter>
    <title>Admin Guide</title>

    <section id="ptimer.overview" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>Overview</title>
        <para>
        This module implements precise timers, which are not limited in
        precision by the kamailio tick frequency.
        </para>
        <para>
        All timers in this module are implemented as background processes,
        using system time for synchronization.
        </para>
        <para>
        Each timer can have 3 config routes associated with it:
        <itemizedlist>
        <listitem>
        <para>
        <emphasis>start</emphasis> - the route to execute when the timer starts
        </para>
        </listitem>
        <listitem>
        <para>
        <emphasis>loop</emphasis> - the route to execute continuously (every interval)
        </para>
        </listitem>
        <listitem>
        <para>
        <emphasis>end</emphasis> - the route to execute when the timer ends
        </para>
        </listitem>
        </itemizedlist>
        </para>
        <para>
        A faked SIP message is given as parameter to called functions,
        so all functions available for REQUEST_ROUTE can be used.
        Additionally, the message state is preserved from start to end of the timer.
        </para>
    </section>

    <section id="ptimer.dependencies" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>Dependencies</title>
        <section>
            <title>Module Dependencies</title>
            <para>
            The following modules must be loaded before this module:
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>No dependencies on other &kamailio; modules</emphasis>.
            </para>
            </listitem>
            </itemizedlist>
            </para>
        </section>
        <section>
            <title>External Dependencies</title>
            <para>
            The following libraries or applications must be installed before running
            &kamailio; with this module loaded:
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>None</emphasis>.
            </para>
            </listitem>
            </itemizedlist>
            </para>
        </section>
    </section>

    <section id="ptimer.mod_params" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>Parameters</title>
        <section>
            <title><varname>default_type</varname> (int)</title>
            <para>
            The default timer type (if not present on the "timer" parameter).
            </para>
            <para>
            If not set, defaults to 1, basic timers.
            </para>
            <para>
            Can be one of:
            </para>
            <itemizedlist>
            <listitem>
            <para>
            0 - basic timer

            A simple sleep / execute timer.
            </para>
            </listitem>
            <listitem>
            <para>
            1 - sync timer

            Synchronizes time each loop, accounting for execution time drift.
            This type of timer will use a nanosecond precision time functions
            when sleeping / syncing, and the interval will be scaled for you.
            </para>
            </listitem>
            <listitem>
            <para>
            2 - slice timer

            A basic timer that slices given tasks into chunks for each loop iteration.
            The current slice and number of tasks are available via the pseudo variables.
            Tasks are split using an interval splitting algorithm:
            <programlisting>
tasks(i) = ⌊((i + 1) * n) / k⌋ − ⌊(i * n) / k⌋
            </programlisting>
            Where, n = total tasks, k = number of slices, and i = current slice index.
            For simplicity, the number of tasks assigned to each worker is roughly:
            <programlisting>
tasks = n / k
            </programlisting>
            with the integer remainder evenly distributed.
            Further slicing per worker is possible within the start the route.
            </para>
            </listitem>
            <listitem>
            <para>
            3 - synced slice timer

            This type of timer combines the functionality of a sync timer and slice timer.
            </para>
            </listitem>
            </itemizedlist>
            <para>
            <emphasis role="bold">Example: set <varname>default_type</varname> parameter</emphasis>
            </para>
            <programlisting>
...

# set the default type to basic timers
modparam("ptimer", "default_type", 0)
# set the default type to sync timers
modparam("ptimer", "default_type", 1)
# set the default type to slice timers
modparam("ptimer", "default_type", 2)
# set the default type to synced slice timers
modparam("ptimer", "default_type", 3)

...
            </programlisting>
        </section>
        <section>
            <title><varname>default_interval</varname> (str)</title>
            <para>
            The default interval to execute loop route for timers (if not present on the "timer" parameter).
            Suffix with "s", "ms", or "us" for an interval in seconds, milliseconds, or microseconds.
            If a number is given without a suffix, the number will be treated as seconds.
            Defaults to default_interval.
            </para>
            <para>
            If not set, defaults to 1s.
            </para>
            <para>
            <emphasis role="bold">Example: set <varname>default_interval</varname> parameter</emphasis>
            </para>
            <programlisting>
...

# set the default interval to 300 seconds
modparam("ptimer", "default_interval", "300")
# set the default interval to 10 seconds
modparam("ptimer", "default_interval", "10s")
# set the default interval to 10 milliseconds
modparam("ptimer", "default_interval", 100ms)
# set the default interval to 100 microseconds
modparam("ptimer", "default_interval", 10ous)

...
            </programlisting>
        </section>
        <section>
            <title><varname>default_nworkers</varname> (int)</title>
            <para>
            The default number of workers to run per timer (if not present on the "timer" parameter).
            </para>
            <para>
            <emphasis role="bold">Example: set <varname>default_nworkers</varname> parameter</emphasis>
            </para>
            <programlisting>
...

# set the default number of timer workers to 8
modparam("ptimer", "default_nworkers", 8)

...
            </programlisting>
        </section>
        <section>
            <title><varname>timer</varname> (str)</title>
            <para>
            The definition of a timer. The value of the parameter must have the
            following format:
            </para>
            <itemizedlist>
            <listitem>
            <para>
            "name=_str_;type=_int_;interval=_str_;nworkers=_int_;ntasks=_int_;nslices=_int_"
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The parameter can be set multiple times to define more timers in
            same configuration file.
            </para>
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>name</emphasis> - name of the timer.
            This attribute is required.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>type</emphasis> - type of timer to use (see default_type for more info).
            This attribute is optional, defaults to default_type.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>interval</emphasis> - interval at which the timer workers
            will execute the loop route. See default_interval for more information.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>nworkers</emphasis> - the number of worker processes to
            spawn for this timer. Defaults to default_nworkers.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>ntasks</emphasis> - number of tasks each worker will process.
            If static, you can use this parameter, otherwise you can set it within
            the start route.
            This attribute is optional.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>nslices</emphasis> - number of slices to split ntasks into.
            This attribute, although set on other timers, only effects tasks when
            used on a slice timer.
            </para>
            </listitem>
            </itemizedlist>
            <para>
            <emphasis role="bold">Example: set <varname>timer</varname> parameter</emphasis>
            </para>
            <programlisting>
...

# basic timer looping every 1 sec
modparam("ptimer", "timer", "name=t1;interval=1s;type=0")
# sync timer that processes 25 tasks every 100 ms
modparam("ptimer", "timer", "name=t2;interval=100ms;type=1;ntasks=25")
# slice timer that processes 10% of the tasks from queue of 2500 every 10 sec
modparam("ptimer", "timer", "name=t3;interval=10s;type=2;nslices=10;ntasks=2500")
# basic timer that runs the loop route on 8 different worker processes every 100us
modparam("ptimer", "timer", "name=t4;interval=100us;type=1;nworkers=8")

...
            </programlisting>
        </section>
        <section>
            <title><varname>start</varname> (str)</title>
            <para>
            Route to be executed when timer stars. The value of the parameter
            must have the following format:
            </para>
            <itemizedlist>
            <listitem>
            <para>
            "timer=_str_;route=_str_"
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The start route is optional, only one start route can be set per timer.
            </para>
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>timer</emphasis> - name of the timer.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>route</emphasis> - the name of the route block to be executed,
            or the name of the function from kemi script. The kemi function
            receives a string parameter with the value being the name of the module.
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The start route is the typical place to put code that sets up for the loop route.
            One can modify some of the pseudo variables in this route, such as ntasks.
            Dynamic or external values can be obtained and stored here, i.e. from an htable,
            database, etc...
            Variables and AVPs set here will remain in-tact in the subsequent loop and end routes.
            If the start route returns -1 the timer will jump execution to the end route.
            </para>
            <para>
            <emphasis role="bold">Example: set <varname>start</varname> parameter</emphasis>
            </para>
            <programlisting>
...

modparam("ptimer", "timer", "name=t1;interval=10")
modparam("ptimer", "start", "timer=t1;route=TIMER_START")
modparam("ptimer", "loop", "timer=t1;route=TIMER_LOOP")

route[TIMER_START] {
    $ptimer(ntasks) = 1000;
}

...

route[TIMER_LOOP] {
    $var(i) = 0;
    while($var(i) &lt; $ptimer(ntasks)) {
        xlog("L_INFO", "handled task $var(i)\n");
        $var(i) = $var(i) + 1;
    }
}

...
            </programlisting>
        </section>
        <section>
            <title><varname>loop</varname> (str)</title>
            <para>
            Route to be executed on timer interval. The value of the parameter
            must have the following format:
            </para>
            <itemizedlist>
            <listitem>
            <para>
            "timer=_str_;route=_str_"
            </para>
            </listitem>
            </itemizedlist>
            <para>
            Each timer requires a loop route, only one loop route can be set per timer.
            </para>
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>timer</emphasis> - name of the timer.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>route</emphasis> - the name of the route block to be executed,
            or the name of the function from kemi script. The kemi function
            receives a string parameter with the value being the name of the module.
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The loop route is executed every interval, continuously.
            This is typically where you would process the tasks.
            If the loop route returns -1 the timer will jump execution to the end route.
            </para>
            <para>
            <emphasis role="bold">Example: set <varname>loop</varname> parameter</emphasis>
            </para>
            <programlisting>
...

modparam("ptimer", "timer", "name=t1;interval=10")
modparam("ptimer", "loop", "timer=t1;route=TIMER_LOOP")

...

route[TIMER_LOOP] {
    xlog("L_INFO", "timer $ptimer(name) executed at $TF\n");
}

...
            </programlisting>
            <para>
            <emphasis role="bold">Example: use <varname>loop</varname> parameter with Kemi engine</emphasis>
            </para>
            <programlisting>
...

modparam("ptimer", "timer", "name=t1;interval=10")
modparam("ptimer", "loop", "timer=t1;route=timer_loop")

...

-- ptimer event callback function implemented in Lua
function timer_loop(evname)
    KSR.info("===== ptimer module triggered event\n");
    return 1;
end

...
            </programlisting>
        </section>
        <section>
            <title><varname>end</varname> (str)</title>
            <para>
            Route to be executed when timer ends. The value of the parameter
            must have the following format:
            </para>
            <itemizedlist>
            <listitem>
            <para>
            "timer=_str_;route=_str_"
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The end route is optional, only one end route can be set per timer.
            </para>
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>timer</emphasis> - name of the timer.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>route</emphasis> - the name of the route block to be executed,
            or the name of the function from kemi script. The kemi function
            receives a string parameter with the value being the name of the module.
            </para>
            </listitem>
            </itemizedlist>
            <para>
            The end route runs after the timer ends.
            Typically this would the place to log or store data about the tasks processed.
            After the end route completes, the environment is cleared, and the worker is paused.
            </para>
            <para>
            <emphasis role="bold">Example: set <varname>end</varname> parameter</emphasis>
            </para>
            <programlisting>
...

modparam("ptimer", "timer", "name=t1;interval=100ms")
modparam("ptimer", "start", "timer=t1;route=TIMER_START")
modparam("ptimer", "loop", "timer=t1;route=TIMER_LOOP")
modparam("ptimer", "end", "timer=t1;route=TIMER_END")

...

route[TIMER_START] {
    $var(tasks_done) = 0;
}

route[TIMER_LOOP] {
    $var(tasks_done) = $var(tasks_done) + 1;
}

route[TIMER_END] {
    xlog("L_INFO", "tasks handled $var(tasks_done)\n");
}

...
        </programlisting>
        </section>
    </section>

    <section id="ptimer.mod_pvs" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>Pseudo Variables</title>
        <section>
            <title><varname>$ptimer(...)</varname></title>
            <para>Access to timer / worker attributes.</para>
            <para>This PV is only available within ptimer executed routes.</para>
            <para>The following attributes are available:</para>
            <itemizedlist>
            <listitem>
            <para>
            <emphasis>name</emphasis> - name of the timer executing this route.
            This attribute is read-only.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>type</emphasis> - type of timer executing this route.
            This attribute is read-only.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>interval</emphasis> - interval for this timer.
            This attribute is r/w inside the start route, and r/o elsewhere.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>nworkers</emphasis> - number of workers spawned for this timer.
            This attribute is read-only.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>worker</emphasis> - current worker processing this route.
            This attribute is read-only.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>pid</emphasis> - pid of current worker processing this route.
            This attribute is read-only.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>ntasks</emphasis> - total number of tasks assigned to this timer.
            This attribute is r/w inside the start route, and r/o elsewhere.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>tasks</emphasis> - number of tasks assigned to this worker.
            This attribute is r/w inside the start route, and r/o elsewhere.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>nslices</emphasis> - number of slices this timer will slice tasks into.
            This attribute is r/w inside the start route, and r/o elsewhere.
            </para>
            </listitem>
            <listitem>
            <para>
            <emphasis>slice</emphasis> - current slice this worker is processing.
            This attribute is r/w inside the start route, and r/o elsewhere.
            </para>
            </listitem>
            </itemizedlist>
        </section>
    </section>

    <section id="ptimer.rpc_cmds" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>RPC Commands</title>
        <section>
            <title><function moreinfo="none">ptimer.list</function></title>
            <para>List the loaded timers and their current state.</para>
        </section>
        <section>
            <title><function moreinfo="none">ptimer.pause</function></title>
            <para>Pause execution of timer worker(s).</para>
        </section>
        <section>
            <title><function moreinfo="none">ptimer.continue</function></title>
            <para>Continue execution of timer worker(s).</para>
        </section>
        <section>
            <title><function moreinfo="none">ptimer.start</function></title>
            <para>
            Jump execution of timer worker(s) to the start route.
            This will inherently continue execution if the worker was paused.
            </para>
        </section>
        <section>
            <title><function moreinfo="none">ptimer.loop</function></title>
            <para>
            Jump execution of timer worker(s) to the loop route.
            This will inherently continue execution if the worker was paused.
            </para>
        </section>
        <section>
            <title><function moreinfo="none">ptimer.end</function></title>
            <para>
            Jump execution of timer worker(s) to the end route.
            This will inherently continue execution if the worker was paused.
            </para>
        </section>
    </section>

    <section id="ptimer.examples" xmlns:xi="http://www.w3.org/2001/XInclude">
        <title>Example Usages</title>
        <section>
            <title>Call Rate Shaping</title>
            <para>
            These examples use the ptimer module to limit a customer's CPS (calls per second),
            while smoothing out CPS spikes from the customer's SIP server.
            "Call rate shaping" and "traffic shaping" will be used interchangeably in this section.
            </para>
            <para>
            Traffic shaping can be useful in cases where a customer has temporary CPS spikes,
            or the customer has an under-performing queueing solution they can not change.
            This is most apparent when the rate limiting solution your upstream provider uses
            does not average CPS (common from my experience, typically 1s sliding window).
            Short bursts in CPS from one of your customers will therefore be blocked, and could
            even block your other customers calls from being delivered.
            </para>
            <para>
            The solution to short CPS bursts is queueing your customers calls, and throttling
            call delivery within the CPS limit allocated to you by your upstream provider.
            When a CPS spike occurs, calls that went over the limit will stay in the queue until
            the next iteration, and relayed to your upstream, spaced in time to stay within the limit.
            If a CPS burst lasts for an extended period of time, calls continue to enqueue and continue
            to process at the rate limit, until stale calls in the queue hit the transaction timeout,
            typically configured via fr_timer modparam in the tm module.
            You can tune the timeout of queued calls by changing transaction timeouts.
            </para>
            <para>
            The hard part with call rate shaping is timing precisely when requests from the queue
            are processed, in order to stay within the CPS limit, and spread out network congestion.
            Hence, ptimer is a good fit to time the call queue processing (or any time sensitive tasks).
            </para>
            <section>
                <title>Global Traffic Shaping</title>
                <para>
                In this example we want to set a global rate limit of 1000 CPS, and
                throttle short CPS bursts so our customers get a higher delivery rate.
                </para>
                <para>
                We will split the tasks in the queue such that our timer processes 10%
                of the queue across 8 workers, every 100ms (1/10 the measurement period).
                Resulting in calls from the queue being smoothed out over that 1sec, and
                staying within our constraint of 1K CPS at any point in time.
                </para>
                <para>
                <emphasis role="bold">Example Implementation:</emphasis>
                </para>
                <programlisting><![CDATA[
...

loadmodule "mqueue.so"
loadmodule "ptimer.so"

...

# global call queue
modparam("mqueue", "mqueue", "name=call_queue")
# synced slice timer
# each worker processes 12-13 calls every 100ms (during max load)
modparam("ptimer", "timer", "name=queue_timer;type=3;interval=100ms;nworkers=8;ntasks=1000;nslices=80")
modparam("ptimer", "start", "timer=queue_timer;route=QUEUE_START")
modparam("ptimer", "loop", "timer=queue_timer;route=QUEUE_LOOP")

...

request_route {
    ...

    # routing choices / message changes here

    route(QUEUE_CALL);
}

route[QUEUE_CALL] {
    if(t_suspend()) {
        xlog("L_INFO", "queued call for transaction [$T(id_index):$T(id_label)]\n");
        mq_add("call_queue", "T(id_index)", "$T(id_label)");
        exit;
    }

    xlog("L_ERR", "failed queuing call $ci\n");
    sl_reply_error();
	exit;
}

route[RELAY] {
    ...

	if(!t_relay()) {
        send_reply_error();
	}
}

route[QUEUE_START] {
    # start each worker on a different slice
    $ptimer(slice) = $ptimer(worker) * 10;
}

route[QUEUE_LOOP] {
    $var(i) = 0;
    while($var(i) < $ptimer(tasks)) {
        if(mq_fetch("call_queue")) {
            if(!t_continue("$mqk(call_queue)", "$mqv(call_queue)", "RELAY")) {
                xlog("L_ERR", "failed resuming transaction [$mqk(myq):$mqv(myq)]\n");
            }
        }
        $var(i) = $var(i) + 1;
    }
}

...
                ]]></programlisting>
            </section>
            <section>
                <title>Per Customer Traffic Shaping</title>
                <para>
                In this example we want to set a different rate limit for each of our customers.
                Customer 0-2 we sold 8,20,50 CPS respectively.
                Again, we will throttle short CPS bursts so our customers get a higher delivery rate.
                </para>
                <para>
                To achieve this we will allocate each customer a queue and worker of their own.
                Similar to the global example, we will split the queue tasks into 10% chunks to
                evenlu distribute within each second (100ms interval).
                </para>
                <para>
                <emphasis role="bold">Example Implementation:</emphasis>
                </para>
                <programlisting><![CDATA[
...

loadmodule "mqueue.so"
loadmodule "ptimer.so"

...

# customer call queues
# we could alternative have another data source to look up this queue in start route
modparam("mqueue", "mqueue", "name=customer_0")
modparam("mqueue", "mqueue", "name=customer_1")
modparam("mqueue", "mqueue", "name=customer_2")
# synced slice timers for each customer
# we could allocate more workers per customer if needed
# if we wanted to lookup the quueue with a different identifier
# we would also define a start route here
modparam("ptimer", "timer", "name=customer_0;type=3;interval=100ms;nworkers=1;ntasks=8;nslices=10")
modparam("ptimer", "loop", "timer=customer_0;route=QUEUE_LOOP")
modparam("ptimer", "timer", "name=customer_1;type=3;interval=100ms;nworkers=1;ntasks=8;nslices=10")
modparam("ptimer", "loop", "timer=customer_1;route=QUEUE_LOOP")
modparam("ptimer", "timer", "name=customer_2;type=3;interval=100ms;nworkers=1;ntasks=8;nslices=10")
modparam("ptimer", "loop", "timer=customer_2;route=QUEUE_LOOP")

...

request_route {
    ...

    # routing choices / message changes here

    route(QUEUE_CALL);
}

route[QUEUE_CALL] {
    if(t_suspend()) {
        xlog("L_INFO", "queued call for transaction [$T(id_index):$T(id_label)]\n");
        mq_add("call_queue", "T(id_index)", "$T(id_label)");
        exit;
    }

    xlog("L_ERR", "failed queuing call $ci\n");
    sl_reply_error();
	exit;
}

route[RELAY] {
    ...

	if(!t_relay()) {
        send_reply_error();
	}
}

route[QUEUE_LOOP] {
    $var(i) = 0;
    while($var(i) < $ptimer(tasks)) {
        if(mq_fetch("$ptimer(name)")) {
            if(!t_continue("$mqk(call_queue)", "$mqv(call_queue)", "RELAY")) {
                xlog("L_ERR", "failed resuming transaction [$mqk(myq):$mqv(myq)]\n");
            }
        }
        $var(i) = $var(i) + 1;
    }
}

...
                ]]></programlisting>
            </section>
        </section>
        <section>
            <title>Adaptive Routing</title>
            <para>
            In this example we demonstrate how ptimer can be used to dynamically
            update destinations in a route set based on admin defined metrics.
            The end goal is to guarantee we are providing the level of service
            we promised the customer.
            </para>
            <para>
            We will use the following metrics (they could be anything you want):
            </para>
            <itemizedlist>
            <listitem>
            <para>
            MOS - Mean Opinion Score
            </para>
            </listitem>
            <listitem>
            <para>
            CCR - Call Completion Ratio
            </para>
            </listitem>
            <listitem>
            <para>
            NER - Network Effectiveness Ratio
            </para>
            </listitem>
            </itemizedlist>
            <para>
            For brevity, assume updating the metrics is done elsewhere.
            </para>
            <para>
            <emphasis role="bold">Example Implementation:</emphasis>
            </para>
            <programlisting><![CDATA[
...

loadmodule "dispatcher.so"
loadmodule "htable.so"
loadmodule "ptimer.so"

...

# how we will grab the dst info
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "xavp_dst", "ds_dst")
modparam("dispatcher", "xavp_ctx", "ds_ctx")
# where the metrics are stored (could be anywhere w/ fast access)
# key format: setid-uri-metric
modparam("htable", "htable", "dst_kpi=>size=8")
# where the customer SLA are stored (what we promised the customer)
modparam("htable", "htable", "min_kpi=>size=8")
# basic timer, each worker is handling one destination set (setid 0-2)
# we use the timer worker number to associate the dst set (0-2)
# that association could be mapped elsewhere and looked up in start route
modparam("ptimer", "timer", "name=route_optimizer;type=0;interval=500ms;nworkers=3")
modparam("ptimer", "start", "timer=route_optimizer;route=OPTIMIZE_ROUTES_START")
modparam("ptimer", "loop", "timer=route_optimizer;route=OPTIMIZE_ROUTES_LOOP")

...

route[OPTIMIZE_ROUTES_START] {
    # grab the metric minimums when starting timer
    $var(min_mos) = (int)$sht(min_kpi=>mos);
    $var(min_ccr) = (int)$sht(min_kpi=>ccr);
    $var(min_ner) = (int)$sht(min_kpi=>ner);
    # sanity check
    if(!ds_list_exists("$ptimer(worker)")) {
        return -1;
    }
    # store all the destinations prior to modifying their state
    # this is our local copy used in the loop route
    ds_select("$ptimer(worker)", "8");
}

route[OPTIMIZE_ROUTES_LOOP] {
    # remove / add from dst set based on metrics
    $var(i) = 0;
	while($var(i) < $xavp(ds_ctx=>cnt)) {
	    $var(prefix) = "$ptimer(worker)-$xavp(ds_dst[$var(i)]=>uri)";
        if (
            $sht(dst_kpi=>$var(prefix)-mos) < $var(min_mos) ||
            $sht(dst_kpi=>$var(prefix)-ccr) < $var(min_ccr) ||
            $sht(dst_kpi=>$var(prefix)-ner) < $var(min_ner)
        ) {
            ds_mark_addr("i", "$ptimer(worker)", "$xavp(ds_dst[$var(i)]=>uri)");
        } else {
            ds_mark_addr("a", "$ptimer(worker)", "$xavp(ds_dst[$var(i)]=>uri)");
        }
        $var(i) = $var(i) + 1;
	}
}

...
            ]]></programlisting>
        </section>
        <section>
            <title>Dynamic Timers</title>
            <para>
            These examples use the execution jumping features of ptimer to handle common
            management use cases.
            </para>
            <section>
                <title>Queue Control</title>
                <para>
                In this example we demonstrate how we can hot-swap the worker processing a queue.
                This could be used to change routing to an IVR during holiday, pause during maintenance,
                to revert new routing logic.
                </para>
                <para>
                <emphasis role="bold">Example Implementation:</emphasis>
                </para>
                <programlisting><![CDATA[
...

loadmodule "mqueue.so"
loadmodule "ptimer.so"

...

# call queue
modparam("mqueue", "mqueue", "name=call_queue")
# basic timer handling normal business hours
modparam("ptimer", "timer", "name=normal_timer;type=1;interval=100ms")
modparam("ptimer", "loop", "timer=normal_timer;route=NORMAL_QUEUE_START")
modparam("ptimer", "loop", "timer=normal_timer;route=HANDLE_QUEUE")
# basic timer handling holiday / after hours
modparam("ptimer", "timer", "name=holiday_timer;type=1;interval=100ms")
modparam("ptimer", "loop", "timer=holiday_timer;route=HOLIDAY_QUEUE_START")
modparam("ptimer", "loop", "timer=holiday_timer;route=HANDLE_QUEUE")

...

request_route {
    ...

    route(QUEUE_CALL);
}

route[QUEUE_CALL] {
    if(t_suspend()) {
        xlog("L_INFO", "queued call for transaction [$T(id_index):$T(id_label)]\n");
        mq_add("call_queue", "T(id_index)", "$T(id_label)");
        exit;
    }

    xlog("L_ERR", "failed queuing call $ci\n");
    sl_reply_error();
	exit;
}

route[RELAY_PSTN] {
    $du = "sip:1.1.1.1:5060";

	if(!t_relay()) {
        send_reply_error();
	}
}

route[RELAY_IVR] {
    $du = "sip:9.9.9.9:5060";

	if(!t_relay()) {
        send_reply_error();
	}
}

route[NORMAL_QUEUE_START] {
    $var(relay) = "RELAY_PSTN";
}

route[HOLIDAY_QUEUE_START] {
    $var(relay) = "RELAY_IVR";
    # by default do not loop
    return -1;
}

route[HANDLE_QUEUE] {
    $var(c) = mq_size("$ptimer(name)");
    $var(i) = 0;
    while($var(i) < $var(c)) {
        if (mq_fetch("call_queue")) {
            if (!t_continue("$mqk(call_queue)", "$mqv(call_queue)", "$var(relay)")) {
                xlog("L_ERR", "failed resuming transaction [$mqk(myq):$mqv(myq)]\n");
            }
        }
        $var(i) = $var(i) + 1;
    }
}

...
                ]]></programlisting>
                <para>
                <emphasis role="bold">External Program:</emphasis>
                </para>
                <programlisting><![CDATA[
# holiday starts
kamcmd ptimer.pause normal_timer
kamcmd ptimer.continue holiday_timer

# holiday ends
kamcmd ptimer.pause holiday_timer
kamcmd ptimer.continue normal_timer
                ]]></programlisting>
            </section>
            <section>
                <title>Hot-Reload Tasks</title>
                <para>
                In this example we demonstrate how we can load new data for the
                timer to process without reloading.
                This flexibility means we can load new tasks from any data source
                directly into the background worker process.
                </para>
                <para>
                <emphasis role="bold">Example Implementation:</emphasis>
                </para>
                <programlisting><![CDATA[
...

loadmodule "http_client.so"
loadmodule "jansson.so"
loadmodule "ptimer.so"

...

# synced slice timer that pulls data from an HTTP API
modparam("ptimer", "timer", "name=t1;type=3;interval=100ms;nslices=10")
modparam("ptimer", "loop", "timer=t1;route=GET_DATA")
modparam("ptimer", "loop", "timer=t1;route=HANDLE_DATA")

...

route[GET_DATA] {
    # data pulled for this example: {"data":[{"a":100},{"a":200}], "len", 2}
    http_client_get("http://api.example.com/data", "$var(res)");
    # "ntasks" will be sliced by the timer into "tasks" available in loop
    jansson_get("len", "$var(res)", "$ptimer(ntasks)");
    jansson_get("data", "$var(res)", "$var(data)");
    jansson_xdecode("$var(data)", "worker_data");
}

route[HANDLE_DATA] {
    $var(i) = 0;
    while($var(i) < $ptimer(tasks)) {
        # do something with the data
        xlog("L_INFO", "a = $xavp(worker_data[$var(i)]=>a)\n");
        $var(i) = $var(i) + 1;
    }
}

...
                ]]></programlisting>
                <para>
                <emphasis role="bold">Another Program:</emphasis>
                </para>
                <programlisting><![CDATA[
# could be within xhttp (as postback) or some other program
# when the data is updated we jump the timer back to start route
kamcmd ptimer.start t1
                ]]></programlisting>
            </section>
        </section>
    </section>
</chapter>