Profiling von PHP mit Xdebug

Oftmals fällt es gar nicht auf. Man entwickelt sein Programm und greift auf Funktionen zurück, die schon seit Dekaden bestens funktionieren. Allerdings stellt man irgendwann fest, daß alles immer langsamer wird. Auf den ersten Blick ist nicht ersichtlich warum es zu diesen Einbußen kommt. Der zweite Blick auf den Profiler zeigt das die vermeintlich robuste Funktion eigentlich langsam ist. Durch mehrmalige Verwendung zwingt es die ganze Applikation in die Knie.
Auch beim tunen hilft’s: PHP ist an vielen Stellen inkonsistent und reagiert entsprechend anders, wie schaut es mit der Geschwindigkeit von Iteratoren zu Schleifen aus – oder array_key_exists zu isset? Hier gibt der Profiler Aufschluss was man am besten vermeidet und was man lieber vermeidet.
Einen guten guten Profiler für PHP kommt von Xdebug (wahrscheinlich auch der einzige). Installiert kriegt man ihn am besten aus dem Distro Repository (z.B. von Ubuntu) oder einfach aus den Sourcen mit phpize && sh configure && make && make install. Nach dem er nicht dauerhaft benötigt kann man ihn mit einem Trigger über die URL aktivieren. Über die Kommandozeile setzt man einfach das entsprechende ini-flag und der Profiler schreibt Valgrind kompatible Ausgabe nach /tmp. Eine Standardkonfiguration für Xdebug schaut demnach folgendermaßen aus:

[xdebug]
xdebug.profiler_enable=0
xdebug.profiler_enable_trigger=1

Trigger Möglichkeiten:

wget http://localhost/myscript.php?XDEBUG_PROFILE=1
php -d xdebug.profiler_enable=On myscript.php

Die Datei (z.B. /tmp/cachegrind.out.24869) kann man mit WinCacheGrind oder KCachegrind einsehen und optimiert entweder anhand der Zeitabläufe oder anhand der Aufrufe. Je nach Menge spart das ordentlich Zeit.
Hier ein Beispiel anhand verschiedener Iterationsmöglichkeiten in PHP:

Der Test ist natürlich nicht objektiv, zeigt aber, daß die vermeintlich gleiche Aufgabe nicht gleich optimal abläuft. Häufig findet man mit dem Profiler dann Funktionen im Code welche gnadenlos vergewaltigt werden (oder für einen anderen Zweck entworfen wurden) – Und das verbrennt dann richtig Zeit!

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

FastCGI und Icinga-web

PHP skaliert in Standardinstallationen relativ schlecht. Grund dafür ist, daß der Webserver jedes mal eine neue Interpreterinstanz erstellen muss und danach wieder zerstört. Das kostet Zeit und eine Menge Performance / Nerven. Besser funktioniert es mit FastCGI. Hier werden die Interpreterinstanzen unabhängig vom Webserver gestartet und stehen fertig geladen bereit wenn der Request beim Webserver eintrifft. Leider ist die Konfiguration etwas schwieriger da es für FastCGI keine richtige Standardlösung gibt. Je nach Webserver werden eigene Wrapper benötigt die gestartet werden müssen. Weshalb ein solches Konstrukt oft auf wackeligen Beinen steht und anfällig für Bedienfehler ist.
Mit PHP 5.3 führt die Interpretersprache einen eigenen Manager für FastCGI Prozesse ein: FPM – FastCGI Process Manager. Mit ihm ist es auf einfache Art möglich Gruppen von Applikationen anzulegen, Skalierung einzustellen und dadurch den Überblick über kritische oder schwer gewichtige Webapplikationen zu behalten.
Ich habe mal versucht den FPM mit Apache2 (mpm-worker) unter Ubuntu am Beispiel von Icinga-web zum laufen zu bekommen. Ausgangssituation ist ein installierter Apache2 und eine Standard Icinga-web Installation aus den Tutorials oder den Quickstart Guides.
1) Installation der benötigten Pakete und aktivieren der benötigten Module:

# aptitude install libapache2-mod-fastcgi php5-fpm
# a2enmod fastcgi actions rewrite
Enabling module fastcgi.
Enabling module actions.
Enabling module rewrite.

2) Vorbereiten der Konfiguration für FPM

# pwd
/etc/php5/fpm
# cat pool.d/icinga-web.conf
[icinga-web]
listen = 127.0.0.1:9000
user = www-data
group = www-data
pm = dynamic
pm.max_children = 100
pm.start_servers = 30
pm.min_spare_servers = 30
pm.max_spare_servers = 50
;pm.max_requests = 500
chroot =
chdir = /usr/local/icinga-web/pub

Ich habe die Standardkonfiguration für “www” vorher deaktiviert. Ansonsten würden sich die Sockets auf Port 9000 einen Strich durch die Rechnung machen.
3) Fehlt noch eine Apache Konfiguration (Icinga-web Standard mit FastCGI Erweiterung):

# pwd
/etc/apache2/sites-available
# cat fcgi-test
	FastCgiExternalServer /var/www/php5.external -host 127.0.0.1:9000
	DocumentRoot /var/www
	AddHandler php5-fcgi .php
	Action php5-fcgi /usr/lib/cgi-bin/php5.external
	Alias /usr/lib/cgi-bin/ /var/www/
	AliasMatch /icinga-web/modules/([A-Za-z0-9]*)/resources/styles/([A-Za-z0-9]*\.css)$ /usr/local/icinga-web/app/modules/$1/pub/styles/$2
	AliasMatch /icinga-web/modules/([A-Za-z0-9]*)/resources/images/([A-Za-z_\-0-9]*\.(png|gif|jpg))$ /usr/local/icinga-web/app/modules/$1/pub/images/$2
	Alias /icinga-web/js/ext3 /usr/local/icinga-web/lib/ext3
	Alias /icinga-web /usr/local/icinga-web/pub
	        Order allow,deny
	        Allow from all
	        Order allow,deny
	        Allow from all
	        Order allow,deny
	        Allow from all
	        DirectoryIndex index.php
	        Options FollowSymLinks
	        AllowOverride all
	        Order allow,deny
	        Allow from all
# a2ensite  fcgi-test
Enabling site fcgi-test.

4) Apachen neu laden und den FPM Pool starten:

# ps aux | grep fpm | head -3
root      5213  0.0  0.0 145792  4872 ?        Ss   13:18   0:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
www-data  5214  0.1  0.4 173028 34312 ?        S    13:18   0:00 php-fpm: pool icinga-web
www-data  5215  0.3  0.4 175140 36824 ?        S    13:18   0:00 php-fpm: pool icinga-web

Das ganze ist zwar nur rudimentär getestet und icinga-web nicht optimiert für FastCGI, bietet aber Potential für mehr: Persistenter Agavi Cache, Connection Pooling für die Datenbank, usw.

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

Speicherwarnung für PHP

Das man bei PHP eigentlich nicht auf den Speicher achten muss ist entweder Freud oder Leid. Die Freude ist begrenzt denn ab einer gewissen Größe ist der Speicherverbrauch nicht mehr so einfach “wegzuimplementieren”.
Abhilfe schafft hier eine PECL Erweiterung namens “memtrack” die PHP Warnung anhand von Schwellwerten in die Fehlerbehandlung von PHP einkippt. Man wird also während der Entwicklung bereits gewarnt oder überprüft regelmäßig seine Logs nach ausreißern.
Die Installation ist denkbar einfach mit PECL oder per Hand:

# wget "http://pecl.php.net/get/memtrack-0.2.1.tgz"
# tar -xvzf memtrack-0.2.1.tgz
# cd memtrack-0.2.1
# phpize
# sh configure
# make install

Danach erstellt man sich einen php.ini Eintrag um das Modul erstmal zu laden:

# cat memtrack.ini
[memtrack]
extension=memtrack.so
memtrack.enabled = 1
memtrack.soft_limit=1M
memtrack.vm_limit=3M

Wir stellen eine Prozessschwelle (vm_limit) von 3MB und eine Ausführungsschwelle von 1MB. Außerdem aktivieren wir das Module global für alle laufenden PHP Interpreter Instanzen.
Mit Hilfe eines kleinen Scripts den Speicherverbrauch aufdrehen und sich über die Warnungen freuen (oder weinen):

# cat test.php

# php test.php
PHP Warning:  [memtrack] [pid 5704] user function buildArray() executed in /tmp/test.php on line 13 allocated 27787264 bytes in Unknown on line 0
PHP Stack trace:
PHP   1. {main}() /tmp/test.php:0
PHP Warning:  [memtrack] [pid 5704] virtual memory usage on shutdown: 31571968 bytes in Unknown on line 0
Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

JasperServer und SOAP

Sich eigene Reports oder sogar erweiterbare Reportframeworks zu schreiben ist mühsam und zeitaufwendig. Vor allem wenn es bereits gute Lösungen in dem Bereich gibt wie z.B. JasperServer. Neben einem eigenem Interface zur Verwaltung von Berichten und deren Ressourcen exportiert der Server gleichzeitig seine ganze Funktionalität durch SOAP nach außen. Die Installation geht mit wenigen Zeilen von statten. Entweder kann man sich ein Komplettpaket ziehen oder nur die Applikation welche auf einem Tomcat Server deployed werden muss.
Jasper Reports können mit einem Reportdesigner (iReport) erstellt werden und auf den Server übertragen werden. Danach können sie entweder direkt abgerufen werden oder zeitgesteuert per Mails verschickt werden.
Mit PHP und etwas gemogel kommt man relativ einfach zu seinem Report im gewünschten Format:

<?php
$jasper_wsdl = 'http://10.121.0.95:8080/jasperserver/services/repository?wsdl';
$client = new SOAPClient($jasper_wsdl, array (
        'login' => 'jasperadmin',
        'password' => 'jasperadmin',
        'trace' => true
));
$request = '<?xml version="1.0" encoding="UTF-8"?>
<request operationName="runReport" locale="en">
        <argument name="RUN_OUTPUT_FORMAT"><![CDATA[PDF]]></argument>
        <resourceDescriptor name="" wsType="" uriString="/Icinga/reports/host.overview" isNew="false">
                <label>null</label>
                <parameter name="p_host_object_id"><![CDATA[1]]></parameter>
        </resourceDescriptor>
</request>';
// Rückgabe ist nicht XML sondern multipart
try {
 $re = $client->runReport($request);
} catch (SOAPFault $e) {
        if ($e->getMessage() !== 'looks like we got no XML document') {
                throw $e;
        }
}
$headers = $client->__getLastResponseHeaders();
$m = array();
// Splitten der Ausgabe und finden des reports
if (preg_match('/boundary="([^"]+)"/', $headers, $m)) {
        $content = explode('--'. $m[1], $client->__getLastResponse());
        foreach ($content as $part) {
                if (strpos($part, 'Content-Id: <report>')!==false) {
                        list($header, $content) = explode("\r\n\r\n", $part);
                        file_put_contents('report.pdf', $content);
                        break;
                }
        }
}

Die Antworten vom Jasper sind MIME multipart/related weshalb man den nativen PHP Soap Client etwas austricken und die die Inhalte zusammensuchen muss.
Der Webservice ist übrigens hervorragend dokumentiert.

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

PHP – beschleunigt!

Wer kennt sie nicht – langsame PHP Seiten. Obwohl mittlerweile Speicher und Rechenleistung keine Rolle mehr spielen kommen manche PHP Applikationen nicht richtig in die Gänge.
Hier könnte der Zend Community Edition Server abhilfe schaffen. Ein kompilierter Apache mit einem Satz ausgewählter Module und ein aktuelles PHP5 bieten eine robuste Basis für die Auslieferung. Der Zend CE Server ist kostenfrei und kann auf der Herstellerseite von Zent heruntergeladen werden.
Im Hintergrund werkeln ein Apache2 für PHP und ein lighttpd für ein Admininterface welches komfortable Einstellungen in PHP erlaubt. Weiterhin ist der Zend Optimizer+ integriert, ein PHP Opcode- und Pagecacher welcher die Seite ziemlich fix generiert.
Auf Distributionen, welche keine aktuelle PHP Version bereitstellen, kann das Packet eine eigenständige Laufzeitumgebung für PHP Anwendungen bieten ohne sich zu tief ins System zu verwurzeln.
Die Installation geht schnell von statten. Man brauch nur den Installer Zend und schon gehts los:

root@rt:/usr/local/src# ls
ZendServer-5.0.3_Tarball_B3-php5.3.3-linux-glibc23-x86_64
ZendServer-CE-php-5.3.3-5.0.3-linux-glibc23-x86_64.tar.gz
root@rt:/usr/local/src# cd \
ZendServer-5.0.3_Tarball_B3-php5.3.3-linux-glibc23-x86_64/
root@rt:/usr/local/src/ZendServer-5.0.3_[...]# bash install.sh
Welcome to Zend Server installation script!
Please specify an installation path [/usr/local]:
[...]

Leider verursachen die Installierten neuen Bibliotheken Probleme, der Ursprungszustand ist schnell wiederhergestellt:

root@rt:~# rm -f /etc/ld.so.conf.d/zend_server.conf
root@rt:~# rm -f /usr/local/zend/lib/libcrypto.so.0.9.8
root@rt:~# /etc/init.d/ssh restart

Gesteuert werden die Dienste mit einem eigenen Script:

root@rt:~# /usr/local/zend/bin/zendctl.sh -h

Danach ist der Server einsatzbereit. Konfiguration und Wartung gestalten sich Apache üblich. Alle wichtigen Konfigurationen befinden sich unter /usr/local/zend (Standardinstallation).



Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

Catalot – cat a lot?

catalogNach dem erfolgreichen relaunch von MonitoringExchange stellen wir das eigens entwickelte Katalogsystem Catalot zu Verfügung. Geschrieben in PHP5, setzt Catalot auf die Frameworks Agavi und Doctrine auf,  wodurch mächtige Werkzeuge für Darstellung und Datenbankhandling zur Verfügung stehen.
Wir haben uns für die Neuentwicklung entschieden, da leider nicht viele freie Katalogsysteme existieren, welche einen ansprechenden Leistungsumfang aufweisen können. Mehr Features, bessere Links und AJAX Unterstützung, ermöglichen MonitoringExchange über 2000 Plugins für Benutzer und Admins, auf zeitgemäße Art und Weise, zur Verfügung zu stellen.
Hier einige Merkmale der aktuellen Version 1.2:

  • Sicherheit, RBAC Modell und Validierung aller Daten
  • CMS-Unterstützung für Einzelseiten
  • Einfache Erweiterungsmöglichkeit (Agavi-MVC, Modularisierung)
  • Datenbankabstraktion (Doctrine)
  • Erstellung von file bundles
  • Geschachtelte und referenzierbare Kategorien (nested sets)
  • Projektrollen: Admin, Eigentümer, sonstige Teilnehmer
  • Erweiterte Kommentar- und Rating-Funktion

Wie sind bei der Entwicklung auf die Wünsche und Anregungen der Benutzer von MonitoringExchange eingegangen und konnten somit eine Plattform schaffen welche das Arbeiten mit kleinen Projekten und Linkeinträgen vereinfacht.
Catalot wird unter der GPLv3 publiziert und ist unter netways.org zu Hause. Dort würden wir uns auch über zahlreiche Downloads, Bugreports und Patches freuen.
In diesem Sinne: Cat a lot …

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

The Agavi Way

Frameworks für PHP5 gibt es wie Sand am Meer. Mehr oder weniger gut sollen sie dem Entwickler helfen, Probleme zu vereinfachen. Dabei treten aber erstmal welche auf: neue Datenbankschnittstelle, Platzierung des Codes unklar, standartisierte Lösungsansätze fehlen – dies sind nur einige Dinge auf die man bei der Benutzung stoßen wird.
Bei einem Vergleich sind wir dabei auf Agavi gestoßen, wiedermal ein Framework welches aus dem Frust über Mojavi3 entstanden ist. Es basiert auf dem MVC Paradigma und hat einen entscheidenen Vorteil: Es zwingt dazu sauberen Code zu schreiben. Außerdem wird einem nicht ein neues Datenbank- oder Templatesystem vorgesetzt. Eher hat man die Qual der Wahl: Propel oder Doctrine,  PHPTal oder normales PHP. Natürlich sind viel mehr Kombinationen möglich.
Agavi beschränkt sich also auf die Kerngeschäfte eines Frameworks und bietet dem Entwickler einen sinnvollen Baukasten mit folgenden Features an:

  • Configuration Management
  • MVC (Models, Views, Controllers)
  • Security
  • Routing und Speaking URL’s
  • Generischer Benutzer mit RBAC
  • Template (Renderer) Baukasten
  • Outputtypes (Web, Console, Soap, JSON)
  • Sessions und deren Storagependants
  • Logging
  • Validation (GET/POST)
  • I18N

Alle Bauteile sind durch abstrakte Klassen und Interfaces beschrieben was eine Erweiterung einfach gestaltet, außerdem wird das PHP5 Exception Modell konsequent durchgezogen. Durch Phing wird zusätzlich ein Buildtool mitgeliefert welches dem Entwickler ermöglich alle Komponenten der Software zu erstellen. Somit werden Stubs für Actions oder Models schnell erstellt.
Natürlich sind die klassischen Probleme der Einarbeitszeit noch vorhanden, die Lernkurve ist allerdings hoch – Und hat man sich den ‘Agavi Way’ angeeignet, geht alles weitere schnell von der Hand!

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

Doctrine für PHP


Bei Datenbankverbindungen in PHP gehen die Meinungen der Entwickler schnell auseinander. Jeder verwendet die schnellste Lösung oder schreibt schnell eine Klasse welche die wichtigsten Funktionen übernimmt. Mittlerweile stehen dem Entwickler ja drei native Schnittstellen zur Verfügung: mysql, mysqli und PDO – wobei jede Vor- und Nachteile aufweist. Und es gibt eine Reihe von Abstraktionsschichten welche das Auslesen und Bearbeiten von Datensätzen erleichtern sollen wie z.B. ADODB, Creole/Jargon oder die Tools um MDB2 auf PEAR. Allerdings deckt keines dieser Werkzeuge den gesamten Bereich richtig ab. Schemadateien müssen von Hand erzeugt werden, Mapper von PHP Objekten sind umständlich zu konfigurieren, initialer Datenimport muss von Hand erledigt werden und komplizierte Abfrageteile werden dann doch wieder in SQL realisiert.
Seit 2006 wird an dem ORM Tool Doctrine geschraubt welches seit September in einer stabilen major release vorliegt. Doctrine orientiert sich an Hibernate aus der Javawelt und an den Active Record Pattern von Ruby on Rails. Das tolle ist: Man schreibt fast (!) keine einzige Zeile SQL mehr.
Die Relationen aus der Datenbank werden auf verschiedene Arten in PHP Objekte übersetzt. Man kann entweder gleich PHP Klassen schreiben (Aus denen dann später auch das Datenbankmodell erzeugt werden kann), lässt sich die Klassen aus einer bestehenden Datenbank erzeugen oder schreibt das Modell in einer deskriptiven, Doctrine eigenen Sprache welche auf YAML basiert. Es ist auch möglich verschiedene Datenbanken in einem logischen Modell zu Verbinden. Selbst solche Strukturen verhalten sich in PHP völlig transparent.
Doctrine an sich besteht aus einer Reihe von Objekten wie Connections, Tables, Collections, Records, Queries. Letztere können durch eine eigene, an SQL angelehnte Sprache, abgefragt werden (Anlehnung an HQL von Hibernate: DQL). Alle diese Objekte folgen speziefischen Entwurfsmustern wie Singletons, Prototypes oder EventListeners und können dadurch einfach erweiter werden.
Für kleinere Projekte schießt man damit sicherlich mit Kanonen auf Spatzen, auch benötigt man etwas Einarbeitszeit um den Baukasten zu verstehen. PHP geht damit aber insgesamt einen großen Schritt in Richtung Enterprisesoftware. Orientiert sich ein geplantes Projekt in diese Richtung ist Doctrine sicherlich nicht verkehrt und nimmt für die Zukunft eine Menge Arbeit ab: Cheatsheet von Doctrine.

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.