CFChart issues
I've been working on tracking down some funkiness with cfcharts. specifically their memory use. This is what I've noticed about it in CF2016 Update 3.
- Adobe uses EHcache to cache cfcharts in a Cache Instance called "CF_Chart_CacheManager".
- The "CF_Chart_CacheManager" doesn't get created until you use the cfchart tag.
- Previous versions of CF used to have the ability to set Threads in the CFAdmin, CF2016 does not, although this is still available in the adminapi. Not sure if it works. The docs says you can set this from 1 to 5.
- You can't set a custom Disk cache location. I can't set it in CFAdmin, the neo-graphing.xml, or the adminapi. It always resets back to the default.
- Changing the settings CFAdmin-> Server Settings -> Charting requires a restart to take affect.
- The Time-To-Live only appears to work when cache type is set to Disk Cache. This setting appears to be ignored when set to Memory.
- It doesn't seem to matter if you set it to Disk Cache or Memory Cache, the cfchart images seem to generate in both places. This means disk activity and memory.
- Although you can create the exact same chart over and over, it never really uses the previous chart its suppose to, it keeps creating new ones.
- When you set to Disk Cache, EHCache Max Elements In Memory are set to 1 and Disk Persistence: true. The timeToLiveSecods is 0, which means forever and timeToIdleSeconds is 0, also forever. Although "Maximum number of cached images" don't seem to apply to the Disk Cache, but it does seem to apply to the Memory Cache.
- Did I mention I hate ZingCharts? If they are going to break it, at lease break it with prettier charts. So many other options they could have chosen. These are ugly.
- Even if you set timeToIdleSeconds or timeToLiveSeconds in EHcache, there is no thread that will come along and expire those unless they get accessed with a get(), then they return null and get expired. This isn't happening with the charts, so they just sit in the CF_Chart_CacheManager. So then you have to wait to hit your maxElementsInMemory for EHcache to expire them. This isn't an Adobe issue, it’s just how EHcache works.
- Ultimately what's happening is that all these cfcharts are getting added to the EHcache Memory and not being removed until you hit a max. This is causing memory issues on my server.
- I also suspect that there may be a memory leak in the jvm heap related to all of this. I haven't gotten proof yet, but I did a heap dump with JProfiler and I had about 5G's of charts in memory. Even if I maxed out my Max Elements In Memory, it shouldn’t have used all that memory. I'm doing some debugging and will get back to this thread with my findings.
I’ve created some code to test some stuff. Feel free to play around with it.
<!--- get our CF_Chart_CacheManager --->
<cfscript>
/* what am I looking for */
chartCacheName='CF_Chart_CacheManager';
/* gets all the Cache Managers in EHCache */
cacheManagers = createObject('java', 'net.sf.ehcache.CacheManager').ALL_CACHE_MANAGERS;
for (item in cacheManagers) {
writeOutput('EHCache Manager Instance Name:' & #item.getName()# & '<br />');
/* pluck our Chart Cache */
if ( item.getName() == chartCacheName ) {
cm = item;
thisCache = cm.getCache(chartCacheName);
}
}
if(!isdefined('thisCache')) {
WriteOutput("CF_Chart_CacheManager doesn't exist yet.");
abort;
}
/* override configuration settings */
//thisCache.getCacheConfiguration().setTimeToLiveSeconds(30);
//thisCache.getCacheConfiguration().setTimeToIdleSeconds(30);
//thisCache.getCacheConfiguration().maxElementsInMemory(5);
</cfscript>
<!--- output some stats --->
<cfoutput>
<ul>
<li>Count: #NumberFormat(thisCache.getStatistics().getSize())#</li>
<li>Max Elements In Memory: #numberformat(thisCache.getCacheConfiguration().getMaxelementsInMemory())#</li>
<li>Disk Persistance: #YesNoFormat(thisCache.getCacheConfiguration().isDiskPersistent())#</li>
<li>Eternal: #YesNoFormat(thisCache.getCacheConfiguration().isEternal())#</li>
<li>Overflow To Disk: #YesNoFormat(thisCache.getCacheConfiguration().isOverflowToDisk())#</li>
<li>Time To Live: #NumberFormat(thisCache.getCacheConfiguration().getTimeToLiveSeconds())#</li>
<li>Time To Idle: #NumberFormat(thisCache.getCacheConfiguration().getTimeToIdleSeconds())#</li>
<li>Exists: #YesNoFormat(cm.cacheExists(chartCacheName))#</li>
</ul>
</cfoutput>
<!--- chart it, ironic --->
<cfchart format="png" chartwidth="600">
<cfchartseries type="bar" colorlist="##00FF00,##CC0000,##CCFF00,##FF0000,##CC0099,##0000FF,##FFFF66,##FFFFFF">
<cfchartdata item="Count" value="#thisCache.getStatistics().getSize()#" />
<cfchartdata item="Hits" value="#thisCache.getStatistics().cacheHitCount()#">
<cfchartdata item="Disk Hits" value="#thisCache.getStatistics().localDiskHitCount()#" />
<cfchartdata item="Memory Hits" value="#thisCache.getStatistics().localHeapHitCount()#" />
<cfchartdata item="Memory Misses" value="#thisCache.getStatistics().localHeapMissCount()#" />
<cfchartdata item="On Disk" value="#thisCache.getStatistics().getLocalDiskSize()#" />
<cfchartdata item="On Heap" value="#thisCache.getStatistics().getLocalHeapSize()#" />
<cfchartdata item="Off Heap" value="#thisCache.getStatistics().getLocalOffHeapSize()#" />
</cfchartseries>
</cfchart>
<!--- get methods we can call --->
<cfset WriteDump(thisCache.getStatistics())>
<!--- dump all the keys in this cache --->
<cfset WriteDump(thisCache.getKeys())>
