From ae0ea56f253f1d5479301d8552e95874f5060d49 Mon Sep 17 00:00:00 2001
From: Stevani Andolo XHProf est un outil de profilage hiérarchique pour PHP. Il relève
+les appels au niveau des fonctions et mesure inclusivement et
+exclusivement des métriques telles que le temps écoulé
+la charge CPU ou l’usage de la mémoire. Un profil de fonction peut être divisé selon ses appelants, ou ses appelés. Le composant qui extrait les données brutes est écrit en C
+et implémenté telle une extension PHP Zend.
+ Les rapports fournis par XHProf permettent souvent de mieux comprendre
+la structure du code qui est éxécuté.
+Le rendu hiérarchique des rapports permet par exemple de déterminer
+quelle chaîne d’appels mène à une fonction particulière.
+
+ XHProf propose également de comparer deux runs (résultat de profilage)
+pour analyser les différences ou aggréger les résultat de multiples runs afin
+d’analyser des données consolidées.
+Les comparaisons et aggrégations de données permettent surtout de visualiser des données plates
+
+ XHProf est un outil de profilage très léger. Pendant la phase de collecte
+Il garde une trace du nombre d’appels et des métriques inclusives viualisables en courbes dans le graphe d’appels dynamique d’un programme.
+Il calcule les métriques exclusives dans la phase de rapport.
+XHProf supporte les fonctions recursives en détectant les cycles dans la pile d’appels dès la capture des données et en utilisant un nom unique pour l’invocation principale. La nature légère d’XHProf, ses performances et ses possibilités de consolidations de données
+en font un outil taillé pour les environnements de production [Voir les notes sur l’usage en production.]
+
+ XHProfLive (qui ne fait pas partie de ce kit open source), par exemple,
+est un système de monitoring de performance utilsé chez Facebook et qui est bâti sur XHProf.
+XHProfLive récupère en permanence les données de profilage en production en lançant XHProf sur un échantillon de pages
+XHProfLive aggrège ensuite les données suivant des critères tels que le temps, type de pages, et peut aider à répondre à tout type de questions comme :
+Quel est le profil de la pile d’appel pour une page spécifique ? Quel est le coût de la méthode "foo" dans toutes les pages, ou sur une page spécifique ? quelles fonctions ont régressé le plus depuis une heure, un jour pou un mois ? Quel est l’historique des tendances, des temps d’executions pour une page ou une fonction …
+
+ Développé à l’origine par Facebook, XHProf est maintenant open source depuis mars 2009. XHProf offre:
+
+ Un résumé des appels de fonctions avec des informations telles que le nombre d’appels,
+inclusivement et exclusivement, les temps, la charge mémoire, et le temps processeur.
+
+ Pour chaque fonction, il fournit le détail des appels et le temps par
+parent (appelant) & enfant (appelé), tel que :
+
+ Vous pouvez comparer les données de deux appels à XHProf pour des raisons diverses;
+Pour voir ce qui cause une régression entre une version du code et une autre,
+Pour évaluer l’impact sur les performances d’une évolution dans le code …
+
+ Une comparaison de rapport prends deux runs en entrée et produit à la fois des informations différencielles au niveau de la fonction, mais aussi des informations hiérarchiques (séparation des différences par fonction parente/enfant) pour chaque fonction.
+
+ La vue tabulaire (copie d’écran) du rapport différentiel pointe les plus grosses améliorations et régressions.
+
+ Cliquer sur un nom de fonction dans la bue tabulaire du rapport différentiel, mène à la vue hiérarchique
+(ou vue parent/enfant) différentielle d’une fonction (copie d’écran). On peut ainsi avoir une séparation des différences par fonctions parent/enfant.
+
+ Les données du rapport peuvent également être visualisées sous forme de graphique.
+Cette vue permet de mettre en lumière les chemins crtiques du programme.
+
+ Le mode profilage mémoire d’XHProf aide à isoler les fonctions qui occupent trop de mémoire.
+
+ On ne peut pas dire qu’XHProf trace exactement chaque opération
+d’allocation/libération de mémoire, en effet il utilise un schéma simplistique;
+Il trace les hausses et les baisse de besoin en mémoire allouée à PHP à chaque entré ou sortie de fonction.
+Il trace aussi les hausses et baisses de pics mémoire alloués à chaque fonction PHP.
+
+ XHProf trace les appels Par exemple, une inclusion include "lib/common.php"; va donner deux entrées pour XHProf :
+
+ Un vrai profileur hiérarchique trace toute la pile d’appel pour chaque donnée., et est capables de répondre aux questions comme : Quel était le coût du 3e appel de foo(), ou quel était le coût de bar() quand il était appelé par a()->b()->bar()?
+
+ XHProf garde une trace d’un seul niveau dans le contexte de l’appel et est seulement capable de répondre aux questions à propos
+d’une fonction qui regarde un niveau en dessus ou en dessous.
+Il appraît que dans la majorité des cas c’est bien suffisant.
+ Pour mieux comprendre, regaredez l’exemple suivant :
+ Quand XHProf peut vous dire que d() a été appelé par c() 50 fois, il ne peut pas vous dire
+combien d’appels dont dus à a() ou b().
+[On peut imaginer que c’est peut être 25 pour a() et 25 pour b(), mais ce n’est pas nécéssairement vrai.]
+ De toutes façons en pratique ce n’est pas vraiment une limitation.
+ L’extension se trouve dans le sous-répertoire "extension/".
+
+ Note: Le portage pour Windows n’est pas encore implémenté. Nous avons testé La version 0.9.2 et les précédentes sont aussi censées fonctionner sur Mac
+OS. [Cela a été testé sur Mac OS 10.5.]
+
+ Note: XHProf utilise les insctructions RDTSC (time stamp counter)
+pour implémenter un compteur de temps vraiment bas niveau. C’est pourquoi actuellement Le timer XHProf bzasé sur RDTSC ne fonctionen pas parfaitement si la techno
+SpeedStep est activée. Cette technologie est disponible sur certains processeurs Intel.
+[Note: Les Macs ont typiquement cette fonctionnalité d’activée par défaut, il faut donc la désactiver pour utiliser XHProf.]
+
+ Les étapes suivantes sont prévues pour un environnement Linux/Unix.
+
+
+ php.ini file: Vous pouvez mettre à jour votre fichier
+php.ini file afin qu’il charge automatiquement votre extension en ajoutant le code suivant :
+
+ Test de génération de donées brutes avec l’exemple simple d’un programme tel que :
+
+ foo.php
+ Lancez ce programme :
+
+ Vous devez avoir un résultat tel que :
+
+ Note: Les données brutes contienent uniquement les métriques inclusives.
+Par exemple les données brutes du tableau de données temporelles represente les temps inclusifs en microsecondes.
+Les temps exclusifs sont calculés pour chaque fonction lors de la phase d’analyse et de rapport.
+
+ Note: Par défault suelemnt le nombre d’appel & et le temps passé sont profilés.
+Vous pouvez aussi profilerle temps CPU et/ou la charge mémoire. Remplacez,
+
+ Vous aurez en sortie :
+
+ Éviter les fonctions natives lors du profilage
+
+ Par défault les fonctions natives de PHP (comme Ignorer des fonctions spécfiques lors du profilage (0.9.2 ou plus récent)
+
+ À partir de la version 0.9.2 d’XHProf, vous pouvez spécifier une liste de
+fonctions à ignorer pendant le profilage. Cela vous permet de ne pas prendre en compte par exemple
+des fonctions utilisées pour des appels indirects comme Pour spécifier cette liste de fonctions à ignorer durant le profilage, il suffit d’utiliser le second paramètre (optionnel) de l’interface graphique d’XHProf est implémentée en PHP. Le code est divisé en deux sous-répertoires,
+ Le répertoire Le répertoire Configuration du server web : Vous devez vous assurer que le répertoire
+ Gérer les runs XHProf
+
+ Les clients web ont une certaine souplesse dans la manière de sauvegarder les données brutes fournies par XHProf.
+XHProf expose une interface utilisateur nommée iXHProfRuns (voir xhprof_lib/utils/xhprof_runs.php) que les clients peuvent implémenter.
+Cela permet aux clients de préciser comment afficher les donées des runs.
+
+ L’interface utilisateur d’XHProf fournit une implementation par défaut nommée,
+"XHProfRuns_Default" (aussi dans xhprof_lib/utils/xhprof_runs.php).
+L’implementation par d"faut stocke les runs dans le répertoire définit par le paramètre INI :
+xhprof.output_dir.
+
+ Un run XHProf doit être définit de manière unique par un espace de nom et un identifiant de run.
+
+ a) Sauver les données XHProf de façon persistente :
+
+ Soit si vous utilisez l’interface par défaut,
+ La suite permet de sauvegarder le run sous forme d’un fichier dans le répertoire spécifié
+par le paramètre ini b) En utilisant votre propre implementation d’iXHProfRuns
+
+ Si vous décidez de stocker différement les runs XHProf
+(soit dans un format compressé, dans une base de données,
+etc.), vous aurez besoin d’implémenter une classe qui implémente l’interface
+iXHProfRuns().
+
+ Vous devrez également modifier les 3 pages PHP d’entrée (index.php,
+callgraph.php, typeahead.php) dans le répertoire "xhprof_html/" pour utiliser la
+nouvelle interface au lieu de celle par défaut ( Vous aurez aussi besoin d’inclure le fichier qui implémente votre classe dans les fichiers cités.
+
+ Acceéder aux runs depuis l’interface utilisateur
+
+ a) Voir un rapport simple
+
+ Pour voir un rapport avec l’identifiant <run_id> et l’espace de nom
+<namespace> utilisez une url de la forme :
+
+ Par example,
+ b) Voir un rapport différentiel
+
+ Pour voir un rapport avec les identifiants <run_id1> et
+<run_id2> et l’espace de nom <namespace> utilisez une url de la forme :
+
+ c) Voir un rapport d’aggrégation
+
+ Vous pouvez aussi spécifier un ensemble de runspour lesquels vous souhaitez un rapport d’aggrégation.
+
+ Si vous avez trois runs XHProf avec les identifiants 1, 2 & 3 pour l’espace de noms
+"benchmark". Pour voir l’aggrégation de ces trois runs :
+
+ Aggrégations pondérées: En supposant que les trois runs
+correspondent à trois types de programmes p1.php, p2.php and p3.php
+qui occupent chacun respectivement 20%, 30% et 50%. Pour voir un rapport d’aggrégation
+pondéré par les poids des runs :
+
+ Quelques observations qui peuvent faire varier votre expérience :
+
+ Nous recommandons d’utiliser le mode de profilage "temps passé" + "memoire" en production.
+[Note: Le surplus de temps passé par le mode de profilage mémoire est non significatif.]
+
+Documentation XHProf (Brouillon)
+
+Sommaire
+
+
+
+
+
Introduction
+
+xhprof. XHProf a une interface utilisateur simple en HTML, (écrite en PHP).
+L’interface permet de visualiser et de partager facilement le résultat des profilages dans un navigateur.
+Un rendu sous forme de graphique est également disponible.
+
+
+
+
+
+
+
+
+Présentation
+
+
+
+
+
+
+
+
+include, include_once, require and
+require_once comme si c’était des fonctions. Le nom du fichier inclus est utilisé pour nommer "fausses" fonctions.
+
+
+Terminologie
+
+
+
+Convention de nommage pour les fonctions spéciales
+
+
+
+
+
+main(): Une fonction fictive qui est à la racine de la pile d’appel.
+
+
+load::<filename>
+et run_init::<filename>:
+
+include/require comme des appels de fonction.
+
+
+
+
+
+load::lib/common.php - Cela représente le travail fait par l’interpréteur pour charger et compiler le fichier.
+[Note: Si vous utilisez un cache d’opcode PHP comme APC, alors la compilation intervient uniquement si le cahce est manquant dans APC.]
+
+run_init::lib/common.php - Cela répresente le code exécuté au niveau du fichier, soit le résultat de l’inclusion.
+
+foo@<n>: Implique un appel récursif de foo(), ou <n> représente le niveau de récursion.
+Cette récursion peut être directe comme foo() --> foo()), ou indirecte comme foo() --> goo() --> foo().
+
+Limitations
+
+
+Vous avez:
+ 1 appel de a() --> c()
+ 1 appel de b() --> c()
+ 50 appels de c() --> d()
+
+
+Installer l’extension XHProf
+
+
+
+
+
+XHProf sur Linux/FreeBSD.
+[NDT : Il existe un fork avec un portage Windows sur Github]
+
+xhprof fonctionne uniquement sur une architecture x86.
+Aussi tant que les valeurs de RDTSC ne pourront pas être synchronisées entre plusieurs CPUs,
+xhprof n’en utilisera qu’un seul lors du profilage.
+
+
+% cd <repertoire_source_xhprof>/extension/
+% phpize
+% ./configure --with-php-config=<chemin vers php-config>
+% make
+% make install
+% make test
+
+
+
+
+[xhprof]
+extension=xhprof.so
+;
+; répertoire utilisé par l’implémentation par défaut de l’interface iXHProfRuns
+; (nommée, XHProfRuns_Default class) pour stocker les runs XHProf.
+;
+xhprof.output_dir=<repertoire_pour_stocker_les_runs_xhprof>
+
+
+
+Profiler avec XHProf
+
+
+
+<?php
+
+function bar($x) {
+ if ($x > 0) {
+ bar($x - 1);
+ }
+}
+
+function foo() {
+ for ($idx = 0; $idx < 2; $idx++) {
+ bar($idx);
+ $x = strlen("abc");
+ }
+}
+
+// début du profileur
+xhprof_enable();
+
+// début du programme
+foo();
+
+// attêt du profileur
+$xhprof_data = xhprof_disable();
+
+// affichage des données de profilage brutes
+print_r($xhprof_data);
+
+
+
+
+% php -dextension=xhprof.so foo.php
+
+
+
+Array
+(
+ [foo==>bar] => Array
+ (
+ [ct] => 2 # 2 appels de bar() depuis foo()
+ [wt] => 27 # temps inclusif dans bar() quand il est appelé par foo()
+ )
+
+ [foo==>strlen] => Array
+ (
+ [ct] => 2
+ [wt] => 2
+ )
+
+ [bar==>bar@1] => Array # un appelrécursif à bar()
+ (
+ [ct] => 1
+ [wt] => 2
+ )
+
+ [main()==>foo] => Array
+ (
+ [ct] => 1
+ [wt] => 74
+ )
+
+ [main()==>xhprof_disable] => Array
+ (
+ [ct] => 1
+ [wt] => 0
+ )
+
+ [main()] => Array # fausse fonction représentant la racine
+ (
+ [ct] => 1
+ [wt] => 83
+ )
+
+)
+
+
+
+xhprof_enable();
+
+dans le programme précédent avec, par exemple :
+
+xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
+
+
+
+Array
+(
+ [foo==>bar] => Array
+ (
+ [ct] => 2 # nombre d’appel à bar() depuis foo()
+ [wt] => 37 # tempas passé dans bar() quand appel de foo()
+ [cpu] => 0 # temps cpu time dans bar() quand appel de foo()
+ [mu] => 2208 # changement dans l’usage de la mémoire par PHP dans bar() quand appel de foo()
+ [pmu] => 0 # changement dans l’usage de pic mémoire par PHP pour bar() quand appel de foo()
+ )
+
+ [foo==>strlen] => Array
+ (
+ [ct] => 2
+ [wt] => 3
+ [cpu] => 0
+ [mu] => 624
+ [pmu] => 0
+ )
+
+ [bar==>bar@1] => Array
+ (
+ [ct] => 1
+ [wt] => 2
+ [cpu] => 0
+ [mu] => 856
+ [pmu] => 0
+ )
+
+ [main()==>foo] => Array
+ (
+ [ct] => 1
+ [wt] => 104
+ [cpu] => 0
+ [mu] => 4168
+ [pmu] => 0
+ )
+
+ [main()==>xhprof_disable] => Array
+ (
+ [ct] => 1
+ [wt] => 1
+ [cpu] => 0
+ [mu] => 344
+ [pmu] => 0
+ )
+
+ [main()] => Array
+ (
+ [ct] => 1
+ [wt] => 139
+ [cpu] => 0
+ [mu] => 5936
+ [pmu] => 0
+ )
+
+)
+
+
+strlen) sont profilées.
+Si vous ne voulez pas les profiler (pour simplifier le résultat et la taille des données brutes générées),
+Vous pouvez utiliser le drapeau XHPROF_FLAGS_NO_BUILTINS comme dans l’exemple ci-dessous :
+
+
+// ne pas profiler les fonctions natives
+xhprof_enable(XHPROF_FLAGS_NO_BUILTINS);
+
+
+
+call_user_func et call_user_func_array.
+Ces fonctions intermédiaires compliquent inutilement la hirarchie des appels et rendent plus ardue l’interprétation des rapports en brouillant les relations parent/enfant.
+
+xhprof_enable.
+Par exemple,
+
+
+
+
+// temps passé en profilage; ignore les appels de call_user_func* pendant le profilage
+xhprof_enable(0,
+ array('ignored_functions' => array('call_user_func',
+ 'call_user_func_array')));
+
+or,
+
+// tempas pasé en profilage + profilage mémoire; ignore call_user_func* durant le profilage
+xhprof_enable(XHPROF_FLAGS_MEMORY,
+ array('ignored_functions' => array('call_user_func',
+ 'call_user_func_array')));
+
+
+
+
+Définir un environnement graphique pour XHProf
+
+
+
+
+
+
+xhprof_html/ and xhprof_lib/.
+
+xhprof_html contient les 3 pages PHP principales.
+
+
+
+
+index.php: Pour visualiser un run ou un différentiel entre deux runs.
+callgraph.php: Pour visualiser sous la forme de graphique avec un rendu en image.
+typeahead.php: Utilisé implicitement pour les fonctions de gestion de pile sur un rapport XHProf.
+xhprof_lib contient le code pour l’analyse et l’affichage.
+(calcul sur les informations de profilage, calcul des différentiels, aggrégation de données, etc.).
+
+xhprof_html/ est accessible depuis le serveur web, et qu’il est configuré pour éxécuter des scripts PHP.
+
+XHProfRuns_Default qui implémente
+iXHProfRuns, Un run XHProf sauvegardé ressemble au code suivant :
+
+
+
+// début du profilage
+xhprof_enable();
+
+// lancement du programme
+...
+
+// fin du profilage
+$xhprof_data = xhprof_disable();
+
+//
+// Sauvegarde du run XHProf
+// en utilisant l’implementation par défaut de iXHProfRuns.
+//
+include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
+include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
+
+$xhprof_runs = new XHProfRuns_Default();
+
+// sauvegarde du run avec l’espace de nom "xhprof_foo".
+//
+// **NOTE**:
+// par défault save_run() va automatiquement générer un identifiant de run
+// unique. [Vous pouvez surcharger cette donnée en passant l’identifiant en paramètre optionnel
+// à la méthode save_run().]
+//
+$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
+
+echo "---------------\n".
+ "En partant du principe que vous avez parametré l’interface utilisateur http \n".
+ "XHProf, vous pouvez visualiser les runs avec l’adresse : \n".
+ "http://<adresse-interface-utilisateur-xhprof>/index.php?run=$run_id&source=xhprof_foo\n".
+ "---------------\n";
+
+
+
+xhprof.output_dir. Le nom du fichier doit être de la forme
+49bafaa3a3f66.xhprof_foo; Les deux parties du nom sont formées par l’identifiant du run
+("49bafaa3a3f66") et l’espace de nom ("xhprof_foo"). [Si vous souhaitez créer un identifiant de run vous-même
+(comme une sequence de base de données, ou un timestamp), vous pouvez explicitementpasser l’identifiant
+du run à la méthode save_run.
+
+XHProfRuns_Default),
+changez cette ligne dans les 3 fichier.
+
+
+$xhprof_runs_impl = new XHProfRuns_Default();
+
+
+
+http://<adresse-interface-utilisateur-xhprof>/index.php?run=<run_id>&source=<namespace>
+
+
+
+http://<adresse-interface-utilisateur-xhprof>/index.php?run=49bafaa3a3f66&source=xhprof_foo
+
+
+
+http://<adresse-interface-utilisateur-xhprof>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace>
+
+
+
+
+
+http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&source=benchmark
+
+
+
+http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&wts=20,30,50&source=benchmark
+Notes sur l’utilisation d’XHProf en production
+
+
+
+
+ // profilage du temps passé (par défault) + profilage mémoire
+ xhprof_enable(XHPROF_FLAGS_MEMORY);
+
+
Pour profiler 1/10000 de vos requêtes, définissez le début du profilage avec un code dans l’esprit de celui-ci : + +
+ if (mt_rand(1, 10000) == 1) {
+ xhprof_enable(XHPROF_FLAGS_MEMORY);
+ $xhprof_on = true;
+ }
+
+
+À la fin de la requête (ou dans une fonction de finalisation de la requête), vous pouvez faire quelque chose comme : + +
+ if ($xhprof_on) {
+ // fin du profilage
+ $xhprof_data = xhprof_disable();
+
+ // sauvegarde $xhprof_data quelquepart (base de données centralisée …)
+ ...
+ }
+
+
+ Vous pouvez alors récupérer et aggréger ces profilages par horaire
+(par exemple 5 minutes, par jour, par jour …), par page ou type de requête, ou n’importe quel
+paramètre utilisé par xhprof_aggregate_runs().
+
+
+
+
L’extension XHProf propose aussi un mode très léger d’échantillonage. +L’intervalle est de 0,1 seconde. Les échantillons enregistrent l’ensemble des données. +Ce mode peut être très utile pour avoir un impact le plus négligeable possible, et permettre +Le mode sample peut être utile si vous désirez un moyen avec peu de dépassement de faire de la surveillance de performances et des diagnostics. + +
Les très pertinentes fonctions utilisées par l’extension pour utiliser le mode
+d’échantillonage sont xhprof_sample_enable() et xhprof_sample_disable().
+
+
[TBD: Documentation plus détaillée pour le mode d’échantillonage.] + +
Le fichier XHProf_lib/utils/xhprof_lib.php contient
+des librairies de fonctions additionellesqui peuvent être utilisées pour manipuler
+et aggréger les runs XHProf.
+
+
Par exemple: + +
xhprof_aggregate_runs():
+peut être utilisé pour aggréger de multiples runs XHProf runs dans un seul run.
+Cela peut être très utile pour fabriquer un outil de monitoring utilisant XHProf et à l’échelle voulue.
+[Par exemple, vous pouvez mixer des runs XHProf issus périodiquement
+d’échantillonage de la production pour générer des rapport journalier.]
+
+xhprof_prune_run(): Aggréger une grande quantité de runs
+(particulièrement si ils correspondent à des zones différentes du programme) peut créer un rendu
+graphique beaucoup trop gros. Vous pouvez donc utiliser la fonction xhprof_prune_run
+élaguer les données à afficher. En supprimant des branches qui compte pour une partie négligeable du temps passé.
+
+xhprof_html/jquery.
+
+Le rendu HTML et l’interface de navigation pour consulter les résultat du profilage sont inspirés par un outil similaire +qui existe pour les procédures stockées PL/SQL d’Oracle. Mais c’est là que la comparaison s’arrête; +Le fonctionnement interne du profileur étant assez différent + +[NDT : Merci à Rudy Rigot (@rudyrigot) pour sa relecture attentive ] +
XHProf is a hierarchical profiler for PHP. It reports
+function-level call counts and inclusive and
+exclusive metrics such as wall (elapsed)
+time, CPU time and memory usage. A function's profile can be broken
+down by callers or callees. The raw data collection component is
+implemented in C as a PHP Zend extension called
+xhprof. XHProf has a simple HTML based user
+interface (written in PHP). The browser based UI for viewing profiler
+results makes it easy to view results or to share results with peers.
+A callgraph image view is also supported.
+
+
XHProf reports can often be helpful in understanding the structure +of the code being executed. The hierarchical nature of the reports can +be used to determine, for example, what chain of calls led to a +particular function getting called. + +
XHProf supports ability to compare two runs (a.k.a. "diff" reports) +or aggregate data from multiple runs. Diff and aggregate reports, much +like single run reports, offer "flat" as well as "hierarchical" views +of the profile. + +
XHProf is a light-weight instrumentation based profiler. During the +data collection phase, it keeps track of call counts and inclusive +metrics for arcs in the dynamic callgraph of a program. It computes +exclusive metrics in the reporting/post processing phase. XHProf +handles recursive functions by detecting cycles in the callgraph at +data collection time itself and avoiding the cycles by giving unique +depth qualified names for the recursive invocations. +
+ +XHProf's light-weight nature and aggregation capabilities make it +well suited for collecting "function-level" performance statistics +from production environments. [See additional notes for use in production.] + +
XHProfLive (not part of the open source kit), for example, is a +system-wide performance monitoring system in use at Facebook that is +built on top of XHProf. XHProfLive continually gathers function-level +profiler data from production tier by running a sample of page +requests under XHProf. XHProfLive then aggregates the profile data +corresponding to individual requests by various dimensions such as +time, page type, and can help answer a variety of questions such as: +What is the function-level profile for a specific page? How expensive +is function "foo" across all pages, or on a specific page? What +functions regressed most in the last hour/day/week? What is the +historical trend for execution time of a page/function? and so on. + +
Originally developed at Facebook, XHProf was open sourced in Mar, 2009.
+ + + + + +XHProf provides: + +
Function-level summary information such as number of calls, +inclusive/exclusive wall time, memory usage, and CPU time. + +
For each function, it provides a breakdown of calls and times per +parent (caller) & child (callee), such as: + +
You may want to compare data from two XHProf runs for various +reasons-- to figure out what's causing a regression between one +version of the code base to another, to evaluate the performance +improvement of a code change you are making, and so on. + +
A diff report takes two runs as input and provides both flat +function-level diff information, and hierarchical information +(breakdown of diff by parent/children functions) for each function. + +
The "flat" view (sample screenshot) in the diff report points out the top +regressions & improvements. + +
Clicking on functions in the "flat" view of the diff report, leads +to the "hierarchical" (or parent/child) diff view of a function (sample screenshot). We can get a +breakdown of the diff by parent/children functions. + + +
The profile data can also be viewed as a callgraph. The callgraph +view highlights the critical path of the program. + + +
XHProf's memory profile mode helps track functions that +allocate lots of memory. + +
It is worth clarifying that that XHProf doesn't strictly track each +allocation/free operation. Rather it uses a more simplistic +scheme. It tracks the increase/decrease in the amount of memory +allocated to PHP between each function's entry and exit. It also +tracks increase/decrease in the amount of peak memory allocated to +PHP for each function. + +
include, include_once, require and
+require_once operations as if they were functions. The name of
+the file being included is used to generate the name for these "fake" functions.
+
+
+main(): a fictitious function that is at the root of the call graph.
+
+
+load::<filename>
+and run_init::<filename>:
+
+XHProf tracks PHP include/require operations as
+function calls.
+
+
For example, an include "lib/common.php"; operation will +result in two XHProf function entries: + +
load::lib/common.php - This represents the work done by the
+interpreter to compile/load the file. [Note: If you are using a PHP
+opcode cache like APC, then the compile only happens on a cache miss
+in APC.]
+
+run_init::lib/common.php - This represents
+initialization code executed at the file scope as a result of the
+include operation.
+
+foo@<n>: Implies that this is a
+recursive invocation of foo(), where <n> represents
+the recursion depth. The recursion may be direct (such as due to
+foo() --> foo()), or indirect (such as
+due to foo() --> goo() --> foo()).
+
+True hierarchical profilers keep track of a full call stack at +every data gathering point, and are later able to answer questions +like: what was the cost of the 3rd invokation of foo()? or what was +the cost of bar() when the call stack looked like +a()->b()->bar()? + +
+ +XHProf keeps track of only 1-level of calling context and is +therefore only able to answer questions about a function looking +either 1-level up or 1-level down. It turns out that in practice this +is sufficient for most use cases. +
+ +To make this more concrete, take for instance the following +example. +
+ ++Say you have: + 1 call from a() --> c() + 1 call from b() --> c() + 50 calls from c() --> d() ++ +
While XHProf can tell you that d() was called from c() 50 times, it +cannot tell you how many of those calls were triggered due to a() +vs. b(). [We could speculate that perhaps 25 were due to a() and 25 +due to b(), but that's not necessarily true.] +
+ +In practice however, this isn't a very big limitation. +
+ +The extension lives in the "extension/" sub-directory. + +
Note: A windows port hasn't been implemented yet. We have
+tested xhprof on Linux/FreeBSD so far.
+
+
Version 0.9.2 and above of XHProf is also expected to work on Mac +OS. [We have tested on Mac OS 10.5.] + +
Note: XHProf uses the RDTSC instruction (time stamp counter)
+to implement a really low overhead timer for elapsed time. So at the
+moment xhprof only works on x86 architecture.
+Also, since RDTSC values may not be synchronized across CPUs,
+xhprof binds the program to a single CPU during the
+profiling period.
+
+
XHProf's RDTSC based timer functionality doesn't work correctly if +SpeedStep technology is turned on. This technology is available on +some Intel processors. [Note: Mac desktops and laptops typically have +SpeedStep turned on by default. To use XHProf, you'll need to disable +SpeedStep.] + +
The steps +below should work for Linux/Unix environments. + + +
+% cd <xhprof_source_directory>/extension/ +% phpize +% ./configure --with-php-config=<path to php-config> +% make +% make install +% make test ++ + +
php.ini file: You can update your +php.ini file to automatically load your extension. Add the following +to your php.ini file. + +
+[xhprof] +extension=xhprof.so +; +; directory used by default implementation of the iXHProfRuns +; interface (namely, the XHProfRuns_Default class) for storing +; XHProf runs. +; +xhprof.output_dir=<directory_for_storing_xhprof_runs> ++ + +
Test generating raw profiler data using a sample test program like: + +
foo.php +
+<?php
+
+function bar($x) {
+ if ($x > 0) {
+ bar($x - 1);
+ }
+}
+
+function foo() {
+ for ($idx = 0; $idx < 2; $idx++) {
+ bar($idx);
+ $x = strlen("abc");
+ }
+}
+
+// start profiling
+xhprof_enable();
+
+// run program
+foo();
+
+// stop profiler
+$xhprof_data = xhprof_disable();
+
+// display raw xhprof data for the profiler run
+print_r($xhprof_data);
+
+
+
+Run the above test program: + +
+% php -dextension=xhprof.so foo.php ++ +
You should get an output like: + +
+Array +( + [foo==>bar] => Array + ( + [ct] => 2 # 2 calls to bar() from foo() + [wt] => 27 # inclusive time in bar() when called from foo() + ) + + [foo==>strlen] => Array + ( + [ct] => 2 + [wt] => 2 + ) + + [bar==>bar@1] => Array # a recursive call to bar() + ( + [ct] => 1 + [wt] => 2 + ) + + [main()==>foo] => Array + ( + [ct] => 1 + [wt] => 74 + ) + + [main()==>xhprof_disable] => Array + ( + [ct] => 1 + [wt] => 0 + ) + + [main()] => Array # fake symbol representing root + ( + [ct] => 1 + [wt] => 83 + ) + +) ++ +
Note: The raw data only contains "inclusive" metrics. For +example, the wall time metric in the raw data represents inclusive +time in microsecs. Exclusive times for any function are computed +during the analysis/reporting phase. + +
Note: By default only call counts & elapsed time is profiled. +You can optionally also profile CPU time and/or memory usage. Replace, + +
+xhprof_enable(); ++in the above program with, for example: +
+xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); ++ +
You should now get an output like: + +
+Array +( + [foo==>bar] => Array + ( + [ct] => 2 # number of calls to bar() from foo() + [wt] => 37 # time in bar() when called from foo() + [cpu] => 0 # cpu time in bar() when called from foo() + [mu] => 2208 # change in PHP memory usage in bar() when called from foo() + [pmu] => 0 # change in PHP peak memory usage in bar() when called from foo() + ) + + [foo==>strlen] => Array + ( + [ct] => 2 + [wt] => 3 + [cpu] => 0 + [mu] => 624 + [pmu] => 0 + ) + + [bar==>bar@1] => Array + ( + [ct] => 1 + [wt] => 2 + [cpu] => 0 + [mu] => 856 + [pmu] => 0 + ) + + [main()==>foo] => Array + ( + [ct] => 1 + [wt] => 104 + [cpu] => 0 + [mu] => 4168 + [pmu] => 0 + ) + + [main()==>xhprof_disable] => Array + ( + [ct] => 1 + [wt] => 1 + [cpu] => 0 + [mu] => 344 + [pmu] => 0 + ) + + [main()] => Array + ( + [ct] => 1 + [wt] => 139 + [cpu] => 0 + [mu] => 5936 + [pmu] => 0 + ) + +) ++ +
Skipping builtin functions during profiling + +
By default PHP builtin functions (such as strlen) are
+profiled. If you do not want to profile builtin functions (to either
+reduce the overhead of profiling further or size of generated raw
+data), you can use the XHPROF_FLAGS_NO_BUILTINS
+flag as in for example:
+
+
+// do not profile builtin functions +xhprof_enable(XHPROF_FLAGS_NO_BUILTINS); ++ + +
Ignoring specific functions during profiling (0.9.2 or higher) + +
Starting with release 0.9.2 of xhprof, you can tell XHProf to
+ignore a specified list of functions during profiling. This allows you
+to ignore, for example, functions used for indirect function calls
+such as call_user_func and
+call_user_func_array. These intermediate functions
+unnecessarily complicate the call hierarchy and make the XHProf
+reports harder to interpret since they muddle the parent-child
+relationship for functions called indirectly.
+
+
To specify the list of functions to be ignored during profiling
+use the 2nd (optional) argument to xhprof_enable.
+For example,
+
+
+
+
+// elapsed time profiling; ignore call_user_func* during profiling
+xhprof_enable(0,
+ array('ignored_functions' => array('call_user_func',
+ 'call_user_func_array')));
+
+or,
+
+// elapsed time + memory profiling; ignore call_user_func* during profiling
+xhprof_enable(XHPROF_FLAGS_MEMORY,
+ array('ignored_functions' => array('call_user_func',
+ 'call_user_func_array')));
+
+
+
+
+The XHProf UI is implemented in PHP. The code resides in two
+subdirectories, xhprof_html/ and xhprof_lib/.
+
+
The xhprof_html directory contains the 3 top-level PHP pages.
+
+
index.php: For viewing a single run or diff report.
+callgraph.php: For viewing a callgraph of a XHProf run as an image.
+typeahead.php: Used implicitly for the function typeahead form
+on a XHProf report.
+The xhprof_lib directory contains supporting code for
+display as well as analysis (computing flat profile info, computing
+diffs, aggregating data from multiple runs, etc.).
+
+
Web server config: You'll need to make sure that the
+xhprof_html/ directory is accessible from your web server, and that
+your web server is setup to serve PHP scripts.
+
+
Managing XHProf Runs + +
Clients have flexibility in how they save the XHProf raw data +obtained from an XHProf run. The XHProf UI layer exposes an interface +iXHProfRuns (see xhprof_lib/utils/xhprof_runs.php) that clients can +implement. This allows the clients to tell the UI layer how to fetch +the data corresponding to a XHProf run. + +
The XHProf UI libaries come with a default file based +implementation of the iXHProfRuns interface, namely +"XHProfRuns_Default" (also in xhprof_lib/utils/xhprof_runs.php). +This default implementation stores runs in the directory specified by +xhprof.output_dir INI parameter. + +
A XHProf run must be uniquely identified by a namespace and a run +id. + + + +
a) Saving XHProf data persistently: + +
Assuming you are using the default implementation
+XHProfRuns_Default of the
+iXHProfRuns interface, a typical XHProf run
+followed by the save step might look something like:
+
+
+
+// start profiling +xhprof_enable(); + +// run program +.... + +// stop profiler +$xhprof_data = xhprof_disable(); + +// +// Saving the XHProf run +// using the default implementation of iXHProfRuns. +// +include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php"; +include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php"; + +$xhprof_runs = new XHProfRuns_Default(); + +// Save the run under a namespace "xhprof_foo". +// +// **NOTE**: +// By default save_run() will automatically generate a unique +// run id for you. [You can override that behavior by passing +// a run id (optional arg) to the save_run() method instead.] +// +$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo"); + +echo "---------------\n". + "Assuming you have set up the http based UI for \n". + "XHProf at some address, you can view run at \n". + "http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n". + "---------------\n"; + ++ +
The above should save the run as a file in the directory specified
+by the xhprof.output_dir INI parameter. The file's
+name might be something like
+49bafaa3a3f66.xhprof_foo; the two parts being the
+run id ("49bafaa3a3f66") and the namespace ("xhprof_foo"). [If you
+want to create/assign run ids yourself (such as a database sequence
+number, or a timestamp), you can explicitly pass in the run id to the
+save_run method.
+
+
+
b) Using your own implementation of iXHProfRuns + +
If you decide you want your XHProf runs to be stored differently +(either in a compressed format, in an alternate place such as DB, +etc.) database, you'll need to implement a class that implements the +iXHProfRuns() interface. + +
You'll also need to modify the 3 main PHP entry pages (index.php,
+callgraph.php, typeahead.php) in the "xhprof_html/" directory to use
+the new class instead of the default class XHProfRuns_Default.
+Change this line in the 3 files.
+
+
+$xhprof_runs_impl = new XHProfRuns_Default(); ++ +
You'll also need to "include" the file that implements your class in +the above files. + + +
Accessing runs from UI + +
a) Viewing a Single Run Report + +
To view the report for run id say <run_id> and namespace +<namespace> use a URL of the form: + +
+http://<xhprof-ui-address>/index.php?run=<run_id>&source=<namespace>
+
+
+
For example, +
+http://<xhprof-ui-address>/index.php?run=49bafaa3a3f66&source=xhprof_foo
+
+
+
+
b) Viewing a Diff Report + +
To view the report for run ids say <run_id1> and +<run_id2> in namespace <namespace> use a URL of the form: + +
+http://<xhprof-ui-address>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace>
+
+
+
c) Aggregate Report + +
You can also specify a set of run ids for which you want an aggregated view/report. + +
Say you have three XHProf runs with ids 1, 2 & 3 in namespace +"benchmark". To view an aggregate report of these runs: + +
+http://<xhprof-ui-address>/index.php?run=1,2,3&source=benchmark
+
Weighted aggregations: Further suppose that the above three runs +correspond to three types of programs p1.php, p2.php and p3.php that +typically occur in a mix of 20%, 30%, 50% respectively. To view an +aggregate report that corresponds to a weighted average of these runs +using: + +
+http://<xhprof-ui-address>/index.php?run=1,2,3&wts=20,30,50&source=benchmark
+
Some observations/guidelines. Your mileage may vary: + +
We recommend using elapsed time + memory profiling mode in +production. [Note: The additional overhead of memory profiling +mode is really low.] + +
+ // elapsed time profiling (default) + memory profiling + xhprof_enable(XHPROF_FLAGS_MEMORY); ++
To profile say 1/10000 of your requests, instrument the beginning of +your request processing with something along the lines of: + +
+ if (mt_rand(1, 10000) == 1) {
+ xhprof_enable(XHPROF_FLAGS_MEMORY);
+ $xhprof_on = true;
+ }
+
+
+At the end of the request (or in a request shutdown function), you might +then do something like: + +
+ if ($xhprof_on) {
+ // stop profiler
+ $xhprof_data = xhprof_disable();
+
+ // save $xhprof_data somewhere (say a central DB)
+ ...
+ }
+
+
+ You can then rollup/aggregate these individual profiles by time
+(e.g., 5 minutely/hourly/daily basis), page/request type,or other
+dimensions using xhprof_aggregate_runs().
+
+
+
+
+
The xhprof extension also provides a very light weight sampling +mode. The sampling interval is 0.1 secs. Samples record the full +function call stack. The sampling mode can be useful if an extremely +low overhead means of doing performance monitoring and diagnostics is +desired. + +
The relevant functions exposed by the extension for using the
+sampling mode are xhprof_sample_enable() and
+xhprof_sample_disable().
+
+
+
[TBD: more detailed documentation on sampling mode.] + + +
The xhprof_lib/utils/xhprof_lib.php file contains
+additional library functions that can be used for manipulating/
+aggregating XHProf runs.
+
+
For example: + +
xhprof_aggregate_runs():
+can be used to aggregate multiple XHProf runs into a single run. This
+can be helpful for building a system-wide "function-level" performance
+monitoring tool using XHProf. [For example, you might to roll up
+XHProf runs sampled from production periodically to generate hourly,
+daily, reports.]
+
+xhprof_prune_run(): Aggregating large number of
+XHProf runs (especially if they correspond to different types of
+programs) can result in the callgraph size becoming too large. You can
+use xhprof_prune_run function to prune the callgraph data
+by editing out subtrees that account for a very small portion of the
+total time.
+
+xhprof_html/jquery subdirectory.
+
+The HTML-based navigational interface for browsing profiler results +is inspired by that of a similar tool that exists for Oracle's stored +procedure language, PL/SQL. But that's where the similarity ends; the +internals of the profiler itself are quite different. + +
_Q0$B0aWXq z6>vmLjzn?UUMtS*c(Ft@;k|dh#NVPzGst$9GTak?nb9jBpHfic%DCeNVv0uVuJ_FE z8I;@|%Bd+ALgS2n&lcYDJ%Jb z4e{@2voMKCgQx%q6ysEfT-?=S{o^Tf1QpMMDHUQrmC~& UaJDrwV zD5F9|)=St{Y Tsv5+v^kM`W_& zV%j5hRI)u8)Y`D1ip&)6BE@016cbWI!Ql#$5pD2*6}Wj!HFI8%YQldJR1lvglPZ7) zuD+$T-%?Ir%vM5eF}Tw4Fr0htv=Z@)zYGNIFEw0?to5=XXQQey64--Ogi8^O4p@l* zVKosSRg!SFTorOwEnnK57nv8WSILnx@#pI+(VNPBF_K#myaNzZ+xGsG+(kEgJ*s|} zAeDia6&v~WurU%Nrb+bjN;b`Erg_ef&Et$efU<=vC6NQBq#xyYsZqwjAM2RH$21&N zMumLNk@nT4Wzg`?rbPF@x1@UYaA5lnwOLR>-9AmS8|4JlLom5=j0XOs7=%(G{ CdE}i zltowp+Al0%26SD10===c@P7dFLyib)TrCxjwyOK<<-^S?QJ_-nB?bD5Y@Pr2U}cWH ziSViXNZ8DC_&NweCATIEL;-;wQU4!LxPD2ssrO;PmFMjqon^uxNP(ow fYZZ$)DwImde{|Ugb|{L!mq{R@q5Xul3u+X)MexBvnW-xu_>Zpq^j(8zf!;ta z4 %h?_jS13R75`JQX&myNI3h$2n$$pIFo-Yf`k ulPuVi5b>2mA*Y+-%RX7!Q+FQUw6QM}sET;xNL zhh%$FoKqD55;p3+^Y^bNH;&`Wc!GA2`(;g7Qq0IQ9@a&VWd)(m-iypIUr?~dyZP8V zw<9p7z2PYwvM^R`=EB+4S )F6P*G@w>9;1*4QDom-$6YGK&YSXNL zqxAV?s>fm+_J;f=GBS)zQe{67x)?4@#*dD(N<{{{s$1$tT1-mmX0hkQG&;z0w~2Ng zTLacwGz#~W9w@3u#cC@Sk*TX)A*6vZ7M?b7kGa;Qmvd)I^j43T;-fTfxoiDV7!6{2 z^E^;x%qy+w%BiaPph+z|txLHE8oaJvcIUrQka4B&Oe`Ob?T(Ve`&Om?LikK@Al)j= z*Lb6;kek>ThEpWNn(~JeppozA(fX}y%Kb-^3;LR6#6`i%6GQ@gBRA(Jh9i~Df-7pF zyT&AePdva3JnmU_Vy1IcFhEUuJ;hh1H=> z`nC4!VuWsSjPfa+v2TSs50~;i)}!)0rL!)xje#TC@5;}A0PeYZwO v)&bJCR><-6I_H~KgKD2Bij4ZB6CeW1T=JLeJGyPVgjKY(x6 zN8LtA54(qH4q-3`&D7)b4>HV7&Dp1+tre$75kN zp_2LosJc0Ne!}KvfBpRTgZE*mSgax_CeZi;sI;lZ$U-( DuS;zex=IE?w?k )+;uSk15$2G^jKkVMQ? zf)}`oNhy)yoKoZ3A|V$xuyO;LD)6H)?eDwB55TF46ksxig(s*B+$i=@C#WP@w3j$; zTx%C#p_YY+(Eyd;19Y}-rf;Y;A1a6~gr>@ozB5&*Pn$sf0}EFbVT$VeRWk(&=AL7w z4^gX^bg~Q<|9+1I`T+g6G~iI8c730mE @Oca^WoGR}QDgiRk`rpT{dNGR$Vmw_Am{UfMqZ&Ct^M088My@3=VY&Cl@O zA$Bna0g35ZdBa -uG>>K|Bm$HhOp2hjFnDj$PD2-$!o5{by z*!depxq&2Jz7KiJ19cwoCPqxxcd`jXlat6J!igcJ=k7K~CR`Q#8y27pN{9wb5&omL zzJ1jAzdQl|z3RZ2p3D8%XqYbKK6|C0<|9^A+lS4z4xZ@j-O>Mi#{TV#;3E*yLnN|; zlK;O0440nt+lRVzq;zfB*L(dR(f>(D6fNpaLI<|(CC!o}Ry1-n`99FCrr1TKRbw=; z_J;zE*K)9Zyx)C*wwfK@VMXER6Wxx1d+Is3!H8xlR@CYg+kE}mCt&m~b~eNJ+|uyX zP`GVJpkbl|cr8V X}Fy%q6ksPhektwI#9VQAdJJcQ(RL z&D+l@xpz((@KNhQ%E1Hp^ZE266)jmZ=Jm?{Y^~i^U%(0bwmx7Y;s2gTJhma4nD| zmhhYr!5wvMWz=+Y2~TX;OG8zI^ozx##c#eKU3)nUq$GG#Db+AXs0M~ofx@&M^(ec! z%u8jYf3KA_oeUa#`1yjq5Hp)O1T&3SyQWBB?{m7PpnZp?m0(~e{biTvYoubeki763 zclT7O!wn}s1m!mb!V^bSSd?m?t+Hd3eDzU3LbcRw+`VSWXF;>*cxxyJxbx&Hyrpy$ zqvr6_kY>X&1p)_0Kp6nu>1K~&2!SFCDidNlErTI5#JGEH>st7L31)qVUMxsudStk> zY__0Xl?*YXC}U>ypNbxxI2I6BoBLr?5W@ht1C3B1PAHdU#42rVm1_1%*eEuv5ZPG~ zvnGNyj|*xwxXLwALA!t;1XQRb$I)^Da;6d6fZ=#`wf1;*H!8(S1&KBWOlEx&Jby$O z_9c9H;ug>&ABXdhg!PDK0q#^S-BM5zQ^>Vl6yUci6B*<#f{LWg6w7`Z^bJp(vIM_^ zqDY9Cf!;kHlZYL41!^r`eSk2}(I&9TzBT~=+rss(6@5|-7ncMu_zxglNx&5CcoKSp zZK1s8h(wtN%}Aut3xda!&I|N|uCk1a`q0jX5gs<0KJur?S&|IK2Xij0D%`u1RUL^1 zJq=exAnp1P?J&W`ita3#QXmAI+{u~&Xh@y!H~w#qu?DY+Hd@Q=Sg>U>sz+AT@N8Wu zTrWu_#Ip1u&QxqFU;&?u3zA!vokkKd8hX4dYSDT&6^I@b%}=_HhZk-Hz}O$Ja>HJ) z<2c)WFy$H;yNjy7okN;9BdB?zX9v#4CRs`hn8Y$ZTxpt5cJ}KVGh)|rw2S %=s$!rkuunY3RCIS7lgS%4&kyBB$Z3a$DmjGCq1aX?oYS?MTw zQBw6tZaPVAt1c~#^@t6xYniEb*TmS=aQ<0LAimbjT0)6fypd8Ssen-Ik=DYlFMxbz zVq_S>LTokf2>&HbCaI>I{B{&ZcB4w2N?lk9o}$F6kVYE|A3SDfpiir|e;G9`Bu&;O z$$|Z+< L{C*fF)Tu@gi$YvZ0qA;+;Be^N?j%#-i}LU7~T>eO;0GcLh>zO*kH1CgH^In zZ>s|JXU^&cI&y6!-CY00cqQo@O-gb!ODZe@<$-z=t)&Su$@kgRh}V<^@TcGW%Phs_ zmulg=pR~kVDi6YWPa0cQi&a@yemKoKxsJ%oir8`)s!1GPqYZ#exYPxcKjRT<*bRJT z>NUnuZqQWiB*Ehuy_@psY^10PC4e$w+brp^i~`bnQ_vjF{}kf9mla)CK}juwj1SCq zHH0e%Gb)b{9ulWsWzzmgUKefU>3Tfa=~JaN={O#cZ|PyIq=BXjo114eJ_3_dM4n-Z zBBCY-q_9=JogHNk|KwuI%4b~btQ{8Xj>ZPs;cqM}V1_^{P7R*-PS$JTp|=YYsfXlM zOsRCH$Dp50LgkeA={#6pCFKaDDtb skMjAx zO*5EpfQGUw*`m(RM#5&jKUcX?rK*Jc6`;XWvP7(4_XDmIZT-v?wkNGZ!&(zv`1AGP z*R>>dse Wl-FKB{#jS&FKW>hXSBH`hAbRiZ!rvW>8Frof1?RdwEXo2vp(#K8_ zZmq0`VbA`LW}VDt&Qyi^LsHBgCi#i%y&=ICr&pux1lyQ*h@9IR7=#Um_m;NlL}o zfb}S)V7G^AAiJvA%3^ >craNE=rg;JpNg{O_FiDriAeVeUP~Ud9xf5+ zC05S|wphtKvPjxmhn>5yTTi{4A!;Es!HGe^`s;Zr4SpV^+^>;f13gA^#1WV88;~U7 z3CMUA=vInc>|VFr&xq)$R9lEq6SFx(oGV_ZU}CX*7nFH;S4!fTy=Nsz{NcC}*1>^2 zwEWv4^*DK6J?5lGeIKnlkbH>J_*58)LZWIGB@+kbh@fB#5=s>xcI w8;<({Wf3VYe1 zpI(DNFr~kPzY&k4QfibM-VYdfY7y3glhtkbpudy94-onhHa{Dx%_(2${#YHoUktp0 zCU-M;aF7%@g6Wg=&6U~h{LPhWj5#mEVkHGBS~pQp?Vjt15Y==J!2|?{vbisI0bVYF z==bjLJzJ|#`J$w7ou60~UrVqZ?(u&b^t1|TvpTTY&kS4~VLa9g%H1BP(0obU+?_^C zAm2h}ZRqd6)mANI5{(iQzb$zx=V`$o1B#F2o4kJVhs%pJAj^f#O^ZYaU=Bu3U6kdY zs?=9&s|JDN*J~HxMcIfD?3a4k-CVpfOslN$W&Q7WJpSXokpCov>TB3ARK}@RZpweb zO}s0N`aIX=eul?o6y5bfT !IP!qbq8DY=O@OZ`&B<(FxO8etzcU)m=BjubgyCm `Xt^iMf*}U znzLK_MXe?6T3vzi*Sp$L ekU@EyN%G2HN}P8rnrUlG@Xo55 &~hK}EXyP#Vgui5?r=sjy4cnS2$25V>b8)|0=FoSd`e*r7xh60E@X zPM?(NX1SHO$c#H6USnna(9#*LPvlR(Z16Cb$j {DLq)iip9F8S2Tn4LNPZan;pE=25a z%Yy42?YvIspA~WyHuBylp>a>OEe}e!OwL*vl^LvDV|)^^YTe` ztWUxm<`iLY#I(co=Sh|<1q*khs^n1Ro_myRABYS!i9r;+oOYaxbB;g|nd-r 2}+RQUKq_&}a1-DhD3 zixD-JXaKqmK>sc)ws0bN7OgfW!q))e+qqDJ*SCy5xAid#uGho6RI-q3&%RlRw18oY zQeX%G?fayD2P^2a>$j$y`irgL(4axd{sr`60D_64@E>s2`F`Hc&5=i;i7J^H+OBZz zK?hs;59Va_vaJHOb*(y$zAPuAOLJMmT^wIW9Oqu4{U*Ha%xsqP9mVE3zqkanqs84^ zC^9>h5fKH|AmC dw3t#L+3EE4Xqg=rI#^kx60IZxrYnCO0C}OX$h_4$C)nV zIzEsP9Yr#djfkX!Z)8{WC>^KYWUw0z=HZbrw*IX4g@w2qu -PP2VW60_ z =;nZ}45nS`%YhM311k&fdH?@1-W{^--~ >HPec9yM*MwF_pyp# SP zhU>WMjRmU)2qq@_&=pK2`~*}5XBsFQ^adgdT&e<}!o#L;jUGY&EW2T?pGsdGkJ6`_ z6 01 z{X2#!eSXwdmS1W$;}jkSIX1s^o3E@_=6=}>qf{pBrg+?M1bLoc*{F7 (excQg{XaY~CU>uqJL?5bt$qw8TTz_G|HDI+)^zX3At)W{ zhwVU!zrPOyf?$7j|AV0cfParb9l!oe$nQrbu4fW5oV(dmbog&N?FB#7zKx`s4}HtM zKg?tQzjSQ05DX~K{+LI*S#@#kVfP9P+Cv753Jr$^; zv%5xHlX;@Vg`gBk&NMpRAL&ySb1X%n@ulu)Wdc{s8Uhf+Hy7CSh=WrzCXgo|;w62^ z^7`*3O-*=3rY{vB7Jfl~K1)c63Bj>4a>xFPON9XVV6Bi;wIpSRwYFxo^MXWMDv6Qs zE#TwomX<_B%#jxizy*`h1dlxq{iTF{(bEj&&i4$fiSotl5hjQFftRQPjWVW1_IXA- z4AUKgHy?RmQLcSa1i`g>;?sQsJRF&Y>!eMyvd(c-zFS09gdH`VOIbC~ViAE>cuQ19 zR!7N=@#+_aD3wp!NQInzoz-pEi8A>d=k LrE^v2gvpEPWwC{}U3moAhuH73NX)n(wu^hx$yY@79J z(F|pw$JDlL8ANcuT3WGUdRDPJO^YI(K2i!V$X2KiZ|FA|0ag`)(*+|CJ_&fQGx2Z8 zHG1aD%r+{fjv5!O8ulc{ PdMST&e6G zjJdHhwaL>coL?OCQQzkwQnmO?N<~ql4PzHfzAi7aD2e?_Oq@>gD_|E>Hu#NY5+D-l zB7b5j${#^$i0}&wkSZaS_yjtymB}QS5;ZW_r?H6#LjK6&U}LUaU<$8nKUGmpY43`O z%1siw^%I7J!yORaD~(reLemVGUfEBs_O>$ Z_j|ArzVq zgAJ#TQY{)o$S+&fzdmb5;XE!JF?gs8p=Y>CsBGHJbZCqi(;2NKMij3~t;7G=q@V;c zfsZ4dN7_VlD4aJr%X8?}54)Lnm#Zbuh;P#ft-r6E*dEu6uJJcI`?{;^``o4pVt%(P z0=UEnr?BD&u@&+m{eqn*E()Fn2!E&S%`f!TAUs4C8cQ{I*QQuS4jzJ7zBEr(SWJ9q znymz|2TD!foe(m;CLooVT8r@|u91mX=Np?ZHcGR!hkt@<7A0nJ_RM#umA#PR*?B6y z6b=oCF0cdZ3uRT#T^pinEbw )p2bgdO<^Kg zVOL6LC0Q|;O5qBzy|EZeacrtN1V4=z3CU}qz(yZs6e!ck6uk!LtuxvD2cWSj7;=y( zE)(-Li7oGI0*rG=Oruv6oUkpX`T&_>3+RZDcNMH4)0rvutFN7TKGdPHaZ8Ez`m*t9 zP)J@IHsYBDe#tBe#8L;hjSGPSmZJ_^Kzl?j2H%Ma>m(q3?pkhf8?f0t>d0BB0t4j3 zQ47LxDa*NmyD8AT^rP}NO9GJbGFZ1Ojw4L8s!)52xrD1~MWk@)@FZn)O+u^)dx=)v zJa6y)0d!6#s-jO;Ij9y{gC)ANy@j_x$stl^=2mqOCW%pK3&)NPPwv-xtkKea!mpTZ zM0LTw;XzfEfmZttmbsq#J*ForZI}!?DrJa>jhA?%edeNcajKgKT6ygcU^_6x)XBxA zn(sSE&(irNP$|g$CnmH1#7FNW+IlWzJ0VQM0V2B^kcM3?FrLgrFF&!nu FYdLTPQKC~4wLT>4%Ttj&Q1~@n+{dyp@;_h%4*U*tba??W+gerCF6|x zzHu)HD5e9`7EGMx6aic8QV;MK dBwvcwCT)pr!*p(5DR~BeCf=G)=zzd4R`BX=7 z4F^`e+^NSp1Ft-d8pgu?b|;V6#%K$&B~C05yHpw?iT0Ev3_QSf%;X-?JP);?mb9gh z+rw*&jKKwn;DeFYv-4;mb($UO?|ZG&DvT_4fcUY4r5U*V?cwn2=vRWYmuuEJ(*0qY zrP8I->!N8H?Cp~)bgruJ2HAN30Fd=1ts&+fgItPIVr6c{B$c@5kpoQ7u?_? zmGv~@<0_ihXT=m 4t8dN{En*yYTOvdJp4D4d+E} F`jpg+8a zYkR$&{}2Ip+}+cc!0e_FyG&YIKI-sB*`+KKQ;IgL(fEW}B1={nGj&bL?q33TCwW>X za>*?*Znxp0#95<+J?a=M%QUV #!%C8RFq zi@7(AD8SGWW1SZh9bly8ZokUKt;Tb7>-u`QD0uUF^#`z1qxKbo; DXR-myKnb!EW5V z5+{B?le-lWw6^g(wBh%0<`%yr#Eokww3Vyd_P5|m2f08N0UdAk)G))%_ol56^=|u5 zTle9}3gx1oh{KmPBbl(9jVY4bMZ5quK)XBe9-nKal(LZv#pt?S*CKH8kaPLfJa{{m zX;z9(c6XO4NiyW-DkWmNmsP8VS|6hIb^!UfykhWkOX-9*)g=-HDLn4$Wv1eXW*$1l zQ7IDkRFa}Vl2_|HDe(YM!%|cR?6GBA!TKVdL`~IOR6c!ElkoC&(>oO )8=X>{yfUiy0mksl#=~yek(k%IeuK?HC@-!G{^H224#fux9 zHm>)otGmwve*oh2nP^vvCc}*olo~B0FKNZH(Om%?`Y-ZApXlcbalIEj2>7Ma^%rXb zWEjd$_zyJ8`e*ArmDl_b=1l{&mqJ0uG-$wY%-13qBvMROTZQQ0niQ-*yhv2n>kdDb zV!H1e;;+4=gXP;} yVvAU5cTD0X zRg5Z|1omI_8RdB3bq32TCtz_MV0B#O=~YS{Y#mIyWg1=JLhajQ#EIO#O>=syZ+ikq z?I=Ow*Whi6#0R=;Ed%XPX}~;rs^mSRYm@C*E>jn#uItI|t{Q(A89A{e^zT`)`%lnu z5Y8~FS@){PSV@lSl+T!uOo`Gcg?|7cKiuDl2U01oa2a4-pCQPuXy~_6qMy^NHb*z2 z+A58(_&lk(>4WYTKDuvV8l?4(Ks*o{k6=LuHm=uf1mNxs+#MX(sF&P+Yyr-~pt5bV znB$1famBx3lioGizzh _ }W>z#SasR5zUjxpD9%Q zF_)hx=l5&+`q|qRo#ZF&7r0Uo2wGFua>0wV!nCY9HGznuh=43vOxNIcL0F{8mRA{? zwi1MS7K@J c0m$F zAu-d+)qI^A^0)jaklBi(QKsgV!rOjJf`CXWoLWIm^M_(n)((@gnur Ouso~3Yoy^Gx|O0Wcs-9P?lM@X0T8_C^SD>*+VuWM-r%+oI?X@NT1s2h9aG3 zQu0rpm}OZVMAhxSv}&eWmhe`?>$C!l8J=Gu!C3HEQA?_PI-yJ%o{C3 &6eArX9Lkb3WsJEkxc9}2O62~W9U7zKg;3A>0+2%kwpuXCaLw2gp# z5#L@0dmbAu`g_r+7Z7sxF&%qGzV!5hC~n(x>qA0B7&vTlEiy{frUz>ir}Ek2`DFz$ zOV%#WK4s?Oas9S)q&TYO`G6gJE?aBb*x7Q6Lpx^71dB89lg1}Ub-67(UbLieWLqd| zCsPXSbWx<5WGhkMZGM6#jUf0!35ynWj(jI*$AU{GH0O5X``q$knx(ml=_X>dghBoO z-)}#1b`)UX7LEhK6{`_l@?ln`xk1{{te86BKj%afNsD30rMtyi)Uh1Z1T!UoyvHTL z!JfsI3`Ab-Oervq)8)2ifu`UwQqC&%dsa>7ibiLwbBNysef81aQ`92`F2_|}*k~HR zm?XomT8uW_h5^NaW zI@%2d9LzmY(s5pp5(v&TktFq^tl%f|3PPz)`qU&$wX|}>jm5%>Eg~e$pcDz=Q{rfr zCxU)Olp%yo#_YRit4RwZ1lOSrcI%qepp s`%U&g1-Wb;tk} 7-!$H6-1l5-&^MEpX&3E@7NiWlcfS>eIZV*mSqxYjce3p@pVC7nF zCgSsVA(4)RtUoMcKKsU@2)$UK4m56l3M*2(kwjE25C(S9axxJRsBk~B$uKKZNrhim zd)e7*=m2z Yg^gQ -Y_VDdGkQjMi@(8FPvMYQnsO9wy&VZuWn zXlS+1>2ikWQUQW6f8idg4cVuxhmaYT0JF+eQK?P5>cWd~a~Nfca4@9b(S#v!-#Shd zCoz!VI*FXPRL<1dFa>64^VU%QKi1wlD6-&L7i^$$8Qi6DcbCE4-QC?`fEfmd#@*fB zT?cn}8wPi`!DqR@8}IGzef#G|WK?x@S9bR~RgrbF^7}Fgi!de0sdW!;@AS6}?J_5( z+`PRDW=JvXJ*Y3Rc*gm?(w4I@o6%~?Mj#Mc%XNTljHxhstrr>ptzZoZg8f`A{{XYf zoe?NHtbgS?=I1}Nzy_!Hq5NvWHh`1%=sJ=;ip0e4qn8ToQy3*oaf%3vN$!R#m@QD3 zVYA{yfQSA>P<_dePQ7Mio87amV+t`r@L~#*;@m=yo{Fp)=NlT$ikR6iw=*<-6OtQ0 zdZ7VQ>d?J$?N(KlH8&xO*3yjjAAmN;Kq=)63#1Yp?)z)6{{Z~I?td&lK$Z>uwFzvK z8RE_A3C+a~O?Sq7PJ2Nl$m@ib=?$x`)(+y#K)B&4BJa_|<8*~-doMUPbmHrZdRqr+ ziVye{jQCJFEVRecC#qQWEc7{3v<_!p!Phw!e 2h+fDD9P4o_lPJSW3kt>0qn>1MjDR)ga-oEGI|`vDIz2cUH!|!R-R}t zhFU_4SQJ26jYoM^F+7L$Y0B_^op_<1yUu@rpPd}4CfMUz@o%4^+3%_<{Z~LIjzSn! zJDdQ|u_ZhNFF1CpJm>rZqrWS5_kTz}i#uJSh+|&nyv(AP^8ilvswkpm 3pNwUUamKky7@mvqJFhEbNgM7 z%poS>*rQeQk>o#r{2GVMSv3WaZ()5p74(}LL+dxkO2aVeGNd^^s^QLM^DW~GtZ+L7 z5q^I`KM+MU4?;)WSPI97Q1zT8PJCGKPyz#vNNW3@#yJ2Fl|444$(wXu{3RkrwG|&= ze))5DEHNmwd91Nx;&prE>c@ZF>v}_%oYV;{0xFPwkN^GdSb?~%>G?0-lb0-_QgYw0 z6Zorp1PIE6vG}#Sgg;Hp=+P@-MFGrLnxnW7Si$eds3740NFdz*(M4G5!$=gjpK|JX z@KPH)X{Co~`d&TGc_6bqaDVfDH+q);1^CFClw9fC`O+3IyqfR|J1Unz{wn-X*d+%8 zxf mfpl5@IYSE0hc)&M;g; zNr03@JI!MR8Qs(gNmbvdJs=}gvJd{*U1OOx8uEKtMpVq_T_P!YA(Ob-7ki?~fCx#2 zbz-cc<$1hEZjNp#lO~)qU3(yr1mCl{!uT0WB3nLe9`6Byur3m3Q yx2lG!?*n zt5Zf#lPa-1qb8!X)lQ-t %zdx`=NS?D25r2XMOmzbD!h?wp-Bp?`-* Lg`K~a^i;?`$ff8*gO3|@Ztb6cq`XHZJ#ayfEaw&C+9OieUJ5uaI?ER~ zM|kD|*@=u4*5y@_!qBp$eBud^5z??41>6*v&{+;jLj=aD><#8W6l}>Rn&*&>vVY6N zK-V_-9pFR%05Xx*)=M$yP8gG<6!}-lLmurcCs$)7Z1c!rC@?7WSZZF54JlcWdx(cO z*ZSH1T1*(kK{i~X{Oirr@GCbVoRHR84sE5ahbpO_P&>K2xhAKZh6dOHSxmHE=e1D9 zt~{5#r)c!YwkeHfvw1l8Geuq_1PkSq&7c!xoR}|(X|(3Dw7a62!++JGi!g@ZdtR;~ z(`ZpdTl|?qLT`JRPs2)6dd{8<# >=WlPD0)Y7Z5nrZA$IdVk2aKxf}&A)SKzc9*g}Y=C;*LffEirAy8A`lpJP(A3ajA zozlf$CkQC7mF?^c+<5sO|6H!Z3KGBxVX;P?)JcEv#zL~b51{PddT;`&AWRq8{sBjK zaz19zjjkg?Sa_vn&{`|(?C1a}XLREY&Q>`LEj*&!+~TJ=+KZ#+z~c&i54+4*sM|Bf zeN^W|Ej=5n>Z11sXd|`_XdL@+Yl{r`1$1sf!7x3G@k?+t*{6>ZCBOuI!~67G`bHTk zTTkUtvswDDqV<2K9o6jP Yi3?7`*- G_}hE+D*i@MrgtRn)4r6jFw;8E2WuK`|(Rg+am5^3N)n2Axv2< zd1_LiLkb4ekq0Aj$b|IS&YhsQqVSB%Xff+xNh8r9b>a>KWL4Fr$A0{@jR+z$nYsAk z_@~BWxGYae?NO$5J__jml7XrtL(fwvitpMx{4-jHsu!u6_2QxKSx@;ye>ie*{>Qos zEV80!Fj9JG3U h-UjPrI`H! O(IhCX4dj2g4*MSVJa#B+_f^0_1u>}lo z&(4}=S>z?2$aSFl*8#+-v-CJG$`c_NRS}DW>)SY!@4f@7a+mL)2;NcWsF8xw%JxKt z!U@v<+BL)lQ`PBsy>WmNfwMYiqL6Ibols8NXPiOy7-@D5;BOZqF#Gi=p0p^&HRBg$ zu{Qow`IjpeH^P1Q+LMNxoW#EPv4YV~8Hhmajc&C|9Dn%1*irF@kjD*=wEmr{2lr-x zF1>>T!sj5VT*< x7EHw$WxxLes0)F)fP7Jh3*;HIT0SHA(@`XxnH5uevfr1L8u%fC zmO7f*68k0WF=T8_RrSb^7n*@=Z@rCaX)AWK&I%0EWRbygmemy~J9OB=V{BdSdBfYu ziS>5Rl1Ujy-^7D +Q`=eDGNl`>0NBE=w2De~0Z zJukjM(`6i})5V5HT~HU1XkLlvTWH@Mgyo)Q^dJvi6mMl|RjmcO#5c(ZN}xYtqkqBr zaTd-p(EOrqN;}Zf*}4m+)k85V(`_efva&(<{8eqpZ4o6Vt?#o=L&?=NNnKm5gqo?l z?kc>X#b?RK@1RYY@KqPJm6J_uHYbeIQP92UM=ZwUf0<9wMQA1%56+4Z21M{u39?wi zGe#UgQU_lC70+n _!(s#UoyT1o%>fVt$x?=tmRW5MndC?@rM5OGt*9sGE+r3qDP!bBL#>|(0-3x8 z&{!NsuwMG7qbE& PG##~%?HG2o&7=UCg`0z{J b7=ks|g)Qigk1!>oZ&;yvh7e ziU~2+Y^UQc^z-}rPf^w zL1ws57z-JT=_zx))MIHL;M!nX7|}$gZd34nY@H4%!z*l1qLYF19p-DutNNiECIbV0 zufLq)wO&B@2@O=Mhod+~dbUo1S=X&2&`2{Raw~3Kq{@r%i#Sa@N;n+Y0o5YD#%{xg z{FVb1cJ Tu>eWlq_vLM`o{3$c(P1qg;}ogs^m^)~ zNp$fqw!LCH#=bA=F(4&~iNX6JOz1qZ0;l-` $0&=kVc@o=VEw6K>;{Lj0_8*vvf+$zX~ z4Y&Xh*4|qsUycGQtZp^@?C|>i%iL?==h&9ujj?#cWoPZ$=g#|Re4a(%JFq`C-;&ey z@cY#W%CE4`;RPSXC?PJv)ZI;(0#znds #6zn&$VG%+prU^lphVC>W z#v9V^plImi+V0_u2oB3@UR_WqpKp%Cm89omHnBH4MQdmEwolWC`=bY~V*t5Lq^_&f zBg^;Q>x#ao{cL6}mFZVt<1mqag;ZrQb@JO^b-BcV$t{Ir;Mep7CrRwBR%vb4TLj+| zUR;q~(@Bjn``{#X)GmXd6!55{n@~t-J)!sglf>zJbc{XwI=|Ec^;v23#_`fjIFOoB zCJ*ufgZ8R?
=TqS{v;)l-ilUBOKCwT1^ut4pmt|{HKHh3%T7|?E`aN+u4gE)61JbSu( z6CX!mpbIF@5oz((2boq 1L2aMPB$%5h?$;5DJ4^bf| 3af3a zk>Ti?I3sza-OQclY&~<&^wAY&y6&m{yZ~$y3OUvLX~%*UsO#+VjUgC`MN~4DJ<&L} z(WPEbg@raZr3$# )8{nj;ejZlCI>6SrU?*Qp!ix&i8p&k|qgYi&~ zj6Ic-H?24Tk0q-r>!C^Y*I2uCSIpKDhY~KAJ2s`~Dw$*(8us>s!DF7Qo{KGpq7X;s z#C*UbiF|HN4!2W&JA27))1tY~mNm^qM }R*SO`3kDQK %ue=)eRWwXs`dG~5JEdfEW&Y-~UXbX0N&aPS z=Bkc*yjpIo11eu7cW<#B+JLWx ^^U2fq&rcH1s+@@>VCZUBsl z2_0_AU42qa)b B-b5`@Ga-fGmX*RsR}a{3{jMuQX0HT~hov44QjMoR{d zY`3^`-3hSvgh+G#clqDx3HzOKZ}N{0szw-;ZaQ!c&3Bpl@_Hk1S^6Htf+PRE-sVMk zM!!52#Q`HB!+70^8E)90jXibEMz->S!EQ9X3dvj) N{;2o5cah2 z+m?p&(6~F2FDAHC^7K{IH`^yHYi#^0GIVOTeNb%Q6EGwRqquR~M&~2i>sLp@IxBUh zwPk)P*T>gzx>QE%+cp|zAg)>+Cx549^oAY!Z}Gf6V-#hM>?@jVAwhMt$Y$h9p?PjZ zpyEwfTR@43$x9Hum!TgO{NF-jEs28D^JV3x^Cy^_iytq_bYp3Kyd{Ni`4lnY9>pga zy(h9?H0FdAoT;&ZsXNPpQuen@VAznA a zudxUkH*Ze0V6|=72T(LRZ>9}IslOJ&7*8SmWCQh6R6hmGiJ3-HM(A|VGDI1jz^e%A z5O%}0oE7@b{rF5* OuK6ydL^17PMU@1oV<2hzyvW#^ zm(_00q3^@J@$$ydWa62r;2igoQxGjDk4y93d=MtfzBy-Hw*BLznPbw#G*6LFAxm(S z0+u;paT9NPG&ygCk~KIulIn4pnr*frC}DdeW(j1CMlsV;&s*pjL}~R+;Od2ogx*hj z=BRa^FC(?U^m(~h_wz7x0EJ~I)MmvyPQEx$uTG8+4U=zw&8{x9Z>L<=(YOHDApa*T zmQO(FJe;kb7gC3D5`MR-^^Z^AnsH8WPUNSLf9lA$)@syy2L=zlul7N0J8+!!FU$eI zKHQk1wBY2TW4Caa^xItzCQ|=r2gV#)m`%>ClctdL$oMQ>6?;qb|9cz7U#7Fg#q|$x z;q6+pE7OFyn<^Y17qzwZi3{jjGG#t<^zoy{T#>HQnqun}W@m;GiJBnw+2}DLbhe8T zN@#NsQ1NZ9RY#g|<4YAUp)OoCA*Wpqm80gOkek2>PY1$9Bca#*CK*i;Q%_d=cH+&J zKx+SzQ%rWDZ8;Z~FU%l3*q!}@GyUy1;$@>uSrW^H#b*IcdLqi;tPEe*( XgyhuER- dHJx0AjrOe@JxMzn%@y* zzz0zqujU-fru@2)M `Pb=~Cei6B%BFhlZ5~Fhm4SBpA =fEXwHAUz+E+vh3fA=Rkw?FU6r z7<8=FjY_4wWO0TVYW$pe<|1T$^u-$LkuQH<9E&j~Hq_cWTN!z_JPe|!2+3UFFU04^ z@)Pm9)N@BvF4&?2eJC%;Yv*h#E0kJH%HrZhr05;F=<$I3W78lg3>}X*l99KPnaa cVd*#uiiO1a*Xe(8h#rPymPkh(3ba%D^`Ye8e3tXUynr2rx{n&RuQM>&i*eawj z+h=q~n*kC_cR>2<0@I{!r$dNvlSy=NVB8foHB&ZG(q4N dvk*#y7kRI*eje#&8p z`NzpVI76xmBFyD_wyE=sq(DsM1SBrze*hw-J>cyqDmRkS-Cq*NX9397>&o~ zDVFNsYIPUsFn>Qh6=o$S(x@9saO8V`-o_6iv_uH({-E2+BUHYI B+^Z zP(unlRskf5k_9b%h){gpOo8@B9mEapQ!~cZ$i>>jzm+F@+%JQA?h><`xrjs3y^F#H zO&vKGmturgna@6jgV?ftago^EzfWkA^yoSe^HXDMKT(9}ZmMoxFKPmNW*;zs$-L4a zZKUXG{x`Dcau-G%VOdQFN*@)eD79rps?&as22PWZJc=!&9mY6(IaIT%UllnD-A*u0 zVEE|2jq8T%_CDYv?~RlF)^`ERhqxg{9Q|6mly!A=b45_?_(n;Riz$iJdpV8_v-7^5 z -av2)kK{^n9%UN;p?s(68d8FiHiC{hfoFwU1+45jl>A>ql(0kfqfaHJqoqVf% z r6o zhg0(3auyPQC- $R}ly&?qq39pK_KeQ2x8L#-@d+Zo9`HSD;*O$u~U%gsl zv(zsOf)CqP4V`ei-jfjHS*Z5;?IQjf?JC;JA7jA7kQ+8(rJ+O|-={8BB8NiuY6hYV zL$qw;*D) vw; Bn#3(OhD-p-9>FnrSE;5#jf7(W6*v)%ad}Lq!4AwX_oSLf`j}i8D-ysJ)v2LK_^D zy2UU@KcK5Zz(?|L@%$n(CbChmySBNLg(6x&8bJ+-Y)H2s(q}N%bH=&-5pnG&A)TN> zRvTM1Dv67$I`xBO&hX1|rM@u%TA;p}ZVcJ{=8y*{3 #zVh%z|} zFMrO0fYt;I)r`jc8iyIG|I7co)+0{i(H>=I(=&H}dzHH1{5+=klL2TB92_gEN1L3h zV&i4-S-9GGrk!Lc3gU>t?%7n&W9-UKszdrykN`s!$e8f iurc!a?yEh+so d0DZ(jfYgbiq6%nCYf-5fj8|ut6TIxd>uMG#H3t7clyESId#5X~QpBoFA(~ zx8yOi<$G@>Fpherd=Wc6o%J9aO>{R9tE^PkQc38Zd3xXGE~jK5@94oTYOz_4 Mb6bcbk0&nJ>;B%a$=!K^QMFDCWlqS`rF+fV)LywS zqP=RJSV3WCrWFGt{};m^O7y?Kx~6+jb4?<9;(BrsDT;AZe3v2NSk5O^N;A^%Q9z@S z!Jj4)@j-`ORf+&89+l3GNVJZSKuK?pv(KKO(=|P&z*HEsd{2iDjy@5nTFFW1=EF?< zUiVipa>D1Y(B5DFKWFeJapg^Tdh?NZ5^ Ta6~wX*fvs6N`CM z<4$M+6CERlzm(iQgs^{Omy_lY=jyojq4ia*($zxO#Z}T%O*L9s7AiZh;wsWVHDcyH z=S+WDW74k+H5N__@A~^89#mCp5_)i)?1+xtSqaZLV}hZWPuGiKW=FZOo>D-m6Rw - -u-$wtz;O5Tf0fOZ;Nnl58mCPf9xckO=vV9g2p%YvO~$}*?_wCttN5h zL&IOVSzglzZh|-Oc#(w*7rYudY&+LoV995MTPlw**s&sW_76Z`^zaXmR2Q`!6M6II z*XyJ|uQ%xeT=8YYj!c8Rt1K>)uTgX5b&40E(nA`e`u6p7md!i+-Di2ro?X+7$#uP9 z;Z;G=n(Ax#H%-7@(i{4IG@XstfJx(KmrcZfZvR$$o;a7qTAHryjT{%ADKn%IN}9GC zhI1o7L~cfc;|rv~2HYtVo4RcV+V=i7nX}vZ=J0{w-JLU>;Sk9qC-S8CR4_arFc_ zk_EmUR73t@+x+>q>DRN*9Kx|%P(H;5#2^Ygf*FzUJbf|IzR2VLYx|)y)q #4 z=@c}$_fVxMo|{&ofpU^i% ytfq*tH-$`tH|2Off%raK#U|M&5f?|;a~dk`=2Ep0s5#5n zZy#5euiii?Q;yF8d5camItFR4$J#LzQ?zSAr;e5c?}$Ob_M8k^<=C~h)836R)=D$i z&XZaVmtYzN$Y&y#mjrg+h_a>2vAaY1Z@qKu75ufAO^(H2d-%%a?peq#uPomG?aMxV zJ5cl!GG1cXFQWz5Ay?M8AohKV*@kWnsIq2BaJdj*y~^O2=pfg{O7-JbFu@ Awq&O)ir_5n9)m4{09p2 zcdA`-#d2jj)Rrfxr+z(qG*Xt8gg32(H;v0s7&4EbPDk5K?Zq^gPXhfSwA!$=eK@&M zu`8ZL(M7#Mqr@7Y&{{J*J@+Tx+M9Vul)eNV?@N(-085N$SL$wLV1Y1xONq8DBqg<9 z{3WT6JehKmMBC1pNMs6v%u*D5hInsp$xh4mr5d__edYS6%BO^c1jGMvx)D u>7d+;!z}=&uf9rn1`1qm+5q^8fQxw9JkzN^vTLx!%X|30HQKyw{#R{1{o& zMZ}a;vvlD3#u%YbfV&BY4D)sU6O!s5o_o%nZE8ut)@zVzX!eIE9OvZD@m2Q$txf!L z^@K!aYeQdJM_8gT;P(_?S^z~u am&3-uYn&BX{BJY2Y?t>$ESSQQt$c-;rC zY-;M)Xa)crjLze?su$#uCk}8sVl#^+l}^l$=URwgH!_u_LA(ib7r}B#bgXtBj2k!) z3`<&!4e~rVzPQu2l2J>=$8Icc$`!&jrq^p$eSuPgQPrIV*Mlh~&G+(X{RD)mbfT!! zC6K%96e(if$&e|lRIkQ9jCC(Dk3Kie1gy;}W@_9m*tMe|t283iRJVeA4iwAV$Q*}! z%f}&t(#ZwX#6bT2Y7ikqyfjMd5f&Bh966T>IHICdGhrf#MZ+S@`|EDPSX&Z7$TM?D zd^7agMmXm-_Xi`#o-wFozMY!N%$H809gYEspf)+bUX%9{21BB-Hb0xzHuQmB5WK*V zU=42(k|tDYJ4gnn7;*`_ee#I%_@`#phHa`|hu~iNH&=}pcAlX0CRL>Of%P}V#PIG@ z`6*7)hRX&m>tpAd@hYULdoK3mh0|+-&k`DCiF!y^QW8enDj(1!|3^b{Om!z%cx
0WmNvC@wkQ&EtzEQUDWq93%-M }7J0pq4slk?D#ycjQ$;Aq2P7DZpkI1712e z|21%jx@q(W24e7jB&oqdZ|XvvY~5~OUD6uq6;SAu0s?>xD|K|pN}m` jrxKsM_yvu+|VkIY-1Cu z++d(TOe?n8&m?GSla@)RP(m($+I3%xLsO&^P$fauH?yDu07fWXZ%zM*zc^gn-o7Y1 z4N{g^>W-V@r7#B=(~x#^G=Mx{*>A0{-f3RloyMMYTcG@x?YPF5qA-$(M=P+=^uZ1q zPbw^EN4HsTw+?TffIJY965s{X-x% ca_hdEMo*`eAF*)$^ zk`owpCFnl2p0+v^!CI78xo4Eh!9ooGJS58uuM1H^Ongk26@P|)u<+^)N(BznvJI$r zo{w%#R^cfkAQqY*Lth05m=fN8+QXbw)DC~q;%5X|;u-L>Ch{k2CXI>URXmlrL=`s$ zwZ>n(2!>BM8b^^q>#_IF^{=V0M>9?BvNU?inH$InqtxfKJ@v#x1=XM1Y@zEoyg(|} zKPcqc@9Bi~Y92Q765wu+KXTTHh0t^&bFig?A-c}gB gvO426ujn`sP-J7qpt^y24RvWfqO~1nNL~Z^Y8>FUN+?Vp*1mJrP(AC zD&;AyZd}_zvV0nO*0?GeFFAJhH?BwtPHEy$UVGW#NTo!cvozq5vG}(n*ab9o @M^n5Hfj32$+Oz2#5U8m=@Au_@S(qr;kyaBP42A&B#