Pages

Wednesday 16 January 2013

Picking apart CRM 2011's diagnostics page - Part 2

In the first part of this article I covered the basics of what goes on behind the scenes of CRM 2011's diagnostics page. If you missed it, catch up on it here: Picking apart CRM 2011's diagnostics page - Part 1.
I am now going to dive deeper into the JavaScript sections of the diagnostics tests. In total there are 8 JavaScript performance tests with 5 of those nested together under the JavaScript Dom stress test. I call these stress tests as they are basically memory and CPU intensive performance tests designed to give an indication of client computer (and browser) performance.
When running these tests they produce the following output (numbers may differ):
=== Array Manipultaion Benchmark ===
Time: 286 ms
Client Time: Wed, 16 Jan 2013 13:59:08 UTC
=== Morph Benchmark ===
Time: 334 ms
Client Time: Wed, 16 Jan 2013 13:59:08 UTC
=== Base 64 Benchmark ===
Time: 35 ms
Client Time: Wed, 16 Jan 2013 13:59:08 UTC
=== DOM Benchmark ===
Total Time: 561 ms
Breakdown:
  Append:  20ms
  Prepend: 25ms
  Index:   465ms
  Insert:  22ms
  Remove:  29ms
Client Time: Wed, 16 Jan 2013 13:59:09 UTC
So what does all that mean? What does each of these tests actually do?
Now it all gets interesting. :)

I spent some time taking apart the exact JavaScript functions executed for each of these tests. I have detailed them below including the source snippet.

Array Manipulation Benchmark
function arrayBenchmark() {
    for (var ret = [], tmp, n = 2e3, j = 0; j < n * 15; j++) {
        ret = [];
        ret.length = n
    }
    for (var j = 0; j < n * 10; j++)
        ret = new Array(n);
    ret = [];
    for (var j = 0; j < n; j++)
        ret.unshift(j);
    ret = [];
    for (var j = 0; j < n; j++)
        ret.splice(0, 0, j);
    for (var a = ret.slice(), j = 0; j < n; j++)
        tmp = a.shift();
    for (var a = ret.slice(), j = 0; j < n; j++)
        tmp = a.splice(0, 1);
    ret = [];
    for (var j = 0; j < n; j++)
        ret.push(j);
    for (var a = ret.slice(), j = 0; j < n; j++)
        tmp = a.pop()
}
This test builds up an array in memory and then performs various manipulations on it. The manipulations performed are (in order) unshift, splice, shift, splice, push, pop. Just simple memory manipulation games.

Morph Benchmark
function morphBenchmark() {
    var loops = 30, nx = 120, nz = 120;
    function morph(a, f) {
        for (var PI2nx = Math.PI * 8 / nx, sin = Math.sin, f30 = -(50 * sin(f * Math.PI * 2)), i = 0; i < nz; ++i)
            for (var j = 0; j < nx; ++j)
                a[3 * (i * nx + j) + 1] = sin((j - 1) * PI2nx) * -f30
    }
    for (var a = Array(), i = 0; i < nx * nz * 3; ++i) a[i] = 0;
    for (var i = 0; i < loops; ++i) morph(a, i / loops)
}
This test performs mathematical manipulations in a loop on an array variable. This is again a memory and CPU stress test.

Base 64 Benchmark
function base64Benchmark() {
    var toBase64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", base64Pad = "=";
    function toBase64(data) {
        for (var result = "", length = data.length, i = 0; i < length - 2; i += 3) {
            result += toBase64Table[data[i] >> 2];
            result += toBase64Table[((data[i] & 3) << 4) + (data[i + 1] >> 4)];
            result += toBase64Table[((data[i + 1] & 15) << 2) + (data[i + 2] >> 6)];
            result += toBase64Table[data[i + 2] & 63]
        }
        if (length % 3) {
            i = length - length % 3;
            result += toBase64Table[data[i] >> 2];
            if (length % 3 == 2) {
                result += toBase64Table[((data[i] & 3) << 4) + (data[i + 1] >> 4)];
                result += toBase64Table[(data[i + 1] & 15) << 2];
                result += base64Pad
            }
            else {
                result += toBase64Table[(data[i] & 3) << 4];
                result += base64Pad + base64Pad
            }
        }
        return result
    }
    var toBinaryTable = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];
    function base64ToString(data) {
        for (var result = "", leftbits = 0, leftdata = 0, i = 0; i < data.length; i++) {
            var c = toBinaryTable[data.charCodeAt(i) & 127], padding = data[i] == base64Pad;
            if (c == -1)
                continue;
            leftdata = leftdata << 6 | c;
            leftbits += 6;
            if (leftbits >= 8) {
                leftbits -= 8;
                if (!padding) result += String.fromCharCode(leftdata >> leftbits & 255);
                leftdata &= (1 << leftbits) - 1
            }
        }
        if (leftbits)
            throw Components.Exception("Corrupted base64 string");
        return result
    }
    for (var str = [], i = 0; i < 819; i++)
        str.push(String.fromCharCode(25 * Math.random() + 97));
    str = str.join("");
    for (var base64, loops = 1, i = 0; i <= loops; i++)
        base64 = toBase64(str);
    for (var i = 0; i <= loops; i++)
        base64ToString(base64)
}
A long and intimidating function that in essence converts values to and from base64 encoding. Once again a fairly simple memory and CPU stress test.

