Heatmap in PHP

March 17th, 2010 by author Leave a reply »

Eine Heatmap ist die grafische Darstellung des Klickverhaltens von Usern auf einer Webseite. Anhand der Klicks kann man erkennen, worauf sich die User einer Webseite konzentrieren, welche Links sie klicken und ob sie z.B. Text markieren, da dieser zu lang ist.

Einfache Heatmap-Implementierung ohne Optimierung

Javascript

Die Klicks werden einfach per Javascript erfasst. Um dies zu erreichen reichen folgende Zeilen:

// Mouseclicks handeln
window.captureEvents(Event.MOUSEDOWN);
document.onmousedown = handleClick;

Bei einem “MouseDown” (Tastenklick auf der Maus) wird nun die Javascript-Funktion handleClick aufgerufen. Diese Funktion wertet die Koordinaten aus und sendet Sie nun mittels AJAX (XMLHttpRequest) an den Server.

// Leider müssen wir auch heutzutage noch den Browser erkennen, weil die Implementierung nicht einheitlich ist
var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
function handleClick(evt) {
evt = (evt) ? evt : ((window.event) ? window.event : "");
if(evt) {
   if(is_ie) {
      if(evt.button == 2) { return; } // kein Rechtsklick
      x = evt.clientX + document.body.scrollLeft;
      y = evt.clientY + document.body.scrollTop;
   } else {
      if(evt.button == 3) { return; } // kein Rechtsklick
      x = evt.pageX;
      y = evt.pageY;
   }
   sendClick(document.URL,x,y);
}
}
function sendClick(url, x,y) {
try {
    // Mozilla, Opera, Safari sowie Internet Explorer (ab v7)
    xmlHttp = new XMLHttpRequest();
} catch(e) {
    try {
        // MS Internet Explorer (ab v6)
        xmlHttp  = new ActiveXObject("Microsoft.XMLHTTP");
    } catch(e) {
        try {
            // MS Internet Explorer (ab v5)
            xmlHttp  = new ActiveXObject("Msxml2.XMLHTTP");
        } catch(e) {
            xmlHttp  = null;
        }
    }
}
if (xmlHttp) {
    xmlHttp.open('GET', 'heatmap.php?url='+escape(url)+'&x='+x+'&y='+y, true);
    xmlHttp.send(null);
}
}

PHP

Das Heatmap-PHP-Skript hat nun die Aufgabe die Werte entgegen zu nehmen und zu speichern. In diesem Beispiel werden die Werte einfach in einer Datei gespeichert, um sie später auswerten zu können. Die unten durchgeführte Performance-Analyse zeigt, dass dies der beste Weg ist, um den Server nicht zu sehr zu belasten.

<?php
        $filename = '/path/to/logfile.txt';
        $fh = fopen($filename, 'a');
	if($fh) {
		fwrite($fh, '['.time().'] '.$_REQUEST['url'].' :: '.$_REQUEST['x'].'-'.$_REQUEST['y']."\n");
		fclose($fh);
	} else {
		// TODO errorhandling
	}
?>

Performance

Ein kleiner Test zur Performance zeigt, dass man die Werte an eine Datei hängen sollte und nicht die Datenbank nutzen sollte. Der Test bezieht sich auf eine MySQL-Datenbank, evtl. andere Implementierungen wie z.B. NoSQL können bessere Werte ergeben, da diese dafür optimiert sind. Als NoSQL-Datenbank könnte man Cassandra der Apache Foundation nutzen.

Das Testskript

Zur Vereinfachung verzichtet das Testskript darauf die URL zu extrahieren und die notwendigen Teile wegzuschreiben, da dies für einen Performancevergleich unwichtig ist.

