This weekend a user posted an interesting question to my forums. He wanted to know if there was a way (in ColdFusion of course) to determine how many seconds a user spent on a page. I decided to give this a try myself to see what I could come up with. Before we look at the code though, there are two things you should consider.

Number one - there is no ironclad way to actually determine the real amount of time a user spends looking at a web page. Yes, you can estimate it, but if I open your web page and than alt-tab over to my World of Warcraft session, then obviously the stats for my time on that page aren't accurate. So keep in mind that any numbers you get here will simply be estimates.

Number two - when it comes to web stats in general, I've found that it's almost always easier to let someone else worry about it - specifically Google. I remember parsing DeathClock.com logs and waiting 12+ hours for a report. The day I stopped parsing log files and just used Google Analytics was a good day indeed. GA does indeed provide this stat. (By the way, you guys spend, on average, one minute and forty-nine seconds on my site.)

So with that said, how can we track this in ColdFusion? There are many ways, but here is one simple method. I began by creating a session variable to store the data:

<cffunction name="onSessionStart" returnType="void" output="false"> <cfset session.pages = []> </cffunction>

The pages array will store the information I'm tracking. I decided on the Session scope as opposed to the Application scope as I wanted to keep it simple and just provide a report for the current user.

Next, in every onRequestStart, I look at the array. For each page request I'm going to log the URL and the current time. I'll then look at your last page and store a duration:

<!--- Run before the request is processed ---> <cffunction name="onRequestStart" returnType="boolean" output="false"> <cfargument name="thePage" type="string" required="true"> <cfset var data = "">

<!--- determine if we have a last page. ---> <cfif arrayLen(session.pages)> <!--- the last page's value is the timestamp, update it with the diff ---> <cfset session.pages[arrayLen(session.pages)].duration = dateDiff("s", session.pages[arrayLen(session.pages)].timestamp, now())> </cfif> <cfset data = {page=getCurrentURL(),timestamp=now()}> <cfset arrayAppend(session.pages,data)>

<cfreturn true> </cffunction>

Nothing too complex here really. If my session.pages array has any data, I must be on the second (or higher) page request. I do a quick dateDiff and store the result in the duration field. Outside the cfif I do a quick array append of a structure containing the current page url and time. The function getCurrentURL comes from CFLib.

The last thing I do with the data is to serialize it and store it when the session end:

<!--- Runs when session ends ---> <cffunction name="onSessionEnd" returnType="void" output="false"> <cfargument name="sessionScope" type="struct" required="true"> <cfargument name="appScope" type="struct" required="false">

<cfset var data = ""> <cfset var filename = "">

<!--- serialize ---> <cfwddx action="cfml2wddx" input="#arguments.sessionScope.pages#" output="data">

<!--- save it based on the sessionid value ---> <cfset filename = expandPath("./" & replace(createUUID(),"-","_","all") & ".txt")>

<cfset fileWrite(filename, data)> </cffunction>

That's it. There are a few things I'd probably do differently if I were to really deploy this code. First I'd use the database to store the updates. With a nice stored procedure it should run rather quickly. Even if I didn't do a DB call on each page update I'd at least change onSessionEnd. Notice that that you will have a 'hanging' page at the end with no duration. You could simply delete that from the array. Or you could use that last page and store it as another stat - the exit page.

I whipped up a real simple index page that dumps the session data and lists a few quick stats:

<cfdump var="#session#">

<cfset times = []> <cfloop index="p" array="#session.pages#"> <cfif structKeyExists(p, "duration")> <cfset arrayAppend(times, p.duration)> </cfif> </cfloop>

<cfoutput> <cfif arrayLen(times)> Average duration: #arrayAvg(times)# seconds.<br/> </cfif> Total number of pages visited: #arrayLen(session.pages)# </cfoutput>

And here is some sample output:

I've attached the code to the blog entry. Not sure how useful this is, but it's not like I've cared about usefulness in my other posts! ;)

Download attached file.