Bryan's thoughts on web design and development

Tool: BareTail: the tail -f that Microsoft forgot

If you found this post, you probably know what the command ‘tail -f’ does in linux, but for those that don’t, a concise definition: You can ‘tail -f’ a file, and it will show you the last n lines in a file, updated as more lines are added. It’s a great way to monitor logs in real-time.

Unfortunately, windows doesn’t have a tail tool built-in. But I discovered BareTail, which adds this functionality. It’s shareware, but a quality product and well worth it. Their freeware product, WinTail, has less features, but can do the job.

Selecting a random weighted record from a mysql database

For our homebrew A/B testing system, groups of landing pages are selected from according to weight. Sometimes you want a landing page to only come up once in 100 times. Luckily for me, it’s easy to make mysql randomize between these rows using their weighted probability. You just attach the following to the end of your query:

ORDER BY RAND()*probability DESC LIMIT 1

Where probability is the column name where you store the required probability for that row. For example, “80″ is going to come up 80% of the time compared to a row with “20″.

Remove duplicate rows from a mysql table

It’s easy to remove duplicates from a mysql table in order to create a UNIQUE index. Whatever the reason, it’s as simple as:

ALTER IGNORE TABLE table_name ADD UNIQUE INDEX(column_name);

Basically you’re asking mysql to add a unique index to the column, but skip any errors (duplicate keys) that occur. And tada - you have a table free of duplicate rows.

Adding users and saving graphs for Graphite

We’ve gotten quite addicted to graphite here in the office since I set it up. It’s just so great to have real-time-graphing of server load and active users.

One constant annoyance was that every morning, I’d need to set up my preferred graphs again, since graphite doesn’t have special URLs or ways to save combo graphs out-of-the-box. To get this working, you need to give graphite a login system. Luckily, DJango has a built-in one that graphite interfaces with (don’t mess with LDAP).

This is all you have to do to get graphite to save user graphs:

Step 1: Disable all the LDAP stuff in local_settings.py if you enabled it above. The default is to use the DJango login system.
Step 2: navigate to $GRAPHITE_ROOT/web
Step 3: type python manage.py shell, which opens up a python interactive shell
Step 4: type: from django.contrib.auth.models import User
Step 5: then, to create a user: user = User.objects.create_user(’username’, ‘email@email.com’, ‘mypassword’)
Step 6: then, type: user.save()
Step 7: repeat 5-6 for more users
Step 8: log in from the graphite window and enjoy saving! also, try editing your profile and enabling the ‘advanced features’ — all I found was the ‘*’ feature, but it’s awesome and totally worth it!

Happy graph saving!

Dynamic rounded corners with PHP + Imagemagick

I love rounded corners. If I had my way, right angles would cease to exist - in web sites, in applications, in cars, in cell phones. Don’t get me wrong: squared corners, sparingly used, can give definition and even meaning to a design. Take the font Georgia, for example; a distinctive and refined serif font, with the soft curves you don’t see in default Times.

So why have right angled corners dominated through the years? I think it boils down to technical limitations, if not laziness. Rounded corners and seamless forms just look more natural. Very few 90 degree angles occur in nature.

Rounded corners on the web are a tricky problem as well. Early UI designers stuck with the same square window look - it has taken more than 10 years to adopt an easy standard for rounded corners on the web. CSS3 offers the web designers of tomorrow a handy corner solution, but considering that widespread support for this is years to come, what can we do today?

Some CSS and javascript hacks emulate them with mixed success across browsers. Honestly, I’d prefer to stick with the tried and true method of using plain old images.

But the problem with this method is just that - Photoshop is a major hurdle. Sure, you can script it to make you rounded corners, but that’s still an extra step. You could also use one of many online rounded corner generators. But I don’t want to have to go to any additional effort to introduce a new rounded box color - it should be seamless and just work; ideally, it should make itself.

