Tuesday, January 12, 2010

Monitoring your servers with sysstat (sar)


There's sometimes things that are so helpfull that you think that everyone is aware of them, but sometimes this is not the case. Here I'll talk about a little package that is so powerful and efficient that you won't change anymore...
Taken from the ubuntu man page:

DESCRIPTION
The sar command writes to standard output the contents of selected cumula-
tive activity counters in the operating system. The accounting system, based
on the values in the count and interval parameters, writes information the
specified number of times spaced at the specified intervals in seconds. If
the interval parameter is set to zero, the sar command displays the average
statistics for the time since the system was started. The default value for
the count parameter is 1. If its value is set to zero, then reports are gen-
erated continuously. The collected data can also be saved in the file spec-
ified by the -o filename flag, in addition to being displayed onto the
screen. If filename is omitted, sar uses the standard system activity daily
data file, the /var/log/sysstat/sadd file, where the dd parameter indicates
the current day. By default all the data available from the kernel are
saved in the data file. Exceptions are interrupts and disks data, for which
the relevant options must be explicitly passed to sar (or to its backend
sadc ) when the data file is created (see options below).


"sar" comes with the sysstat package. Once it's installed you can monitor your server like never before...

Here's the description of the sysstat package from the author
The sysstat utilities are a collection of performance monitoring tools for Linux.
These include sar, sadf, mpstat, iostat, pidstat and sa tools. Go to the Features page to display
a list of sysstat's features, or see the Documentation page to learn some more about them.

For example, you can watch realtime the network usage:

# sar -n DEV 1 0
Linux 2.6.22-15-generic (xXxXx) 07/09/2008

11:26:36 AM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
11:26:37 AM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:26:37 AM eth0 5.05 0.00 0.86 0.00 0.00 0.00 0.00

11:26:37 AM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
11:26:38 AM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:26:38 AM eth0 4.00 0.00 0.45 0.00 0.00 0.00 0.00
...

Today, I'll introduce the erlang-sar package that's able to retrieve information from the sar command.

The application is composed of a collector "sar_collector", a helper module "sar_values" and the main module "sar".
Here comes a quick sample session:

% Starting the collector
sar_collect:start().

% Retrieving the data
sar:stats(cpu).
[{cpu,idle,<<"98.62">>},
{cpu,steal,<<"0.00">>},
{cpu,iowait,<<"0.00">>},
{cpu,system,<<"0.18">>},
{cpu,nice,<<"0.00">>},
{cpu,user,<<"1.20">>}]

% Retrieving more data
sar:stats([cpu,mem]).
[{swap,swpcad,<<"33236">>},
{swap,usage,<<"64.72">>},
{swap,used,<<"389872">>},
{swap,free,<<"212492">>},
{mem,kbcached,<<"84496">>},
{mem,kbbuffers,<<"63408">>},
{mem,memused,<<"98.78">>},
{mem,kbmemused,<<"508984">>},
{mem,kbmemfree,<<"6308">>},
{cpu,idle,<<"97.83">>},
{cpu,steal,<<"0.00">>},
{cpu,iowait,<<"0.75">>},
{cpu,system,<<"0.20">>},
{cpu,nice,<<"0.00">>},
{cpu,user,<<"1.22">>}]


The module "sar_values" also export an "extractor" function that can be used to build fun()s:

% build a Mem fun()
Mem = sar_values:extractor(mem).

% Calling Mem fun() on sar:stats()
Mem(sar:stats([cpu,mem])).
[{kbcached,<<"84496">>},
{kbbuffers,<<"63480">>},
{memused,<<"98.77">>},
{kbmemused,<<"508976">>},
{kbmemfree,<<"6316">>}]

% Calling it on sar:stats()
Mem(sar:stats()).
[{kbcached,<<"84496">>},
{kbbuffers,<<"63520">>},
{memused,<<"98.80">>},
{kbmemused,<<"509100">>},
{kbmemfree,<<"6192">>}]


With this package you have access to all the data sar can export for you.
Here's the "sar.erl" code:

-module(sar).

-export([systat/0, stats/0, stats/1, option/1]).
-export([extract/1]).
-define(OPTIONS, "-u -r -v -c -q -n DEV").
-define(DATA, "/tmp/last").

systat() ->
Cmd = "sadf " ++ ?DATA ++ " -- " ++ ?OPTIONS,
execute(".", Cmd).

