Tuesday 20 February 2007

Using GetTickCount() More Effectively in ColdFusion

We've got some legacy code that we're wrapping into a Model Glue: Unity (http://model-glue.com/) environment at the moment. Some of it was behaving badly and running like the proverbial dog.

I needed something to measure performance of sections of code, possibly sections spanning multilpe templates. I needed it to report in a helpful way. So I knocked together Daves Tick Counting Machine (tm).

It enables you to drop 2 lines of code in with existing code to start performance measuring (first line=start, 2nd lin=stop, like a stopwatch).

It gives you a nice sequential, nested output at the end. And it lets you display the results either on screen, to a log file, or both.

Here's the code: -

In application.cfm: -

<cfset request.tcm=createObject("component","DavesTickCountingMachine").init(enabled=true,outputToScreen=true,outputToLog=false)>


To start/stop monitoring: -

<cfset request.tcm.toggleCounter("Counter Name")>


To view output: -

<cfoutput>#request.tcm.report()#</cfoutput>


DavesTickCountingMachine.cfc: -

<Cfcomponent name="davesTickCountingMachine" hint="Provides a method of recording performance data">
<cffunction name="init" hint="creates the tick counting machine">
<cfargument name="enabled" default="true" type="boolean" hint="Turns the TCM on (true) and off (false)">
<cfargument name="outputToScreen" default="true" type="boolean" hint="Turns the TCM screen output on (true) and off (false)">
<cfargument name="outputToLog" default="false" type="boolean" hint="Turns the TCM csv file output on (true) and off (false). File is saved in root of calling file.">
<cfset this.enabled=arguments.enabled>
<cfset this.outputToScreen=arguments.outputToScreen>
<cfset this.outputToLog=arguments.outputToLog>
<!---holds performance data--->
<cfset this.TCM=structNew()>
<cfset this.seq=0>
<Cfreturn this>
</cffunction>

<cffunction name="toggleCounter" hint="Start or Stop timing an item">
<Cfargument name="counterName" hint="The unique identifier for the section of code being analysed">
<cfset var whatTimeIsIt=getTickCount()>
<cfif this.enabled eq "true">
<cfset request.seq=this.seq+1><!--- increment seq no--->
<Cfif not structKeyExists(this.TCM,arguments.counterName)>
<!--- if the counter doesn't exist, create a new struct--->
<cfset this.TCM[arguments.counterName]=structnew()>
<cfset this.TCM[arguments.counterName].start=whatTimeIsIt>
<cfset this.TCM[arguments.counterName].seq=this.seq>
<cfset this.TCM[arguments.counterName].name=arguments.counterName>
<cfset this.TCM[arguments.counterName].stop=0>
<Cfelseif this.TCM[arguments.counterName].stop eq 0>
<!--- it's an existing counter, so this must be the end time--->
<cfset this.TCM[arguments.counterName].stop=whatTimeIsIt>
</Cfif>
<Cfelse>
<!--- it's turned off. return fast--->
<Cfreturn "">
</cfif>
</cffunction>

<cffunction name="report" hint="Returns the analysis report" output="true">
<cfif this.enabled eq "true">
<style>
.tcm{
background:##eeeeee;
padding:5px;
margin:5px;
background-repeat:no-repeat;
border:1px solid navy;
}
</style>
<!--- calculate total times --->
<Cfloop collection="#this.tcm#" item="fn">
<Cfif structKeyExists(this.tcm[fn],"start") and structKeyExists(this.tcm[fn],"start")>
<cfset this.tcm[fn].time=this.tcm[fn].stop-this.tcm[fn].start>
<Cfelse>
<cfset this.tcm[fn].err=true>
</Cfif>
</Cfloop>

<cfset tickData=arrayNew(1)>
<Cfloop collection="#this.tcm#" item="fn">
<!--- build an array so we can sort the data --->
<cfset log="">
<cfset log=structNew()>
<cfset log.fn=fn>
<cfset log.seq=this.tcm[fn].seq>
<cfset log.time=this.tcm[fn].start>
<cfset log.action="start">
<cfset arrayAppend(tickData,log)>
<cfset log="">
<cfset log=structNew()>
<cfset log.fn=fn>
<cfset log.seq=this.tcm[fn].seq>
<cfset log.time=this.tcm[fn].stop>
<cfset log.action="stop">
<cfset arrayAppend(tickData,log)>
</Cfloop>

<cfset MovedSomething=1>
<!--- now sort the data so it can be represented in the order it actually happened--->
<Cfloop condition="MovedSomething eq 1">
<cfset MovedSomething=0>
<Cfloop from=2 to="#arraylen(tickData)#" index="i">
<cfset leftItem=tickData[i-1]>
<cfset rightItem=tickData[i]>
<cfif leftItem.time gt rightItem.time>
<!--- if first item started after 2nd, then swap --->
<cfset arraySwap(tickData,i-1,i)>
<cfset movedSomething=1>
<Cfelseif leftItem.time eq rightItem.time>
<!--- They started at same time. --->
<cfif leftItem.action eq rightItem.action>
<!--- they are both the same action --->
<Cfif leftItem.action eq "stop">
<!--- they are both EOF --->
<Cfif leftItem.seq lt rightItem.seq>
<!--- assume that the item that started first, stopped last - i.e. nested --->
<cfset arraySwap(tickData,i-1,i)>
<cfset movedSomething=1>
</Cfif>
<Cfelse>
<!--- they are both SOF --->
<Cfif leftItem.seq gt rightItem.seq>
<!--- move to start seq order--->
<cfset arraySwap(tickData,i-1,i)>
<cfset movedSomething=1>
</Cfif>
</Cfif>
<Cfelseif leftItem.seq gt rightItem.seq>
<cfset arraySwap(tickData,i-1,i)>
<cfset movedSomething=1>
</cfif>
</cfif>
</Cfloop>
</Cfloop>

<!--- display it. nest child processes --->
<cfif this.outputToScreen eq "true">
<div class="tcm" >
<div style="background:navy;color:white;padding:5px;">Dave's TickCounting Machine (tm)</div>
<div style="float:left;">
<Cfif arrayLen(tickData) neq 0>
<cfset base=tickData[1].time-1>
<cfset depth=0>
<TABLE>
<Cfloop from=1 to="#arrayLen(tickData)#" index="i">
<Cfif tickData[i].action eq "start"><cfset depth=depth+1></Cfif>
<tr>
<td>#repeatString("   ",depth)##tickData[i].fn#</td>
<td>(#tickData[i].action#)</td>
<td align=right>#tickData[i].time-base#ms</td>
</tr>
<Cfif tickData[i].action eq "stop"><cfset depth=depth-1></Cfif>
</Cfloop>
</TABLE>
<Cfelse>
No data was recorded for this request
</Cfif>
</div>
<div style="float:left;padding-left:20px">
<strong>How's It Work?</strong>
<br><br>
Simple.
<br><br>1. Drop a ##request.tcm.toggleCounter("ID String")## at the point you want to start timing.
<br>2. Drop a ##request.tcm.toggleCounter("ID String")## at the point you want to end timing.
<br>3. Relax - Dave's TickCounting Machine (tm) does the rest...
<br><br>
</div>
<br style="clear:both">
</div>
</cfif>
<cfif this.outputToLog eq "true">
<div class="tcm" >
<div style="background:navy;color:white;padding:5px;">Dave's TickCounting Machine (tm)</div>
<cfset date=dateformat(now(),"dd-mmm-yyyy")>
<cfset time=timeformat(now(),"HH:MM:SS")>
<cfset path_trans=cgi.path_translated>
<cfset url_path=url.path>

<Cfif arrayLen(tickData) neq 0>
<Cfloop from=1 to="#arrayLen(tickData)#" index="i">
<Cfif tickData[i].action eq "start"><cfset depth=depth+1></Cfif>
<cfset logrow="">
<cfset logRow=listAppend(logRow,date)>
<cfset logRow=listAppend(logRow,time)>
<cfset logRow=listAppend(logRow,depth)>
<cfset logRow=listAppend(logRow,path_trans)>
<cfset logRow=listAppend(logRow,url_path)>
<cfset logRow=listAppend(logRow,tickData[i].fn)>
<cfset logRow=listAppend(logRow,tickData[i].action)>
<cfset logRow=listAppend(logRow,tickData[i].time-base)>
<Cffile action="append" file="#expandpath("tcm.log.csv")#" output="#logRow#" addnewline="true" mode="775">
<Cfif tickData[i].action eq "stop"><cfset depth=depth-1></Cfif>
</Cfloop>
TCM Data logged to: <a target="_new" href="tcm.log.csv">#expandpath("tcm.log.csv")#</a>
<Cfelse>
No data was recorded for this request
</Cfif>
</div>
</cfif>
</cfif>
</cffunction>
<cffunction name="deleteLog" hint="Deletes the log file">
<Cffile action="delete" file="#expandpath("tcm.log.csv")#">
</cffunction>
</Cfcomponent>

Monday 19 February 2007

Rant About The Royal Mail

Oh yeah. Before I go I want to have a rant about the Royal Mail.

I ordered a bunch of stuff for my lovely wife for Valentines last week (hello lovely wife!). One of the things was a box with some balloons and some other bits and pieces (just novelty stuff). For fun.

The supplier (http://www.balloonsbypost.com/ - who incidentally have some great gift balloon packages and will bend over backwards to be helpful - and they're a charity too!) duly posted everything off in plenty of time.

Whereupon the Royal Mail decided the best thing to do with a "Next Day, Before Midday" package would be to ship it to the wrong hub, leave it in a corner and deliver it almost a week later.

They finally managed to deliver the package this morning. Rather than the requested last Wednesday. So maybe when they say "next day" they really mean "next monday".

So well done Royal Mail. Thanks very much.

/rant

Website Habits...

Some websites are like crack. You just can't give them up.

This is my daily list... just in case anybody cares...

www.thedailywtf.com
www.ajaxian.com
www.theregister.co.uk
www.bbc.co.uk/news
www.slashdot.org

Right. I'll be off. I've got a bunch of other CF and JS stuff to post later, but I'm going to ration it. If I post it all at once I'll have nothing to talk about...

Cut, Copy & Paste the ColdFusion PageContext Buffer

I've been messing about with some localisation stuff lately and I wanted a way to intercept the ColdFusion output buffer so I could mess with it.

Now I know you can sort of use <CFSAVECONTENT> for that. But it's mildy irritating since it only lets you use it on a single page. You can’t (for example) start it on one template and end it on another. Tsk!

So what I needed was a solution. After much poking things with sticks (metaphorical sticks) I found the answer I was looking for.

I’ve knocked up a CFC that gives the ability to manipulate the contents of the output buffer of ColdFusion exposing the following methods: -

  • Cut(item) – Write the buffer contents to variable named item and clear the buffer.
  • Copy(item) – Write the buffer contents to variable named item.
  • Paste(item) – Past the contents of variable named item into the buffer.
  • Get(item) – Get the contents of variable named item
  • Put(item) – Update the contents of variable named item
  • Clear() – Clear the buffer.

Here’s the code: -

Buffer.cfc

<cfcomponent name="buffer" hint="Tools for messing with the ColdFusion string buffer">
<cffunction name="init" hint="I initialise the cfc">
<cfset this.capturedBuffers=structNew()>
<cfreturn this>
</cffunction>

<cffunction name="copy" hint="Copy contents of the buffer to the named item">
<Cfargument name="target" type="string" default="clipboard" hint="Item to copy to. Default is clipboard.">
<cfset tmpBuffer=GetPageContext().getOut().getBuffer().toString()>
<cfset this.capturedBuffers[arguments.target]=duplicate(tmpBuffer)>
</cffunction>

<cffunction name="paste" hint="Paste the selected buffer item to the output stream">
<Cfargument name="source" type="string" default="clipboard">
<cfoutput>#this.capturedBuffers[arguments.source]#</cfoutput>
</cffunction>

<cffunction name="cut" hint="Cut contents of the buffer to the named item">
<Cfargument name="target" type="string" default="clipboard">
<cfset tmpBuffer=GetPageContext().getOut().getBuffer().toString()>
<cfset this.capturedBuffers[arguments.target]=duplicate(tmpBuffer)>
<cfset this.clear()>
</cffunction>

<cffunction name="get" hint="Returns the contents of the named buffer item">
<Cfargument name="source" type="string" default="clipboard">
<cfreturn this.capturedBuffers[arguments.source]>
</cffunction>

<cffunction name="put" hint="Overwrites the contents of the named buffer item">
<Cfargument name="content" type="string" default="" required=true>
<Cfargument name="target" type="string" default="clipboard">
<cfset this.capturedBuffers[arguments.target]=arguments.content>
</cffunction>

<cffunction name="clear" hint="Resets the buffer">
<Cfcontent reset="yes">
</cffunction>
</cfcomponent>

So, how do I use this buffer.cfc thing? - Here’s an example

<!--- create the cfc --->

<cfset bufferObj=createObject("component","buffer").init()>

<!--- clear the buffer --->

<cfset bufferObj.clear()>

<!--- output some text --->

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

<!--- cut the text from the output buffer --->

<cfset bufferObj.cut("firstBit")>

<!--- output some more text --->

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

<!--- cut the text from the output buffer --->

<cfset bufferObj.cut("secondBit")>

<!--- Retrieve the "secondBit" from the cfc, bold all the e characters, update the cfc data--->

<cfset tmpText=bufferObj.get("secondBit")>

<cfset tmpTextp=replace(tmpText,"e","<strong>e</strong>","all")>

<cfset bufferObj.put(tmpText,"secondBit")>

<!--- Paste secondBit and firstBit back into the stream - but in reverse order --->

<cfset bufferObj.paste("secondBit")>

<cfset bufferObj.paste("firstBit")>