Archive for the ‘English’ Category

kdevelop3 on opensuse 11.2

Friday, November 27th, 2009

I recently installed OpenSUSE 11.2 on my netbook. There’s a lot of great new features like KDE 4.3, much better kernel 2.6.31 support for Intel Graphics and the Atheros wifi, but also good things disappear from the distro like some good working old kde3 applications. KDevelop3 is still a favorite for my daily work. However kdevelop4 is there, it is still having bugs and lacking functionality and not in a final release. I still want to work with KDevelop3. Well, it’s quite easy to fix that.
(more…)

Google via IPv6

Saturday, March 28th, 2009

After all the signs from the ICT business world about the lack of interrest in deploying IPv6 because it should be too difficult and wouldn’t worth the investment, yesterday I found an article (Dutch techworld.nl, read more on Googles blog post) about the deployment of IPv6 at Google. They stress about the ease it takes to implement it. Actually exactly the same I think about it. When I deployed IPv6 about 2 years ago on my own network with the SiXXS and XS4All IPv6 tunnel brokers, it took only a little effort, I even didn’t have to restart my old BSD machines that were used as network gateways to get the network connected via IPv6. (more…)

Improved memcache php classes

Wednesday, January 14th, 2009

Today I rewrote the previous mysql - memcache classes, mentioned in my previous post.
I wrote a clas with static methods (why should you need an object at all?) with logic. supporting an easy way store items in a PHP application. A second DB class extends this one, for an easy way of caching complicated queries.
(more…)

Zend_Db and UTF-8

Sunday, December 14th, 2008

Today I discovered a problem with my PHP/MySQL application. SQL data coming from the Zend_Db logic has the wrong encoding.

Like IMO all modern applications should, my application only uses UTF-8 for displaying and handling all non-ASCII characters. To make the MySQL server understand that I talk UTF-8 to it, I always use the following immediately after connecting:
(more…)

Acer Aspire One

Sunday, November 23rd, 2008

Three Laptops

For a few years I have been working with a Toshiba Satellite Pro 4600. A nice one, but heavy and huge and the machine started to get ramshackle and having more and more serious problems, like a bad battery, unexpected fallouts and display backlight problems. Therefore I decided to buy a new laptop so I can still work in the train.
My attention felt on the new series of netbooks. Nice small laptops of only about one kilogram, I could easily take with me when traveling, instead of the almost 3kg Toshiba brick I was used to.
(more…)

Caching MySQL queries with memcache in PHP

Monday, November 17th, 2008

Recently I had to improve performance of several PHP sites. One of the problems is complicated queries, that take a long time to compute by the MySQL server. Therefore, I decided to look for a solution.

I found a nice solution on http://pureform.wordpress.com/2008/05/21/using-memcache-with-mysql-and-php/ and I decided to work this out for my sites.

For php4 sites I rewrote this code to a simple class:


/**
* Bastiaan Welmers - 20081013
* instataniate this class and use mysql_query_cache instead of mysql_query for slow queries
*
* php4 style class
*
*/

class memCacheDb
{
	var $memcache;

	// constructor
	function memCacheDb()
	{
		# Connect to memcache:
		$this->memcache = new Memcache;
		$this->memcache->connect('localhost', 11211) or die ("Could not connect to memcache");
	}

	# Gets key / value pair into memcache ... called by mysql_query_cache()
	function getCache($key) {
		return ($this->memcache) ? $this->memcache->get($key) : false;
	}

	# Puts key / value pair into memcache ... called by mysql_query_cache()
	function setCache($key,$object,$timeout = 60) {
		return ($this->memcache) ? $this->memcache->set($key,$object,MEMCACHE_COMPRESSED,$timeout) : false;
	}

	# Caching version of mysql_query()
	function mysql_query_cache($sql, $linkIdentifier = false, $timeout = 600 /* = 10 minutes expiration */) {
		if (!($cache = $this->getCache(md5("mysql_query" . $sql)))) {
			$cache = false;
			$r = ($linkIdentifier !== false) ? mysql_query($sql,$linkIdentifier) : mysql_query($sql);
			if (is_resource($r) && (($rows = mysql_num_rows($r)) != 0)) {
				for ($i=0;$i<$rows;$i++) {
					$fields = mysql_num_fields($r);
					$row = mysql_fetch_array($r);
					for ($j=0;$j<$fields;$j++) {
						if ($i == 0) {
							$columns[$j] = mysql_field_name($r,$j);
						}
						$cache[$i][$columns[$j]] = $row[$j];
					}
				}
				if (!$this->setCache(md5("mysql_query" . $sql),$cache,$timeout)) {
					die('Error trying to connect to memcache');
					# If we get here, there isn't a memcache daemon running or responding
				}
			}
		}
		return $cache;
	}
}