stats() ->
Cmd = "sadf " ++ ?DATA ++ " -- " ++ ?OPTIONS,
{ok, _, Bin} = execute(".", Cmd),
extract(Bin).

stats(List) when is_list(List) ->
Args = lists:foldl(fun(X, Acc) -> case option(X) of
error ->
Acc;
T ->
[ $ , T | Acc ]
end end, [], List),
Cmd = "sadf " ++ ?DATA ++ " -- " ++ lists:reverse(Args),
{ok, _, Bin} = execute(".", lists:flatten(Cmd)),
extract(Bin);

stats(Elem) ->
stats([Elem]).

option(cpu) ->
"-u";
option(disk) ->
"-d";
option(sock) ->
"-n SOCK";
option(eth0) ->
"-n DEV";
option(eth1) ->
"-n DEV";
option(eth2) ->
"-n DEV";
option(proc) ->
"-c";
option(run) ->
"-q";
option(mem) ->
"-r";
option(inode) ->
"-v";
option(switch) ->
"-w";
option(swaping) ->
"-W";
option(_) ->
error.

execute(_Host, Cmd) ->
Port = open_port({spawn, Cmd}, [ exit_status, binary ] ),
wait(Port, []).

wait(Port, Content) ->
receive
{Port, {data, BinData}} ->
%error_logger:info_msg("dump:~n~p~n", [BinData]),
NewContent = [ BinData | Content ],
wait(Port, NewContent);

{Port, {exit_status, Status}} ->
%error_logger:info_msg("exit_code: ~p~n", [Status]),
{ok, Status, Content};

{Port, eof} ->
%error_logger:info_msg("Port closed"),
port_close(Port),
{ok, eof, Content};

{Port, exit} ->
error_logger:info_msg("Received : ~p~n", [Port]),
Content
end.

extract(Bin) ->
sar_values:extract(iolist_to_binary(Bin)).


You can see the "option/1" function that let you convert atoms into command line arguments easily. I use also this function to test if sar is able to handle a specific parameter. For example and with the help of my webservice I can query remote stats easily:
http://monitoring.lan/stats/q/cpu/servername


Here's the "sar_collect" module

-module(sar_collect).

-export([systat/1, sartime/1, start/0, start/1]).
-export([extract/1]).
spawn(?MODULE, systat, []).

start(Seconds) ->
spawn(?MODULE, systat, [Seconds]).

% update the file every second
systat(0) ->
loop(1);

systat(Seconds) ->
loop(Seconds).

%update the file every 59 seconds
systat() ->
loop(59).

loop(Seconds) when Seconds <>
Cmd = lists:flatten([ "sar -o /tmp/last.tmp ", integer_to_list(Seconds), " 1" ]),
execute(".", Cmd),
file:rename("/tmp/last.tmp", "/tmp/last"),
timer:sleep(60 - Seconds),
receive
stop ->
exit(normal);

{interval, NewSeconds} ->
loop(NewSeconds);

_A ->
loop(Seconds)

after 0 ->
loop(Seconds)

end;

%default update 20 seconds (arbitrary chosen)
loop(_Seconds) ->
loop(20).

execute(Host, Cmd) ->
Port = open_port({spawn, Cmd}, [ {cd, Host}, exit_status, binary ] ),
wait(Port, []).

wait(Port, Content) ->
receive
{Port, {data, _BinData}} ->
wait(Port, Content);

{Port, {exit_status, _Status}} ->
ok;

{Port, eof} ->
port_close(Port),
Content;

{Port, exit} ->
error_logger:info_msg("Received : ~p~n", [Port]),
Content
end.


Finally there is the "sar_values" source code:

-module(sar_values).

-export([extract/1, extractor/1, sort/1]).
-export([parse/1, parse_value/2]).

extract(Bin) ->
extract(Bin, []).

extract(Bin, Stats) ->
case parse(Bin) of
{Class, Type, Rest} ->
%io:format("~p.~p", [Class, Type]),
case parse_value(Rest, <<>>) of
{more, Value, More} ->
NewStats = [ {Class, Type, Value} | Stats ],
extract(More, NewStats);

{eof, Value} ->
NewStats = [ {Class, Type, Value} | Stats ],
NewStats
end;

eof ->
Stats
end.

parse(<<"%user", Rest/binary >>) -> {cpu, user, Rest};
parse(<<"%nice", Rest/binary>>) -> {cpu, nice, Rest};
parse(<<"%system", Rest/binary>>) -> {cpu, system, Rest};
parse(<<"%iowait", Rest/binary>>) -> {cpu, iowait, Rest};
parse(<<"%steal", Rest/binary>>) -> {cpu, steal, Rest};
parse(<<"%idle", Rest/binary>>) -> {cpu, idle, Rest};

parse(<<"kbmemfree", Rest/binary>>) -> {mem, kbmemfree, Rest};
parse(<<"kbmemused", Rest/binary>>) -> {mem, kbmemused, Rest};
parse(<<"%memused", Rest/binary>>) -> {mem, memused, Rest};
parse(<<"kbbuffers", Rest/binary>>) -> {mem, kbbuffers, Rest};
parse(<<"kbcached", Rest/binary>>) -> {mem, kbcached, Rest};

parse(<<"kbswpfree", Rest/binary>>) -> {swap, free, Rest};
parse(<<"kbswpused", Rest/binary>>) -> {swap, used, Rest};
parse(<<"%swpused", Rest/binary>>) -> {swap, usage, Rest};
parse(<<"kbswpcad", Rest/binary>>) -> {swap, swpcad, Rest};

parse(<<"dentunusd", Rest/binary>>) -> {inode, dentryunused, Rest};
parse(<<"file-sz", Rest/binary>>) -> {inode, fileopened, Rest};
parse(<<"inode-sz", Rest/binary>>) -> {inode, inodes, Rest};
parse(<<"super-sz", Rest/binary>>) -> {inode, super, Rest};
parse(<<"%super-sz", Rest/binary>>) -> {inode, superusage, Rest};
parse(<<"dquot-sz", Rest/binary>>) -> {inode, dquotsz, Rest};
parse(<<"%dquot-sz", Rest/binary>>) -> {inode, dquotszusage, Rest};
parse(<<"rtsig-sz", Rest/binary>>) -> {rtsig, count , Rest};
parse(<<"%rtsig-sz", Rest/binary>>) -> {rtsig, usage, Rest};

parse(<<"totsck", Rest/binary>>) -> {sock, total, Rest};
parse(<<"tcpsck", Rest/binary>>) -> {sock, tcp, Rest};
parse(<<"udpsck", Rest/binary>>) -> {sock, udp, Rest};
parse(<<"rawsck", Rest/binary>>) -> {sock, raw, Rest};
parse(<<"ip-frag", Rest/binary>>) -> {sock, ipfrag, Rest};

parse(<<"runq-sz", Rest/binary>>) -> {procs, running, Rest};
parse(<<"plist-sz", Rest/binary>>) -> {procs, total, Rest};

parse(<<"ldavg-15", Rest/binary>>) -> {load, min15, Rest};
parse(<<"ldavg-1", Rest/binary>>) -> {load, min1, Rest};
parse(<<"ldavg-5", Rest/binary>>) -> {load, min5, Rest};

parse(<<"pswpin/s", Rest/binary>>) -> {swaping, pswpin, Rest};
parse(<<"pswpout/s", Rest/binary>>) -> {swaping, pswpout, Rest};

parse(<<"l0", Rest/binary>>) -> parsebis(Rest, l0);
parse(<<"eth0", Rest/binary>>) -> parsebis(Rest, eth0);
parse(<<"eth1", Rest/binary>>) -> parsebis(Rest, eth1);
parse(<<"eth2", Rest/binary>>) -> parsebis(Rest, eth2);

parse(<<>>) -> eof;

parse(Bin) ->
{_, Next} = split_binary(Bin, 1),
parse(Next).

parsebis(<<"rxpck/s", Rest/binary>>, Category) -> {Category, rxpck, Rest};
parsebis(<<"txpck/s", Rest/binary>>, Category) -> {Category, txpck, Rest};
parsebis(<<"rxbyt/s", Rest/binary>>, Category) -> {Category, rxbyt, Rest};
parsebis(<<"txbyt/s", Rest/binary>>, Category) -> {Category, txbyt, Rest};
parsebis(<<"rxcmp/s", Rest/binary>>, Category) -> {Category, rxcmp, Rest};
parsebis(<<"txcmp/s", Rest/binary>>, Category) -> {Category, txcmp, Rest};
parsebis(<<"rxmcst/s", Rest/binary>>, Category) -> {Category, rxmcst, Rest};
parsebis(Bin, Category) ->
{_, Next} = split_binary(Bin, 1),
parsebis(Next, Category).

parse_value(<<$\t, Rest/binary>>, _Value) ->
parse_value(Rest, _Value);
parse_value(<<$ , Rest/binary>>, _Value) ->
parse_value(Rest, _Value);

parse_value(<<$\n, _Rest/binary>>, Value) ->
{more, Value, _Rest};

parse_value(<<>>, Value) ->
{eof, Value};

parse_value(Bin, Value) ->
{H, Next} = split_binary(Bin, 1),
parse_value(Next, iolist_to_binary([Value, H])).

extractor(Motif) ->
fun(L) when is_list(L) ->
[ {Y, Z} || {X, Y, Z} <- L, X == Motif]
end.

sort(List) ->
lists:sort( fun({X, _V}, {Y, _W}) when X <>
true;
(_A, _B) -> false
end, List).


Now that Erlang is R12B, I'm not so sure if "binary parsing code" is really as efficient as it can...

Wednesday, January 6, 2010

Technology Management

Nice Curriculum

MANAGE MENT SKILL S
The Management Skills part of the NIT program
consists of lectures and seminars as well
as two four-week full-time Spring Schools. The
first part teaches skills necessary to develop an
initial idea for a ready-to-market product, while
the second part emphasizes on developing a
business idea into a functioning business model.
SYLLABUS
Principles of Economics and Management
• Introduction to International Management and Economics
• Statistics & Decision Making Techniques
• Global Economy
Primary Value Chain Activities
• Supply Chain Management
• Research and Development
• Production and Operations
• Marketing and Sales
Strategy, Organization, Human Resources
Accounting and Finance
• Financial Accounting, Management Accounting
• Finance and Investment, Corporate Finance
Entrepreneurship and Elements of Law
• Business Planning
• Project Management
• International Law
• Intellectual Property Rights
Technology Management
• Quality and Information Management
• Management of IT, IT-Security
• Technology Road-Mapping
• Technology Acquisition and Technology Transfer
The curriculum heavily draws on case studies and group work.
Scholarships from:
MBA /MASTER IN TEC HNOLOG Y MANAGE MENT
LEADER SHIP SKILL S
The Leadership Skills of the NIT program deal
with the human factor, focusing on three different
areas: on key qualifications that make a
difference in modern leadership, on determining
non-economic factors of today’s German
and European thinking, and on basic ethical
issues in a global business.
SYLLABUS
Ethics
• Principles of Philosophy and Ethics
• Corporate Governance
• Corporate Responsibility
Management Communication
• Principles of Communication
• Organizational Behavior
• Conflict Management and Negotiation
• Leadership, Teamwork, Presentation
The curriculum offers a balanced blend of lecture courses,
weekend-workshops and intensive study weeks.

SMPP PHP connector

require_once('smppclass.php');

$smpphost = "203.199.142.41"; // your host address
$smppport = 2345;
$systemid = "idsfds"; // Your system id
$password = "es223"; // Your smpp account password
$system_type = "Rdsd "; // Your userid
$from = "919846341106"; // From number

$smpp = new SMPPClass();
$smpp->SetSender($from);
/* bind to smpp server */
$smpp->Start($smpphost, $smppport, $systemid, $password, $system_type);
/* send enquire link PDU to smpp server */
$smpp->TestLink();
/* send single message; large messages are automatically split */
$smpp->Send("971532663061", "This is a test message. Give me a missed call if you get this. sajith");
/* send unicode message */
//$smpp->Send("31648072766", "صباحالخير", true);
/* send message to multiple recipients at once */
//$smpp->SendMulti("31648072766,31651931985", "This is my PHP message");
/* unbind from smpp server */
$smpp->End();

//Click read more for smppclass.php library

More...

//file smppclass.php

define(CM_BIND_TRANSMITTER, 0x00000002);
define(CM_SUBMIT_SM, 0x00000004);
define(CM_SUBMIT_MULTI, 0x00000021);
define(CM_UNBIND, 0x00000006);
define(CM_ENQUIRELINK, 0x00000015);

