Vu que ça intéresse du monde, et même si on attend la zone floue entre 
plateforme et appli (ceci étant dit, je suis partant pour un grand débat sur la 
séparation des pouvoirs entre système et dev), voilà la version "rustique" de 
la chose :

La config PHP :

[xhprof]
extension=xhprof.so
xhprof.output_dir=[répertoire temporaire où sont stockés les traces]

En tout début de code ou dans un fichier à part, chargé en auto_prepend_file 
(attention, tout ce qui est exécuté avant n'est pas profilé) - je ne trace que 
ce qui est en HTTP (d'où le isset($_SERVER...), et je ne trace pas les 
fonctions PHP (XHPROF_FLAGS_NO_BUILTINS) pour ne pas trop surcharger les traces 
:

if (function_exists('xhprof_enable') && isset($_SERVER['HTTP_HOST']) && 
mt_rand(1, XXX) == 1) {
    function save_xhprof_data() {
        $xhprof_data = xhprof_disable();

        include_once CODE_BASE . "lib/xhprof/utils/xhprof_lib.php";
        include_once CODE_BASE . "lib/xhprof/utils/xhprof_runs.php";
        $xhprof_runs = new XHProfRuns_Default();
        $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_data");
    }
    xhprof_enable(XHPROF_FLAGS_NO_BUILTINS + XHPROF_FLAGS_MEMORY);
    register_shutdown_function('save_xhprof_data');
}

NB :
- les libs xhprof_xxx sont founies avec xhprof, remplacer le CODE_BASE par 
l'endroit où sont placées ces libs.
- remplacer le XXX du mt_rand par la valeur qui vous permet d'avoir quelques 
traces par minute

Pour l'analyse :

xhprof_aggregate.php
- croner xhprof_aggregate.php toutes les nuits pour consolider les données de 
la veille
- lancer xhprof_aggregate.php simul pour simuler l'exécution et afficher le 
rapport en cours

Ca sort tout sur la sortie standard, ça peut donc se rediriger dans un log, ou 
générer un mail, comme toute cron...

Je vous laisse vous inspirer de tout ça pour faire quelque chose de plus 
industriel (fusion de logs de plusieurs serveurs, stockage des données en base, 
etc.)

#!/usr/bin/php
<?php

define('SEUIL_DELTA', 20);
define('NB_TOP', 20);

$output_dir = ini_get("xhprof.output_dir");

$dir = new DirectoryIterator($output_dir);

include_once CODE_BASE . "lib/xhprof/utils/xhprof_lib.php";
include_once CODE_BASE . "lib/xhprof/utils/xhprof_runs.php";
include_once CODE_BASE . "lib/xhprof/display/xhprof.php";

$simul = (count($argv) > 1 && $argv[1] == "simul");

// On prend tous les fichiers de run présents dans le répertoire
// qu'on va ensuite agréger

$aggregatedfile = $output_dir."/".date("Ymd").".aggregated";
if (!$simul && file_exists($aggregatedfile)) {
    $aggregated = unserialize(file_get_contents($aggregatedfile));
    $files = array();
} else {
    foreach ($dir as $file) {
        if ($file->isFile() && preg_match('/([a-z0-9]+).xhprof_data/', 
$file->getBasename(), $matches)) {
            $run_ids[] = $matches[1];
            $files[] = $file->getPathname();
        }
    }
    if (count($run_ids) == 0) {
        die("Pas de run à analyser");
    }

    $xhprof_runs = new XHProfRuns_Default();
    $aggregated = xhprof_aggregate_runs($xhprof_runs, $run_ids, array(), 
"xhprof_data");
    if (!$simul) {
        file_put_contents($aggregatedfile, serialize($aggregated));
    }
}

$computed = xhprof_compute_flat_info($aggregated['raw'], $total);
//print_r($computed);

// Calcul des tops

function displayTop($computed, $key, $scale = 1000) {
    $slice = array_slice($computed, 0, NB_TOP);
    $max = null;
    foreach($slice as $call => $data) {
        $val = round($data[$key]/$scale);
        if ($max == null) $max = $val;
        printf("%s : %d (count %d / avg %.2f / %d %%)\n", $call, $val, 
$data['ct'], $val/$data['ct'], 100*$val/$max);
    }
}
// Top par wall-time (self)
function compareWT($a, $b) {
    global $computed;
    return $computed[$a]['excl_wt'] < $computed[$b]['excl_wt'];
}
uksort($computed, "compareWT");
print "\nTop 10 (temps self/msec)\n";
displayTop($computed, 'excl_wt');

// Top par wall-time (total)
function compareWT2($a, $b) {
    global $computed;
    return $computed[$a]['wt'] < $computed[$b]['wt'];
}
uksort($computed, "compareWT2");
print "\nTop 10 (temps incl/msec)\n";
displayTop($computed, 'wt');

// Top par mémoire utilisée (self)
function compareMU($a, $b) {
    global $computed;
    return $computed[$a]['excl_mu'] < $computed[$b]['excl_mu'];
}
uksort($computed, "compareMU");
print "\nTop 10 (memory self/Mo)\n";
displayTop($computed, 'excl_mu', 1024*1024);

// Calcul des diff par rapport à hier
// On ne prend que le top des entrées

function delta($before, $today_pruned) {
    global $computed;

    $delta = false;
    $computed = xhprof_compute_flat_info($before['raw'], $total);
    uksort($computed, "compareWT");
    $before_pruned = array_slice($computed, 0, NB_TOP);
    //$before_pruned = 
xhprof_compute_flat_info(xhprof_prune_run($before['raw'], SEUIL_DELTA), $total);
    foreach ($today_pruned as $fn => $data) {
        if (!isset($before_pruned[$fn])) {
            //print "Apparition : $fn (temps incl ".$data['wt']."/self 
".$data['excl_wt'].")\n";
            $val = round($data['excl_wt']/1000);
            printf("Apparition de %s : self %d (count %d / avg %.2f)\n", $fn, 
$val, $data['ct'], $val/$data['ct']);
            $delta = true;
        } else {
            $beforedata = $before_pruned[$fn];
            //print "Déjà présent : $fn (temps incl ".$data['wt']."/self 
".$data['excl_wt'].")\n";
            $val = round($data['excl_wt']/1000);
            $beforeval = round($beforedata['excl_wt']/1000);
            printf("Déjà présent %s : self %d (count %d / avg %.2f vs %.2f)\n", 
$fn, $val, $data['ct'], $val/$data['ct'], $beforeval/$beforedata['ct']);
        }
    }
    foreach ($before_pruned as $fn => $data) {
        if (!isset($today_pruned[$fn])) {
            //print "Disparition : $fn (temps incl ".$data['wt']."/self 
".$data['excl_wt'].")\n";
            $val = round($data['excl_wt']/1000);
            printf("Disparition de %s : self %d (count %d / avg %.2f)\n", $fn, 
$val, $data['ct'], $val/$data['ct']);
            $delta = true;
        }
    }
    return $delta;
}

