Desktop Analytics: The Embeddable Library


In the spirit of me actually spending the bulk of my time writing code, I’m going to dispense with the meandering long-winded stories and just show a few details of the upcoming implementation of the Desktop Analytics platform.

Within the next couple of months, I’m going to release the full product suite. The server is written in Java and the reporting GUI will be deployed as an Adobe AIR application, but those aren’t what I want to talk about today. Today I just want to provide a sneak-peek into the embeddable library that developers will link into their own applications to enable statistical reporting.

The embeddable library is written in the D programming language, which compiles to native code and exposes C linkage. (DLLs on Windows and SOs on Linux; the OSX compiler is a little out of date and has some bugs, but rumor has it that it’s going to get a major overhaul in the near future.) Initially, I’m only going to support C/C++ deployment on Windows and Linux. But shortly thereafter, I’m going to write wrappers for Java and .NET. And hopefully the OSX compiler will be up to snuff by this summer.

I’m shooting for maximum compatibility with the most in-demand development platforms, so I’m going to let users tell me which wrappers they’re most interested in seeing. (Chances are very good that I’ll also write a Flash/Flex/ActionScript implementation, to support analytics on RIA applications. My recent development experience in Flex has been exceptionally pleasant. Stay tuned!)

Another one of my goals is to make it absolutely dirt-simple for developers to use the library. In the most basic cases, it should take fewer than five lines of code.

Here’s a simple case:

#include <AnalyticsLib.h>

void main() {

  char* sessionId = AnalyticsLib.createSession(
     "https://stats.mydomain.com:8080/",
     "MyDeveloperId", "MyApplicationId", "MyApp Version 1.0.6"
  );

  /* ...application logic goes here... */

  AnalyticsLIb.closeSession(sessionId);
}

Aaaaaand that’s it!

Easy like Sunday morning!

The analytics library starts a background thread that handles all the nitty-gritty details. It caches all of its data locally (on disk), periodically communicating with the server (over HTTPS, if you like) and flushing its local cache. If the application (or computer) crashes suddenly, the data is all recoverable. The next time the user starts your application the analytics library will flush the data from the previous session, along with a flag indicating that session terminated abnormally.

If the user disconnects form the internet, or if the server goes offline for whatever reason (upgrades, restarts, etc), no problem. The library will cache its data on disk and re-submit later, when the network is connected and server is online. If it can’t submit the data during the current session, it’ll submit the next time the user runs your application.

When the library finally does connect with the server, they exchange a series of one-time-only, one-way-hashed authentication tokens, to prevent casual mischief-makers from sending the server bogus data.

Every time a new session is created, the library inspects the local operating environment, collecting information about the current device:

  • The operating system name and version.
  • The CPU name, speed, and number of cores.
  • The total amount of system RAM.
  • The number of local hard drives, and their total capacity and available space.
  • The number of screens, and their dimensions.
  • The presence (and versions) of the JVM and CLR.
  • The network bandwidth (bytes per second, downstream).

If you need other information about the local environment (like maybe the installed version of some particular DLL) you can collect that info too. It’s part of the API, which we’ll get into later, but it’s not built into the default environment inspection process.

NOTE: As a customer of this product, you’ll have to agree not to collect any personally-identifiable user information. It will be one of the terms of the license. You’ll also have to disclose to your users that you’re collecting anonymous usage data. The last decade has proven that it’s possible to collect anonymous statistical data on the web without breaching user privacy, and I stay within those bounds.

Along with the environment data, the library will also automatically report some historical device-usage statistics:

  • The start and end times of the session.
  • The currently-deployed version of the software.
  • The total cumulative number of sessions originating from this device.
  • The elapsed time since the previous session.
  • The elapsed time since installation.

Beyond the automatically-collected device and session information, the analytics library also provides a simple API for reporting developer-configurable data, in four different categories: environment variables, benchmarks, events, and logs.

Once you’ve created a session, if you need to collect any environment variables not already included in the default installation, you can call a simple function in the analytics library to submit your own:

AnalyticsLib.envNumeric("Workstation Uptime (Minutes)", 673.25);
AnalyticsLib.envText("Python Version", "2.5.2");

Environment variables are reported only once per session. Re-submitting a new value for a previously submitted environment variable will overwrite the old value with the new.

If you’re concerned about the performance characteristics of your code on your users’ computers, you can run benchmarks locally and submit the results of those benchmarks for analysis later. Wouldn’t it be great to know the mean, median, and standard deviations of the execution time for some expensive function in your code? Do most of your users have blazing fast computers, or are most of them running pokey old-timer machines?

double units = 10000; // The cross-referenced document contains 10,000 words.
AnalyticsLib.submitBenchmark("Document Cross-Reference", millis, units);

The benchmark API allows you to specify the number of work-units performed by your code as well as its execution time. Later, you can use this information to produce charts and graphs examining how your code’s execution time scales with its dataset. Do your algorithms have logarithmic or quadratic performance characteristics? If you submit benchmark data from within your application, you’ll be able to produce histograms reports for your entire user base.

And, of course, you’ll be able to compare the benchmarks of various different user groups. Is your application’s performance more sensitive to CPU speed or to the amount of system RAM?

The analytics library also provides an API for submitting arbitrary events. An event is an instantaneous moment in time, with an associated name, and optional textual and numeric values. Here are a few examples:

// User disconnected from the network.
AnalyticsLib.event("NetworkDisconnect");

