Memory profiling in hooks

Public

Note: this involves hacking core so do it in dev to learn more about what's taking so much memory to deliver the page. You can learn a lot about drupal's memory and general loading structure by modifying module_invoke_all and drupal_alter. These both live in includes/module.inc. Drop in the following after enabling devel, add this function so that it can convert bytes to something readable.

Get raw version
php
  1. function formatBytes($bytes, $precision = 2) {
  2. // ignore small values, anything under 50KB
  3. if ($bytes > 51200) {
  4. $units = array('B', 'KB', 'MB', 'GB', 'TB');
  5. $bytes = max($bytes, 0);
  6. $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
  7. $pow = min($pow, count($units) - 1);
  8. // Uncomment one of the following alternatives
  9. $bytes /= pow(1024, $pow);
  10.  
  11. return round($bytes, $precision) . ' ' . $units[$pow];
  12. }
  13. return 'small';
  14. }

use this in place of module_invoke_all

Get raw version
php
  1. function module_invoke_all($hook) {
  2. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  3. dpm("$hook");
  4. }
  5. $rt_pre = memory_get_usage();
  6. $args = func_get_args();
  7. // Remove $hook from the arguments.
  8. unset($args[0]);
  9. $return = array();
  10. foreach (module_implements($hook) as $module) {
  11. $function = $module . '_' . $hook;
  12. if (function_exists($function)) {
  13. $hook_pre = memory_get_usage();
  14. $result = call_user_func_array($function, $args);
  15. $hook_post = memory_get_usage();
  16. $hook_mem = formatBytes($hook_post - $hook_pre);
  17. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  18. dpm("---- $function RAM $hook_mem");
  19. }
  20. if (isset($result) && is_array($result)) {
  21. $return = array_merge_recursive($return, $result);
  22. }
  23. elseif (isset($result)) {
  24. $return[] = $result;
  25. }
  26. }
  27. }
  28. $rt_post = memory_get_usage();
  29. $rt = formatBytes($rt_post - $rt_pre);
  30. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  31. dpm("total RAM $rt");
  32. dpm("**************");
  33. }
  34. return $return;
  35. }

drop this in for drupal_alter

Get raw version
php
  1. function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) {
  2. // Use the advanced drupal_static() pattern, since this is called very often.
  3. static $drupal_static_fast;
  4. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  5. dpm("ALTER");
  6. dpm($type);
  7. }
  8. $rt_pre = memory_get_usage();
  9. if (!isset($drupal_static_fast)) {
  10. $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__);
  11. }
  12. $functions = &$drupal_static_fast['functions'];
  13.  
  14. // Most of the time, $type is passed as a string, so for performance,
  15. // normalize it to that. When passed as an array, usually the first item in
  16. // the array is a generic type, and additional items in the array are more
  17. // specific variants of it, as in the case of array('form', 'form_FORM_ID').
  18. if (is_array($type)) {
  19. $cid = implode(',', $type);
  20. $extra_types = $type;
  21. $type = array_shift($extra_types);
  22. // Allow if statements in this function to use the faster isset() rather
  23. // than !empty() both when $type is passed as a string, or as an array with
  24. // one item.
  25. if (empty($extra_types)) {
  26. unset($extra_types);
  27. }
  28. }
  29. else {
  30. $cid = $type;
  31. }
  32.  
  33. // Some alter hooks are invoked many times per page request, so statically
  34. // cache the list of functions to call, and on subsequent calls, iterate
  35. // through them quickly.
  36. if (!isset($functions[$cid])) {
  37. $functions[$cid] = array();
  38. $hook = $type . '_alter';
  39. $modules = module_implements($hook);
  40. if (!isset($extra_types)) {
  41. // For the more common case of a single hook, we do not need to call
  42. // function_exists(), since module_implements() returns only modules with
  43. // implementations.
  44. foreach ($modules as $module) {
  45. $functions[$cid][] = $module . '_' . $hook;
  46. }
  47. }
  48. else {
  49. // For multiple hooks, we need $modules to contain every module that
  50. // implements at least one of them.
  51. $extra_modules = array();
  52. foreach ($extra_types as $extra_type) {
  53. $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter'));
  54. }
  55. // If any modules implement one of the extra hooks that do not implement
  56. // the primary hook, we need to add them to the $modules array in their
  57. // appropriate order. module_implements() can only return ordered
  58. // implementations of a single hook. To get the ordered implementations
  59. // of multiple hooks, we mimic the module_implements() logic of first
  60. // ordering by module_list(), and then calling
  61. // drupal_alter('module_implements').
  62. if (array_diff($extra_modules, $modules)) {
  63. // Merge the arrays and order by module_list().
  64. $modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
  65. // Since module_implements() already took care of loading the necessary
  66. // include files, we can safely pass FALSE for the array values.
  67. $implementations = array_fill_keys($modules, FALSE);
  68. // Let modules adjust the order solely based on the primary hook. This
  69. // ensures the same module order regardless of whether this if block
  70. // runs. Calling drupal_alter() recursively in this way does not result
  71. // in an infinite loop, because this call is for a single $type, so we
  72. // won't end up in this code block again.
  73. drupal_alter('module_implements', $implementations, $hook);
  74. $modules = array_keys($implementations);
  75. }
  76. foreach ($modules as $module) {
  77. // Since $modules is a merged array, for any given module, we do not
  78. // know whether it has any particular implementation, so we need a
  79. // function_exists().
  80. $function = $module . '_' . $hook;
  81. if (function_exists($function)) {
  82. $functions[$cid][] = $function;
  83. }
  84. foreach ($extra_types as $extra_type) {
  85. $function = $module . '_' . $extra_type . '_alter';
  86. if (function_exists($function)) {
  87. $functions[$cid][] = $function;
  88. }
  89. }
  90. }
  91. }
  92. // Allow the theme to alter variables after the theme system has been
  93. // initialized.
  94. global $theme, $base_theme_info;
  95. if (isset($theme)) {
  96. $theme_keys = array();
  97. foreach ($base_theme_info as $base) {
  98. $theme_keys[] = $base->name;
  99. }
  100. $theme_keys[] = $theme;
  101. foreach ($theme_keys as $theme_key) {
  102. $function = $theme_key . '_' . $hook;
  103. if (function_exists($function)) {
  104. $functions[$cid][] = $function;
  105. }
  106. if (isset($extra_types)) {
  107. foreach ($extra_types as $extra_type) {
  108. $function = $theme_key . '_' . $extra_type . '_alter';
  109. if (function_exists($function)) {
  110. $functions[$cid][] = $function;
  111. }
  112. }
  113. }
  114. }
  115. }
  116. }
  117.  
  118. foreach ($functions[$cid] as $function) {
  119. $hook_pre = memory_get_usage();
  120. $function($data, $context1, $context2, $context3);
  121. $hook_post = memory_get_usage();
  122. $hook_mem = formatBytes($hook_post - $hook_pre);
  123. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  124. dpm("---- $function RAM $hook_mem");
  125. }
  126. }
  127. $rt_post = memory_get_usage();
  128. $rt = formatBytes($rt_post - $rt_pre);
  129. if ($GLOBALS['user']->uid == 1 && function_exists('dpm')) {
  130. dpm("total RAM $rt");
  131. dpm("**************");
  132. }
  133. }