class SMPPClass {
// public members:
/*
Constructor.
Parameters:
none.
Example:
$smpp = new SMPPClass();
*/
function SMPPClass()
{
/* seed random generator */
list($usec, $sec) = explode(' ', microtime());
$seed = (float) $sec + ((float) $usec * 100000);
srand($seed);

/* initialize member variables */
$this->_debug = true; /* set this to false if you want to suppress debug output. */
$this->_socket = NULL;
$this->_command_status = 0;
$this->_sequence_number = 1;
$this->_source_address = "";
$this->_message_sequence = rand(1,255);
}

/*
For SMS gateways that support sender-ID branding, the method
can be used to set the originating address.
Parameters:
$from : Originating address
Example:
$smpp->SetSender("31495595392");
*/
function SetSender($from)
{
if (strlen($from) > 20) {
$this->debug("Error: sender id too long.\n");
return;
}
$this->_source_address = $from;
}

/*
This method initiates an SMPP session.
It is to be called BEFORE using the Send() method.
Parameters:
$host : SMPP ip to connect to.
$port : port # to connect to.
$username : SMPP system ID
$password : SMPP passord.
$system_type : SMPP System type
Returns:
true if successful, otherwise false
Example:
$smpp->Start("smpp.chimit.nl", 2345, "chimit", "my_password", "client01");
*/
function Start($host, $port, $username, $password, $system_type)
{
/*
$testarr = stream_get_transports();
$have_tcp = false;
reset($testarr);
while (list(, $transport) = each($testarr)) {
if ($transport == "tcpp") {
$have_tcp = true;
}
}
if (!$have_tcp) {
$this->debug("No TCP support in this version of PHP.\n");
return false;
}
*/
$this->_socket = fsockopen($host, $port, $errno, $errstr, 20);
// todo: sanity check on input parameters
if (!$this->_socket) {
$this->debug("Error opening SMPP session.\n");
$this->debug("Error was: $errstr.\n");
return;
}
socket_set_timeout($this->_socket, 1200);
$status = $this->SendBindTransmitter($username, $password, $system_type);
if ($status != 0) {
$this->debug("Error binding to SMPP server. Invalid credentials?\n");
}
return ($status == 0);
}

/*
This method sends out one SMS message.
Parameters:
$to : destination address.
$text : text of message to send.
$unicode: Optional. Indicates if input string is html encoded unicode.
Returns:
true if messages sent successfull, otherwise false.
Example:
$smpp->Send("31649072766", "This is an SMPP Test message.");
$smpp->Send("31648072766", "صباحالخير", true);
*/
function Send($to, $text, $unicode = false)
{
if (strlen($to) > 20) {
$this->debug("to-address too long.\n");
return;
}
if (!$this->_socket) {
$this->debug("Not connected, while trying to send SUBMIT_SM.\n");
// return;
}
$service_type = "";
//default source TON and NPI for international sender
$source_addr_ton = 1;
$source_addr_npi = 1;
$source_addr = $this->_source_address;
if (preg_match('/\D/', $source_addr)) //alphanumeric sender
{
$source_addr_ton = 5;
$source_addr_npi = 0;
}
elseif (strlen($source_addr) < 11) //national or shortcode sender
{
$source_addr_ton = 2;
$source_addr_npi = 1;
}
$dest_addr_ton = 1;
$dest_addr_npi = 1;
$destination_addr = $to;
$esm_class = 3;
$protocol_id = 0;
$priority_flag = 0;
$schedule_delivery_time = "";
$validity_period = "";
$registered_delivery_flag = 0;
$replace_if_present_flag = 0;
$data_coding = 241;
$sm_default_msg_id = 0;
if ($unicode) {
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES"); /* UCS-2BE */
$data_coding = 8; /* UCS2 */
$multi = $this->split_message_unicode($text);
}
else {
$multi = $this->split_message($text);
}
$multiple = (count($multi) > 1);
if ($multiple) {
$esm_class += 0x00000040;
}
$result = true;
reset($multi);
while (list(, $part) = each($multi)) {
$short_message = $part;
$sm_length = strlen($short_message);
$status = $this->SendSubmitSM($service_type, $source_addr_ton, $source_addr_npi, $source_addr, $dest_addr_ton, $dest_addr_npi, $destination_addr, $esm_class, $protocol_id, $priority_flag, $schedule_delivery_time, $validity_period, $registered_delivery_flag, $replace_if_present_flag, $data_coding, $sm_default_msg_id, $sm_length, $short_message);
if ($status != 0) {
$this->debug("SMPP server returned error $status.\n");
$result = false;
}
}
return $result;
}

/*
This method ends a SMPP session.
Parameters:
none
Returns:
true if successful, otherwise false
Example: $smpp->End();
*/
function End()
{
if (!$this->_socket) {
// not connected
return;
}
$status = $this->SendUnbind();
if ($status != 0) {
$this->debug("SMPP Server returned error $status.\n");
}
fclose($this->_socket);
$this->_socket = NULL;
return ($status == 0);
}

/*
This method sends an enquire_link PDU to the server and waits for a response.
Parameters:
none
Returns:
true if successfull, otherwise false.
Example: $smpp->TestLink()
*/
function TestLink()
{
$pdu = "";
$status = $this->SendPDU(CM_ENQUIRELINK, $pdu);
return ($status == 0);
}

/*
This method sends a single message to a comma separated list of phone numbers.
There is no limit to the number of messages to send.
Parameters:
$tolist : comma seperated list of phone numbers
$text : text of message to send
$unicode: Optional. Indicates if input string is html encoded unicode string.
Returns:
true if messages received by smpp server, otherwise false.
Example:
$smpp->SendMulti("31777110204,31649072766,...,...", "This is an SMPP Test message.");
*/
function SendMulti($tolist, $text, $unicode = false)
{
if (!$this->_socket) {
$this->debug("Not connected, while trying to send SUBMIT_MULTI.\n");
// return;
}
$service_type = "";
$source_addr = $this->_source_address;
//default source TON and NPI for international sender
$source_addr_ton = 1;
$source_addr_npi = 1;
$source_addr = $this->_source_address;
if (preg_match('/\D/', $source_addr)) //alphanumeric sender
{
$source_addr_ton = 5;
$source_addr_npi = 0;
}
elseif (strlen($source_addr) < 11) //national or shortcode sender
{
$source_addr_ton = 2;
$source_addr_npi = 1;
}
$dest_addr_ton = 1;
$dest_addr_npi = 1;
$destination_arr = explode(",", $tolist);
$esm_class = 3;
$protocol_id = 0;
$priority_flag = 0;
$schedule_delivery_time = "";
$validity_period = "";
$registered_delivery_flag = 0;
$replace_if_present_flag = 0;
$data_coding = 241;
$sm_default_msg_id = 0;
if ($unicode) {
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES");
$data_coding = 8; /* UCS2 */
$multi = $this->split_message_unicode($text);
}
else {
$multi = $this->split_message($text);
}
$multiple = (count($multi) > 1);
if ($multiple) {
$esm_class += 0x00000040;
}
$result = true;
reset($multi);
while (list(, $part) = each($multi)) {
$short_message = $part;
$sm_length = strlen($short_message);
$status = $this->SendSubmitMulti($service_type, $source_addr_ton, $source_addr_npi, $source_addr, $dest_addr_ton, $dest_addr_npi, $destination_arr, $esm_class, $protocol_id, $priority_flag, $schedule_delivery_time, $validity_period, $registered_delivery_flag, $replace_if_present_flag, $data_coding, $sm_default_msg_id, $sm_length, $short_message);
if ($status != 0) {
$this->debug("SMPP server returned error $status.\n");
$result = false;
}
}
return $result;
}

// private members (not documented):

function ExpectPDU($our_sequence_number)
{
do {
$this->debug("Trying to read PDU.\n");
if (feof($this->_socket)) {
$this->debug("Socket was closed.!!\n");
}
$elength = fread($this->_socket, 4);
if (empty($elength)) {
$this->debug("Connection lost.\n");
return;
}
extract(unpack("Nlength", $elength));
$this->debug("Reading PDU : $length bytes.\n");
$stream = fread($this->_socket, $length - 4);
$this->debug("Stream len : " . strlen($stream) . "\n");
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $stream));
$command_id &= 0x0fffffff;
$this->debug("Command id : $command_id.\n");
$this->debug("Command status : $command_status.\n");
$this->debug("sequence_number : $sequence_number.\n");
$pdu = substr($stream, 12);
switch ($command_id) {
case CM_BIND_TRANSMITTER:
$this->debug("Got CM_BIND_TRANSMITTER_RESP.\n");
$spec = "asystem_id";
extract($this->unpack2($spec, $pdu));
$this->debug("system id : $system_id.\n");
break;
case CM_UNBIND:
$this->debug("Got CM_UNBIND_RESP.\n");
break;
case CM_SUBMIT_SM:
$this->debug("Got CM_SUBMIT_SM_RESP.\n");
if ($command_status == 0) {
$spec = "amessage_id";
extract($this->unpack2($spec, $pdu));
$this->debug("message id : $message_id.\n");
}
break;
case CM_SUBMIT_MULTI:
$this->debug("Got CM_SUBMIT_MULTI_RESP.\n");
$spec = "amessage_id/cno_unsuccess/";
extract($this->unpack2($spec, $pdu));
$this->debug("message id : $message_id.\n");
$this->debug("no_unsuccess : $no_unsuccess.\n");
break;
case CM_ENQUIRELINK:
$this->debug("GOT CM_ENQUIRELINK_RESP.\n");
break;
default:
$this->debug("Got unknown SMPP pdu.\n");
break;
}
$this->debug("\nReceived PDU: ");
for ($i = 0; $i < strlen($stream); $i++) {
if (ord($stream[$i]) <>debug("(" . ord($stream[$i]) . ")"); else $this->debug($stream[$i]);
}
$this->debug("\n");
} while ($sequence_number != $our_sequence_number);
return $command_status;
}

