Why is this ColdFusion 8 app causing CPU to spike?

197 views Asked by At

Thank you for taking the time to look at this question.... Bear with me....

The other day one of our Web Servers stopped serving up web pages. The Web Server is a physical server running Windows Server 2003 R2 Standard Edition Service Pack 2 with IIS 6. It runs ColdFusion 8.0.1 Standard with Java 1.6.0_24. This server has 3 public-facing websites that really don't get much usage. Pages on all three websites were timing out or returning 500 errors.

When I signed on to the server to see what the problem was, I was expecting to see the JRUN service using a ton of memory. The memory was fine, but, I noticed that the CPU was running at or near 100%. About 50% was being used by JRUN and the other 50% by a runaway backup process, which I killed. But then JRUN quickly ate up the CPU and was using close to 100%.

I looked at the ColdFusion logs and noticed several Java heap space errors occurring.

I looked at the IIS logs and noticed that there were a bunch of requests to an app which allows one of our customers to upload multiple image files for their products using uploadify. The app is written in ColdFusion, and uses jQuery to call a Web Service that handles the upload and resizes the uploaded images using <CFIMAGE>.

After seeing this information in the logs, I figured some part of this app must be the culprit.

I just can't seem to find what exactly caused the Java heap space errors and CPU spike. Any thoughts?

WebService CFC method:

<cffunction
    name="uploadFile"
    access="remote"
    returntype="Struct"
    output="false"
    returnformat="JSON">
    <cfargument name="FileName" type="String" default="" />
    <cfargument name="FileData" type="String" default="" />

    <cfscript>
        var _response = NewAPIResponse();
        var _tempFilePath = APPLICATION.TempDir & "\" & ARGUMENTS.FileName;
        var _qItem = QueryNew("");
        var _product = CreateObject("component", "cfc.Product");
        var _result = {};
        var _sku = "";

        /*
            Each file must be named [Part Number].[file extension], so, \
            parse the file name to get everything before the file extension
        */
        _sku =
            Trim(
                REQUEST.UDFLib.File.getFileNameWithoutExtension(
                    ARGUMENTS.FileName
                    )
                );
    </cfscript>

    <cfif Len(_sku) GT 20>
        <cfthrow
            message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
    </cfif>

    <cfset _qItem = _product.readSKU(_sku) />

    <cfif NOT _qItem.RECORDCOUNT>
        <cfthrow
            message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
    </cfif>

    <cfscript>
        FileCopy(ARGUMENTS.FileData, _tempFilePath);

        _aMessages =
            _product.setId(
                _qItem.SKU
                ).updateThumbnailImages(uploadFilePath = _tempFilePath);
    </cfscript>

    <cfif ArrayLen(_aMessages)>
        <cfthrow
            message="#ARGUMENTS.FileName#: #_aMessages[1].getText()#" />
    </cfif>

    <cfscript>
        _result["SKU"] = _product.getSKU();
        _result["SMALLIMAGESRC"] = _product.getSmallImageSrc();
        _result["LARGEIMAGESRC"] = _product.getLargeImageSrc();

        ArrayAppend(_response.data, _result);
    </cfscript>

    <cfreturn _response />
</cffunction>

Image Resizing Function:

<cffunction name="updateThumbnailImages" returntype="Array" output="false">
    <cfargument name="uploadFilePath" type="String" required="true" />

    <cfset var _image = {} />

    <cfif FileExists(ARGUMENTS.uploadFilePath)>
        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 500,
                maxWidth = 700
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getLargeImagePath()#" />

        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 300,
                maxWidth = 300
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getMediumImagePath()#" />

        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 50,
                maxWidth = 50
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getSmallImagePath()#" />
    </cfif>
</cffunction>

Image scaling UDFs:

<cffunction name="getDimensionsToEnlarge" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="minWidth" type="Numeric" required="true" />
    <cfargument name="minHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        if  (
                ARGUMENTS.minHeight > 0
            &&  ARGUMENTS.minWidth > 0
            &&  imageHeight < ARGUMENTS.minHeight
            &&  imageWidth < ARGUMENTS.minWidth
            ) {
            dimensions.width = ARGUMENTS.minWidth;
            dimensions.height = ARGUMENTS.minHeight;
        }

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="getDimensionsToShrink" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="maxWidth" type="Numeric" required="true" />
    <cfargument name="maxHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        if  (
                ARGUMENTS.maxHeight > 0
            &&  ARGUMENTS.maxWidth > 0
            &&  (
                    imageHeight > ARGUMENTS.maxHeight
                ||  imageWidth > ARGUMENTS.maxWidth
                )
            ) {
            dimensions.width = ARGUMENTS.maxWidth;
            dimensions.height = ARGUMENTS.maxHeight;
        }

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="getDimensionsToFit" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="minWidth" type="Numeric" required="true" />
    <cfargument name="minHeight" type="Numeric" required="true" />
    <cfargument name="maxWidth" type="Numeric" required="true" />
    <cfargument name="maxHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        dimensions =
            getDimensionsToEnlarge(
                imageHeight = ARGUMENTS.imageHeight,
                imageWidth = ARGUMENTS.imageWidth,
                minWidth = ARGUMENTS.minWidth,
                minHeight = ARGUMENTS.minHeight
                );

        if (dimensions.width < 0 && dimensions.height < 0)
            dimensions =
                getDimensionsToShrink(
                    imageHeight = ARGUMENTS.imageHeight,
                    imageWidth = ARGUMENTS.imageWidth,
                    maxWidth = ARGUMENTS.maxWidth,
                    maxHeight = ARGUMENTS.maxHeight
                    );

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="scale" returntype="Any" output="false">
    <cfargument name="imagePath" type="String" required="true" />
    <cfargument name="action" type="String" default="fit" hint="shrink, enlarge, or fit"/>
    <cfargument name="minWidth" type="Numeric" default="-1" />
    <cfargument name="minHeight" type="Numeric" default="-1" />
    <cfargument name="maxWidth" type="Numeric" default="-1" />
    <cfargument name="maxHeight" type="Numeric" default="-1" />

    <cfscript>
        var scaledDimensions = {
                width = -1,
                height = -1
            };
        var scaledImage = ImageNew();

        scaledImage = ImageNew(ARGUMENTS.imagePath);

        switch (ARGUMENTS.action) {
            case "shrink":
                scaledDimensions =
                    getDimensionsToShrink(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        maxWidth = ARGUMENTS.maxWidth,
                        maxHeight = ARGUMENTS.maxHeight
                    );

                break;
            case "enlarge":
                scaledDimensions =
                    getDimensionsToEnlarge(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        minWidth = ARGUMENTS.minWidth,
                        minHeight = ARGUMENTS.minHeight
                    );

                break;
            default:
                scaledDimensions =
                    getDimensionsToFit(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        minWidth = ARGUMENTS.minWidth,
                        minHeight = ARGUMENTS.minHeight,
                        maxWidth = ARGUMENTS.maxWidth,
                        maxHeight = ARGUMENTS.maxHeight
                    );

                break;
        }

        if (scaledDimensions.width > 0 && scaledDimensions.height > 0) {
            // This helps the image quality
            ImageSetAntialiasing(scaledImage, "on");

            ImageScaleToFit(
                scaledImage,
                scaledDimensions.width,
                scaledDimensions.height
                );
        }

        return scaledImage;
    </cfscript>
</cffunction>
1

There are 1 answers

4
Alan Bullpitt On

Below is the example code I spoke of in the comments section. This improved the reliability for us but under extreme load we found the issue still occured which is why we moved to Java 1.7. However 1.7 had it's own problems as the performance drops dramatically as outlined here. Hope it helps.

<cffunction name="init" access="private" output="false" returntype="void">
    <cfset variables.instance = StructNew() />
    <!--- create object and set to variables.instance --->
    <cfset setProductObject()>
</cffunction>

<cffunction name="setProductObject" access="private" returntype="void" output="false">
    <!--- create object once and set it to the variables.instance scope --->
    <cfset var product = createObject("component","cfc.Product")>
    <cfset variables.instance.product = product/>
    <cfset product = javacast('null', '')>
</cffunction>

<cffunction name="getProductObject" access="private" returntype="cfc.Product" output="false">
    <!--- instead of creating object each time use the one already in memory and duplicate it --->
    <cfset var productObj = duplicate(variables.instance.product)>
    <cfreturn productObj />
</cffunction>

<cffunction name="uploadFile" access="remote" returntype="struct" returnformat="JSON" output="false">
    <cfargument name="FileName" type="String" default="" />
    <cfargument name="FileData" type="String" default="" />
        <cfscript>
            var _response = NewAPIResponse();
            var _tempFilePath = APPLICATION.TempDir & "\" & ARGUMENTS.FileName;
            var _qItem = QueryNew("");
            var _product = "";
            var _result = {};
            var _sku = "";
            //check init() function has been run if not run it
            if NOT structKeyExists(variables,'instance') {
                init();         
            }

            _product = getProductObject();
            /*
                Each file must be named [Part Number].[file extension], so, \
                parse the file name to get everything before the file extension
            */
            _sku =
                Trim(
                    REQUEST.UDFLib.File.getFileNameWithoutExtension(
                        ARGUMENTS.FileName
                        )
                    );
        </cfscript>

        <cfif Len(_sku) GT 20>
            <cfthrow
                message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
        </cfif>

        <cfset _qItem = _product.readSKU(_sku) />

        <cfif NOT _qItem.RECORDCOUNT>
            <cfthrow
                message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
        </cfif>

        <cfscript>
            FileCopy(ARGUMENTS.FileData, _tempFilePath);

            _aMessages =
                _product.setId(
                    _qItem.SKU
                    ).updateThumbnailImages(uploadFilePath = _tempFilePath);
        </cfscript>

        <cfif ArrayLen(_aMessages)>
            <cfthrow
                message="#ARGUMENTS.FileName#: #_aMessages[1].getText()#" />
        </cfif>

        <cfscript>
            _result["SKU"] = _product.getSKU();
            _result["SMALLIMAGESRC"] = _product.getSmallImageSrc();
            _result["LARGEIMAGESRC"] = _product.getLargeImageSrc();

            ArrayAppend(_response.data, _result);
        </cfscript>

        <cfreturn _response />
</cffunction>