Archive for December 2006

WAP: Part 3 - WAP Push with Kannel & PHP

This article will go through setting up Kannel to send "WAP Push" messages to mobile phones. It assumes you have a working Kannel
installation with a real modem -- see parts 1 & 2 of this series for more information.

Before we continue with this article, let me save you some time. I have found WAP Push messages to be very unreliable. Specifically I was only able to get a WAP Push message sent to a T-Mobile phone from my GSM modem with a T-Mobile SIM card. I tried a Cingular SIM card in the modem, but was unable to successfully send a WAP Push message to a Cingular or other network phone.

I searched for 3rd party companies that will send WAP Push messages, and contacting a few of them. It seems here in the U.S. this type of service is relatively unreliable and sometimes only the network providers themselves can send WAP Push messages that will get through. Case in point: I couldn't get a WAP Push message to a Cingular phone, but you can download ringtones from cingular.com, for which the URL is sent to the phone via WAP Push.

So, if you want to send WAP Push messages from a T-Mobile modem to only T-Mobile phones, read on! If not, you might save yourself the headache and time and skip the next article of my WAP series: Send SMS from PHP.

Sending WAP Push from the command line


To our working configuration file from WAP: Part 2 - Send SMS from Kannel, we'll add the ppg and wap-push-user groups. Also add wapbox to the startup file and restart Kannel.

/etc/kannel/kannel.conf
...
# PUSH PROXY GATEWAY CONFIG
group = ppg
ppg-url = /wappush
ppg-port = 8080
service-name = ppg
trusted-pi = true

# WAP USER
group = wap-push-user
wap-push-user = SellingSource
ppg-username = sellingsource
ppg-password = sellingsource


start_kannel.sh
#!/bin/sh

rm /var/log/kannel/*
bearerbox --verbosity 4 --logfile /var/log/kannel/bearerbox.log /etc/kannel/kannel.conf &
sleep 10
smsbox --verbosity 4 --logfile /var/log/kannel/smsbox.log /etc/kannel/kannel.conf &
wapbox --verbosity 4 --logfile /var/log/kannel/wapbox.log /etc/kannel/kannel.conf &


I initially tested WAP Push using the included test_ppg executable included with the Kannel source. If you don't want/need to test it from the command line you can skip to the PHP segment.

Unfortunately the test programs are not included with the Gentoo/portage installation of Kannel. So I downloaded the source and
compiled (but did not install) it to get the test programs. Then I copied the example xml files used by test_ppg to my home directory for modification.

~ # cd ~/src
src # wget http://kannel.org/download/1.4.1/gateway-1.4.1.tar.bz2
src # tar -jxvf gateway-1.4.1.tar.bz2
src # cd gateway-1.4.1
gateway-1.4.1 # ./configure && make
gateway-1.4.1 # cp test/si.txt ~/si.xml
gateway-1.4.1 # cp test/smstestppg.txt ~/pap.xml

In si.xml I removed the created and si-expires attributes so there's no delivery timing issues (deliver immediately). The indication href
is a URL that will be shown as the 'From:' in the message, and is where the message will take them. The si-id is a unique message
identifier and should be changed everytime the message is sent. I like to use number@domain and up the number once before sending.
Change the message to something suitable, hopefully describing where the href URL will take them.

si.xml
<?xml version="1.0"?>
<!DOCTYPE si PUBLIC "-//WAPFORUM//DTD SI 1.0//EN"
"http://www.wapforum.org/DTD/si.dtd">
<si>
    <indication href="http://wap.yahoo.com"
        si-id="01@sellingsource.com"
        action="signal-high">
            Visit Yahoo! on your phone
    </indication>
</si>

In pap.xml, I changed to the push-id to the same as the si-id, this value should change per message as well. I changed the WAPPUSH number to my own, this time with country code (leading "+1"). I also changed the "carrier" after the 'PLMN@' to be our domain.

pap.xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP//EN"
          "http://www.wapforum.org/DTD/pap_1.0.dtd">
<pap>
  <push-message push-id="01@sellingsource.com"
  deliver-after-timestamp="2001-02-28T06:45:00Z"
  progress-notes-requested="false">
    <address address-value="WAPPUSH=+17025551212/TYPE=PLMN@sellingsource.com">
    </address>
    <quality-of-service
    priority="low"
    delivery-method="unconfirmed"
    network-required="true"
    network="gsm"
    bearer-required="true"
    bearer="sms">
    </quality-of-service>
  </push-message>
</pap>


So let's test this from the command line:
gateway-1.4.1 # ./test/test_ppg "http://localhost:8080/wappush?username=wap_user&password=wap_pass" ~/si.xml ~/pap.xml

On my T-Mobile Motorola phone, I now have a message under "Browser Msgs." I can click the "GO TO" button and it will take me to the URL specified in the 'href' attribute of si.xml.

Sending WAP Push from PHP


Now let's do the same thing using PHP. But first let me explain how things are going to change. XML is pretty chatty, so we'll be using a binary format called WBXML. Since we'll be communicating directly to Kannel via HTTP with a URL, we'll represent the hexadecimal numbers with a prefix of '%' for URL encoding, rather than '0x'. Along with the Service Indication (SI) document in WBXML, we'll also have to pass a User Data Header (udh) for WAP Push. For an explanation of the udh and WBXML, please see the reference information links below, I've also documented them as best I know in the code:

<?php

require_once 'SMSBase.php';

class WAPPush extends SMSBase
{
        public function __construct(KannelInfo $kannel_info)
        {
                parent::__construct($kannel_info);
        }

        public function sendSMSLink(LinkSMS $sms)
        {
                $fields = array('to' =>; urlencode($sms->getTo()),
                                                'udh' => '',
                                                'text' => '');

                //Nokia User Data Header (UDH) for WAP Push
                $fields['udh'] .= '%06'; //length of UDH - 6 bytes
                $fields['udh'] .= '%05'; //information element (IE) identifier - 0x05 = 16-bit port addressing scheme
                $fields['udh'] .= '%04'; //IE data length - 4 bytes
                $fields['udh'] .= '%0B%84'; //IE data - destination port, 0x0B84 = port 2948 (WAP Push)
                $fields['udh'] .= '%23%F0'; //IE data - origination port, 0x23F0 = port 9200

                //WBXML version of Service Indication (si)
                //headers
                $fields['text'] .= '%1B'; //Transaction ID
                $fields['text'] .= '%06'; //PDU Type - Push
                $fields['text'] .= '%01'; //length of headers
                $fields['text'] .= '%AE'; //Content-type: application/vnd.wap.sic

                //xml body
                $fields['text'] .= '%02'; //WBXML Version 1.2
                $fields['text'] .= '%05'; //DTD Version SI 1.0 Public Identifier
                $fields['text'] .= '%6A'; //Charset UTF-8
                $fields['text'] .= '%00'; //String Table Length (0)
                $fields['text'] .= '%45'; //<si>
                $fields['text'] .= '%C6'; //<indication>
                $fields['text'] .= '%0C'; //href="http://
                $fields['text'] .= '%03'; //start of string value
                $fields['text'] .= $this->urlHexEncode($sms->getURL());
                $fields['text'] .= '%00'; //end of string value
                $fields['text'] .= '%01'; //si-id attribute
                $fields['text'] .= '%03'; //start of string value
                $fields['text'] .= $this->urlHexEncode($sms->getText());
                $fields['text'] .= '%00'; //end of string value
                $fields['text'] .= '%01'; //end element (</si>)
                $fields['text'] .= '%01'; //end element ()

                return $this->sendSMS($fields);
        }

        private function urlHexEncode($text)
        {
                $string = '';
                for ($i=0; $i < strlen($text); $i++)
                {
                        $letter = $text[$i];
                        //get the numeric ascii value of the letter
                        //convert it to hex and add a percent (%)
                        $string .= sprintf('%%%02X', ord($letter));
                }
                return $string;
        }
}

?>

sendSMS() in the parent, SMSBase, simply uses the file() function to send the URL via HTTP GET -- you'll have to make sure allow_url_fopen is set to On in your php.ini or with ini_set().

Also, now that we're doing all of the encoding ourselves and sending directly to the smsbox daemon, we can turn off wapbox. In the startup script, comment out the wapbox line. In kannel.conf you can comment out any wap related entries in the core group (wapbox-port, wdp-interface-name, etc.). You can also comment out the entire wapbox, ppg, and wap-push-user group blocks. You'll have to restart kannel for these changes to take effect.

Resources


Download the entire example.

(udh explanation)
http://discussion.forum.nokia.com/forum/archive/index.php/t-13518.html

(wbxml breakdown)
http://discussion.forum.nokia.com/forum/archive/index.php/t-16775.html
http://www.activexperts.com/activsms/sms/wappushsi/

(wappush using PHP)
http://www.mail-archive.com/users@kannel.org/msg07893.html

Articles In This Series:


WAP: Part 1 - MultiTech USB GPRS Modem in Linux
WAP: Part 2 - Send SMS from Kannel
WAP: Part 3 - WAP Push with Kannel & PHP
WAP: Part 4 - Send SMS from PHP
WAP: Part 5 - Customizing content with WURFL
WAP: Part 6 - Microbrowser content in WML / XHTML MP

Optimizing Using Xdebug and Kcachegrind

Introduction


Recently it came to light that a product of ours was going to be receiving more and more volume. Our client had plans of putting more users on our system (about ten times as many), and we had already experienced some performance problems before. It is the type of problem that you would have a hard time fixing by throwing more servers at it. The original application wasn’t all that well designed. We made a lot of mistakes and we learned a lot about product development on this particular application. It would seem that horizontal scalability (adding more servers) wouldn’t solve our problem. There are far too many bottlenecks in the database, so more application servers don’t really help us. That leaves the option of vertical scaling (upgrading or replacing servers). Vertical scaling, however, brings with it another element: Cost. Not wanting to spend a ton of money on replacing our already very expensive servers, we turned our eyes to the code itself.

Installing Xdebug and KCacheGrind


How does one find these problems? My first thought was some way of profiling code. Xdebug (http://xdebug.org/) provides a pretty good profiler, and exports results to cachegrind files that can be analyzed using KCacheGrind (http://kcachegrind.sourceforge.net/). This particular combination of open source ventures is very useful. You simply install the xdebug extension, enabled it, then edit your xdebug settings. I use mostly default settings, and you can enable the profiler with the following setting:

xdebug.profiler_enable="1"


XDebug has an enormous amount of options, and the default configuration provided with the extension is pretty solid. There’s a page on their website outlining all the settings and their uses (http://xdebug.org/docs/all_settings).

First Run


Once you have installed and XDebug and KCacheGrind, run your application, and then look in /tmp to see if you have a cachegrind output file. For example:

root@localhost /tmp # ls -al -r cachegrind.out.*
-rw-r--r-- 1 apache apache 105863 Jun 17 12:35 cachegrind.out.510234603


If you do not have a file similar to this, be sure the extension is loaded. If you are using a web server such as apache, remember to restart the daemon after installing xdebug. If you’re using the command line, simply running a php –v should tell you whether or not XDebug is installed and working. For example:

root@localhost /etc/php/cli-php5/ext-active # php -v
PHP 5.2.2-pl1-gentoo (cli) (built: May 25 2007 12:34:43)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
with Xdebug v2.0.0RC3, Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, by Derick Rethans


If you are using a web server for your application, XDebug will show up in the output of a phpinfo() call if it is installed properly. You can also use this to see if the profiler is properly enabled. It should look something like this (There's a lot more options than I show here):

XDebug PHP Info


An Example


I have created a simple application which loops 10,000 times, printing stuff to the screen, performing arithmetic, generating random numbers, and file output. The entire purpose of this little noisy script is to do a whole bunch of stuff, and give us an opportunity to see which parts take the longest, using Xdebug and KCacheGrind. I wanted to profile an actual application at some point, but I felt as though it would be too cumbersome and it might be difficult to illustrate the idea. Below is my example.

profile1.php
<?php

    define ('NUM_LOOPS', 10000);

    function complex_calculation()
    {
        return 1.034587763 * mt_rand() % mt_rand();
    }

    function print_something()
    {
        echo "something";
    }

    function write_something()
    {
        $fp = fopen("test.tmp", "w");
        fwrite($fp, "something_important");
        print_something();
        fclose($fp);
    }

    for ($i = 0; $i < NUM_LOOPS; $i++)
    {
        complex_calculation();
        print_something();
        write_something();
    }
?>


Once you have loaded the cachegrind output file into KCacheGrind, you should be presented with a few panels. One is a list view, another is a panel with a bunch of tabs, and another is a tree view which provides a very useful graphical representation of where CPU is being spent. In the list (and the graph) you should notice an item labeled {main}. This is the all-inclusive element that shows the total execution of the program you're profiling. It should show as 100% of your CPU usage. Here is the list view:

List View


Here's a breakdown of each column:

  • Incl.: The total CPU time spent in this function and every function it called inclusively.

  • Self: Only CPU time spent in this function, not counting the time spent in functions CALLED by this function.

  • Called: Total number of times this function was called.

  • Function: Name of this function.

  • Location: Script file containing this function.



Next is the graph/table view. The nice thing about this feature is its ability to show major choke points in a very noticeable format. Typically, this graph excludes calls which are very tiny compared to the rest of the application. Very useful. If you select the {main} box in the graph and you should see something very close to this:


Tree View


Even the most untrained eye can probably guess that write_something() is the slower part of this application. Double click the write_something() box to make it become the new focal point of the graph. Now that you have that selected, you should see something similar to this:


Tree View 2


With write_something() centered, you can see who has called this method (100% of the time, it is {main}), and how much time is spent in functions called by write_something(). So, of the time spent in write_something(), most of that time is spent opening the file, and less time is spent in fwrite().

Conclusion


I could go into details about optimizing my example script, but I feel as though the point has been delivered. Xdebug and KCacheGrind work together to become a very powerful tool when attempting to optimize a PHP application. There are many features to KCacheGrind and I encourage everyone to explore them all. Additionally, it has the capability to track memory usage, but I've had personal experiences which suggest that does not work perfectly.

Good luck!