Tuesday Nov 05, 2013

Tracing Silex from PHP to the OS with DTrace

In this blog post I show the full stack tracing of Brendan Gregg's php_syscolors.d script in the DTrace Toolkit. The Toolkit contains a dozen very useful PHP DTrace scripts and many more scripts for other languages and the OS.

For this example, I'll trace the PHP micro framework Silex, which was the topic of the second of two talks by Dustin Whittle at a recent SF PHP Meetup. His slides are at Silex: From Micro to Full Stack.

Installing DTrace and PHP

The php_syscolors.d script uses some static PHP probes and some kernel probes. For Oracle Linux I discussed installing DTrace and PHP in DTrace PHP Using Oracle Linux 'playground' Pre-Built Packages. On other platforms with DTrace support, follow your standard procedures to enable DTrace and load the correct providers. The sdt and systrace providers are required in addition to fasttrap.

On Oracle Linux, I loaded the DTrace modules like:

# modprobe fasttrap
# modprobe sdt
# modprobe systrace
# chmod 666 /dev/dtrace/helper

Installing the DTrace Toolkit

I download DTraceToolkit-0.99.tar.gz and extracted it:

$ tar -zxf DTraceToolkit-0.99.tar.gz

The PHP scripts are in the Php directory and examples in the Examples directory.

Installing Silex

I downloaded the "fat" Silex .tgz file from the download page and extracted it:

$ tar -zxf silex_fat.tgz

I changed the demonstration silex/web/index.php so I could use the PHP development web server:

<?php

// web/index.php

$filename = __DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']);
if (php_sapi_name() === 'cli-server' && is_file($filename)) {
    return false;
}

require_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

//$app['debug'] = true;

$app->get('/hello', function() {
        return 'Hello!';
    });

$app->run();

?>

Running DTrace

The php_syscolors.d script uses the -Z option to dtrace, so it can be started before PHP, i.e. when there are zero of the requested probes available to be traced. I ran DTrace like:

# cd DTraceToolkit-0.99/Php
# ./php_syscolors.d

Next, I started the PHP developer web server in a second terminal:

$ cd silex
$ php -S localhost:8080 -t web web/index.php

At this point, the web server is idle, waiting for requests. DTrace is idle, waiting for the probes in php_syscolors.d to be fired, at which time the action associated with each probe will run.

I then loaded the demonstration page in a browser:

http://localhost:8080/hello

When the request was fulfilled and the simple output of "Hello" was displayed, I ^C'd php and dtrace in their terminals to stop them.

DTrace output over a thousand lines long had been generated. Here is one snippet from when run() was invoked:

C    PID/TID   DELTA(us)              FILE:LINE TYPE     -- NAME
...
1   4765/4765         21   Application.php:487  func     -> run
1   4765/4765         29   ClassLoader.php:182  func       -> loadClass
1   4765/4765         17   ClassLoader.php:198  func         -> findFile
1   4765/4765         31                 ":-    syscall        -> access
1   4765/4765         26                 ":-    syscall        <- access
1   4765/4765         16   ClassLoader.php:198  func         <- findFile
1   4765/4765         25                 ":-    syscall      -> newlstat
1   4765/4765         15                 ":-    syscall      <- newlstat
1   4765/4765         13                 ":-    syscall      -> newlstat
1   4765/4765         13                 ":-    syscall      <- newlstat
1   4765/4765         22                 ":-    syscall      -> newlstat
1   4765/4765         14                 ":-    syscall      <- newlstat
1   4765/4765         15                 ":-    syscall      -> newlstat
1   4765/4765         60                 ":-    syscall      <- newlstat
1   4765/4765         13                 ":-    syscall      -> newlstat
1   4765/4765         13                 ":-    syscall      <- newlstat
1   4765/4765         20                 ":-    syscall      -> open
1   4765/4765         16                 ":-    syscall      <- open
1   4765/4765         26                 ":-    syscall      -> newfstat
1   4765/4765         12                 ":-    syscall      <- newfstat
1   4765/4765         17                 ":-    syscall      -> newfstat
1   4765/4765         12                 ":-    syscall      <- newfstat
1   4765/4765         12                 ":-    syscall      -> newfstat
1   4765/4765         12                 ":-    syscall      <- newfstat
1   4765/4765         20                 ":-    syscall      -> mmap
1   4765/4765         14                 ":-    syscall      <- mmap
1   4765/4765       3201                 ":-    syscall      -> mmap
1   4765/4765         27                 ":-    syscall      <- mmap
1   4765/4765       1233                 ":-    syscall      -> munmap
1   4765/4765         53                 ":-    syscall      <- munmap
1   4765/4765         15                 ":-    syscall      -> close
1   4765/4765         13                 ":-    syscall      <- close
1   4765/4765         34       Request.php:32   func         -> main
1   4765/4765         22       Request.php:32   func         <- main
1   4765/4765         31   ClassLoader.php:182  func       <- loadClass
1   4765/4765         33       Request.php:249  func       -> createFromGlobals
1   4765/4765         29       Request.php:198  func         -> __construct
1   4765/4765         24       Request.php:218  func           -> initialize
1   4765/4765         26   ClassLoader.php:182  func             -> loadClass
1   4765/4765         89   ClassLoader.php:198  func               -> findFile
1   4765/4765         43                 ":-    syscall              -> access
...