DOM Benchmark
This test is actually a combination of 5 sub tests. I have broken up the main function below so to explain more clearly.

function domBenchmark() {
    var count = 1500, divs = new Array(count);
This first JavaScript snippet creates the base structure within which the other functions operate. Basically creating the variables used by each sub function. Note that the 5 following functions are nested within the domBenchmark() function and are not root functions.
Later in this function the HTML DOM structure is created. The DIV element all functions relate to is created in just a few lines.

DOM Benchmark - Append
function testAppend(div) {
        for (var i = 0; i < count; i += 1) {
            var add = document.createElement("div");
            div.appendChild(add)
        }
    }
A function that creates 1500 DIV elements all nested under the source DIV.

DOM Benchmark - Prepend
function testPrepend(div) {
        for (var i = 0; i < count; i += 1) {
            var add = document.createElement("div");
            div.insertBefore(add, div.firstChild)
        }
    }
A function that creates an additional 1500 DIV elements but instead of appending them as before it prepends them. They are added to the top of the parenting DIV elements instead of the bottom.

DOM Benchmark - Index
function testIndex(div) {
        for (var i = 0; i < count; i += 1)
            divs[i] = div.childNodes[count * 2 - i * 2 - 1]
    }
A function that indexes 1500 of the div elements created in the testAppend() and testPrepend() functions. It only indexes every second DIV.
This is considered indexing as it takes the DIV structure and stores it in an array which acts as an index table. Again, meant at stressing memory and CPU.

DOM Benchmark - Insert
function testInsert(div) {
        for (var i = 0; i < count; i += 1) {
            var add = document.createElement("div");
            div.insertBefore(add, divs[i])
        }
    }
A function that takes the index table array variable and inserts each of its DIV elements into the HTTP DOM. A very similar test to the testPrepend() test but with array data instead of cleanly created.. A simple array insert test to stress memory and CPU.

DOM Benchmark - Remove
function testRemove(div) {
        for (var i = 0; i < count; i += 1)
            div.removeChild(divs[i])
    }
The final test function which removes 1500 of the DIV elements created earlier.

DOM Benchmark - Run Tests
var div = document.createElement("div");
    div.style.display = "none";
    div.setAttribute("id", "domBenchmarkDiv");
    document.body.appendChild(div);
    var start, end;
    start = new Date;
    testAppend(div);
    end = new Date;
    var appendTime = end - start;
    start = new Date;
    testPrepend(div);
    end = new Date;
    var prependTime = end - start;
    start = new Date;
    testIndex(div);
    end = new Date;
    var indexTime = end - start;
    start = new Date;
    testInsert(div);
    end = new Date;
    var insertTime = end - start;
    start = new Date;
    testRemove(div);
    end = new Date;
    var removeTime = end - start;
    document.body.removeChild(div);
    var total = appendTime + prependTime + insertTime + indexTime + removeTime, results = "Breakdown:\r\n  Append:  " + appendTime + "ms\r\n  Prepend: " + prependTime + "ms\r\n  Index:   " + indexTime + "ms\r\n  Insert:  " + insertTime + "ms\r\n  Remove:  " + removeTime + "ms\r\n";
    return [total, results]
}
And finally, actually executing all the above functions. This is done in turn and by order right after creating the parent DIV elements as well as some time tracking variables used for duration reporting of the results.
The final steps clean up the parent DIV element and return the stress test results as text.


As you can clearly see, these stress tests have very little to do with actual CRM performance on either the client or server. They are memory and CPU client stress tests designed to measure how browsers behave.
It is important to point out that these functions are rather short and simple and as such are not affected by antivirus scans (the dredded McAfee for example which can kill CRM through its ScriptScan feature).

I hope that sums it all up well enough.
I am open to any additional questions.

1 comment:

Indika Athukorala said...

Thanks for the wonderful article,

I need your help to check one thing, Once we run this tool, how we can compare these results to understand whether client machine is performing well?

is there is any defined normal range for these numbers?

Appreciate your help on this.