That’s how I heard about Google’s (undocumented) Dynamic Rounded Corner Generator. Apparently the techs at Google Groups had the same thoughts as me. Some were advocating using it to produce rounded corners on the fly. But as much as this seemed like the perfect solution, I wasn’t about to steal Google’s resources for my own (nefarious?) purposes. Until they released an API (like google charts?), I would have to write my own generator.

The following is the simple rounded corner generator script I wrote. It produces rounded corners on the fly, and caches them, to ensure that corners are generated quickly again and again. Using it means that I can let my designs run free without the burden of creating a new set of rounded corners, whether it be size or color:

  1. <?
  2. // Xobni's Awesome Rounded-Corner Maker
  3. // v0.1
  4.  
  5. $corner_cache_dir = "/var/www/corners/";
  6.  
  7. // set expires header to encourage long client cache
  8. define('YEAR',31556926);
  9. header("Content-type: image/png");
  10. header('Expires: '.gmdate("D, d M Y H:i:s", time() + YEAR).' GMT');
  11. define('ISCOLOR','/^#(?:(?:[a-f\d]{3}){1,2})$/i');
  12.  
  13. // which side to generate
  14. switch ($_GET['a']) {
  15.  case 'tl': $side='tl';
  16.  break;
  17.  case 'tr': $side='tr';
  18.  break;
  19.  case 'br': $side='br';
  20.  break;
  21.  case 'bl': $side='bl';
  22.  break;
  23.  default: $side='tl';
  24. }
  25.  
  26. // the size of the corner
  27. $width = (int)$_GET['w'];
  28. $height = (int)$_GET['h'];
  29. if (!$height) {
  30.  $height=$width;
  31. }
  32.  
  33. // the color of the corner
  34. $color = $_GET['c']; //hex only
  35. $bgcolor = $_GET['b'];
  36.  
  37. $string = "#666666";
  38. if (!$color || !preg_match(ISCOLOR, $color)) {
  39.  $color = "000000";
  40. }
  41. if (!$bgcolor || !preg_match(ISCOLOR, $bgcolor)) {
  42.  $bgcolor = "ffffff";
  43. }
  44.  
  45. $bgcolort = ' -fill "#'.$bgcolor.'"';
  46.  
  47. // figure corners
  48. if ($side == "tr") {
  49.  $rotate = '-rotate "90"';
  50. } elseif ($side == "tl") {
  51.  $rotate = '';
  52. } elseif ($side == "bl") {
  53.  $rotate = '-rotate "-90"';
  54. } elseif ($side == "br") {
  55.  $rotate = '-rotate "180"';
  56. }
  57.  
  58.  
  59. $filename = $corner_cache_dir.$color."_".$bgcolor."_".$width."_".$height."-".$side.".png";
  60. if (!file_exists($filename)) {
  61.  // make the image
  62.  $command = 'convert -size '.$width.'x'.$height.' xc:none '.$bgcolort.' -draw \'rectangle 0,0 '.$width.','.$height.'\' -fill "#'.$color.'" -draw \'roundRectangle 0,0 '.($width*2).','.($height*2).' '.($width).','.($height).'\' '.$rotate.' -composite '.$filename;
  63.  $result = exec($command);
  64. }
  65. readfile($filename);
  66. ?>

It’s simple to install - just place it in your /images/ folder, install imagemagick (or convert the script to use gd) and change the $corner_temp_dir to an appropriate public URL.

To generate an image, simply pass it the parameters in the querystring, like so:
http://www.yoursite.com/images/corner.php?c={corner-color}&b={background-color}&w={corner-width}&h={corner-height}&a={tr/tl/br/bl}

  • height is optional and will default to square dimensions
  • a = the corner to generate, top-right (tr), bottom-left (bl), top-left (tl), or bottom-right (br)

You could just use the script above as-is, whether it be in tables with corners or whathaveyou. I recommend generating boxes using css, and it’s a pretty easy technique:

  1. <?
  2. $page_color = "FFFFFF";
  3. $box_color = "333333";
  4. $corner_width = "8";
  5. $corner_height = "8";
  6. $image_base_url = "http://www.yoursite.com/images/corner.php?c=$box_color&b=$page_color&w=$corner_width&h=$corner_height";
  7. ?>
  8. <div style="position:relative;background-color:#<?=$box_color?>;padding:12px;width:<?=$box_width?>;height:<?=$box_height?>">
  9.  <div class="rounded-tr" style="background: url('<?=$image_base_url?>&a=tr') no-repeat top left;width:<?=$corner_width?>px;height:<?=$corner_height?>px;"></div>
  10.  <div class="rounded-tl" style="background: url('<?=$image_base_url?>&a=tl') no-repeat top left;width:<?=$corner_width?>px;height:<?=$corner_height?>px;"></div>
  11.  <div class="rounded-br" style="background: url('<?=$image_base_url?>&a=br') no-repeat bottom left;width:<?=$corner_width?>px;height:<?=$corner_height?>px;"></div>
  12.  <div class="rounded-bl" style="background: url('<?=$image_base_url?>&a=bl') no-repeat bottom left;width:<?=$corner_width?>px;height:<?=$corner_height?>px;"></div>
  13.  
  14. What's in the box?
  15.  
  16. </div>

In the example above, replace the top four variables with those of your choosing and put what you want in the box.

Then add this to your global stylesheet:

  1. .rounded-tr {
  2.  position:absolute;top:0px;right:0px;
  3. }
  4. .rounded-tl {
  5.  position:absolute;top:0px;left:0px;
  6. }
  7. .rounded-br {
  8.  position:absolute;bottom:0px;right:0px;overflow: none;
  9. }
  10. .rounded-bl {
  11.  position:absolute;bottom:0px;left:0px;overflow: none;
  12. }

For a real-world example, view the source of any of our rounded boxes throughout our site, like on our blog.

Happy rounding!

Scalable realtime performance monitoring with Graphite and PHP


If you’ve not heard of Graphite, it’s the Enterprise Scalable Realtime Graphing package that allows you to quickly set up a monitoring system for your servers (or any other application of real-time graphing!). It’s pretty bare-bones, but spares you the need to set up your own scalable storage engine and live graphing interface.

At Xobni, we use it to report the load averages of our servers, our active www sessions, our server activity, and more. It beats the heck out of watching top all day.

Most of our web services here are built in PHP, and Graphite is a python-based product. The only example they provide to show you how to get data into your graphs is written in python and is very limited. I figured a quick port to PHP and better documentation would be helpful to someone out there.

I’m assuming you already have a Graphite server running somewhere. At Xobni, we use a mini Slicehost slice to handle the stats collecting and display from all our servers.

Step 1: Get your graphite database ready
Graphite (Carbon) uses fixed-sized databases, so you need to give it a hint as to how big your data will grow before you begin. You can read this page for more verbose (and confusing) information, but I’ve summarized it here.

Graphite doesn’t crunch anything for you - it’s not going to function itself like a ‘hit counter’ on your home page. You need to build the script that reports the data averaged over increments of time. To make a hit counter, for example, you’d probably want to store the hits in a flat-file database (or memcached?) and sum them every minute with a cron script. All it does it take your averaged data and graph it.

To create your database schema, edit /usr/local/graphite/storage/schemas on your graphite server. Append the following:

[schema_name]
  1. priority = 100
  2. pattern = ^test\.
  3. retentions = 60:10080,300:8640,3600:43800</code>

schema_name = anything, just a name
priority = default:100, doesn’t really matter
pattern = regex of the metric schema you want to use. this could be anything and will be what you see in the root level of your graphite list. for server load graphing, you might choose ’server_load’.
retentions = size of the database, number of data points you need. this part is automatic - anything graphite can’t store, it won’t keep. The example above has it storing a data point every 60 seconds for 7 days (10080 minutes), then a data point every 5 minutes (300 seconds) for 30 days (8640 5-minute increments), and a data point every hour (3600 seconds) for 5 years (43800 hours). You could of course store less data, this is just an example.

Step 2: Send the data
Sending data to graphite is a little different than you might normally expect to send data to a server. There’s no XML, no post/get - you actually open a socket connection and write data to it. I assume this is a performance thing - reading from a socket is undoubably a million times faster than running an apache server to accept requests. It makes it a little harder to figure out how to send it data, but that’s why I wrote this blog post.

First, put this function in your functions library (or copy-paste it to the script below):

  1. function report_rtg($schema,$data) { //$data/schema can be arrays
  2.  $carbon_ip = "ip_of_your_graphite_server";
  3.  $carbon_port = 2003;  // graphite listens on this port
  4.  
  5.  // what are we going to send them?
  6.  // format: metric_path value timestamp\n
  7.  
  8.  $msg="";
  9.         //assemble the key/value string to send
  10.  if (is_array($schema) && is_array($data)) {
  11.   $count = count($schema);
  12.   for ($i=$count-1;$i>=0;$i) {
  13.    if($schema[$i] && $data[$i]) {
  14.     $msg .= $schema[$i].' '.$data[$i].' '.time()."\n";
  15.    }
  16.   }
  17.  } else {
  18.   $msg = $schema.' '.$data.' '.time()."\n";
  19.  }
  20.  
  21.         // open the socket
  22.  $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  23.  //socket_set_nonblock($sock); // if you want the script to continue without waiting for a response
  24.  socket_connect($sock,$carbon_ip, $carbon_port);
  25.  socket_write($sock,$msg,strlen($msg));
  26.  //print socket_strerror(socket_last_error()); //debugging
  27.  
  28.  socket_close($sock);
  29.  return true;
  30. }

All you have to do is change the IP to be the IP of your RTG server. This worked on a standard php5 install on debian for me, but there may be permission issues on your machine, depending on how it’s set up. Check the PHP manual if you have problems opening the socket.

Here’s the RTG script that generates and sends the appropriate data to our graphite server. I have it set to run every minute as a cron job:

#!/usr/bin/php -q
  1. <?php
  2. require 'functions.php';      // change this to the uri of your functions library if you have one
  3.  
  4. // ***************************************************
  5. // ***************************************************
  6. // SERVER STATS RTG
  7. // ***************************************************
  8. // calcs some stats for our rtg
  9. //
  10. // ***************************************************
  11. // ***************************************************
  12.  
  13.  //** WWW CPU
  14.        // report on some server load averages
  15.  $loadavg = file_get_contents('/proc/loadavg');
  16.  $loadavg = split(' ',$loadavg);
  17.  
  18.  $report_schemas[]='server_status.www.load1';
  19.  $report_data[]=$loadavg[0];
  20.  
  21.  $report_schemas[]='server_status.www.load5';
  22.  $report_data[]=$loadavg[1];
  23.  
  24.  $report_schemas[]='server_status.www.load15';
  25.  $report_data[]=$loadavg[2];
  26.  //**
  27.  
  28.  //** WWW MEMORY
  29.         // how much memory do we have available of this machine
  30.  $memoryfree= file_get_contents("grep MemFree: /proc/meminfo | sed -e 's/^MemFree:[[:space:]]*\([0-9]*\) kB$/\1/'");
  31.  $report_schemas[]='server_status.www.memfree';
  32.  $report_data[]=$memoryfree/1000; //in mb
  33.  
  34.  //*** WWW ACTIVITY
  35.         // if your sessions are in a db, you can count from that
  36.  $sql = "SELECT count(*) FROM sessions WHERE DATE_ADD( dtSessionStart, INTERVAL 20 MINUTE ) > NOW()";
  37.  $result = mysql_query_safe($sql);
  38.  $active_users = mysql_result($result,0,0);
  39.  $report_schemas[]='server_status.www.active';
  40.  $report_data[]=$active_users;
  41.  //***
  42.  
  43.  print_r($report_data);
  44.  
  45.  report_rtg($report_schemas,$report_data);
  46.  
  47. ?>

For all the report_schemas, I chose names that began with ’server_status.www.’. Think of this as ‘root folder’.’subdirectory’ - if I wanted to add the load stats for another server called ‘chubby’, I probably would make the schema name ’server_status.chubby.load1′. These are all more or less arbitrary, but the root folder needs to be the same one you specified in your schema or you’ll be left with a default-sized database and unexpected results.

You of course can use any metrics you want, the possibilities are limitless.

Happy tracking!

Quick and Easy Mysql Replication on a live server - tutorial

As our database grew, and the data within became more complex, we were starting to run into more and more situations where a complicated query would accidentally lock up our site. I decided it was time that Xobni put on some britches and started using mysql replication.

There are many reasons why you should set up a master-slave relationship for your mysql server. Redundancy and fail-over, fast live backups (dump your slave instead of your master), speed (you can read from your slave, and write to your master), scaling potential (it’s much easier to add a new slave to an existing master-slave pair), the list goes on! No matter your reason, IMHO this is by far the easiest way to start replicating a database from one server to another with a live server.

Most tutorials I found deal with new set ups without existing data, or use deprecated transfer methods, or have you copy down bin-log locations, and some go so far as mysqldump->scp->mysql. I wanted a single command, more or less, to ensure as little downtime as possible.

Since our web site is live and handling over 400 requests/sec off-peak, downtime wasn’t an option. The method had to transfer existing data, and do it quickly. With our ~2GB database, and using my technique, our mysql server was locked for less than 5 minutes.

First, make some config changes:
For me, this meant editing my my.cng file on my existing mysql machine (on my debian machine, that’s ‘vim /etc/mysql/my.cnf’) and adding the following:

# settings for master-slave replication
log-bin=mysql-bin
server-id=1
expire-logs-days = 20
max_binlog_size = 104857600

log-bin tells the server where to store the logs, server-id basically tells the server it’s the master, expire/max tell the server how to clean up the logs (we don’t want them to grow forever!).

Now, log in to mysql on the commandline (for me: ‘mysql -uuser -p’), and run the following to set up our replication user:


GRANT REPLICATION SLAVE ON *.* TO 'replicant'@'{ip_of_your_slave_server}' IDENTIFIED BY '{your_password}';

Now, log into your to-be-slave server, and edit its my.cnf file and add the following:


# who's your daddy
server-id=2
master-host = {server_ip}
master-port = 3306
master-user = replicant
master-password = {pass}
replicate-do-db={db_name}
replicate-ignore-db={db_name}

replicate-do-db and replicate-ignore-db are optional and only needed if you want to restrict your replication to a particular database and not another. Be sure to exclude the curly brackets and change the port if needed.

Then restart both mysql servers on both machines to effect the changes.


/etc/init.d/mysql restart
or
/etc/rc.d/init.d/mysqld restart

The slaving will start on the restart, but we don’t want it to do anything yet, because our servers aren’t yet synced up. So after restart, you’ll need to log into mysql on your slave machine, and type:

STOP SLAVE;

On to the magic! On the master server, run:


mysqldump --extended-insert --master-data -u{mysql_user} -p{pass} {db_name} | mysql --compress -h{ip_of_slave} -u{mysql_user} -p{pass} {db_name}

Here, you need the username and password of a privileged user on each machine and the ip of the slave machine. If you want to set up replication for all databases, replace {db_name} with ‘–all-databases’. Be sure to exclude the curly brackets.

What this is going to do is make a special dump that 1) locks the server to any new writes, and 2) appends a tag at the end for the slave server to use so it knows where to start its slaving. It pipes the output directly to mysql, which copies the data directly to the server. No need to find and scp the file over; it’s easier and faster this way.

Once that’s done, all that you need to do is log into mysql on your slave machine and type:
START SLAVE;

That’s all there is to it! Check it by inserting some data in your master and then selecting on your slave. It works? Awesome! Found a better way? Let me know so I can update or link!

Hopefully your site suffered very little downtime during the transfer, and now you have a working slave server to do your bidding. Muhahaha!

Update: It seems that by default, a slave server will stop replication if an error occurs. For me, I did not exclude databases that I didn’t want replicated — using replicate-do-db needs to be used in conjunction with replicate-ignore-db (added above). So it kept on trying to replicate changes in other databases that didn’t exist on the slave server, and I ended up with a disobeying slave.

If your slave stops running, type ‘SHOW SLAVE STATUS;’ in mysql, which will tell you if an error has occurred. Once you fix the error, add:

slave-skip-errors={error-code-from-slave-status}

Replace the error code with the code reported in the slave status, restart mysql, and the slave should start up again.

How to auto-remove old files/directories from linux

I usually just keep old backups, perhaps for some undefined historical purposes, but more likely just out of laziness. But clearly, this method (or lack thereof) can eat up disk space rather quickly.

I recently created a simple bash script that will find an remove all files or directories that are older than a specified number of days (as determined by modification dates). This helps keep Xobni’s backup directories small without having to manually go in there every few months, which is even more lazy-friendly. Awesome.

#!/bin/bash
# removes old directories to clear space
# 7 = number of days to keep directories/files

find /var/backup/ -type d -mtime +7 -exec rm {} -R \;
find /var/backup/something -type d -mtime +7 -exec rm {} -R \;

I have this script running via cron every week and it works like a charm.

Copy tables from one mysql database to another


At Xobni, I’ve developed a simple testing and deployment platform that helps us ensure that new web code is good before it’s pushed out the door. Part of this means maintaining two separate databases, one for development and one for live mode (Ruby on Rails developers will find this concept familiar). That way, we can test our database-enabled pages without adversely affecting the live database.

But this means the development database gets stale over time and must be refreshed. Ideally it should contain data from the day before or some reasonable approximation. In the past, I relied on the “copy-database” command in phpMyAdmin to do this. But being a manual process, and being fairly intensive on the server to boot, it rarely got done.

So I set out to create a php script that would mirror the two databases on a regular schedule. Essentially to copy one database’s tables to another, erasing the old data in the development database.

It’s not rocket science, as it turns out. Here it goes:

$todb = “development”;
$fromdb = “live”;

$sql = “SHOW TABLES”;
$result = mysql_query($sql);
while($row = mysql_fetch_array($result)) {
$table = $row[0];
$sql = “DROP TABLE IF EXISTS `$todb.$table`”;
mysql_query($sql);
$sql = “CREATE TABLE $todb.$table LIKE $fromdb.$table”;
mysql_query($sql);
$sql = “INSERT INTO $todb.$table SELECT * FROM $fromdb.$table”;
mysql_query($sql);
}

The script just loops through each table in your $fromdb and copies it to new tables in the $todb. I have it set to run every 24 hours via cron.

The LIKE syntax is fairly new in mysql (as of 4.1) and permits a fully copy of table, including all indexes and keys. The rest is all pretty standard stuff.

Be sure to fill in the appropriate mysql connection strings, and you’re good to go. And do a dry run or two before you run back crying that your deployment database got erased and you don’t have any backups.

I hope this was helpful.

Windows Tools: TortoiseSVN

If you’re on a windows machine and you use SVN, I’ll bet you already know about TortoiseSVN. If not, it’s seriously the most important application in my arsenal. It makes svn management a snap in windows, where before I had to adopt a whole clunky editor (Eclipse) to do the job.

If you don’t know the magic of TortoiseSVN yet, try it!