For the PHP5 applications I wrote a PHP5 style style class and I added expiration logic. You can now use MemCacheDb::deleteMatching($arrayOfKeywords) to remove queries from cache that contain certain keywords. You can use this for example in your CMS, just call the deleteMatching method in the update/creation logic with the updated table name in the keywords and all cached queries will expire.


/**
* Bastiaan Welmers - 20081013
* instataniate this class and use this mysql_query methoid instead of mysql_query for slow queries
* it wil return a array with assosiative arrays with the output.
* So use foreach($mdb->mysql_query($query) as $rows) {}
*
* php5 style class
*
*/

class MemCacheDb
{

	private $_memcache;
	private $_appKey = 'Change this key for your application';
	private $_queries = null;

	// constructor
	public function __construct()
	{
		// Connect to memcache:
		$this->_memcache = new Memcache();
		$this->_memcache->connect('localhost', 11211) or die ("Could not connect to memcache");
		$this->_appKey = md5(__FILE__ . $this->_appKey);
	}

	public function __destruct()
	{
		if (is_array($this->_queries))
		{
			$this->_saveQueries();
		}
	}

	public function getStats()
	{
		return $this->_memcache->getStats();
	}

	private function _loadQueries()
	{
		if (!is_array($this->_queries))
		{
			$queries = $this->_memcache->get($this->_appKey . '__queries');
			if ($queries !== false && is_array($queries))
				$this->_queries = $queries;
			else
				$this->_queries = array();
		}
	}

	public function getQueries()
	{
		$this->_loadQueries();
		return $this->_queries;
	}

	public function searchQueries($search)
	{
		$queries = array();
		foreach($this->getQueries() as $sql => $timestamp)
		{
			if (strpos($sql, $search) !== false)
			{
				$queries[] = $sql;
			}
		}
		return $queries;
	}

	public function deleteQuery($sql)
	{
		$this->_memcache->delete($this->_getQueryKey($sql));
		$this->_loadQueries();
		if (isset($this->_queries[$sql]))
			unset($this->_queries[$sql]);
		$this->_saveQueries();
	}

	public function deleteMatching($searchItems)
	{
		if (!is_array($searchItems))
		{
			$searchItems = array($searchItems);
		}

		foreach($searchItems as $searchItem)
		{
			foreach($this->searchQueries($searchItem) as $query)
				$this->deleteQuery($query);
		}
	}

	public function deleteAll()
	{
		foreach($this->getQueries() as $sql => $timestamp)
		{
			$this->deleteQuery($sql);
		}
	}

	private function _saveQueries($timeout = 1800)
	{
		if (is_array($this->_queries))
		{
			// clean old stuff
			foreach($this->_queries as $sql => $timestamp)
			{
				if (time() - $timestamp > $timeout)
					unset($this->_queries[$sql]);
			}

			return $this->_memcache->set($this->_appKey . '__queries', $this->_queries, 0, $timeout);
		}
		else
			return false;
	}

	// Gets key / value pair into memcache … called by mysql_query_cache()
	private function _getCache($key) {
		return ($this->_memcache) ? $this->_memcache->get($key) : false;
	}

	// Puts key / value pair into memcache … called by mysql_query_cache()
	private function _setCache($key, $object, $timeout) {
		$this->_loadQueries();

		if ($this->_memcache instanceof Memcache)
			return $this->_memcache->set($key, $object, 0, $timeout);
		else
			return false;
	}

	private function _cacheQuery($sql, $object, $timeout)
	{
		if ($this->_setCache($this->_getQueryKey($sql), $object, $timeout))
		{
			$this->_loadQueries();
			$this->_queries[$sql] = time();
			$this->_saveQueries($timeout);
			return true;
		}
		else
		{
			return false;
		}
	}

	public function expireQuery($query)
	{
		$this->_memcache->delete($this->_getQueryKey($query));
	}

	public function mysql_query($sql, $linkIdentifier = false, $timeout = 600)
	{
		return $this->mysql_query_cache($sql, $linkIdentifier, $timeout);
	}

	private function _getQueryKey($sql)
	{
		return md5($this->_appKey . $sql);
	}

