Application Analytics

Mighty Moose is nearing release. One of the new items that we have built into it is some user tracking. The user tracking is extremely benign (we are tracking some various usage scenarios of how people are using the software). This feature was implemented in about two hours so I want to discuss a bit about the tracking and how we implemented because its a pretty cool way of doing things that many other applications could value from. So yes keep reading as there is technical stuff as to how its done as well :)

To begin with, we do not track who you are. We only track certain events that happen in the software such as bringing up a graph or failing a build. No user information is stored or correlated. As of this point there is no software switch to turn off tracking information. We are a small group in beta and we will add the ability to turn off this in the future. If you don’t want tracking don’t upgrade to the newest version (we will make this clear in the release notes as well).

Now how did we add all sorts of analytics to Mighty Moose in just a few hours? Well in Good Enough Software fashion we leveraged something existing. We used google analytics. Basically we made Mighty Moose pretend to be a web browser. It sends up information to google pixels directly. Doing this allows us to look at the users in the same way as what you would get for a web site. We made a whole series of “urls” that represent things happening in the software. After implementing it, I see some people have done this in mobile apps but I have never seen previously people doing it in a desktop app so the approach is fairly interesting. Just because I have not seen someone doing it does not mean that nobody has done it, but it does not seem to be very popular

Code is included at the end of the post but basically we just push pixel hits to urls like continuoustests.com/events/BuildCompleted. From there we can easily jump into real time view (or analyze historical data, the flows are particularly interesting to us!).

Screen Shot 2012 03 04 at 5 38 35 AM

Again this is very simple to add to your application and you can get quite good analytics out of it. We spent all of two hours doing. How do users use your app?

Code: Just use TrackEvent … Feel free to change how you see fit. Much is just changed code from google server side ASPX examples. One note, this code is not called very often in MM so the ThreadPool.QueueUserWorkItem is probably good enough. If you did a lot of events you would probably want to use a queue + a thread that read from it there (and probably detect if you were failing :-D to stop sending).

If you prefer gist here you go https://gist.github.com/1972785


public class Analytics
{
// Tracker version.
private const string Version = "4.4sa";

private const string CookieName = "__utmmobile";

// The path the cookie will be available to, edit this to use a different
// cookie path.
private const string CookiePath = "/";

// Two years in seconds.
private readonly TimeSpan CookieUserPersistence = TimeSpan.FromSeconds(63072000);

// 1x1 transparent GIF
private readonly byte[] GifData = {
0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
0x01, 0x00, 0x01, 0x00, 0x80, 0xff,
0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x02,
0x02, 0x44, 0x01, 0x00, 0x3b
};

private static readonly Regex IpAddressMatcher =
new Regex(@"^([^.]+\.[^.]+\.[^.]+\.).*");

// A string is empty in our terms, if it is null, empty or a dash.
private static bool IsEmpty(string input)
{
return input == null || "-" == input || "" == input;
}

// Get a random number string.
private static String GetRandomNumber()
{
Random RandomClass = new Random();
return RandomClass.Next(0x7fffffff).ToString();
}

// Make a tracking request to Google Analytics from this server.
// Copies the headers from the original request to the new one.
// If request containg utmdebug parameter, exceptions encountered
// communicating with Google Analytics are thown.
private static void SendRequestToGoogleAnalytics(string utmUrl)
{
try
{
WebRequest connection = WebRequest.Create(utmUrl);

((HttpWebRequest)connection).UserAgent = "";
connection.Headers.Add("Accept-Language",
"EN-US");

using (WebResponse resp = connection.GetResponse())
{
// Ignore response
}
}
catch (Exception ex)
{

throw new Exception("Error contacting Google Analytics", ex);

}
}

// Track a page view, updates all the cookies and campaign tracker,
// makes a server side request to Google Analytics and writes the transparent
// gif byte data to the response.
private static void TrackPageView(string path)
{
TimeSpan timeSpan = (DateTime.Now - new DateTime(1970, 1, 1).ToLocalTime());
string timeStamp = timeSpan.TotalSeconds.ToString();
string domainName = "continuoustests.com";
if (IsEmpty(domainName))
{
domainName = "";
}

var documentReferer = "-";

string documentPath = path;
var userAgent = "";

// Try and get visitor cookie from the request.
string utmGifLocation = "http://www.google-analytics.com/__utm.gif";

// Construct the gif hit url.
string utmUrl = utmGifLocation + "?" +
"utmwv=" + Version +
"&utmn=" + GetRandomNumber() +
"&utmhn=" + "continuoustests.com" +
"&utmr=" + "moose" +
"&utmp=" + path.Replace("/", "%2F") +
"&utmac=" + "MO-29683017-1" +
"&utmcc=__utma%3D999.999.999.999.999.1%3B" +
"&utmvid=" + (visitor - DateTime.Today.GetHashCode());

SendRequestToGoogleAnalytics(utmUrl);
}

static int visitor = Guid.NewGuid().GetHashCode();

private const string GaAccount = "MO-29683017-1";
private const string GaPixel = "/ga.aspx";
private static string GoogleAnalyticsGetImageUrl(string _url)
{
System.Text.StringBuilder url = new System.Text.StringBuilder();
url.Append(GaPixel + "?");
url.Append("utmac=").Append(GaAccount);
Random RandomClass = new Random();
url.Append("&utmn=").Append(RandomClass.Next(0x7fffffff));
url.Append("&utmr=").Append("moose");
url.Append("&utmp=").Append(_url.Replace("/", "%2F"));
url.Append("&guid=ON");
return url.ToString().Replace("&", "&");
}
public static void SendEvent(string name)
{
ThreadPool.QueueUserWorkItem(x =>
{
try
{
TrackPageView("/event/" + name);
}
catch { }
});
}
}

One Comment

  1. Javier Troconis
    Posted March 16, 2012 at 4:32 am | Permalink | Reply

    Man this post is awesome, I’ve been developing this little project in silverlight where I needed to track a whole bunch of events. What I did was to create a queue of events that eventually get pushed and persisted to the database. In the reporting side I was using sql report services to analize the data. Had I known what the hell was google analytics before, I wouldn’t have done any of this. I’ve heard about it but never paid that much attention.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 8,578 other followers

%d bloggers like this: