Frühzeitig Memory Limit Probleme entdecken
Vorausschauendes oder defensives Programmieren wird häufig vernachlässigt. Man geht allzu häufig davon aus, dass die Umgebung immer die selbe ist und wenn es einmal funktioniert, dann funktioniert es immer. Zum defensiven Programmieren gehört aber nicht nur, alle möglichen Fälle von Parametern abzufangen die jemand in eine Methode reinstopfen könnte, sondern auch die Prüfung der Webservereigenschaften. Denn wer weiß, ob das Projekt in einigen Monaten oder Jahren nicht auf einen anderen Webserver (z.B. IIS -> Apache) umgezogen wird, oder bei der Installation einer neuen PHP-Version vergessen wurde, die php.ini korrekt anzupassen.
Häufig gibt es aus diesem Grund in einem Initialisierungsscript oder einer Bootstrap-Datei Prüfungen zur verwendeten ZendFramework-Version, PHP-Version, register_globals, magic_quotes_gpc, memory_limit usw. Diese sind recht einfach zu schreiben (häufig Dreizeiler), und ich möchte hier noch eine weitere kleine Prüfung vorstellen die sicherlich die wenigsten haben.
Es geht um das Memory-Limit, also den maximalen Speicherverbrauch eines PHP-Scriptes. Diesen kann man auf 3 verschiedene Arten setzen:
In der php.ini:
memory_limit = 96M
In der .htaccess:
php_value memory_limit 96M
Im PHP Script:
ini_set('memory_limit', '96M');
Falls man also während der Programmierung auf einen Fehler stößt wie diesen
PHP Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate … bytes) in …
dann stellt man das Limit einfach etwas höher und schon funktioniert alles. Doch nur die wenigsten denken daran dass später im Live-Betrieb die Datenbank-Ergebnisse größer werden, die Anzahl der User höher ist als in der Entwickler-Datenbank usw.
Um genau diese Probleme frühzeitig zu erkennen kann man den Speicherverbrauch messen und eine Warn-Email verschicken falls das Limit bald erreicht wird, zum Beispiel wenn die Datenbank noch etwas wächst.
<?php // this script should be auto_appended by PHP (php.ini or .htaccess) if (memory_get_peak_usage() > getBytes(ini_get('memory_limit')) * 0.7) { $body = "Peak usage: ".memory_get_peak_usage()."\n". "memory_limit: ".ini_get('memory_limit')."\n". "File: ".$_SERVER["REQUEST_URI"]; mail('admin@firma.de', 'Memory limit > 70% reached', $body); } function getBytes($val) { $val = trim($val); $last = strtolower($val{strlen($val)-1}); switch($last) { // The 'G' modifier is available since PHP 5.1.0 case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } return $val; }
Um den entsprechenden Request besser nachbilden zu können kann man sich natürlich noch $_POST, $_SERVER und weitere interessante Informationen zusenden lassen.
Normalerweise würde man eine solche Prüfung hinter jedes PHP-Script anhängen, doch das wäre unter Umständen etwas mühsam. Doch dafür hat PHP eine Lösung. Man lässt es einfach automatisch an jedes Script anfügen:
– in der php.ini
auto_append_file = check_memory.php
– oder in der .htaccess
php_value auto_prepend_file check_memory.php
Vielleicht kann es ja der ein oder andere von euch gebrauchen.
Durchaus informativ wäre in einem solchen Fall auch sicherlich der aktuelle debug backtrace; nicht immer ist die REQUEST_URI besonders aufschlussreich (FrontController / MVC, Cookie-abhängiges Dispatching, POST-Requests, etc.):
// PSEUDOCODE, keine Gewähr auf Richtigkeit 😉
foreach(debug_backtrace as $key=>$value)
$body.= $key . „\t“ $value . „\r\n“;
Dominik
18 Okt 10 at 17:15
Das hatte ich erst auch mit drin, aber der backtrace an der Stelle ist leer 😉
$_POST, $_SERVER, $_COOKIE etc. sind da wohl noch am sinnvollsten.
Michael Kliewe
18 Okt 10 at 17:18
Ich muss ja zugeben ich hab das früher auch mal gemacht .. mail() *irgendwohin* gepackt, und dann laufen lassen.
Das ist ja auch ganz nett, so lang wie nix passiert. aber _wenn_ was passiert, reicht die Info ja auch eigentlich einmal .. und nicht permament – und ja, genau so oft kommt sie dann nämlich im Zweifelsfall.
Weil, wenn mal was schief geht, dann natürlich schon dauerhaft, gelle? 🙂
Und um jetzt nicht nichts gesagt zu haben: Logfile + Monitoring drauf .. Nagios, Munin oder irgend sowas.
steffkes
18 Okt 10 at 21:03
@Michael, sorry, klar kann das nicht funktionieren, wo war ich nur mit meinen Gedanken? 😉
Aber interessant WÄRE der Backtrace schon 😛
Dominik
19 Okt 10 at 08:18
Da hat steffkes recht. Wenn es eng wird, wird man so mit Mails bombardiert. Da kann einem Server am Limit schnell den letzten Rest geben.
Sinnvoller wäre es solche Werte allgemein in eine Datenbank zu schreiben und von einem Monitoring-Tool auswerten zu lassen. Weil wenn will man ja auch noch schauen wie der Speicher allgemein auf dem Server aussieht, wie der Plattenplatz ist, ob die CPU langsam glüht…
Sven
19 Okt 10 at 10:09
Sehr nette Idee! Hab das jetzt mal in eine meiner Zend Framework Applikationen als Plugin eingebaut. Scheint tadellos zu funktionieren.
Danke
Christian
Christian
19 Okt 10 at 10:24
Schön wäre es wenn der Backtrace gehen würde.
Ansonsten, danke für das kleine Script. Angepasst an DB Eintragung um dann Stats auszugeben.
Oliver
19 Okt 10 at 10:31
Man könnte das Ganze natürlich in eine Funktion packen, die man an mehreren Stellen einbindet:
function check_memory_usage() {
if (!defined(‚MEMORY_WARNING‘)
&& memory_get_peak_usage() > getBytes(ini_get(‚memory_limit‘)) * 0.0)
{
// … warning …
define(‚MEMORY_WARNING‘, true);
}
}
Innerhalb dieser Funktion ist dann auch die sinnvole Ausgabe des backtraces möglich.
Je öfters man dies allerdings nutzt, desto mehr leidet natürlich die Performance. Ob das für Produktivsysteme also eine gute Lösung ist, wage ich zumindest zu bezweifeln. Evtl macht es noch Sinn dies nur dort einzubinden, wo es aus Performance-Sicht sowieso schon etwas kritischer werden kann.
Hat jemand da vielleicht noch eine bessere Lösung? Generell finde ich das Ganze nämlich schon sehr gut.
Tim
19 Okt 10 at 13:47
Finde ich ganz gut. Das hier sollte auch funktionieren, oder (he he he):
..
…
switch($last) {
case ‚g‘:
case ‚m‘:
case ‚k‘:
$val *= 1024;
}
return $val;
…
..
dwooup
20 Okt 10 at 11:04
Möchte den Satz „dann stellt man das Limit einfach etwas höher und schon funktioniert alles.“ hinzufügen: vielleicht sollte man vorher kurz darüber nachdenken, *wieso* PHP kein Speicher mehr hatte, bevor man „einfach“ das Limit erhöht..
Das automatische anhängen einer Datei zu jedem Skript auf einem produktiven Server halte ich auch für fragwürdig. Für private Seiten kein Problem, aber ansonsten ziehe ich den Einsatz von Tools wie Nagios vor..
Sonata
20 Okt 10 at 11:16
@steffkes und andere: Ja, ihr habt recht, Mail-Bomben sollte man vermeiden, deshalb ist Logging + Monitoring der Logdatei besser geeignet.
@Tim: Laut einer einfachen Messung hatte es bei mir eine Laufzeit von unter 1ms. Aber du hast Recht, eventuell ist es sinnvoller, es nur bei komplexen Requests einzusetzen.
@dwooup: Nein, das funktioniert nicht. In deinem Fall wird immer nur mit 1024 multipliziert, egal ob es g, m oder k ist. Es muss aber (im Fall von g und m) mehrfach multipliziert werden.
@Sonata: Tools wie Nagios können nicht den Peak-Speicherverbrauch eines PHP-Scripts bestimmen, irgendwo muss man nunmal memory_get_peak_usage() aufrufen. Dann kann man das Ergebnis in eine Datei schreiben und die Datei via Nagios überwachen, aber ganz ohne ein entsprechendes Script wird es nicht gehen. Die Idee von Christian, dies als Zend Framework postDispatch Plugin zu machen ist auch nett.
Michael Kliewe
20 Okt 10 at 12:01
Hab das eben mal ausprobiert und prompt gesehen, dass alle Startseiten meines Systems einen ca. 30-fach höheren Speicherverbrauch haben als die Unterseiten. Hätte nicht gedacht, dass da ein derartiges Problem besteht – muss mich gleich mal auf die Suche machen.
@Dominik
Die Ausgabe von debug_backtrace (oder allgemein Arrays mit unbekannter Struktur) kann man sich auch einfacher machen – print_r() hat seit einiger Zeit einen zweiten Parameter ‚return‘ – setz den auf true und du bekommst das Array schön formatiert in einem String zurück und nicht direkt an die Ausgabe gesendet.
Quetsch
21 Okt 10 at 12:15
@Quetsch: dessen bin ich mir bewusst, aber die Formatierungsmöglichkeiten sind so vielfältiger.
Dominik
21 Okt 10 at 12:50
Ich hab mal – basierend auf deinem Code – ein ZF Plugin erstellt und dieses hier (http://www.web-punk.com/2010/10/php-memory-limit-detection-with-zf/) online gestellt. Vielleicht kann das ja wer brauchen.
Gruß
Christian
Christian
21 Okt 10 at 14:00
[…] Frühzeitig Memory Limit Probleme entdecken Schöne Tipps wie man den Problemen auf die Spur kommen kann. […]
Letzte Woche im Web | ZweitGedanken
23 Okt 10 at 21:00
Man könnte so eine Funktion mit ticks kombinieren. So könnte man die Funktion alle x ticks (zB 20 oder so) ausführen. In dem Fall könnte man den „momentanen“ Speicherverbauch annähernd ermitteln und gleich den Stacktrace auswerten, um das Leck selbst zu finden, anstatt nur zu sehen, dass es ein Problem gibt.
KingCrunch
25 Okt 10 at 11:25
@KingCrunch: Eine garnicht uninteressante Idee!
Wenn man Lust hat könnte man eventuell eine Grafik über die Scriptlaufzeit generieren, und wo der Speicher extrem ansteigt (um x MB steigt) wird dann in der Grafik der entsprechende Stacktrace drangeschrieben.
Übrigens hier noch ein interessantes Script von Derick Rethans (Xdebug), das einem die Funktionen mit dem größte Memory-Verbrauch anzeigen kann:
http://derickrethans.nl/xdebug-and-tracing-memory-usage.html
Michael Kliewe
25 Okt 10 at 12:18
[…] Nun bin ich über eine Lösung gestolpert mit der PHP es möglich macht diesen Wert auszulesen. Frühzeitig Memory Limit Probleme entdecken Vorausschauendes oder defensives Programmieren wird häufig vernachlässigt. Man geht allzu häufig […]
PHP memory usage messen » Bananas Development Blog
2 Nov 10 at 10:56
für Skripte, welche im cli ausgeführt werden, sollte man natürlich $_SERVER[„REQUEST_URI“] durch __FILE__ ersetzen.
ppeters
6 Dez 10 at 12:51