Qmail Queue

Today I wanted to check how many emails were in the queue for Qmail. I wanted to display where all the outgoing emails were getting sent, and get a count of how many are going to that email address. To complete this task, I put together a quick 1 liner that loops through the queued emails, greps the “to” field, and then sorts and counts the results. The line of bash I wrote to do this is shown below.

for i in `find /var/qmail/queue/mess -type f`;do cat $i | egrep -o ‘To:.*[com,org,net]‘ | awk ‘{print $2}’ >> /tmp/emailTmp;done; cat /tmp/emailTmp | sort | uniq -c | sort -n;rm -f /tmp/emailTmp;

Memcache In PHP

This week I have been doing a lot of caching with Memcache. Memcache is a great distributed memory caching system. I was running various tests and wanted to see general statistics. To check the stats for memcache, you can telnet into the Memcache ip/port and run the command stats.

Example:
telnet ip port
stats
quit

After doing this I wanted to write a simple class to get the statistics of Memcache in PHP. Below is the Memcache Stats class I wrote to connect to a Memcache server and run a command.

class MemcacheStats {

    private $ip;

    private $data;

    private $errors = array();

    public function __construct($ip)
    {
        $this->ip = $ip;
    }

    public function command($command)
    {
        try {
            $fh = @fsockopen($this->ip, 11211, $errno, $errstr, 30);
            if (!$fh) {
                array_push($this->errors, "$errno: $errstr");
                throw new Exception("Could not connect to memcache server at {$this->ip}");
            } else {
                $out = "$command\r\n";
                $out .= "quit\r\n";
                fwrite($fh, $out);
                while (!feof($fh)) {
                    $this->data .= fgets($fh, 128);
                }
            }
        } catch (Exception $e) {
            $this->errors = $e;
        }
        return $this->data;
    }

    public function isError()
    {
        if (!empty($this->errors)) {
            return $this->errors;
        }
        return false;
    }

    public function getData()
    {
        return $this->data;
    }
}

Zend Form Hack

Zend_Form_Element_Radio does not allow you to remove the preselected option. You can set it initially to an integer value that is not one of your radio option values. The problem with this is, when you reload the form and it doesn’t pass validation, the integer value you set to hack this gets reset to the first element option. To fix this problem, I wrote a hack you can add just before you give the view script the form object that resets it to an integer value that is not already set. If the user has selected an option that is valid, it will stay populated and not remove this selected option.

//Zend_Form_Radio has a bug where you cannot remove preselected
//values. The element default to the first option. This will
//loop through every radio element and set the value to 3
//if the value is empty. This is a hack which unsets the default value.
foreach ($form->getElements() as $element) {
    if ($element->getType() == 'Zend_Form_Element_Radio') {
        if ($element->getValue() == '') {
            $element->setValue('3');
        }
    }
}

PHP gzip test

Today I wrote a quick script to download a web page with both a compressed(gzip) version and a non-compressed version. I wanted something quick that I could run from the command line. The PHP gzip script returns the size of the page in both gzipped and non-gzipped versions. It also calculates the time it took to download each version in seconds. One last feature of the script is that it downloads the page 10 times in both versions, and displays the average of both gzipped and non-gzipped compressed versions.

How to run the script:

tully@hydralisk:/tmp$ php download.php http://example.com

No-Compression:    85047 Time: 0.00 seconds

With-Compression:  11178 Time: 1.00 seconds

Downloading compressed and non-compressed versions 10 times each and then calculating average…

Non-Compressed version: 0.5

Compressed version: 0.35

function download($site, $gzip=0)
{
// Headers
$headers = array('Accept-Encoding: compress, gzip');
$ch = curl_init($site);
if ($gzip==1)
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$content = curl_exec($ch);
return strlen($content);
}

if ($argc < 2) {
echo "Usage: php $argv[0]\n";
} else {
$start = time();
echo "No-Compression:    ".download($argv[1]);
printf("\tTime: %2.2f seconds \n", number_format(((time() - $start))));
$start = time();
echo "With-Compression:  ".download($argv[1], 1);
printf("\tTime: %2.2f seconds \n", number_format(((time() - $start))));
}

echo "\nDownloading compressed and non-compressed versions\n10 times each and then calculating average... \n";
$times = array();
for($i=0;$i<20;$i++) {
$start = time();
download($argv[1]);
array_push($times, (time() - $start));
}
echo "Non-Compressed version: ".average($times) . "\n";

$times = array();
for($i=0;$i<20;$i++) {
$start = time();
download($argv[1], 1);
array_push($times, (time() - $start));
}
echo "Compressed version: ".average($times) . "\n";

// Helper to get average
function average(Array $a)
{
return array_sum($a) / count($a);
}

Jira Memory Error