	// Caching version of mysql_query()
	public function mysql_query_cache($sql, $linkIdentifier = false, $timeout = 600 /* = 10 minutes expiration */) {
		if (!($cache = $this->_getCache($this->_getQueryKey($sql)))) {
			$cache = array();
			$r = ($linkIdentifier !== false) ? mysql_query($sql,$linkIdentifier) : mysql_query($sql);
			if (is_resource($r) && (($rows = mysql_num_rows($r)) != 0)) {
				for ($i=0;$i<$rows;$i++) {
					$fields = mysql_num_fields($r);
					$row = mysql_fetch_array($r);
					for ($j=0;$j<$fields;$j++) {
						if ($i == 0) {
							$columns[$j] = mysql_field_name($r,$j);
						}
						$cache[$i][$columns[$j]] = $row[$j];
					}
				}
				if (!$this->_cacheQuery($sql, $cache, $timeout)) {
					die('Error trying to connect to memcache');
					// If we get here, there isn't a memcache daemon running or responding
				}
			}
		}
		return $cache;
	}
}

TODO: this code need to get more documentation, and a static method to store the object instance so it can be called everywhere in the application.

Installing AWstats

Monday, August 4th, 2008

Today I decided to install AWStats for analysing my webserver traffic.

Running Debian Etch with apache 2.2 on the webserver / webproxy, the following steps are taken:

  • Install awstats with apt-get install awstats
  • Put following in /etc/awstats/awstats.conf.local:

    LoadPlugin="ipv6"
    LoadPlugin="decodeutfkeys"
  • Install the needed perl libs:
    apt-get install libnet-dns-perl libnet-ip-perl libnet-ipv6addr-perl liburi-perl
  • Create config files for each website running:
    vim /etc/awstats/awstats.www.welmers.net.conf

    Include "/etc/awstats/awstats.conf"
    
    LogFile=”/var/log/apache2/www.welmers.net-access.log”
    LogType=W
    LogFormat=1
    SiteDomain=”www.welmers.net”
    DefaultFile=”index.php index.html”
    LevelForWormsDetection=2
    

    The log format is set to 1. This is the apache combined log format, with referer URLs and browser info. Use 4 for the simple apache log format (however I would recommend combined format anyway)

  • After this, some weird bug showed up in awstats. It warns about nested includes that don’t work because perl version < 5.6 is installed. Great, I got 5.8.8. I found this bug on http://www.mail-archive.com/debian-bugs-dist%40lists.debian.org/msg385558.html and applied the patch.
  • Next thing to do is to create initial awstat databases with the logfiles I already have.
    One problem: they are alreay rotated several times. This can be solved this way:
    rm /tmp/logfile

    for i in `ls www.welmers.net-access.log.* | sort -n -r -t . -k 5`; do
    zcat $i >> /tmp/logfile;
    done

    cat www.welmers.net-access.log.1 >> /tmp/logfile
    cat www.welmers.net-access.log >> /tmp/logfile

    Edit /etc/awstats/awstats.www.welmers.net.conf to temporarily use /tmp/logfile instead of /var/log/apache2/www.welmers.net-access.log
    Then:

    perl /usr/lib/cgi-bin/awstats.pl -config=www.welmers.net -update

    And edit back /etc/awstats/awstats.www.welemrs.net.conf with the usual logfile.

  • Great. all old stuf is in awstats database! Now make it visible:

    cp /usr/share/doc/awstats/examples/apache.conf /etc/apache2/awstats.conf

    Include this file in your favorite virtual host container where awstats stuff can be viewed by authorized people only. Higly recommended, the reputation of awstats.pl CGI makes it neccessary to do so.
    After inclusion, the script alias ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ could be removed because it was already mentioned in my virtual host.
    Now, when calling https://securehost.domain.com/cgi-bin/awstats.pl?config=www.welmers.net , you should be able to see all gathered data in the log files.
  • Now we want the awstats dbases update automagicly every 10 minutes, and just before rotating the apache logfiles:
    EDITOR=vim crontab -e

    # m h dom mon dow command
    */10 * * * * for config in `ls /etc/awstats/awstats.*.conf | cut -f2,3,4 -d"."`; do perl /usr/lib/cgi-bin/awstats.pl -config=$config -update; done

    vim /etc/logrotate.d/apache2
    and add:

    prerotate
    for config in `ls /etc/awstats/awstats.*.conf | cut -f2,3,4 -d"."`; do
    perl /usr/lib/cgi-bin/awstats.pl -config=$config -update
    done
    endscript

    in the /var/log/apache2/*.log block.

Done! My statistics get updated once per 10 minutes and I can view them easily via the awstats.pl CGI with the paramater ?config=www.mysite.com

Server Health Statistics script

Tuesday, July 29th, 2008

Today I wrote a script for gathering health statistics on my servers. It gets data from hddtemp and sensors and writes it to a MySQL table.

The script and how-to-use can be found on the wiki: http://wiki.welmers.net/en/Health2MySQL

The MySQL table can easily be analized by (web) scripts to graphs e.d., functionality I still need to write.

Things that already can be done is finding interresting issues like:

SELECT AVG(temp_cpu) FROM health WHERE datetime BETWEEN '2008-07-25 00:00:00' AND '2008-07-30 23:59:59'

SELECT MAX(temp_sda) FROM health

SELECT MIN(rpm_fan) FROM health

New server for Colo (RAID-1, Debian with vservers)

Monday, June 9th, 2008

Yesterday and today I’ve set up a new server for colocation.
Properties and Features:

  • celeron 2GHz
  • 2,5GB mem
  • 2×500GB ordinary sata disks in RAID-1
  • debian etch installed on it
  • serial console (both grub and linux itself)
  • all it will serve and compute nicely sorted and devided in vservers

This was the first time I’ve installed a system in RAID and with serial console.

First thing to do was buying the hardware and wire and install everything properly.

Next thing to do was to check everything works in the BIOS. Nice to see 2,6GB RAM and 2×500GB disks in the BIOS startup screen at once without any trouble.

Then install the operating system. I attached an old DVD drive to the mainboard to start up from CD-ROM.

Server when installing

The choosen disk layout will be:

  • both disks a 200MB boot partition and a near-500GB system partition as linux-raid
  • combine partitions on each disk to md devices
  • install ext3 for /boot on the first 200MB md and install LVM on the big second md for al the other partitions
  • in LVM partitions:
    • root 512MB
    • /usr 5GB
    • /var 3GB
    • swap 3GB
    • /tmp 1GB
    • /home 20GB
    • /home/mail 10GB optimized for Maildir, 1 inode per 4kb (news options) and mount options noatime, nosuid, noexec
    • /home/backup 25GB
    • /var/local/nntpcache 2GB news cache spool, 1 inode per 4kb and mount options noatime

    This will be enough for a while in the future. I left almost 400GB of non-partitioned space on LVM, which can be used to extend volumes or add volumes for future expanding.

To fix this with de debian installer:

  • First create a DOS label on both disks
  • Create on each disk a 200MB for booting and a second partition of almost 500GB for LVM. Partition Type fd (linux-raid). Give the 200MB partition a boot flag.
  • Set up RAID, couple both 200MB and the near-500GB partition to two RAID-1 devices, md0 and md1
  • setup the 200MB md0 device to format as ext3 and use /boot as mount point
  • setup the big md1 device to use as LVM
  • setup LVM: make a volume group on the LVM device (I named it raid1)
  • create logical volumes like root, swap, usr, var, etc.
  • close the logical volume manager setup
  • format all logical volumes properly (I choose ext3) and give it mount points
  • finish partitioning!

After partitioning and installing base .deb packages I choose to install only the standard system set of packages. All other functionality will be in the vservers, which are already there and will be copied from the old servers.

Unfortunatly the debian installer left me no choice between boot loaders, I had to use lilo as boot loader. Strange, since I left a boot md0 specialy to use with grub. I’d like grub rather than lilo because if I once forget to run lilo properly after a kernel upgrade, I’ll get stuck with an unbootable server and need to travel to the colocation to fix up things. Grub will always work with serial console even if it can’t find a kernel, because it can load kernels dynamicly from the grub CLI.

After reboot I noted two things:

  • the installer didn’t use my seperate /boot partition at all, although I’m sure I’ve setup a ext3 formated /boot file system for it in the installer. The md0 device was left ext3 formatted by the installer. Bug somewhere?
  • The raid had to be build up according to /proc/mdstat and it took hours… I just left the server on without reboot to finish this, since I don’t know if rebooting before with slow down or restart the process…

Next thing to do is to install grub and get it work with serial console. More on this later…

Hello world!

Sunday, June 8th, 2008

Launched my first blog post after installing WordPress! Wow….

I installed wordpress in a codebase -> symlink jailed public_html structure. So the WordPress code is seperated from the blog specific content. On this way Wordpress can easliy be upgraded, and I can easily create another independend blogs.

  • ~/wordpress/ contains all unchanged wordpress code and can be upgraded by extracting a new tar ball.
  • ~/public_html/bastiaan/ contains a symlink jail to ~/wordpress/
    Non-shared items like wp-config.php are just individual files.

I had to hack a new ~/wordpress/wp-config.php so it uses the wp-config.php from the link jail directory (found by $_SERVER['SCRIPT_FILENAME']):


/* 2008 Bastiaan Welmers haasje@welmers.net - wp-config.php usefull for symlink jails */
$dir = dirname($_SERVER['SCRIPT_FILENAME']) . '/';
for ($i = strlen($dir); $i > 0; $i–) {
    if ($dir[$i] == '/') {
        if (file_exists(substr($dir, 0, $i) . '/wp-config.php')) {
              include(substr($dir, 0, $i) . '/wp-config.php');
              break;
        }
    }
}
?>

Update: fixed code block :D