file_scan_directory () prend environ 10 secondes pour s'exécuter

10

En utilisant xhprof, j'ai remarqué que l' file_scan_directory()exécution de la première page prend plus de 10 secondes. Pourquoi cela prendrait-il autant de temps?

Voici la sortie de xhprofile:

capture d'écran

hknik
la source
Vous ne pouvez pas "file_scan_directory" la "page d'accueil", car la page d'accueil est une entrée dans une table de base de données, pas un chemin d'accès au système de fichiers.
Letharion
@Letharion Je pense que vous avez mal compris ma question. Je veux dire le temps que prend cette fonction lors du chargement de la première page. J'ai édité la question.
hknik
La première page a-t-elle vraiment quelque chose à voir avec la fonction qui prend un certain temps? Quel répertoire numérisez-vous réellement? Que contient le répertoire?
Letharion
Ah! Je pensais que vous appeliez la fonction vous-même et je me demandais pourquoi vous n'aviez pas fourni plus de détails. La réponse de Berdir semble très raisonnable. :)
Letharion

Réponses:

14

Il semble que vous soyez affecté par un problème connu dans Drupal 7 .

Très probablement, vous frappez Évitez de ré-analyser le répertoire des modules lorsque plusieurs modules sont manquants . Cela se produit si certains modules manquent dans votre installation. Essayez de vérifier votre table système:

SELECT name, filename FROM system WHERE type = 'module' AND status = 1 ORDER BY filename

Et nettoyez tous les modules qui sont toujours activés mais manquants dans le système de fichiers.

Dans l'ensemble, Drupal 7 est bien plus convivial et évolutif que Drupal 6, à part quelques régressions malheureuses comme celle-ci.

En regardant ces fonctions, il semble qu'il manque un module ou peut-être un seul fichier d'un module. Jetez un œil à drupal_get_filename () , il appelle la drupal_system_listing (), qui appelle cette fonction s'il ne trouve pas le fichier demandé. Ajoutez un dpm (func_get_args ()) juste avant d'appeler drupal_system_listing (), qui devrait vous dire quel fichier il ne trouve pas.

Berdir
la source
Non. Malheureusement (!) Aucun module ne manque dans le système de fichiers
hknik
Ensuite, vous avez besoin de savoir d'où vient l'appel, peut-être qu'un module personnalisé ou contrib fait quelque chose de mal. Cliquez sur les fonctions parentes de file_scan_directory () et mettez à jour la publication initiale avec la liste des fonctions parentes.
Berdir
En regardant ces fonctions, il semble qu'il manque un module ou peut-être un seul fichier d'un module. Jetez un œil à drupal_get_filename: api.drupal.org/api/drupal/includes!bootstrap.inc/function/… . Il appelle la fonction s'il ne trouve pas le fichier demandé. Ajoutez un dpm (func_get_args ()) juste avant d'appeler drupal_system_listing (), qui devrait vous dire quelle fonction il ne trouve pas.
Berdir
@Berdir Votre dernier commentaire devrait figurer dans votre réponse, car il est pertinent.
kiamlaluno
Les liens vers «problème connu dans Drupal 7» et «éviter de réanalyser le répertoire du module» sont rompus. Les deux sont d'anciennes réponses de stackexchange. Quelqu'un at-il d'autres références?
rfay
4

Il y a plusieurs raisons pour lesquelles ce problème peut survenir, et à ma grande consternation, je me trouve maintenant un peu informé de ces raisons. Frustrant, si vous venez de remarquer ce problème après la mise à niveau de Drupal core vers 7.33+, cela pourrait être une faute de frappe dans n'importe quel module, même si vous n'avez pas mis à niveau ce module.

Modules supprimés de la base de code

Vous voudrez peut-être d'abord vérifier le bogue connu mentionné par @Berdir, surtout si vous avez récemment supprimé des modules "inutilisés" de la base de code. Pour savoir si certains modules sont activés mais ont été supprimés du système de fichiers, vous pouvez exécuter un script tel que celui mentionné ici - ou utiliser le mien, écrit pour une installation multisite sur un système avec drush, à exécuter à partir du répertoire de base Drupal:

find sites -maxdepth 1 -iname '*.*' -type d | sed -rne 's:sites/(.+):echo \1; drush @\1 sqlq "select filename from system where status = 1" | grep "/" | sed -rne "s_(.+)_test -f \\1 || echo \\1_p" | bash:p' | bash

ou ce qui suit:

while read -r file; do [ -f "$file" ] || echo "$file is missing."; done < <(drush sqlq "SELECT filename FROM system WHERE status = 1")

Si vous trouvez un module qui a été supprimé de la base de code, suivez les instructions des problèmes mentionnés par @Berdir.

Erreurs de codage

Si ce n'est pas le cas, votre situation est probablement due à une erreur de codage, comme un fichier qui a été supprimé mais qui est toujours ajouté par un appel drupal_add_js (du commentaire 19 dans le numéro # 1082892) ou une faute de frappe malheureuse dans un module ou un thème , par exemple imagecache_actions(voir https://drupal.org/node/2381357 ).