The output shows PHP functions being called and returning (and where they are located) and which system calls the PHP functions in turn invoked. The time each line took from the previous one is displayed in the third column.

The first column is the CPU number. In this example, the process was always on CPU 1 so the output is naturally ordered without requiring post-processing, or the D script requiring to be modified to display a time stamp.

On a terminal, the output of php_syscolors.d is color-coded according to whether each function is a PHP or system one, hence the file name.

Summary

With one tool, I was able to trace the interaction of a user application with the operating system. I was able to do this to an application running "live" in a web context.

The DTrace Toolkit provides a very handy repository of DTrace information. Even though the PHP scripts were created in the time frame of the original PHP DTrace PECL extension, which only had PHP function entry and return probes, the scripts provide core examples for custom investigation and resolution scripts. You can easily adapt the ideas and create scripts using the other PHP static probes, which are listed in the PHP Manual.

Because DTrace is "always on", you can take advantage of it to resolve development questions or fix production situations.

Tuesday May 14, 2013

Getting Started with PHP Zend Framework 2 for Oracle DB

This post shows the changes to the ZF2 tutorial application to allow it to run with Oracle Database 11gR2.

Oracle Database SQL identifiers are case insensitive by default so "select Abc from Xyz" is the same as "select abc from xyz". However the identifier metadata returned to programs like PHP is standardized to uppercase by default. After executing either query PHP knows that column "ABC" was selected from table "XYZ".

In PHP code, array indices and object attributes need to match the schema identifier case that is returned by the database. This is either done by using uppercase indices and attributes in the PHP code, or by forcing the SQL schema to case-sensitively use lower-case names.

The former approach is more common, and is shown here.

The instructions for creating the sample ZF2 application are here. Follow those steps as written, making the substitutions shown below.

Schema

In Oracle 11gR2, the schema can be created like:

DROP USER ZF2 CASCADE;

CREATE USER ZF2 IDENTIFIED BY WELCOME
    DEFAULT TABLESPACE USERS QUOTA UNLIMITED ON USERS
    TEMPORARY TABLESPACE TEMP;

GRANT CREATE SESSION
    , CREATE TABLE
    , CREATE PROCEDURE
    , CREATE SEQUENCE
    , CREATE TRIGGER
    , CREATE VIEW
    , CREATE SYNONYM
    , ALTER SESSION
TO ZF2;

CONNECT ZF2/WELCOME

CREATE TABLE ALBUM (
  ID NUMBER NOT NULL,
  ARTIST VARCHAR2(100) NOT NULL,
  TITLE VARCHAR2(100) NOT NULL,
  PRIMARY KEY (ID)
);

CREATE SEQUENCE ALBUMSEQ;

CREATE TRIGGER ALBUMTRIGGER BEFORE INSERT ON ALBUM FOR EACH ROW
BEGIN
  :NEW.ID := ALBUMSEQ.NEXTVAL;
END;
/

INSERT INTO ALBUM (ARTIST, TITLE)
    VALUES ('The  Military  Wives', 'In  My  Dreams');
INSERT INTO ALBUM (ARTIST, TITLE)
    VALUES ('Adele', '21');
INSERT INTO ALBUM (ARTIST, TITLE)
    VALUES ('Bruce  Springsteen', 'Wrecking Ball (Deluxe)');
INSERT INTO ALBUM (ARTIST, TITLE)
    VALUES ('Lana  Del  Rey', 'Born  To  Die');
INSERT INTO ALBUM (ARTIST, TITLE)
    VALUES ('Gotye', 'Making  Mirrors');

