This software article series can be applied to gadget/widget programming, and is exemplified on Google Desktop gadgets.


Gadget Usage Stats

Teo (Teodor Filimon), Gadget Developer
July 2008
"It's the question that drives us" - Trinity, The Matrix

Introduction

One of the most rewarding parts of making a gadget is seeing that people use it and send feedback. Detailed statistics about the gadget usage combined with the feedback will help you make improvements in the right direction. This article will show you how to fetch usage stats (like number of users, session lengths, etc.) and when it's ok to do it keeping in mind the privacy of the user.

Information Flow

The easiest way to understand the stat fetching process is to break it down into steps. For example, you can take a look at the diagram below:

Path of information
Figure 1: Information flow

Let's start from the end: the best way to store our stats is a database. A free and widely used database system is MySQL and it is supported by most web hosting providers. Elements can be automatically inserted in this database by a server-side script file (PHP, for instance) which is activated by your gadget through XMLHttpRequest whenever a monitored event occurs (gadget installation, gadget closing, etc.)

In the following sections, every step will be detailed using as a case study the methods i used in the Web TV & Radio gadget. The Javascript file which comprises this methodology is also available in an open-source project.

Database

If you choose a MySQL database, a tool that can help you with its administration is called phpMyAdmin, also offered by most providers. Every element in this database will ultimately represent an action, so it makes sense to have a field called 'Actions'.

So what actions can we have? For now, lets consider the ones that interest us the most:

Now that we think about it, more fields will be needed to for extra information. Why? Well, we could have a 'Parameters' field, which contains, let's say the version of your gadget for the 'install' action or a user ID for the 'confirm' action. This way we gain so much more extra information, like how many people install different versions of your gadgets. Notice that the user ID doesn't imply a feature available to the user, so it has to be generated in an anonymous way so it will help us only with gadget life stats. We'll look at how we can do this in the following sections. Another field that would be helpful is one that we can call 'Moment', and it will hold the time when an action occurred. Here is the necessary code to create the table on the structure we defined (you can run this code with phpMyAdmin, in your desired database):

CREATE TABLE `Actions` (
   `Action` varchar(20) NOT NULL,
   `Parameters` text NOT NULL,
   `Moment` datetime NOT NULL,
   KEY `Action` (`Action`)
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;

At the end of the day, when the system is up and running and people start installing your gadgets you'll be able to see entries in your table:

Table
Figure 2: Stats sample

Server-side Script

As i've said, we need a server-side script which can be called by gadgets with certain parameters and which can, in turn, connect to the database and insert an entry in the table. In this example, we'll use PHP (check out the comments for details):

<?php

	//----VARIABLE PART OF THE PHP
	//location of the database host (given by providers):
	$dbhost = '_THE_HOST_';
	//username with which to log in the database:
	$dbuser = '_USERNAME_';
	//password:
	$dbpass = '_PASSWORD_';
	//database name:
	$dbname = 'web_tv_radio_stats';
	//insertion query into the right table, using the parameters which the PHP receives
	$dbquery = "INSERT INTO `Actions` (Action,Parameters,Moment) VALUES ".$_POST['Action']."','".$_POST['Parameters']."','NOW()')";

	//----CONSTANT PART OF THE PHP 
	//connecting to the database:
	$conn = mysql_connect($dbhost, $dbuser, $dbpass);
	mysql_select_db($dbname);
	//running the query:
	mysql_query($dbquery);
	//closing the database:
	mysql_close($conn);
	//optional return message:
	echo 'finished';
 ?>

So basically, you connect to the database, you run the query and then you close the database:

Connection to the database
Figure 3: What actually happens within the PHP

Initiating the Process within the Gadget

We acknowledged 2 aspects that can be of interest to us: how many people install the gadget and how long they keep it. In the case of these 2 issues, we can automatically send the messages to the PHP when the gadget is opened:

function _onOpen(){

	//setting default options
	//...
	options.putDefaultValue("firstInstall",true);
	options.putDefaultValue("UID",new Date().getTime());

	//getting a confirmation, and, if it's the case, an install notification
	var f=new Fetcher();
	if (options.getValue("firstInstall")){
		options.putValue("firstInstall",false);
		f.post(parameters.WebTvCentral,"install",parameters.version);
	}
	f.post(parameters.WebTvCentral,"confirm",options.getValue("UID"));
}

Notice how we get a unique ID, through the Date's object getTime() function which returns the number of milliseconds passed since 1970. :-) Keep in mind that this is not actually an ID, it's a key which allows us to group the confirmation messages and measure time lengths; everything is still perfectly anonymous. Extra actions which are triggered by users (like settings, feature use, etc.) should be fetched only with explicit permission; this can easily be done using a checkbox in the options dialog.

You might be wondering what exactly the Fetcher object is. I called it that way because it allowed me to fetch usage stats from a remote database (the object basically calls the PHP). Its implementation is below:

function Fetcher(){
}

//...

Fetcher.prototype.post=function(URL,Action,Parameters){

	try{ 
		var xmlObject = new XMLHttpRequest(); 
	} 
	catch (error){
		var xmlObject = new ActiveXObject("Microsoft.XMLHTTP");
	}

	//setting up the parameters:
	params="Action="+Action+"&Parameters="+Parameters;
	xmlObject.open("POST",URL,true);
	xmlObject.setRequestHeader('Content-type','application/x-www-form-urlencoded');
	//xmlObject.setRequestHeader("Content-length", params.length);
	//xmlObject.setRequestHeader("Connection", "close");
	xmlObject.send(params);
}

Conclusion

There we have it! A perfectly flexible and expandable usage stats system to help us measure the evolution of a gadget.

Resources


Author Bio

Teodor Filimon

I'm a natural born programmer. My first contact with a techno-gadget was Star Trek (remember those cool sensors?). I used to fill up a whole room with drawings of them when I was only 3 years old. Generally, I find a lot of inspiration for intuitive interfaces in things with "star" in their names (like Star Wars, Stargate,... :-) . I like the border between interface and function—it's the essence of a program, I think. Anyway, I'm a software engineering student now, and you can learn more about me and what I'm thinking and doing at my website or blog. My best gadgets are Web TV & Radio and DigiWatch.