Content notification

Unless I screwed it up, I now have content notification available on angrydonuts. If you have a local account you can just go to My Account, click My Notify Settings and turn it on If you have a drupal.org account, you can login here using your drupal.org account. I don’t think the livejournal module works for 4.7 yet, but I’ll install that at some point when it’s updated.

mysql tuning parameters

<?php
function hit_ratio_monitor() {
  echo
"<tr><td><b>" . t('Threads Created') . "</b></td><td><b>" . t('Connections') . "</b></td><td><b>" . t('Thread Cache Size') . "</b></td><td><b>" . t('Hit Ratio') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
 
$threads_created = db_fetch_object(db_query('SHOW STATUS LIKE "Threads_created"'));
 
$thread_cache_size = db_fetch_object(db_query('SHOW VARIABLES LIKE "thread_cache_size"'));
 
$mysql_connections = db_fetch_object(db_query('SHOW STATUS LIKE "Connections"'));
 
$hit_ratio = (100 - ((int) $threads_created->Value / (int) $mysql_connections->Value) * 100);
  if (
$hit_ratio < 0.99 || $hit_ratio > 1.10) {
   
$error_value = t('The ideal situation is to get Threads Created as close as possible to Thread Cache Size. So no new connections are having to wait for new thread allocation. Stay as close to a 99% hit ratio as you can as this will reduce bottlenecks in your caching. Adjust your Thread Cache Size until this is achieved. You can set your Thread Cache Size on the fly by doing "SET GLOBAL thread_cache_size=N". Where N is the desired size of your Thread Cache. For additional information on the SET command please read <a href="%url">SET Syntax</a>', array('%url' => url('http://dev.mysql.com/doc/refman/5.1/en/set-option.html')));
  }
    else {
     
$error_value = "";
    }
  echo
"<tr><td>" . $threads_created->Value . "</td><td>" . $mysql_connections->Value . "</td><td>" . $thread_cache_size->Value . "</td><td>" . round($hit_ratio, 3) . "</td><td>" . $error_value . "</td></tr>";
}
function
calc_uptime_stats($stats_to_calc) {
 
$uptime = db_fetch_object(db_query('SHOW STATUS LIKE "Uptime"'));
   
$stat_hour = ($stats_to_calc->Value / ($uptime->Value / 3600));
   
$stat_day = ($stats_to_calc->Value / ($uptime->Value / 86400));
   
$stat_year = ($stats_to_calc->Value / ($uptime->Value / 31536000));
    echo
"<tr><td>" . $stats_to_calc->Variable_name . "</td><td>" . $stat_hour . "</td><td>" . $stat_day . "</td><td>" . $stat_year . "</td><td>" . $stats_to_calc->Value . "</td></tr>\n";
}
function
uptime_stats() {
  echo
"<tr><td><b>" . t('Database Action') . "</b></td><td><b>" . t('Per-Hour') . "</b></td><td><b>" . t('Per-Day') . "</b></td><td><b>" . t('Per-Year') . "</b></td><td><b>" . t('Total') . "</b></td></tr>";
 
$results_load_stats = db_query('SHOW STATUS LIKE "Handler%"');
    while (
$stats = db_fetch_object($results_load_stats)) {
      switch (
$stats) {
        case (
$stats->Variable_name == 'Handler_write'):
         
$stats->Variable_name = t("Writes to DB");
         
calc_uptime_stats($stats);
          break;
        case (
$stats->Variable_name == 'Handler_update'):
         
$stats->Variable_name = t("Updates to DB");
         
calc_uptime_stats($stats);
          break;
        case (
$stats->Variable_name == 'Handler_delete'):
         
$stats->Variable_name = t("Deletes from DB");
         
calc_uptime_stats($stats);
          break;
        default:
          break;
    }
  }
}
function
table_stats() {
  echo
"<tr><td><b>" . t('Table Cache') . "</b></td><td><b>" . t('Open Tables') . "</b></td><td><b>" . t('Opened Tables') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
 
$open_tables = db_fetch_object(db_query('SHOW STATUS LIKE "Open_tables"'));
 
$opened_tables = db_fetch_object(db_query('SHOW STATUS LIKE "Opened_tables"'));
 
$uptime = db_fetch_object(db_query('SHOW STATUS LIKE "Uptime"'));
 
$table_cache = db_fetch_object(db_query('SHOW VARIABLES LIKE "table_cache"'));
  switch (
$table_cache) {
    case (
$uptime < 1000000):
     
$error_msg = t("Your MySQL server has not been running long enough to make a quality assessment of the performance of your table cache. Put some traffic on there and come back soon!");
      echo
"<tr><td>" . $table_cache->Value . "</td><td>" . $open_tables->Value . "</td><td>" . $opened_tables->Value . "</td><td>" . $error_msg . "</td><td>Uptime: " . $uptime->Value . "</td></tr>";
      break;
    case (
$table_cache->Value == $open_tables->Value && $opened_tables->Value > 1000):
     
$error_msg = t("Your table cache is currently full. This can severely impact the performance of your MySQL server. If you have the memory, it may be time to increase your table cache. However, if your table cache is set too high, MySQL may start dropping connections. You can read about how MySQL uses the table cache <a href='%url'>here</a>. Increase your thread cache by issuing a 'SET thread_cache_size=N' command. Where N is the desired size of your Thread Cache. More Information on the SET command can be found <a href='%set'>here</a>.", array('%set' => url('http://dev.mysql.com/doc/refman/5.0/en/set-option.html')), array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/table-cache.html'))) ;
      echo
"<tr><td>" . $table_cache->Value . "</td><td>" . $open_tables->Value . "</td><td>" . $opened_tables->Value . "</td><td>" . $error_msg . "</td></tr>";
      break;
    case (
$table_cache->Value == $open_tables->Value && $opened_tables->Value < 1000):
     
$error_msg = t("Your table cache is currently full. Although this can normally be bad as it forces MySQL to goto the DB for queries. However, due to the low number of opened_tables, it may provide little benefit to increase your table cache");
      echo
"<tr><td>" . $table_cache->Value . "</td><td>" . $open_tables->Value . "</td><td>" . $opened_tables->Value . "</td><td>" . $error_msg . "</td></tr>";
      break;
    case ((
$table_cache->Value - $open_tables->Value) > 10):
     
$error_msg = t("If MySQL is using a significant amount of resources your system. You may want to free up memory by lowering your Table Cache. You can read about how MySQL uses the table cache <a href='%url'>here</a>. Adjust your thread cache by issuing a 'SET GLOBAL thread_cache_size=N' command. Where N is the desired size of your Thread Cache. More Information on the SET command can be found <a href='%set'>here</a>.", array('%set' => url('http://dev.mysql.com/doc/refman/5.0/en/set-option.html')), array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/table-cache.html'))) ;
      echo
"<tr><td>" . $table_cache->Value . "</td><td>" . $open_tables->Value . "</td><td>" . $opened_tables->Value . "</td><td>" . $error_msg . "</td></tr>";
      break;
    default:
     
$error_msg = "";
      echo
"<tr><td>" . $table_cache->Value . "</td><td>" . $open_tables->Value . "</td><td>" . $opened_tables->Value . "</td><td>" . $error_msg . "</td></tr>";
      break;
  }
}
function
qcache_stats() {
$uptime = db_fetch_object(db_query('SHOW STATUS LIKE "Uptime"'));
$qcache_hits = db_fetch_object(db_query('SHOW STATUS LIKE "qcache_hits"'));
$qcache_inserts = db_fetch_object(db_query('SHOW STATUS LIKE "qcache_inserts"'));
$qcache_litmus = db_fetch_object(db_query('SHOW VARIABLES LIKE "have_query_cache"'));
$qcache_size = db_fetch_object(db_query('SHOW VARIABLES LIKE "query_cache_size"'));
$qcache_type = db_fetch_object(db_query('SHOW VARIABLES LIKE "query_cache_type"'));
    if (
$qcache_litmus->Value == 'NO') {
     
$error_msg = t("Your Query Cache has been disabled. The Query Cache is useful for when you regularly serve the same exact page. Please recompile MySQL without the --without-query-cache configuration option. See <a href='%url'>here</a> for more information on compiling MySQL.", array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/installing-source.html')));
      echo
"<tr><td><b>" . t('Have Query Cache?') . "</b></td><td><b>" . t('Query Cache Type') . "</b></td><td><b>" . t('Query Cache Size') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
      echo
"<tr><td>" . $qcache_litmus->Value . "</td><td>" . $qcache_type->Value . "</td><td>" . $qcache_size->Value . "</td><td>" . $error_msg . "</td></tr>";
      }
        elseif (
$qcache_type->Value == 'OFF') {
         
$error_msg = t("Your query_cache_type is set to 0. This effectively disables your Query Cache. The Query Cache is useful for when you regularly serve the same exact page. To correct this use the command 'SET GLOBAL query_cache_type=N' where N is equal to OFF,ON, or DEMAND. More Information on the SET command can be found <a href='%set'>here</a> and more information on configuring your Query Cache can be found <a href='%qcache'>here</a>", array('%set' => url('http://dev.mysql.com/doc/refman/5.0/en/set-option.html')), array('%qcache' => url('http://dev.mysql.com/doc/refman/5.0/en/query-cache-configuration.html')));
          echo
"<tr><td><b>" . t('Have Query Cache?') . "</b></td><td><b>" . t('Query Cache Type') . "</b></td><td><b>" . t('Query Cache Size') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
          echo
"<tr><td>" . $qcache_litmus->Value . "</td><td>" . $qcache_type->Value . "</td><td>" . $qcache_size->Value . "</td><td>" . $error_msg . "</td></tr>";
        }
          elseif ((int)
$qcache_size->Value == 0) {
         
$error_msg = t("Your query_cache_size is set to 0. This effectively disables your Query Cache. To correct this use the command 'SET GLOBAL query_cache_size=N' where N is equal to the desired size of your Query Cache. More Information on the SET command can be found <a href='%set'>here</a> and more information on configuring your Query Cache can be found <a href='%qcache'>here</a>", array('%set' => url('http://dev.mysql.com/doc/refman/5.0/en/set-option.html')), array('%qcache' => url('http://dev.mysql.com/doc/refman/5.0/en/query-cache-configuration.html')));
          echo
"<tr><td><b>" . t('Have Query Cache?') . "</b></td><td><b>" . t('Query Cache Type') . "</b></td><td><b>" . t('Query Cache Size') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
          echo
"<tr><td>" . $qcache_litmus->Value . "</td><td>" . $qcache_type->Value . "</td><td>" . $qcache_size->Value . "</td><td>" . $error_msg . "</td></tr>";
          }
            elseif (
$qcache_hits->Value = 0 || $qcache_inserts->Value = 0) {
              if ((int) (
$qcache_inserts->Value / $qcache_hits->Value) > 0.10) {
               
$value_error = t("Your Query Cache Inserts/Hits ratio exceeds the recommended limit.");
                echo
"<tr><td>" . $qcache_hits->Variable_name . "</td><td>" . $qcache_hits->Value . "</td><td>" . $error_msg . "</td></tr>";
                echo
"<tr><td>" . $qcache_inserts->Variable_name . "</td><td>" . $qcache_inserts->Value . "</td><td>" . $error_msg . "</td></tr>";
                echo
"<tr><td><b>" . t('Have Query Cache?') . "</b></td><td><b>" . t('Query Cache Type') . "</b></td><td><b>" . t('Query Cache Size') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
                echo
"<tr><td>" . $qcache_litmus->Value . "</td><td>" . $qcache_type->Value . "</td><td>" . $qcache_size->Value . "</td><td>" . $error_msg . "</td></tr>";
              }
            }
              else {
               
$value_error = "";
                  echo
"<tr><td>" . $qcache_hits->Variable_name . "</td><td>" . $qcache_hits->Value . "</td><td>" . $value_error . "</td></tr>";
                  echo
"<tr><td>" . $qcache_inserts->Variable_name . "</td><td>" . $qcache_inserts->Value . "</td><td>" . $value_error . "</td></tr>";
                  echo
"<tr><td><b>" . t('Have Query Cache?') . "</b></td><td><b>" . t('Query Cache Type') . "</b></td><td><b>" . t('Query Cache Size') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
                  echo
"<tr><td>" . $qcache_litmus->Value . "</td><td>" . $qcache_type->Value . "</td><td>" . $qcache_size->Value . "</td><td>" . $value_error . "</td></tr>";
                }
}
function
everything_else() {
 
$results = db_query('SHOW STATUS');
  echo
"<tr><td><b>" . t('Variable Name') . "</b></td><td><b>" . t('Current Value') . "</b></td><td><b>" . t('Warnings') . "</b></td></tr>";
  while (
$node = db_fetch_object($results)) {
    switch (
$node) {
      case (
$node->Value > 0 && $node->Variable_name == 'Slow_queries'):
       
$value_error = t("You Have queries which are executing slower than normal. Enable the <a href='%url2'>Slow Query Log</a> and use <a href='%url'>Explain</a> to examine your queries.", array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/explain.html')), array('%url2' => url('http://dev.mysql.com/doc/refman/5.0/en/slow-query-log.html')));
        echo
"<tr><td>" . $node->Variable_name . "</td><td>" . $node->Value . "</td><td>" . $value_error . "</td></tr>\n";
        break;
      case (
$node->Variable_name == 'Select_scan'):
       
$value_error = t("A high value here can be an indication of bottlenecks in your server optimization. This happens because Mysql is not using the indexes for the tables and so is having to do extra work for inefficient queries. Enable the <a href='%url'>Slow Query Log</a> and use <a href='%url2'>Explain</a> to examine your queries.", array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/slow-query-log.html')), array('%url2' => url('http://dev.mysql.com/doc/refman/5.0/en/explain.html')));
        echo
"<tr><td>" . $node->Variable_name . "</td><td>" . $node->Value . "</td><td>" . $value_error . "</td></tr>\n";
        break;
      case (
$node->Variable_name == 'Select_full_join'):
       
$value_error = t("A high value here means that MySQL is not using indexes and is therefore taking longer to build a result set. The problem can be fixed by indexing important fields of the join.Enable the <a href='%url'>Slow Query Log</a> and use <a href='%url2'>Explain</a> to examine your queries.", array('%url' => url('http://dev.mysql.com/doc/refman/5.0/en/slow-query-log.html')), array('%url2' => url('http://dev.mysql.com/doc/refman/5.0/en/explain.html')));
        echo
"<tr><td>" . $node->Variable_name . "</td><td>" . $node->Value . "</td><td>" . $value_error . "</td></tr>\n";
        break;
      default:
        break;
    }
  }
}
function
memory_check() {
 
$key_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "key_buffer_size"'));
 
$net_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "net_buffer_length"'));
 
$max_connections = db_fetch_object(db_query('SHOW VARIABLES LIKE "max_connections"'));
 
$read_rnd_buffer_size = db_fetch_object(db_query('SHOW VARIABLES LIKE "read_rnd_buffer_size"'));
 
$sort_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "sort_buffer_size"'));
 
$myisam_sort_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "myisam_sort_buffer_size"'));
 
$read_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "read_buffer_size"'));
 
$join_buffer = db_fetch_object(db_query('SHOW VARIABLES LIKE "join_buffer_size"'));
 
$global_buffers = (int) ($key_buffer->Value + $net_buffer->Value);
 
$thread_buffers = (int) ($read_rnd_buffer_size->Value + $sort_buffer->Value + $myisam_sort_buffer->Value + $read_buffer->Value + $join_buffer->Value);
 
$min_memory_needed = (int) ($global_buffers + ($thread_buffers * $max_connections->Value));
  echo
"<tr><td><center<b>" . t('Minimum Memory Needed : ') . round(($min_memory_needed / 1048576), 0) . "M</b></center></td></tr>";
  echo
"<tr><td>" . t('This number is a recommendation of the minimum amount of memory your server should have available to mySQL. It is a sum of all the caches and buffers that would benefit by not using swap.') . "</td><tr>";
  echo
"<tr><td>" . t('The formula : Min_memory_needed = Global Buffers + (Thread Buffers * Max_Connections)') . "</td></tr>";
  echo
"<tr><td>" . t('Global Buffers = Key Buffer + Net Buffer') . "</td></tr>";
  echo
"<tr><td>" . t('Thread Buffers = Read RND Buffer Size + Sort Buffer + MyiSAM Sort Buffer + Read Buffer + Join Buffer') . "</td></tr>";
  echo
"<tr><td><center<b>" . t('Global Buffer : ') . $global_buffers . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Thread Buffers : ') . $thread_buffers . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Key Buffer : ') . $key_buffer->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Net Buffer : ') . $net_buffer->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Max Connections : ') . $max_connections->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Read Rnd Buffer Size : ') . $read_rnd_buffer_size->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Sort Buffer : ') . $sort_buffer->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Myisam Sort Buffer : ') . $myisam_sort_buffer->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Read Buffer : ') . $read_buffer->Value . "</b></center></td></tr>";
  echo
"<tr><td><center<b>" . t('Join Buffer : ') . $join_buffer->Value . "</b></cwww.drupenter></td></tr>";
}
echo
"<center><b> Thread Cache Monitor </b></center>";
echo
"<table>";
hit_ratio_monitor();
echo
"</table>";
echo
"<br /><center><b> Table Cache Monitor </b></center>";
echo
"<table>";
table_stats();
echo
"</table>";
echo
"<br /><center><b> Query Cache Monitor </b></center>";
echo
"<table>";
qcache_stats();
echo
"</table>";
echo
"<br /><center><b> Memory Monitor </b></center>";
echo
"<table>";
memory_check();
echo
"</table>";
echo
"<br /><center><b> Select Monitor </b></center>";
echo
"<table>";
everything_else();
echo
"</table>";
/*
Min_memory_needed = global_buffers + (thread_buffers * max_connections)
Max_connections
Global_buffers= key_buffer + net_buffer(???)
thread_buffers= read_rnd_buffer_size = sort_buffer myisam_sort_buffer + read_buffer join_buffer
*/
?>

node_access and ACLs

I’ve already discussed my issues with the current node access, and even presented a possible solution. The nice thing about that solution is that it can work as a contrib module and live in its own little universe; other contrib modules can rely on it, and things can work, and I believe it can work well.

But it doesn’t address another issue, and that issue is fine-tuning access control. There are times when role-based access control just doesn’t work well. When that happens, the only solution ends up being something along the lines of access control lists. The first thing that came to mind when ACLs were mentioned to me was “OH DEAR GOD NO!!!” but as I thought about it, it started to seem to work a little bit better for me.

Taxonomy as Container?

When I first downloaded and installed Drupal, I did some of the usual things. I installed the blog module and forum module and played around with them. After about 5 minutes of playing I turned off blog.module. Too restrictve. Did its thing its way and that was pretty much it. Didn’t seem like anything I needed, and really, I don’t think it seems like anything anybody needs, except maybe in a couple of particular situations.

When I played with the forum—and later the image—modules, I realized something interesting. This ‘great and powerful tool’ that many people define Drupal with, Taxonomy, was being used not to classify, but contain.

The Totalitarian Beast that is Node_Access

I have a love/hate relationship with Node_Access in Drupal. It’s a very wonderful idea, and I’m glad we have it; it is very important to be able to create restricted content. That’s important in both a community system, where some levels of the community are behind closed doors, and it’s important in a publishing system where not all content is for all eyes, especially prior to publishing.

Unfortunately, the node_access system is very all or nothing. Multiple node access modules can’t work together. A node access module has to control every bit of content or none at all. This is especially inconvenient in systems where it’s only a small piece of the site you want to control access to, or worse—two small, unrelated pieces of the site.

Another Module Idea

This actually started out as a single idea, then it blossomed into three ideas, and then at the very end of my thinking, it reduced to two ideas. Apparently I’d forgotten why I’d written a certain high profile module, and it took me a good 30 minutes to realize I was approaching it from the slightly wrong angle.

In order to preserve that flawed logic, I will describe my train of thought: One of the things I have long wanted, but have not yet gotten around to doing, is to allow scheduling based upon the Node Queue. One of the original design points, and the reason it’s even called a queue is that it’s supposed to be a bucket that a site maintainer can drop nodes in. They go in on one end, and eventually they go out another end.

Yet Another Module

I meant to have this one finished something like 2 weeks ago, but it kept getting more complex than I expected. Well, that’s why Views is in development and not a finished product, since it points me at weaknesses in the system that module developers may notice.

In any case the Views Bookmark project exists to illustrate some advanced functionality available to module developers who wish to use Views functionality to enhance their own modules. Also it is a useful module in its own right.

Multi-Page forms

Some time ago, chx posted a method to do a multi-part form. The problem with the method was that it required a lot of interesting tricks that made the actual flow very difficult to understand and was very frustrating to use.

I have a simpler one. Here is a demo.

Because I share...

It makes me very happy that there are people out there who find Views module or Node Queue module and find that it makes their site better/faster/easier (or just makes it work).

merlinofchaos,

pcdonohue (http://drupal.org/user/16527) has sent you a message via your
contact form (http://drupal.org/user/26979/contact) at drupal.org.

If you don't want to receive such e-mails, you can change your settings at
http://drupal.org/user/26979.

DrupalCon Vancouver -- wrapup and highlights

I’d call this Day 4, because to me it is, but DrupalCon was only really 3 days. The day has been quite relaxed; many folks have left, and many of the ones that are around are beyond my ability to find them. I think they’re doing various things at Moosecamp/Northern Voices which is a blogging conference/hacking get together, but I’m not sure what rooms things are going on in. I’ve gotten to see a couple of people here and there, at least, and I’m typing up these blog entries in relative silence sitting next to hunmonk who is very absorbed doing some kind of actual work.

Page 1 »