{"id":175,"date":"2010-03-17T10:13:58","date_gmt":"2010-03-17T09:13:58","guid":{"rendered":"http:\/\/www.webmaid.de\/?p=175"},"modified":"2010-03-28T20:00:08","modified_gmt":"2010-03-28T19:00:08","slug":"heatmap-in-php","status":"publish","type":"post","link":"https:\/\/www.webmaid.de\/2010\/03\/heatmap-in-php\/","title":{"rendered":"Heatmap in PHP"},"content":{"rendered":"

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.
\n<\/p>\n

Einfache Heatmap-Implementierung ohne Optimierung<\/h2>\n

Javascript<\/h3>\n

Die Klicks werden einfach per Javascript erfasst. Um dies zu erreichen reichen folgende Zeilen:<\/p>\n

\/\/ Mouseclicks handeln\r\nwindow.captureEvents(Event.MOUSEDOWN);\r\ndocument.onmousedown = handleClick;<\/code><\/pre>\n

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.<\/p>\n

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

PHP<\/h3>\n

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\u00e4ter auswerten zu k\u00f6nnen. Die unten durchgef\u00fchrte Performance-Analyse zeigt, dass dies der beste Weg ist, um den Server nicht zu sehr zu belasten.<\/p>\n

<?php\r\n        $filename = '\/path\/to\/logfile.txt';\r\n        $fh = fopen($filename, 'a');\r\n\tif($fh) {\r\n\t\tfwrite($fh, '['.time().'] '.$_REQUEST['url'].' :: '.$_REQUEST['x'].'-'.$_REQUEST['y'].\"\\n\");\r\n\t\tfclose($fh);\r\n\t} else {\r\n\t\t\/\/ TODO errorhandling\r\n\t}\r\n?><\/code><\/pre>\n

Performance<\/h2>\n

Ein kleiner Test zur Performance zeigt, dass man die Werte an eine Datei h\u00e4ngen sollte und nicht die Datenbank nutzen sollte. Der Test bezieht sich auf eine MySQL-Datenbank, evtl. andere Implementierungen wie z.B. NoSQL k\u00f6nnen bessere Werte ergeben, da diese daf\u00fcr optimiert sind. Als NoSQL-Datenbank k\u00f6nnte man Cassandra<\/a> der Apache Foundation nutzen.<\/p>\n

Das Testskript<\/h3>\n

Zur Vereinfachung verzichtet das Testskript darauf die URL zu extrahieren und die notwendigen Teile wegzuschreiben, da dies f\u00fcr einen Performancevergleich unwichtig ist.<\/p>\n

<?php\r\n\t\/** \r\n\t* Copyright by codefab.eu - Dieses Skript darf genutzt und verteilt werden, jedoch muss \r\n\t* der Copyright-Vermerk erhalten bleiben! http:\/\/www.codefab.eu\/\r\n\t* Es wird keinerlei Verantwortung f\u00fcr Sch\u00e4den \u00fcbernommen, die durch das Skript entstehen.\r\n\t* Die Nutzung erfolgt auf eigene Gefahr.\r\n\t**\/\r\n        $hostname = 'hostname';\r\n        $username = 'username';\r\n        $password = 'passwort';\r\n        $database = 'databasename';\r\n\t\/\/ create table heatmap (ctime datetime not null, x int unsigned not null, y int unsigned not null);        \r\n        $table    = 'heatmap';\r\n        \/\/ Anzahl der Eintraege\r\n        $datasets = 250000;\r\n        \/\/ Filename fuer den Appendlauf\r\n        $filename = 'heatmap_log.txt';\r\n\r\n        \/\/ 1. Variante n Datensaetze mit einem einzigen Connect in die DB\r\n        $start = time();\r\n        $dbh = mysql_connect($hostname,$username,$password) or die ('Keine DB-Connection');\r\n        mysql_select_db($database,$dbh) or die ('DB unbekannt');\r\n        for($i=0;$i<$datasets;$i++) {\r\n                mysql_query('INSERT INTO '.$table.' VALUES (\\''.time().'\\','.$i.','.$i.')');\r\n        }\r\n        mysql_close($dbh);\r\n        echo '1. Dauer: '.(time()-$start).\"\\n\";\r\n\r\n        \/\/ 2. Variante n Datensaetze mit je einem Connect in die DB\r\n        $start = time();\r\n        for($i=0;$i<$datasets;$i++) {\r\n                $dbh = mysql_connect($hostname,$username,$password) or die ('Keine DB-Connection');\r\n                mysql_select_db($database,$dbh) or die ('DB unbekannt');\r\n                mysql_query('INSERT INTO '.$table.' VALUES (\\''.time().'\\','.$i.','.$i.')');\r\n                mysql_close($dbh);\r\n        }\r\n        echo '2. Dauer: '.(time()-$start).\"\\n\";\r\n\r\n        \/\/ 3. Variante n Datensaetze mit je einem pconnect in die DB\r\n        $start = time();\r\n        for($i=0;$i<$datasets;$i++) {\r\n                $dbh = mysql_pconnect($hostname,$username,$password) or die ('Keine DB-Connection');\r\n                mysql_select_db($database,$dbh) or die ('DB unbekannt');\r\n                mysql_query('INSERT INTO '.$table.' VALUES (\\''.time().'\\','.$i.','.$i.')');\r\n                mysql_close($dbh);\r\n        }\r\n        echo '3. Dauer: '.(time()-$start).\"\\n\";\r\n\r\n        \/\/ 4. Variante n Datensaetze an ein File haengen\r\n        $start = time();\r\n        $fh = fopen($filename, 'a') or die ('Datei unbekannt');\r\n        for($i=0;$i<$datasets;$i++) {\r\n                fwrite($fh, '['.time().'] '.$i.'-'.$i.\"\\n\");\r\n        }\r\n        fclose($fh);\r\n        echo '4. Dauer: '.(time()-$start).\"\\n\";\r\n        \/\/ 5. Variante n Datensaetze an ein File haengen mit explizitem fopen\r\n        $start = time();\r\n        for($i=0;$i<$datasets;$i++) {\r\n                $fh = fopen($filename, 'a') or die ('Datei unbekannt');\r\n                fwrite($fh, '['.time().'] '.$i.'-'.$i.\"\\n\");\r\n                fclose($fh);\r\n        }\r\n        echo '5. Dauer: '.(time()-$start).\"\\n\";\r\n?><\/code><\/pre>\n

Die Auswertung mit 250000 Eintragungen<\/h3>\n

F\u00fcr die einzelnen Methoden ergibt sich die folgende Laufzeit. Der Test wurde auf einem aktiven Webserver durchgef\u00fchrt; es kann also zu Messschwankungen kommen. Mit dem Skript kann jeder auf einem Entwicklungssystem die Werte selbst nachvollziehen und seine Heatmap optimieren.<\/p>\n

1. Dauer: 22\r\n2. Dauer: 127\r\n3. Dauer: 45\r\n4. Dauer: 2\r\n5. Dauer: 8<\/pre>\n

Auswertung<\/h3>\n

Man kann deutlich erkennen, dass die Fileoperationen wesentlich schneller gehen, als die Datenbankzugriffe. Um einen weiteren Performancegewinn zu erhalten k\u00f6nnte man evtl. auch noch einen Log-Daemon anbinden und den Performancetest dahingehend erweitern. Der dritte und f\u00fcnfte Test d\u00fcrfte der Realit\u00e4t am n\u00e4chsten kommen, wenn man ein unoptimiertes Heatmap-Logging-Skript verwendet.<\/p>\n

Optimierung<\/h3>\n

Zu aller erst sollte man die Optimierung auf der Clientseite vornehmen und sich an die Regeln f\u00fcr eine schnelle(re) Webseite<\/a> halten. Das A&O ist also die Minimierung der Anfragen zum Server. Da die Klicks mittels Javascript geloggt und \u00fcbertragen werden, sollte man also die Klicks zwischenspeichern und nur alle x Klicks bzw. alle y Sekunden die Daten an den Server \u00fcbertragen. Je Anfrage erh\u00e4lt der Server somit mehrere Eintr\u00e4ge und der Overhead (Datenbank-Connection \u00f6ffnen, File \u00f6ffnen o.\u00e4.) wird miniert. Den Gewinn der Optimierung entspricht den Testergebnissen bei einer Datenbank vom dritten zum ersten Fall und bei einem Logfile vom f\u00fcnften zum vierten Fall. Des weiteren wird die allgemeine Serverlast gesenkt, die durch das Aufrufen des Logskripts verursacht wird.<\/p>\n

Das Tutorial besteht aus zwei Teilen. Im zweiten Teil<\/a> wird kurz erkl\u00e4rt, wie man nun aus den gewonnenen Daten die grafische Auswertung herstellt. <\/p>\n","protected":false},"excerpt":{"rendered":"

Eine einfache Heatmap in PHP und Javascript erstellen inkl. Performance-Tipps.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,3],"tags":[32,89],"_links":{"self":[{"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/posts\/175"}],"collection":[{"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/comments?post=175"}],"version-history":[{"count":20,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/posts\/175\/revisions"}],"predecessor-version":[{"id":258,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/posts\/175\/revisions\/258"}],"wp:attachment":[{"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/media?parent=175"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/categories?post=175"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webmaid.de\/wp-json\/wp\/v2\/tags?post=175"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}