You are here

performance.install in Performance Logging and Monitoring 6.2

Install and update for Performance Logging

Copyright Khalid Baheyeldin 2008 of http://2bits.com

File

performance.install
View source
<?php

/**
 * @file
 * Install and update for Performance Logging
 *
 * Copyright Khalid Baheyeldin 2008 of http://2bits.com
 */

// Minimum APC shm memory size to require
define('PERFORMANCE_MIN_MEMORY', 48);

/**
 * Implementation of hook_schema().
 */
function performance_schema() {
  $schema = array();
  $schema['cache_performance'] = array(
    'description' => 'Table that holds summary performance data.',
    'fields' => array(
      'cid' => array(
        'description' => 'Primary Key: Unique cache ID.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      ),
      'data' => array(
        'description' => 'A collection of data to cache.',
        'type' => 'blob',
        'not null' => FALSE,
        'size' => 'big',
      ),
      'expire' => array(
        'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'created' => array(
        'description' => 'A Unix timestamp indicating when the cache entry was created.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'headers' => array(
        'description' => 'Any custom HTTP headers to be added to cached data.',
        'type' => 'text',
        'not null' => FALSE,
      ),
      'serialized' => array(
        'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
        'type' => 'int',
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'indexes' => array(
      'expire' => array(
        'expire',
      ),
    ),
    'primary key' => array(
      'cid',
    ),
  );
  $schema['performance_detail'] = array(
    'fields' => array(
      'pid' => array(
        'description' => 'Primary Key: Unique path ID.',
        'type' => 'serial',
        'not null' => TRUE,
      ),
      'timestamp' => array(
        'description' => 'A Unix timestamp indicating when the entry was created.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'bytes' => array(
        'description' => 'Memory consumed in bytes.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'ms' => array(
        'description' => 'Time consumed in milliseconds.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'query_count' => array(
        'description' => 'Number of queries executed.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'query_timer' => array(
        'description' => 'Time taken to execute queries.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'anon' => array(
        'description' => 'Whether the page was accessed by an anonymous user.',
        'type' => 'int',
        'not null' => FALSE,
        'default' => 1,
      ),
      'path' => array(
        'description' => 'The path the data belongs to.',
        'type' => 'varchar',
        'length' => '255',
        'not null' => FALSE,
      ),
      'language' => array(
        'description' => 'The {languages}.language used when calling this path.',
        'type' => 'varchar',
        'length' => 12,
        'not null' => TRUE,
        'default' => '',
      ),
      'data' => array(
        'description' => 'Additional (debug) info for future expansion.',
        'type' => 'blob',
        'not null' => FALSE,
        'size' => 'big',
      ),
    ),
    'primary key' => array(
      'pid',
    ),
    'indexes' => array(
      'timestamp' => array(
        'timestamp',
      ),
    ),
  );
  return $schema;
}

/**
 * Implementation of hook_install().
 */
function performance_install() {
  drupal_install_schema('performance');

  // Set the weight so this module runs last
  db_query('UPDATE {system} SET weight = 3000 WHERE name = "performance"');
}

/**
 * Implementation of hook_uninstall().
 */
function performance_uninstall() {
  drupal_uninstall_schema('performance');
  db_query('DELETE FROM {variable} WHERE name LIKE "performance\\_%"');
}

/**
 * Implementation of hook_requirements().
 */
function performance_requirements($phase) {
  $requirements = array();
  if ($phase != 'runtime') {
    return $requirements;
  }
  if (variable_get('performance_detail', 0)) {
    $requirements['performance_detail'] = array(
      'title' => t('Performance logging details'),
      'value' => 'Enabled',
      'severity' => REQUIREMENT_WARNING,
      'description' => t('Performance detailed logging is !link. This can cause severe issues on live sites.', array(
        '!link' => l(t('enabled'), PERFORMANCE_SETTINGS),
      )),
    );
  }
  if (variable_get(PERFORMANCE_QUERY_VAR, 0)) {
    if (variable_get('performance_detail', 0) || variable_get('performance_summary', 0)) {
      $requirements['performance_query'] = array(
        'title' => t('Performance logging query'),
        'value' => 'Enabled',
        'severity' => REQUIREMENT_WARNING,
        'description' => t('Query timing and count logging is !link. This can cause memory size per page to be larger than normal.', array(
          '!link' => l(t('enabled'), PERFORMANCE_SETTINGS),
        )),
      );
    }
  }
  $default = './includes/cache.inc';
  $cache = variable_get(PERFORMANCE_CACHE, $default);
  if ($cache == $default) {
    $requirements['performance_cache'] = array(
      'title' => t('Performance logging summary'),
      'value' => 'Disabled',
      'severity' => REQUIREMENT_WARNING,
      'description' => t('Performance logging on live web sites works best if an alternative caching mechanism, like APC or Memcache, is enabled.'),
    );
  }
  $shm_size = ini_get('apc.shm_size');
  if (function_exists('apc_fetch') && $shm_size < PERFORMANCE_MIN_MEMORY) {
    $requirements['performance_apc_mem'] = array(
      'title' => t('Performance logging APC memory size'),
      'value' => $shm_size,
      'severity' => REQUIREMENT_WARNING,
      'description' => t('APC has been configured for !size, which is less than the recommended !min_memory MB of memory. If you encounter errors when viewing the summary report, then try to increase that limit for APC.', array(
        '!size' => 1 * $shm_size,
        '!min_memory' => PERFORMANCE_MIN_MEMORY,
      )),
    );
  }
  return $requirements;
}

/**
 * Remove title field from performance tables.
 */
function performance_update_1() {
  $ret = array();
  db_drop_field($ret, 'performance_detail', 'title');
  db_drop_field($ret, 'performance_summary', 'title');
  return $ret;
}

/**
 * Add data field to performance_detail table.
 */
function performance_update_2() {
  $ret = array();
  db_add_field($ret, 'performance_detail', 'data', array(
    'type' => 'blob',
    'not null' => FALSE,
    'size' => 'big',
  ));
  return $ret;
}

/**
 * Harmonize notations for milliseconds to "ms".
 */
function performance_update_6001() {
  $int_field = array(
    'type' => 'int',
    'not null' => TRUE,
    'default' => 0,
    'disp-width' => '11',
  );
  $ret = array();
  db_change_field($ret, 'performance_summary', 'millisecs_max', 'ms_max', $int_field);
  db_change_field($ret, 'performance_summary', 'millisecs_avg', 'ms_avg', $int_field);
  db_change_field($ret, 'performance_detail', 'millisecs', 'ms', $int_field);

  // We don't have a cache update method, so it's better to clear it
  if (function_exists('apc_fetch')) {
    apc_clear_cache('user');
  }
  return $ret;
}

/**
 * Move summary data from old performance_summary table to new cache bin.
 * Note that we now cache per language and separate anonymous users from logged
 * in users! During this migration, we do BASIC separation between anonymous and
 * logged in users based on admin paths. Back up your data if you want to keep
 * the original logs!
 * DRUSH NOTE: make sure to set a proper $base_url using the --uri parameter or
 * by means of a drushrc.php config file! Alternatively you can setup a
 * $conf['performance_key'] = 'my_unique_key'; in settings.php when using
 * multiple domains for one site.
 */
function performance_update_6200() {
  global $language;

  // Abort when improperly invoked when using drush. See DRUSH NOTE above.
  if (performance_is_cli() && variable_get('performance_key', FALSE) === FALSE && $GLOBALS['base_url'] == 'http://default') {

    // Force PHP error to make this update fail. Found no other way. Better
    // suggestion anyone? Tried the D7 approach by throwing an exception, but
    // that spews out a drupal HTML rendered error page (gasp). Update was
    // always marked as 'completed' and was not re-executed when re-running
    // drush updb.
    drush_set_error(dt("Please run drush with the --uri parameter or setup a drushrc.php file to successfully migrate the summary data! The site's domain name will be used in the cache key! Use \$conf['performance_key'] = 'my_unique_key'; in settings.php when using multiple domains on one site."));

    // Assure the user that the undefined function error is intended and
    // harmless ;-)
    drush_set_error(dt('The following undefined function error is deliberate to allow rerunning of drush updatedb. Kindly ignore it.') . "\n\n");
    performance_fake_error();

    // Ideally the first drush_set_error() message should be inside 'query' but
    // we never get here due to the line above.
    return array(
      array(
        'success' => FALSE,
        'query' => '',
      ),
    );
  }

  // Create new cache bin, see http://drupal.org/node/150220.
  $table = array(
    'description' => 'Table that holds summary performance data.',
    'fields' => array(
      'cid' => array(
        'description' => 'Primary Key: Unique cache ID.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      ),
      'data' => array(
        'description' => 'A collection of data to cache.',
        'type' => 'blob',
        'not null' => FALSE,
        'size' => 'big',
      ),
      'expire' => array(
        'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'created' => array(
        'description' => 'A Unix timestamp indicating when the cache entry was created.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'headers' => array(
        'description' => 'Any custom HTTP headers to be added to cached data.',
        'type' => 'text',
        'not null' => FALSE,
      ),
      'serialized' => array(
        'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
        'type' => 'int',
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'indexes' => array(
      'expire' => array(
        'expire',
      ),
    ),
    'primary key' => array(
      'cid',
    ),
  );
  $ret = $keys_cache = array();

  // Keep records for 1 day.
  $expire = time() + 24 * 60 * 60;

  // Extra check to prevent errors when running this update multiple times.
  if (db_table_exists('performance_summary') && !db_table_exists('cache_performance')) {
    db_create_table($ret, 'cache_performance', $table);
    $result = db_query('SELECT * FROM {performance_summary}');
    while ($row = db_fetch_object($result)) {

      // Make some BASIC separation between anonymous users and logged in users.
      $anonymous = 1;
      if (preg_match('/^(admin|admin\\/.*|node\\/add.*)$/i', $row->path) || preg_match('/^(node|user)\\/\\d+\\/(edit|translate|delete|revisions.*)$/i', $row->path)) {
        $anonymous = 0;
      }
      $data = array(
        'path' => $row->path,
        'bytes_max' => $row->bytes_max,
        'bytes_sum' => $row->bytes_avg * $row->num_accesses,
        'ms_max' => $row->ms_max,
        'ms_sum' => $row->ms_avg * $row->num_accesses,
        'query_timer_max' => $row->query_timer_max,
        'query_timer_sum' => $row->query_timer_avg * $row->num_accesses,
        'query_count_max' => $row->query_count_max,
        'query_count_sum' => $row->query_count_avg * $row->num_accesses,
        'num_accesses' => $row->num_accesses,
        'last_access' => $row->last_access,
        'anon' => $anonymous,
        'language' => $language->language,
      );

      // We now cache per language and distinguish between anonymous users and
      // logged in users. As usual, we keep logs for one day!
      $key = PERFORMANCE_KEY . $row->path . ':' . $language->language . ':' . $anonymous;
      cache_set($key, $data, 'cache_performance', $expire);

      // Keep the key for the key cache store. We do it this way so that keys
      // will replace eachother which would not happen when using
      // $keys_cache[] = $key;
      $keys_cache[$key] = 1;
    }

    // This will allow easy retrieval of data as we cannot use SQL queries since
    // the cache store can by anything!
    cache_set(PERFORMANCE_KEY, $keys_cache, 'cache_performance');

    // No longer needed!
    db_drop_table($ret, 'performance_summary');

    // Preserve summary logging setting if enabled.
    // There can, in theory, be unlimited variables named performance_summary_%
    // where we do not know what % is, hence this approach.
    $result = db_query('SELECT value FROM {variable} WHERE name LIKE "performance_summary\\_%"');
    while ($row = db_fetch_object($result)) {
      if (unserialize($row->value)) {
        variable_set('performance_summary', 1);
        break;
      }
    }

    // Delete old performance summary variables!
    db_query('DELETE FROM {variable} WHERE name LIKE "performance_summary\\_%"');
    return $ret;
  }
}

/**
 * Add new language field to performance_detail table.
 */
function performance_update_6201() {
  $ret = array();
  $field = 'language';
  $table = 'performance_detail';

  // See http://drupal.org/node/150220.
  $language = array(
    'description' => 'The {languages}.language used when calling this path.',
    'type' => 'varchar',
    'length' => 12,
    'not null' => TRUE,
    'default' => '',
  );
  if (!db_column_exists($table, $field)) {
    db_add_field($ret, $table, $field, $language);
  }
  return $ret;
}

Functions

Namesort descending Description
performance_install Implementation of hook_install().
performance_requirements Implementation of hook_requirements().
performance_schema Implementation of hook_schema().
performance_uninstall Implementation of hook_uninstall().
performance_update_1 Remove title field from performance tables.
performance_update_2 Add data field to performance_detail table.
performance_update_6001 Harmonize notations for milliseconds to "ms".
performance_update_6200 Move summary data from old performance_summary table to new cache bin. Note that we now cache per language and separate anonymous users from logged in users! During this migration, we do BASIC separation between anonymous and logged in users based on…
performance_update_6201 Add new language field to performance_detail table.

Constants