// User was idle for 1 hour.
AnalyticsLib.eventNumeric("BackFromIdleState", 60.0);

// User invoked the SpellCheck feature, from the Tools|Text hierarchy.
// You can report on any level of this hierarchy.
AnalyticsLib.eventText("FeatureInvoke", "Tools|Text|SpellCheck");

// User spent 6 minutes reading the 'Beginner Tutorial' page in the Help system.
AnalyticsLib.eventTextNumeric("HelpSystem", "Beginner Tutorial", 6.0);

With these events, you’ll be able to write complex reports to analyze the usage patterns of your users. How fully do your trial users explore the user interface? Do your “premium version” users actually use the premium features that they paid for? Although your users have long-running sessions (maybe they run your software all day long at work), how much time do they spend actively in the application, verses letting it sit idle in the background?

Finally, the analytics library provides a mechanism for submitting log data.

try {
   doSomething();
} catch (const MyException& e) {
   char* failureType = e.getFailureType();
   char* message = e.getMessage();
   AnalyticsLib.log("3D Rendering Subsystem", failureType, message);
}

In your ongoing effort to improve product quality, you can inspect those remote error logs to find the hard-to-reproduce faults that never seem to occur in your own development environment. You can even run statistical reports about the modules where the errors originated, the types of errors that occur, the types of devices where they occur most often, and the events immediately preceding the crash.

So those are the features! I hope you’re as excited as I am to see this stuff finally see the light of day in a few months.

Like I’ve said before, the embeddable library is about 90% complete (which is why I can describe it with such detail at this point). Currently, the lion’s share of my time is going into the GUI implementation. And boy is it going to be sexy. I couln’t be happier with the Adobe AIR platform. The Flex API is extremely well-designed, and the event-driven model is excellent. Also: the graphics are stunning. Pixel-perfection in a highly-functional GUI is finally possible, and I’m happy to say that the user interface is a near-verbatim recreation of my original mockup.

ActionScript, as a programming language, leaves a *little* to be desired, but it’s pretty decent, and the development environment is very productive.

Anyhow, I’m still working my ass off to get a 1.0 version released by the end of Q1, but I wanted to provide you guys with some of the implementation details that I’m hammering out, so that you can start salivating for the release.

Anyone interested in beta testing?

6 Responses to “Desktop Analytics: The Embeddable Library”

  1. James Says:

    “Initially, I’m only going to support C/C++ deployment on Windows and Linux.”

    This rather suggest you chose a technology which suited you, as opposed to a technology which suited your customers.

  2. benji Says:

    Thanks for the comment, James!

    Actually, I *did* choose the technology most suited to me. Here were the characteristics I was looking for in an implementation language:

    * The more compatibility the better. If I write the library in Java, then it’s absolutely limited to JVM languages. If I want lots of people to buy my product (and I do) then I have to shoot for the least common denominator: I absolutely must provide a C interface. Once the C interface exists, I can build a wrapper for practically any other language that exists.

    * I want my software to be able to report interesting details about the hardware (CPU, memory, etc), so I need to use a language that can execute low-level system calls.

    Personally, I dislike C++ (though C isn’t quite so bad), and I didn’t want to write any C/C++ if I didn’t have to.

    Luckily, the D programming language meets my most important criteria. It’s not my favorite programming language, but it gets the job done well enough.

    I suppose if I could have written the library in any language at all, I would have chosen C#, which I think is very well designed.

    Throughout my career, I’ve written code in JavaScript, Visual Basic, Java, C#, Perl, Python, Ruby, C++, D, ActionScript, and a handful of other niche languages (including a special-purpose language whose compiler I built myself!)

    So I’m no language bigot, and I don’t find programming-langauge-choice a very interesting topic (though some people get VERY passionate about it). For me, the fun part of software development is the THING you’re building rather than the languages and tools that you use to build it.

  3. Jakob Says:

    Ah, so you are going to have a Java Wrapper – exciting! I’ll keep watching your blog – I think I would like to beta test when that wrapper is done.

  4. benji Says:

    Oh, there will absolutely be a Java version.

    I just figured the C/C++ version would be best to release first, since it’ll give me a foundation for developing all the subsequent higher-level language libraries.

    Personally, my hunch is that the .NET version is going to be the most popular, and I’d really rather release it first. But it’d be silly to keep writing the same code over and over again in different languages when I can write all the core features once and build upon those initial releases.

  5. Donnie Hale Says:

    One thought that came to mind is how this will work with all the “silent outbound connection-blocking” firewalls like ZoneAlarm? It seems like the end users of the applications that *your* users develop will have to be instructed to allow those applications outbound access.

  6. Simon Says:

    > One thought that came to mind is how this will work with all the “silent outbound connection-blocking” firewalls like ZoneAlarm?

    My company writes autoupdate software, and there was consideration to leverage this tool for client-side statistics (i.e. we have had requests, and so there is a market for such a tool). Products like ZoneAlarm and anti-virus software are an occasional problem but I wouldn’t rank them as a show-stopper. My only recommendation is that you write your code with excellent error handling capabilities, for every single API call. The ZoneAlarm/anti-virus tools can mess with almost all API calls and will generate funny errors if the user decides to block the outgoing server requests.

    Simon@AutoUpdate+
    http://AutoUpdatePlus.com
    Professional update management tool

Leave a Reply

You must be logged in to post a comment.