function SendPDU($command_id, $pdu)
{
$length = strlen($pdu) + 16;
$header = pack("NNNN", $length, $command_id, $this->_command_status, $this->_sequence_number);
$this->debug("Sending PDU, len == $length\n");
$this->debug("Sending PDU, header-len == " . strlen($header) . "\n");
$this->debug("Sending PDU, command_id == " . $command_id . "\n");
fwrite($this->_socket, $header . $pdu, $length);
$status = $this->ExpectPDU($this->_sequence_number);
$this->_sequence_number = $this->_sequence_number + 1;
return $status;
}

function SendBindTransmitter($system_id, $smpppassword, $system_type)
{
$system_id = $system_id . chr(0);
$system_id_len = strlen($system_id);
$smpppassword = $smpppassword . chr(0);
$smpppassword_len = strlen($smpppassword);
$system_type = $system_type . chr(0);
$system_type_len = strlen($system_type);
$pdu = pack("a{$system_id_len}a{$smpppassword_len}a{$system_type_len}CCCa1", $system_id, $smpppassword, $system_type, 0x33, 0, 0, chr(0));
$this->debug("Bind Transmitter PDU: ");
for ($i = 0; $i < strlen($pdu); $i++) {
$this->debug(ord($pdu[$i]) . " ");
}
$this->debug("\n");
$status = $this->SendPDU(CM_BIND_TRANSMITTER, $pdu);
return $status;
}

function SendUnbind()
{
$pdu = "";
$status = $this->SendPDU(CM_UNBIND, $pdu);
return $status;
}

function SendSubmitSM($service_type, $source_addr_ton, $source_addr_npi, $source_addr, $dest_addr_ton, $dest_addr_npi, $destination_addr, $esm_class, $protocol_id, $priority_flag, $schedule_delivery_time, $validity_period, $registered_delivery_flag, $replace_if_present_flag, $data_coding, $sm_default_msg_id, $sm_length, $short_message)
{
$service_type = $service_type . chr(0);
$service_type_len = strlen($service_type);
$source_addr = $source_addr . chr(0);
$source_addr_len = strlen($source_addr);
$destination_addr = $destination_addr . chr(0);
$destination_addr_len = strlen($destination_addr);
$schedule_delivery_time = $schedule_delivery_time . chr(0);
$schedule_delivery_time_len = strlen($schedule_delivery_time);
$validity_period = $validity_period . chr(0);
$validity_period_len = strlen($validity_period);
// $short_message = $short_message . chr(0);
$message_len = $sm_length;
$spec = "a{$service_type_len}cca{$source_addr_len}cca{$destination_addr_len}ccca{$schedule_delivery_time_len}a{$validity_period_len}ccccca{$message_len}";
$this->debug("PDU spec: $spec.\n");

$pdu = pack($spec,
$service_type,
$source_addr_ton,
$source_addr_npi,
$source_addr,
$dest_addr_ton,
$dest_addr_npi,
$destination_addr,
$esm_class,
$protocol_id,
$priority_flag,
$schedule_delivery_time,
$validity_period,
$registered_delivery_flag,
$replace_if_present_flag,
$data_coding,
$sm_default_msg_id,
$sm_length,
$short_message);
$status = $this->SendPDU(CM_SUBMIT_SM, $pdu);
return $status;
}