COMMIT;

Driver and Credentials

The driver and credentials are Oracle-specific. Always use the OCI8 adapter in ZF, since it is more stable and has better scalability. Specifying a character set will make connection faster.

zf2-tutorial/config/autoload/global.php:
 return array(
     'db' => array(
-        'driver'         => 'Pdo',
-        'dsn'            => 'mysql:dbname=zf2tutorial;host=localhost',
-        'driver_options' => array(
-            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
-        ),
+        'driver'         => 'OCI8',
+        'connection_string' => 'localhost/orcl',
+        'character_set' => 'AL32UTF8',
     ),
     'service_manager' => array(
         'factories' => array(
zf2-tutorial/config/autoload/local.php:
 return array(
     'db' => array(
-        'username' => 'YOUR USERNAME HERE',
-        'password' => 'YOUR USERNAME HERE',
+        'username' => 'ZF2',
+        'password' => 'WELCOME',
     ),
     // Whether or not to enable a configuration cache.
     // If enabled, the merged configuration will be cached and used in

Attribute & Index Changes

The rest of the application changes are just to handle the case of the Oracle identifiers correctly.

zf2-tutorial/module/Album/Module.php
                     $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                     $resultSetPrototype = new ResultSet();
                     $resultSetPrototype->setArrayObjectPrototype(new Album());
-                    return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
+                    return new TableGateway('ALBUM', $dbAdapter, null, $resultSetPrototype);
                 },
             ),
         );
zf2-tutorial/module/Album/view/album/album/add.phtml
 $form->prepare();
 
 echo $this->form()->openTag($form);
-echo $this->formHidden($form->get('id'));
-echo $this->formRow($form->get('title'));
-echo $this->formRow($form->get('artist'));
+echo $this->formHidden($form->get('ID'));
+echo $this->formRow($form->get('TITLE'));
+echo $this->formRow($form->get('ARTIST'));
 echo $this->formSubmit($form->get('submit'));
 echo $this->form()->closeTag();
zf2-tutorial/module/Album/view/album/album/delete.phtml
 <h1><?php echo $this->escapeHtml($title); ?></h1>
 
 <p>Are you sure that you want to delete
-'<?php echo $this->escapeHtml($album->title); ?>' by
-'<?php echo $this->escapeHtml($album->artist); ?>'?
+'<?php echo $this->escapeHtml($album->TITLE); ?>' by
+'<?php echo $this->escapeHtml($album->ARTIST); ?>'?
 </p>
 <?php
 $url = $this->url('album', array(
            'action' => 'delete',
-           'id'     => $this->id,
+           'id'     => $this->ID,
        ));
 ?>
 <form action="<?php echo $url; ?>" method="post">
     <div>
-    <input type="hidden" name="id" value="<?php echo (int) $album->id; ?>" />
+    <input type="hidden" name="id" value="<?php echo (int) $album->ID; ?>" />
     <input type="submit" name="del" value="Yes" />
     <input type="submit" name="del" value="No" />
     </div>
zf2-tutorial/module/Album/view/album/album/edit.phtml
         'album',
         array(
             'action' => 'edit',
-            'id'     => $this->id,
+            'id'     => $this->ID,
         )
     ));
 $form->prepare();
 
 echo $this->form()->openTag($form);
-echo $this->formHidden($form->get('id'));
-echo $this->formRow($form->get('title'));
-echo $this->formRow($form->get('artist'));
+echo $this->formHidden($form->get('ID'));
+echo $this->formRow($form->get('TITLE'));
+echo $this->formRow($form->get('ARTIST'));
 echo $this->formSubmit($form->get('submit'));
 echo $this->form()->closeTag();
zf2-tutorial/module/Album/view/album/album/index.phtml
</tr>
 <?php foreach ($albums as $album) : ?>
 <tr>
-<td><?php echo $this->escapeHtml($album->title);?></td>
-<td><?php echo $this->escapeHtml($album->artist);?></td>
+<td><?php echo $this->escapeHtml($album->TITLE);?></td>
+<td><?php echo $this->escapeHtml($album->ARTIST);?></td>
 <td>
 <a href="<?php echo $this->url('album',
-            array('action'=>'edit', 'id' => $album->id));?>">Edit</a>
+            array('action'=>'edit', 'id' => $album->ID));?>">Edit</a>
     <a href="<?php echo $this->url('album',