Dans tous les cas, pour comprendre exactement pourquoi cela se produit, vous devez savoir exactement quel fichier Drupal ne peut pas trouver. Ainsi, selon le commentaire de Berdir, vous pouvez temporairement pirater drupal_get_filenameen bootstrap.incajoutant un journal ou un appel de message juste avant l'appel à drupal_system_listing(). Si vous avez installé le module Devel, dpmcela fonctionnera; sinon, vous pouvez utiliser drupal_set_messageou syslog. Exemples:

dpm(func_get_args());
drupal_set_message(implode(', ', func_get_args()));
syslog(LOG_WARNING, implode(', ', func_get_args()));

Une fois que vous savez ce que Drupal recherche, c'est un bon pari que vous serez en mesure de savoir où aller à partir de là. Mon problème a été provoqué par un appel pour inclure un fichier du module inexistant imagcache_actions(notez la faute de frappe). Donc, j'ai cherché imagecache_actionsdans ma base de code (par exemple grep -r imagcache_actions .), et j'ai trouvé que la version 1.4 de imagecache_canvasactions.modulemodule_load_include utilise en dehors de tout appel de fonction, dans la portée du fichier, avec une faute de frappe. Encore une fois, cette erreur n'a été exposée qu'après la mise à jour vers Drupal 7.33+. J'ai constaté qu'un problème avait déjà été créé pour imagecache_actions, ai appliqué le correctif et était de retour dans les affaires.

David Hunt
la source
2

J'ai eu un problème très similaire: file_scan_directory()tuer le site. Il s'avère qu'un node_modulesdossier huuge intégré à mon thème personnalisé gulpétait analysé à chaque vidage du cache. Le déplacement de ces fichiers hors du dossier de thème (et la mise à jour de certains chemins dans mon gulpfile) a semblé le réparer pour moi. Alternativement: je pense que vous pouvez pirater file.inc:

'nomask' => '/(\.\.?|CVS|node_modules)$/', // https://www.drupal.org/node/2329453#comment-9360519

williamsowen
la source
0

La file_scan_directory()est une fonction récursive qui regroupe tous les fichiers correspondant à un répertoire donné. Ce sont des utilisations is_dir()et des opendir()appels PHP qui peuvent être plus coûteux en termes d'appels système d'E / S. Un simple bootstrap Drupal (par exemple time drush ev "") peut appeler file_scan_directoryplusieurs milliers de fois (selon la complexité de votre hiérarchie de dossiers Drupal, par exemple le nombre de modules et ses dossiers).

Dans mon cas , j'avais ~ 1500 appels à file_scan_directory(24 secondes comprenant un total de 2 appels à partir drupal_system_listingdans common.inc, les autres appels ont été divisés par des appels récursifs à file_scan_directoryelle-même.

Afin d'améliorer les performances des appels d'E / S, vous devez implémenter la mise en cache des fichiers. Ceci peut être réalisé en installant et en activant OPCache ( opcache.enable=1) et en ajustant ses paramètres (voir: Comment utiliser PHP OPCache? ). L'utilisation de la mise en cache basée sur la mémoire telle que memcached / redis est également conseillée.

Lorsque vous utilisez l'interface de ligne de commande (telle que drush), vous devez également l'activer opcache.enable_cli=1.

Après la modification, vous pouvez vérifier les appels système les plus consommateurs à l'aide de certains débogueurs disponibles.

Par exemple

  • Sur Linux en utilisant strace(appuyez sur Ctrl- Cpour terminer):

    sudo strace -c -fp $(pgrep -n php)
  • Sous Unix utilisant dtrace(en utilisant les sondes statiques PHP de DTrace ), par exemple

    sudo dtrace -n 'inline string NAME = "php"; syscall:::entry /(NAME == strstr(NAME, execname)) || (execname == strstr(execname, NAME))/ { @num[probefunc] = count(); }'

Vous pouvez également envisager d'optimiser drupal_system_listing()ou file_scan_directory()d'implémenter un cache statique, par exemple

--- a/includes/file.inc
+++ b/includes/file.inc
@@ -2104,6 +2104,8 @@ function file_download_access($uri) {
  *   'filename', and 'name' members corresponding to the matching files.
  */
 function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
+  static $dirs = array();
+
   // Merge in defaults.
   $options += array(
     'nomask' => '/(\.\.?|CVS)$/',
@@ -2120,7 +2122,12 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
       if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
         $uri = "$dir/$filename";
         $uri = file_stream_wrapper_uri_normalize($uri);
-        if (is_dir($uri) && $options['recurse']) {
+
+        if (empty($dirs[$uri])) {
+          $dirs[$uri] = is_dir($uri);
+        }
+
+        if ($dirs[$uri] && $options['recurse']) {
           // Give priority to files in this folder by merging them in after any subdirectory files.
           $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);

Ou pour la mise en cache des file_scan_directoryappels de drupal_system_listing(), vérifiez le correctif suivant disponible sur: file_scan_directory doit être mis en cache .

Kenorb
la source