So this week I ran into memory errors with Jira. The exact error message that caused Jira to stop responding to requests was “java.lang.OutOfMemoryError: PermGen space”. After some research, I found out that this memory is not the common object heap space memory. This memory actually deals with the binary code of classes and methods. A good explanation of this error can be found here. After understanding what this error meant, I did some more research for increasing this particular type of memory. The solution I came up with is shown below.

In your favorite text editor(mine is VIM) open up the following file:
atlassian-jira-enterprise-4.1.2-standalone/bin/setenv.sh

Replace the JAVA_OPTS= line with the following:
export JAVA_OPTS=”-Xms768m -Xmx1024m -XX:MaxPermSize=256m”

The above line sets my minimum memory size to 768mb and max size to 1020mb. It also sets the Perm size to 256MB. The default perm size varies. Mine was defaulted to 64mb.

After changing this variable, save the file and restart Jira. Now login as an administrator. From the admin panel click the arrow next to Administrator and select “System Info”. Now scroll down to the section “Java VM Memory Statistics”. From here you can view how much memory you have allocated to JIRA and how much is being used.

Google Weather API

Today I needed to display weather results for given cities. After reading reviews and API docs from various sites, I decided to use the Google Weather API. The Google Weather API was very easy to work with. All you need to do is hit the API with your city/zip and Google would return the 4 day forecast in XML. The API also included links to the weather condition images. If you wanted too, you could replace these links easily to use your own custom weather images.

Example of how to get the current condition:

$weatherObj = new GoogleWeather(‘Los Angeles California’);
$weatherResult = $weatherObj->getCurrentCondition ();

Example of how to retrieve 4 day forecast:

$weatherObj = new GoogleWeather(‘Los Angeles California’);
$weatherResult = $weatherObj->getAllDays();

/**
 * Used to return Weather results from Googles Weather API.
 *
 * Requires (PHP Curl,PHP Tidy)
 *
 * @author Tully Rankin
 *
 */
class GoogleWeather {

/**
 * URL of API
 *
 * @param string
 */
protected $url;

/**
 * Search Query
 *
 * @param string
 */
protected $query;

/**
 * Response from API Query
 *
 * @param string
 */
protected $data;

/**
 * DOM Instance 
 *
 * @param DOMDocument
 */
protected $dom;

	/**
	 * Initializes GoogleWeather properties and
	 * queries API with search string.
	 *
	 * @param string $query Location to search
	 * @param string $url URL of the Google Weather API
	 */
	public function __construct($query, $url = 'http://www.google.com/ig/api?weather=')
	{
		$query = str_replace(' ', '+', $query);
		$this->query = $query;
		$this->url = $url;
		$this->_retrieveDATA();
		$this->dom = new DomDocument();
		$this->dom->loadXML($this->data);
		if ($this->_isResultEmpty())
			return false;
	}

	/**
	 * Queries GoogleWeather API.
	 * uses PHP Curl extention.
	 */
	protected function _retrieveDATA()
	{
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $this->url . $this->query);
		curl_setopt($ch, CURLOPT_HEADER, 0);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		$data = curl_exec($ch);
		$this->data = $this->_tidyXML($data);
	}

	/**
	 * Cleans up malformed XML Data.
	 *
	 * @param string $xml XML Formatted string
	 * @return string
	 */
	protected function _tidyXML($xml)
	{
		$options = array('input-xml' => true, 'output-xml' => true, 'clean' => true);
		$clean = tidy_parse_string($xml, $options, 'UTF8');
		tidy_clean_repair($clean);
		return $clean;
	}

	/**
	 * Checks if returned DATA has results.
	 *
	 * @return bool 
	 */
	protected function _isResultEmpty()
	{
		$error = $this->dom->getElementsByTagName('problem_cause')->item(0);
		return $error;
	}

	/**
	 * Gets the current weather condition.
	 *
	 * @return array
	 */
	public function getCurrentCondition()
	{
		$node = $this->dom->getElementsByTagName('current_conditions')->item(0);
		$current = array();
		foreach ($node->childNodes as $c) {
			$current[$c->nodeName] = $c->getAttribute('data');
		}
		return $current;
	}

	/**
	 * Get weather conditions for all days. As of now
	 * Google currently returns 4 days.
	 *
	 * @return array
	 */
	public function getAllDays()
	{
		$nodes = $this->dom->getElementsByTagName('forecast_conditions');
		$i = 1;
		$weather = array();
		foreach ($nodes as $node) {
			foreach ($node->childNodes as $c) {
				if ($c->hasAttributes())
					$weather[$i][$c->nodeName] = $c->getAttribute('data');
			}
			$i++;
		} 
		return $weather;
	}
	
}
// set tabstop=4

Download the Google Weather PHP Class