-            array('action'=>'delete', 'id' => $album->id));?>">Delete</a>
+            array('action'=>'delete', 'id' => $album->ID));?>">Delete</a>
     </td>
     </tr>
 <?php endforeach; ?>
zf2-tutorial/module/Album/src/Album/Model/Album.php
 class Album
 {
-    public $id;
-    public $artist;
-    public $title;
+    public $ID;
+    public $ARTIST;
+    public $TITLE;
     protected $inputFilter;
 
     public function exchangeArray($data)
     {
-        $this->id     = (!empty($data['id'])) ? $data['id'] : null;
-        $this->artist = (!empty($data['artist'])) ? $data['artist'] : null;
-        $this->title  = (!empty($data['title'])) ? $data['title'] : null;
+        $this->ID     = (!empty($data['ID'])) ? $data['ID'] : null;
+        $this->ARTIST = (!empty($data['ARTIST'])) ? $data['ARTIST'] : null;
+        $this->TITLE  = (!empty($data['TITLE'])) ? $data['TITLE'] : null;
     }
 
     public function getArrayCopy()

and

             $factory     = new InputFactory();
 
             $inputFilter->add($factory->createInput(array(
-                        'name'     => 'id',
+                        'name'     => 'ID',
                         'required' => true,
                         'filters'  => array(
                             array('name' => 'Int'),

and

                     )));
 
             $inputFilter->add($factory->createInput(array(
-                        'name'     => 'artist',
+                        'name'     => 'ARTIST',
                         'required' => true,
                         'filters'  => array(
                             array('name' => 'StripTags'),

and

                     )));
 
             $inputFilter->add($factory->createInput(array(
-                        'name'     => 'title',
+                        'name'     => 'TITLE',
                         'required' => true,
                         'filters'  => array(
                             array('name' => 'StripTags'),
zf2-tutorial/module/Album/src/Album/Model/AlbumTable.php
     public function getAlbum($id)
     {
         $id  = (int) $id;
-        $rowset = $this->tableGateway->select(array('id' => $id));
+        $rowset = $this->tableGateway->select(array('ID' => $id));
         $row = $rowset->current();
         if (!$row) {
             throw new \Exception("Could not find row $id");

and

     public function saveAlbum(Album $album)
     {
         $data = array(
-            'artist' => $album->artist,
-            'title'  => $album->title,
+            'ARTIST' => $album->ARTIST,
+            'TITLE'  => $album->TITLE,
         );
 
-        $id = (int)$album->id;
+        $id = (int)$album->ID;
         if ($id == 0) {
             $this->tableGateway->insert($data);
         } else {
             if ($this->getAlbum($id)) {
-                $this->tableGateway->update($data, array('id' => $id));
+                $this->tableGateway->update($data, array('ID' => $id));
             } else {
                 throw new \Exception('Form id does not exist');
             }

and

     public function deleteAlbum($id)
     {
-        $this->tableGateway->delete(array('id' => $id));
+        $this->tableGateway->delete(array('ID' => $id));
     }
 }
zf2-tutorial/module/Album/src/Album/Form/AlbumForm.php
         parent::__construct('album');
         $this->setAttribute('method', 'post');
         $this->add(array(
-                'name' => 'id',
+                'name' => 'ID',
                 'type' => 'Hidden',
             ));
         $this->add(array(
-                'name' => 'title',
+                'name' => 'TITLE',
                 'type' => 'Text',
                 'options' => array(
                     'label' => 'Title',
                 ),
             ));
         $this->add(array(
-                'name' => 'artist',
+                'name' => 'ARTIST',
                 'type' => 'Text',
                 'options' => array(
                     'label' => 'Artist',
zf2-tutorial/module/Album/src/Album/Controller/AlbumController.php
         }
 
         return array(
-            'id' => $id,
+            'ID' => $id,
             'form' => $form,
         );
     }

and

         }
 
         return array(
-            'id'    => $id,
+            'ID'    => $id,
             'album' => $this->getAlbumTable()->getAlbum($id)
         );
     }

When you create applications from scratch it will be straightforward to get it all working.

Friday Sep 03, 2010

Zend Framework .htacess and Multiple Controllers

[Read More]
About

Tourists looking out over an Opal mine
I'm a Product Manager in Server Technologies, working on scripting languages and developer-access.
Email: christopher.jones@oracle.com
Twitter: http://twitter.com/ghrd
Book: Free PHP Oracle book
Download: PHP Linux RPMs with the OCI8 extension
Links: OTN PHP Developer Center

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today