function SendSubmitMulti($service_type, $source_addr_ton, $source_addr_npi, $source_addr, $dest_addr_ton, $dest_addr_npi, $destination_arr, $esm_class, $protocol_id, $priority_flag, $schedule_delivery_time, $validity_period, $registered_delivery_flag, $replace_if_present_flag, $data_coding, $sm_default_msg_id, $sm_length, $short_message)
{
$service_type = $service_type . chr(0);
$service_type_len = strlen($service_type);
$source_addr = $source_addr . chr(0);
$source_addr_len = strlen($source_addr);
$number_destinations = count($destination_arr);
$dest_flag = 1;
$spec = "a{$service_type_len}cca{$source_addr_len}c";
$pdu = pack($spec,
$service_type,
$source_addr_ton,
$source_addr_npi,
$source_addr,
$number_destinations
);

$dest_flag = 1;
reset($destination_arr);
while (list(, $destination_addr) = each($destination_arr)) {
$destination_addr .= chr(0);
$dest_len = strlen($destination_addr);
$spec = "ccca{$dest_len}";
$pdu .= pack($spec, $dest_flag, $dest_addr_ton, $dest_addr_npi, $destination_addr);
}
$schedule_delivery_time = $schedule_delivery_time . chr(0);
$schedule_delivery_time_len = strlen($schedule_delivery_time);
$validity_period = $validity_period . chr(0);
$validity_period_len = strlen($validity_period);
$message_len = $sm_length;
$spec = "ccca{$schedule_delivery_time_len}a{$validity_period_len}ccccca{$message_len}";

$pdu .= pack($spec,
$esm_class,
$protocol_id,
$priority_flag,
$schedule_delivery_time,
$validity_period,
$registered_delivery_flag,
$replace_if_present_flag,
$data_coding,
$sm_default_msg_id,
$sm_length,
$short_message);

$this->debug("\nMulti PDU: ");
for ($i = 0; $i < strlen($pdu); $i++) {
if (ord($pdu[$i]) <>debug("."); else $this->debug($pdu[$i]);
}
$this->debug("\n");

$status = $this->SendPDU(CM_SUBMIT_MULTI, $pdu);
return $status;
}

function split_message($text)
{
$this->debug("In split_message.\n");
$max_len = 153;
$res = array();
if (strlen($text) <= 160) {
$this->debug("One message: " . strlen($text) . "\n");
$res[] = $text;
return $res;
}
$pos = 0;
$msg_sequence = $this->_message_sequence++;
$num_messages = ceil(strlen($text) / $max_len);
$part_no = 1;
while ($pos < strlen($text)) {
$ttext = substr($text, $pos, $max_len);
$pos += strlen($ttext);
$udh = pack("cccccc", 5, 0, 3, $msg_sequence, $num_messages, $part_no);
$part_no++;
$res[] = $udh . $ttext;
$this->debug("Split: UDH = ");
for ($i = 0; $i < strlen($udh); $i++) {
$this->debug(ord($udh[$i]) . " ");
}
$this->debug("\n");
$this->debug("Split: $ttext.\n");
}
return $res;
}

function split_message_unicode($text)
{
$this->debug("In split_message.\n");
$max_len = 134;
$res = array();
if (mb_strlen($text) <= 140) {
$this->debug("One message: " . mb_strlen($text) . "\n");
$res[] = $text;
return $res;
}
$pos = 0;
$msg_sequence = $this->_message_sequence++;
$num_messages = ceil(mb_strlen($text) / $max_len);
$part_no = 1;
while ($pos < mb_strlen($text)) {
$ttext = mb_substr($text, $pos, $max_len);
$pos += mb_strlen($ttext);
$udh = pack("cccccc", 5, 0, 3, $msg_sequence, $num_messages, $part_no);
$part_no++;
$res[] = $udh . $ttext;
$this->debug("Split: UDH = ");
for ($i = 0; $i < strlen($udh); $i++) {
$this->debug(ord($udh[$i]) . " ");
}
$this->debug("\n");
$this->debug("Split: $ttext.\n");
}
return $res;
}

function unpack2($spec, $data)
{
$res = array();
$specs = explode("/", $spec);
$pos = 0;
reset($specs);
while (list(, $sp) = each($specs)) {
$subject = substr($data, $pos);
$type = substr($sp, 0, 1);
$var = substr($sp, 1);
switch ($type) {
case "N":
$temp = unpack("Ntemp2", $subject);
$res[$var] = $temp["temp2"];
$pos += 4;
break;
case "c":
$temp = unpack("ctemp2", $subject);
$res[$var] = $temp["temp2"];
$pos += 1;
break;
case "a":
$pos2 = strpos($subject, chr(0)) + 1;
$temp = unpack("a{$pos2}temp2", $subject);
$res[$var] = $temp["temp2"];
$pos += $pos2;
break;
}
}
return $res;
}

function debug($str)
{
if ($this->_debug) {
echo $str;
}
}
};