<?php
	/** 
	* Copyright by codefab.eu - Dieses Skript darf genutzt und verteilt werden, jedoch muss 
	* der Copyright-Vermerk erhalten bleiben! http://www.codefab.eu/
	* Es wird keinerlei Verantwortung für Schäden übernommen, die durch das Skript entstehen.
	* Die Nutzung erfolgt auf eigene Gefahr.
	**/
        $hostname = 'hostname';
        $username = 'username';
        $password = 'passwort';
        $database = 'databasename';
	// create table heatmap (ctime datetime not null, x int unsigned not null, y int unsigned not null);        
        $table    = 'heatmap';
        // Anzahl der Eintraege
        $datasets = 250000;
        // Filename fuer den Appendlauf
        $filename = 'heatmap_log.txt';

        // 1. Variante n Datensaetze mit einem einzigen Connect in die DB
        $start = time();
        $dbh = mysql_connect($hostname,$username,$password) or die ('Keine DB-Connection');
        mysql_select_db($database,$dbh) or die ('DB unbekannt');
        for($i=0;$i<$datasets;$i++) {
                mysql_query('INSERT INTO '.$table.' VALUES (\''.time().'\','.$i.','.$i.')');
        }
        mysql_close($dbh);
        echo '1. Dauer: '.(time()-$start)."\n";

        // 2. Variante n Datensaetze mit je einem Connect in die DB
        $start = time();
        for($i=0;$i<$datasets;$i++) {
                $dbh = mysql_connect($hostname,$username,$password) or die ('Keine DB-Connection');
                mysql_select_db($database,$dbh) or die ('DB unbekannt');
                mysql_query('INSERT INTO '.$table.' VALUES (\''.time().'\','.$i.','.$i.')');
                mysql_close($dbh);
        }
        echo '2. Dauer: '.(time()-$start)."\n";

        // 3. Variante n Datensaetze mit je einem pconnect in die DB
        $start = time();
        for($i=0;$i<$datasets;$i++) {
                $dbh = mysql_pconnect($hostname,$username,$password) or die ('Keine DB-Connection');
                mysql_select_db($database,$dbh) or die ('DB unbekannt');
                mysql_query('INSERT INTO '.$table.' VALUES (\''.time().'\','.$i.','.$i.')');
                mysql_close($dbh);
        }
        echo '3. Dauer: '.(time()-$start)."\n";

        // 4. Variante n Datensaetze an ein File haengen
        $start = time();
        $fh = fopen($filename, 'a') or die ('Datei unbekannt');
        for($i=0;$i<$datasets;$i++) {
                fwrite($fh, '['.time().'] '.$i.'-'.$i."\n");
        }
        fclose($fh);
        echo '4. Dauer: '.(time()-$start)."\n";
        // 5. Variante n Datensaetze an ein File haengen mit explizitem fopen
        $start = time();
        for($i=0;$i<$datasets;$i++) {
                $fh = fopen($filename, 'a') or die ('Datei unbekannt');
                fwrite($fh, '['.time().'] '.$i.'-'.$i."\n");
                fclose($fh);
        }
        echo '5. Dauer: '.(time()-$start)."\n";
?>

Die Auswertung mit 250000 Eintragungen

Für die einzelnen Methoden ergibt sich die folgende Laufzeit. Der Test wurde auf einem aktiven Webserver durchgeführt; es kann also zu Messschwankungen kommen. Mit dem Skript kann jeder auf einem Entwicklungssystem die Werte selbst nachvollziehen und seine Heatmap optimieren.

1. Dauer: 22
2. Dauer: 127
3. Dauer: 45
4. Dauer: 2
5. Dauer: 8

Auswertung

Man kann deutlich erkennen, dass die Fileoperationen wesentlich schneller gehen, als die Datenbankzugriffe. Um einen weiteren Performancegewinn zu erhalten könnte man evtl. auch noch einen Log-Daemon anbinden und den Performancetest dahingehend erweitern. Der dritte und fünfte Test dürfte der Realität am nächsten kommen, wenn man ein unoptimiertes Heatmap-Logging-Skript verwendet.

Optimierung

Zu aller erst sollte man die Optimierung auf der Clientseite vornehmen und sich an die Regeln für eine schnelle(re) Webseite halten. Das A&O ist also die Minimierung der Anfragen zum Server. Da die Klicks mittels Javascript geloggt und übertragen werden, sollte man also die Klicks zwischenspeichern und nur alle x Klicks bzw. alle y Sekunden die Daten an den Server übertragen. Je Anfrage erhält der Server somit mehrere Einträge und der Overhead (Datenbank-Connection öffnen, File öffnen o.ä.) wird miniert. Den Gewinn der Optimierung entspricht den Testergebnissen bei einer Datenbank vom dritten zum ersten Fall und bei einem Logfile vom fünften zum vierten Fall. Des weiteren wird die allgemeine Serverlast gesenkt, die durch das Aufrufen des Logskripts verursacht wird.

Das Tutorial besteht aus zwei Teilen. Im zweiten Teil wird kurz erklärt, wie man nun aus den gewonnenen Daten die grafische Auswertung herstellt.

VN:F [1.9.22_1171]
Rating: 8.7/10 (3 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)
Heatmap in PHP, 8.7 out of 10 based on 3 ratings
Advertisement

2 comments

  1. Greg says:

    Ein guter Ansatz, aber der ganze Teil mit der Auswertung bzw. grafischen Darstellung fehlt noch. Wann kommt dieser nach?
    Auch störten mich beim Lesen einige Typos wie hier (Sie statt “sie”):
    “welche Links Sie klicken und ob Sie z.B. Text markieren”
    Weiter so, danke für das Script.

    VA:F [1.9.22_1171]
    Rating: 3.0/5 (1 vote cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
  2. author says:

    Hallo Greg, den zweiten Teil werde ich im Laufe der nächsten Woche nachreichen. Ich habe den Beitrag entsprechend angepasst und der zweite Teil wird nun auch erwähnt (das hatte ich vergessen zu schreiben).

    VN:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.22_1171]
    Rating: 0 (from 0 votes)

Leave a Reply