While reviewing reports on Lucee's Dev Forum regarding performance differences between using an application-based CFC singleton and a global UDF, I thought I'd release our replacement for Adobe's CFTimer that I wrote back in 2013.
No more editing "Debugging IP Addresses"
If you wanted to use the built-in CFTimer function, Adobe required devs to specifically add their current IP address to the list of debugging IP addresses. This is inconvenient given that the task that it performs shouldn't really require special security... unless it's possible that Adobe's implementation of it could be compromised somehow. (Not sure, just guessing as I can't see any reason why this function requires special rules when you should be able to pass an option to suppress output under desired conditions.) Some third-party developers may not have access to CFAdmin or admin-based API functions, so they are left without any options other than to manually capture and output getTickCount()
. (NOTE: I did have rules to pass a list of AllowedIPs, but the filter required a ipUtils.cfc
library that is not available for public release yet due to third-party dependencies.)
Not Supported: Type="Debug"
I don't believe that developers have any way of influencing or adding to the output of Adobe's debug output. I used debug mode "once" to see what it did and then immediately disabled it and never bothered to use it again. (It can break too much if it is blindly enabled server or application-wide.)
Introducing nanoTime (already installed w/java.lang.system
)
I didn't want to be required to loop over thousands or millions of iterations to compare performance differences, so the addition of nanoTime proved to be extremely beneficial as it provides accuracy down to the "one billionth of a second". It's not perfect, but it is at least an option.
Check out the differences between nanoTime and using millesconds.
Java System.nanoTime() vs System.currentTimeMillis
https://www.geeksforgeeks.org/java-system-nanotime-vs-system-currenttimemillis/
Use as CFScript
This can be called within CFScript by calling it using the following syntax.
cf_timer(type="outline", label="My timer test", nano=true){
performAnythingThatYouWantToMeasure();
}
Anyway, here's the CFTag. Enjoy!
https://gist.github.com/JamoCA/905c21d434cbef10b03cf799f9b93f16
<cfsilent><!--- | |
CF_Timer: Like Adobe's built-in CFTimer, but without requiring admin IP rule and also including nanoTime. | |
Author: James Moberg | |
Date: 9/20/2013 | |
Shared: 7/3/2022 | |
Notes: Called like cftimer but without enabling debugging. (NOTE: "debug" mode not supported.) | |
Types: outline (default) | |
Credit: Initial "myTimer" CFTag by Chris Phillips ( http://www.cfchris.com/cfchris/index.cfm/2007/4/5/My-CFTIMER-Custom-Tag ) | |
Adobe CFTimer Docs: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-t/cftimer.html | |
Blog Entry: https://dev.to/gamesover/cftimer-no-debugging-ip-address-required-introducing-nanotime-1k8h | |
Tweet: https://twitter.com/gamesover/status/1543638190761816065 | |
EXAMPLE USAGE | |
<cf_timer label="Sleep Test" type="outline"> | |
<!--- Do some stuff ---> | |
<cfset sleep(50)> | |
</cf_timer> | |
STORE EXECUTION TIME (MILLISECONDS) AS VARIABLE "variables.TimeLapsed" | |
<cf_timer variable="TimeLapsed"> | |
<!--- Do some stuff ---> | |
<cfset sleep(50)> | |
</cf_timer> | |
<cfoutput><p>Time Lapsed = #TimeLapsed# ms</p></cfoutput> | |
STORE EXECUTION TIME (MILLISECONDS) AS VARIABLE "variables.TimeLapsed" | |
<cf_timer> | |
<!--- Do some stuff ---> | |
<cfset sleep(50)> | |
</cf_timer> | |
<cfoutput> | |
---> | |
<cfparam name="attributes.label" default="cftimer"> | |
<cfparam name="attributes.type" default="outline"><!--- comment, inline, outline ("debug" not supported) ---> | |
<cfparam name="attributes.variable" default=""> | |
<cfparam name="attributes.allowedIPs" default=""> | |
<cfparam name="ATTRIBUTES.nano" type="boolean" default="false"> | |
<cfparam name="ATTRIBUTES.returnType" type="string" default=""> <!--- "ns", "μs", "ms", "s" ---> | |
<cfscript> | |
numeric function getNano() hint="returns nano time (more accurate)" { | |
return createObject("java", "java.lang.System").nanoTime(); | |
} | |
numeric function getTick() hint="Railo or OpenBD may not return ms since EPOC" { | |
var tickCount = GetTickCount(); | |
if (not find("ColdFusion", server.coldfusion.productname)){ | |
try { | |
tickCount = CreateObject("java", "java.lang.System").currentTimeMillis(); | |
} catch(any e){}; | |
} | |
return tickCount; | |
} | |
string function prettyDecimal(numeric inputString=0){ | |
var response = trim(REReplace(numberformat(val(arguments.inputString), '9,999.999999999999'), "0+$", "")); | |
return response.replaceAll("\.$", ""); | |
} | |
</cfscript> | |
</cfsilent> | |
<cfif thistag.executionmode is "Start"> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.startTimer = getNano()> | |
<cfelse> | |
<cfset variables.startTimer = getTick()> | |
</cfif> | |
<cfelseif thistag.executionmode is "End"> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.endTimer = getNano()> | |
<cfelse> | |
<cfset variables.endTimer = getTick()> | |
</cfif> | |
<cfset variables.final = (variables.endTimer - variables.startTimer)> | |
<cfif len(ATTRIBUTES.returnType)> | |
<cfif ATTRIBUTES.nano> | |
<cfif variables.final gt 10000000000> | |
<cfset ATTRIBUTES.returnType = "s"> | |
<cfelse> | |
<cfset ATTRIBUTES.returnType = "ms"> | |
</cfif> | |
<cfelse> | |
<cfif variables.final gt 10000> | |
<cfset ATTRIBUTES.returnType = "s"> | |
<cfelse> | |
<cfset ATTRIBUTES.returnType = "ms"> | |
</cfif> | |
</cfif> | |
</cfif> | |
<cfswitch expression="#ATTRIBUTES.returnType#"> | |
<cfcase value="ns nanoseconds" delimiters=" "> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final) & " ns")> | |
<cfelse> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final * 1000000) & " ns")> | |
</cfif> | |
</cfcase> | |
<cfcase value="μs microseconds" delimiters=" "> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final / 1000) & " μs")> | |
<cfelse> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final * 1000) & " μs")> | |
</cfif> | |
</cfcase> | |
<cfcase value="ms milliseconds" delimiters=" "> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final / 1000000) & " ms")> | |
<cfelse> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final) & " ms")> | |
</cfif> | |
</cfcase> | |
<cfcase value="s seconds" delimiters=" "> | |
<cfif ATTRIBUTES.nano> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final / 1000000000) & " s")> | |
<cfelse> | |
<cfset variables.PerfTime = (prettyDecimal(variables.final / 1000) & " s")> | |
</cfif> | |
</cfcase> | |
<cfdefaultcase> | |
<cfset variables.PerfTime = variables.final> | |
</cfdefaultcase> | |
</cfswitch> | |
<cfif len(trim(attributes.variable)) AND isvalid("variableName", attributes.variable)> | |
<cfset caller[attributes.variable] = variables.PerfTime> | |
<cfelseif attributes.type IS "outline"> | |
<cfoutput><fieldset style="word-break:break-word;"><legend><CFIF LEN(Attributes.Label)>#attributes.label#: </CFIF>#variables.PerfTime#</legend>#THISTAG.GeneratedContent#</fieldset></cfoutput> | |
<CFSET THISTAG.GeneratedContent = ""> | |
<cfelseif attributes.type IS "inline"> | |
<cfoutput><CFIF LEN(Attributes.Label)>#attributes.label#: </CFIF>#variables.PerfTime# ms</cfoutput> | |
<cfelseif attributes.type IS "comment"> | |
<cfoutput><!-- <CFIF LEN(Attributes.Label)>#attributes.label#: </CFIF>#variables.PerfTime# ms--></cfoutput> | |
<cfelse> | |
<!--- unknown, just output the content ---> | |
</cfif> | |
</cfif> |
Top comments (1)
Cool tool, James. I've added a feature request related to this. More in a moment.
FWIW, if it may not be clear to some, a design goal of that original cftimer (added in CF7) was indeed that it WAS intentionally tied to the CF Admin debugging feature, such that if it was enabled then like that debugging output the timer info was only shown to those on the IP address list (for better or worse).
Conversely, one could even leave the cftimer in the code in prod (where one might conceivably not enable debugging output) and they would not need to remove it.
I guess what you're saying is that it should have offered a way to run even with debugging disabled, and thus regardless of the client making the request being in the debug ip list. That's a fair point. They could have added that as an attribute to the tag (or arg to the script equivalent).
I suspect there was not much call to it, because really I bet 99% of CF devs don't even realize the feature is there, since CF7 about 20 years ago. So few may have been pressed to consider what you've raised. I just checked, and I see no one has filed a feature request for it, so I just did. :-)
tracker.adobe.com/#/view/CF-4214324
I realize I may not say what you would have, but you can certainly add a comment. And interested readers here can at least a vote if they think the idea may help.
Of course, until Adobe may address it, at least folks who find this post and code of yours can use this instead. And I added a comment pointing folks here.
Thanks for the efforts.