uksort($computed, "compareWT");
$today_pruned = array_slice($computed, 0, NB_TOP);

$yesterdayfile = $output_dir."/".date("Ymd", strtotime("-1 day")).".aggregated";
if (file_exists($yesterdayfile)) {
    print "\nDelta par rapport à hier :\n";
    $before = unserialize(file_get_contents($yesterdayfile));
    delta($before, $today_pruned);
}

$lastweekfile = $output_dir."/".date("Ymd", strtotime("-1 week")).".aggregated";
if (file_exists($lastweekfile)) {
    print "\nDelta par rapport à la semaine dernière :\n";
    $before = unserialize(file_get_contents($lastweekfile));
    delta($before, $today_pruned);
}

if (!$simul) {
    foreach($files as $path) {
        unlink($path);
    }
}

NB:
- il est possible qu'il reste des dépendances par rapport au reste de l'appli 
(même si un nettoyage a été fait), et que ça ne demande donc des adaptations 
pour marcher chez vous
- le pourcentage affiché est par rapport à la fonction qui consomme le plus, 
pas par rapport au temps global d'exécution (qu'on ne connait pas), histoire 
d'avoir des ordres de comparaison
- si vous n'arrivez pas à faire marcher le code, c'est que c'était une mauvaise 
idée d'essayer

JFB
_______________________________________________
FRsaG mailing list
[email protected]
http://www.frsag.org/mailman/listinfo/frsag

Répondre à