    // This override adds diagnostics to the Ext.layout.Context class.

    /**
     */
    Ext.define('Ext.diag.layout.Context', {
        override: 'Ext.layout.Context',

        requires: [
            'Ext.perf.Monitor'
        ],

        logOn: {
            //boxParent: true,
            //calculate: true,
            //cancelComponent: true,
            //cancelLayout: true,
            //doInvalidate: true,
            //flush: true,
            //flushInvalidate: true,
            //invalidate: true,
            //initItem: true,
            //layoutDone: true,
            //queueLayout: true,
            //resetLayout: true,
            //runCycle: true,
            //setProp: true,
            0:0
        },

        //profileLayoutsByType: true,

        //reportOnSuccess: true,

        cancelComponent: function (comp) {
            if (this.logOn.cancelComponent) {
                Ext.log('cancelCmp: ', comp.id);
            }
            this.callParent(arguments);
        },

        cancelLayout: function (layout) {
            if (this.logOn.cancelLayout) {
                Ext.log('cancelLayout: ', this.getLayoutName(layout));
            }
            this.callParent(arguments);
        },

        callLayout: function (layout, methodName) {
            var accum = this.accumByType[layout.type],
                frame = accum && accum.enter();

            this.callParent(arguments);

            if (accum) {
                frame.leave();
            }
        },

        checkRemainingLayouts: function () {
            var me       = this,
                expected = 0,
                key, layout;

            for (key in me.layouts) {
                layout = me.layouts[key];

                if (me.layouts.hasOwnProperty(key) && layout.running) {
                    ++expected;
                }
            }

            if (me.remainingLayouts != expected) {
                Ext.Error.raise({
                    msg: 'Bookkeeping error me.remainingLayouts'
                });
            }
        },

        flush: function () {
            if (this.logOn.flush) {
                var items = this.flushQueue;
                Ext.log('--- Flush ', items && items.getCount());
            }

            return this.callParent(arguments);
        },

        flushInvalidates: function () {
            if (this.logOn.flushInvalidate) {
                Ext.log('>> flushInvalidates');
            }

            var ret = this.callParent(arguments);

            if (this.logOn.flushInvalidate) {
                Ext.log('<< flushInvalidates');
            }

            return ret;
        },

        getCmp: function (target) {
            var ret = this.callParent(arguments);
            if (!ret.wrapsComponent) {
                Ext.Error.raise({
                    msg: target.id + ' is not a component'
                });
            }
            return ret;
        },

        getEl: function (parent, target) {
            var ret = this.callParent(arguments);
            if (ret && ret.wrapsComponent) {
                Ext.Error.raise({
                    msg: parent.id + '/' + target.id + ' is a component (expected element)'
                });
            }
            return ret;
        },

        getLayoutName: function (layout) {
            return layout.owner.id + '<' + layout.type + '>';
        },

        layoutDone: function (layout) {
            var me = this,
                name = me.getLayoutName(layout);

            if (me.logOn.layoutDone) {
                Ext.log('layoutDone: ', name, ' ( ', me.remainingLayouts, ' running)');
            }

            if (!layout.running) {
                Ext.Error.raise({
                    msg: name + ' is already done'
                });
            }
            if (!me.remainingLayouts) {
                Ext.Error.raise({
                    msg: name + ' finished but no layouts are running'
                });
            }

            me.callParent(arguments);
        },

        layoutTreeHasFailures: function (layout, reported) {
            var me = this;

            function hasFailure (lo) {
                var failure = !lo.done,
                    key, childLayout;

                if (lo.done) {
                    for (key in me.layouts) {
                        if (me.layouts.hasOwnProperty(key)) {
                            childLayout = me.layouts[key];

                            if (childLayout.owner.ownerLayout === lo) {
                                if (hasFailure(childLayout)) {
                                    failure = true;
                                }
                            }
                        }
                    }
                }

                return failure;
            }

            if (hasFailure(layout)) {
                return true;
            }

            function markReported (lo) {
                var key, childLayout;

                reported[lo.id] = 1;

                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        childLayout = me.layouts[key];

                        if (childLayout.owner.ownerLayout === lo) {
                            markReported(childLayout);
                        }
                    }
                }
            }

            markReported(layout);
            return false;
        },

        queueLayout: function (layout) {
            if (layout.done || layout.blockCount || layout.pending) {
                Ext.Error.raise({
                    msg: this.getLayoutName(layout) + ' should not be queued for layout'
                });
            }
            if (this.logOn.queueLayout) {
                Ext.log('Queue ', this.getLayoutName(layout));
            }
            return this.callParent(arguments);
        },

        reportLayoutResult: function (layout, reported) {
            var me           = this,
                owner        = layout.owner,
                ownerContext = me.getCmp(owner),
                blockedBy    = [],
                triggeredBy  = [],
                key, value, i, length, childLayout,
                item, setBy, info;

            reported[layout.id] = 1;

            for (key in layout.blockedBy) {
                if (layout.blockedBy.hasOwnProperty(key)) {
                    blockedBy.push(layout.blockedBy[key]);
                }
            }
            blockedBy.sort();

            for (key in me.triggersByLayoutId[layout.id]) {
                if (me.triggersByLayoutId[layout.id].hasOwnProperty(key)) {
                    value = me.triggersByLayoutId[layout.id][key];
                    triggeredBy.push({ name: key, info: value });
                }
            }
            triggeredBy.sort(function (a, b) {
                return a.name < b.name ? -1 : (b.name < a.name ? 1 : 0);
            });

            Ext.log({indent: 1}, (layout.done ? '++' : '--'), me.getLayoutName(layout),
                (ownerContext.isBoxParent ? ' [isBoxParent]' : ''),
                (ownerContext.boxChildren ? ' - boxChildren: ' + ownerContext.state.boxesMeasured + '/' + ownerContext.boxChildren.length : ''),
                ownerContext.boxParent ? (' - boxParent: ' + ownerContext.boxParent.id) : '',
                ' - size: ', ownerContext.widthModel.name, '/', ownerContext.heightModel.name);

            if (!layout.done || me.reportOnSuccess) {
                if (blockedBy.length) {
                    ++Ext.log.indent;
                    Ext.log({indent: 1}, 'blockedBy:  count=',layout.blockCount);

                    length = blockedBy.length;
                    for (i = 0; i < length; i++) {
                        Ext.log(blockedBy[i]);
                    }

                    Ext.log.indent -= 2;
                }
                if (triggeredBy.length) {
                    ++Ext.log.indent;
                    Ext.log({indent: 1}, 'triggeredBy: count='+layout.triggerCount);

                    length = triggeredBy.length;
                    for (i = 0; i < length; i++) {
                        info = value.info || value;
                        item  = info.item;
                        setBy = (item.setBy && item.setBy[info.name]) || '?';

                        value = triggeredBy[i];

                        Ext.log(
                            value.name,
                            ' (',
                            item.props[info.name],
                            ') dirty: ',
                            (item.dirty ? !!item.dirty[info.name] : false),
                            ', setBy: ',
                            setBy
                        );
                    }

                    Ext.log.indent -= 2;
                }
            }

            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    childLayout = me.layouts[key];

                    if (!childLayout.done && childLayout.owner.ownerLayout === layout) {
                        me.reportLayoutResult(childLayout, reported);
                    }
                }
            }

            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    childLayout = me.layouts[key];

                    if (childLayout.done && childLayout.owner.ownerLayout === layout) {
                        me.reportLayoutResult(childLayout, reported);
                    }
                }
            }

            --Ext.log.indent;
        },

        resetLayout: function (layout) {
            var me = this,
                type = layout.type,
                name = me.getLayoutName(layout),
                accum = me.accumByType[type],
                frame;

            if (me.logOn.resetLayout) {
                Ext.log('resetLayout: ', name, ' ( ', me.remainingLayouts, ' running)');
            }

            if (!me.state) { // if (first time ... before run)
                if (!accum && me.profileLayoutsByType) {
                    me.accumByType[type] = accum = Ext.Perf.get('layout_' + layout.type);
                }
                me.numByType[type] = (me.numByType[type] || 0) + 1;
            }

            frame = accum && accum.enter();
            me.callParent(arguments);
            if (accum) {
                frame.leave();
            }

            me.checkRemainingLayouts();
        },

        round: function (t) {
            return Math.round(t * 1000) / 1000;
        },

        run: function () {
            var me = this,
                ret, time, key, value, i, layout,
                boxParent, children, n,
                reported, unreported,
                calcs, total,
                calcsLength, calc;

            me.accumByType = {};
            me.calcsByType = {};
            me.numByType = {};
            me.timesByType = {};
            me.triggersByLayoutId = {};

            Ext.log.indentSize = 3;
            Ext.log('==================== LAYOUT ====================');

            time = Ext.perf.getTimestamp();
            ret = me.callParent(arguments);
            time = Ext.perf.getTimestamp() - time;

            if (me.logOn.boxParent && me.boxParents) {
                for (key in me.boxParents) {
                    if (me.boxParents.hasOwnProperty(key)) {
                        boxParent = me.boxParents[key];
                        children  = boxParent.boxChildren;
                        n         = children.length;

                        Ext.log('boxParent: ', boxParent.id);
                        for (i = 0; i < n; ++i) {
                            Ext.log(' --> ', children[i].id);
                        }
                    }
                }
            }

            if (ret) {
                Ext.log('----------------- SUCCESS -----------------');
            } else {
                Ext.log(
                    {level: 'error' },
                    '----------------- FAILURE -----------------'
                );
            }

            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    layout = me.layouts[key];

                    if (layout.running) {
                        Ext.log.error('Layout left running: ', me.getLayoutName(layout));
                    }
                    if (layout.ownerContext) {
                        Ext.log.error('Layout left connected: ', me.getLayoutName(layout));
                    }
                }
            }

            if (!ret || me.reportOnSuccess) {
                reported = {};
                unreported = 0;

                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        layout = me.layouts[key];

                        if (me.items[layout.owner.el.id].isTopLevel) {
                            if (me.reportOnSuccess || me.layoutTreeHasFailures(layout, reported)) {
                                me.reportLayoutResult(layout, reported);
                            }
                        }
                    }
                }

                // Just in case we missed any layouts...
                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        layout = me.layouts[key];

                        if (!reported[layout.id]) {
                            if (!unreported) {
                                Ext.log('----- Unreported!! -----');
                            }
                            ++unreported;
                            me.reportLayoutResult(layout, reported);
                        }
                    }
                }
            }

            Ext.log('Cycles: ', me.cycleCount, ', Flushes: ', me.flushCount,
                ', Calculates: ', me.calcCount, ' in ', me.round(time), ' msec');

            Ext.log('Calculates by type:');
            /*Ext.Object.each(me.numByType, function (type, total) {
                Ext.log(type, ': ', total, ' in ', me.calcsByType[type], ' tries (',
                    Math.round(me.calcsByType[type] / total * 10) / 10, 'x) at ',
                    me.round(me.timesByType[type]), ' msec (avg ',
                    me.round(me.timesByType[type] / me.calcsByType[type]), ' msec)');
            });*/
            calcs = [];
            for (key in me.numByType) {
                if (me.numByType.hasOwnProperty(key)) {
                    total = me.numByType[key];

                    calcs.push({
                            type       : key,
                            total      : total,
                            calcs      : me.calcsByType[key],
                            multiple   : Math.round(me.calcsByType[key] / total * 10) / 10,
                            calcTime   : me.round(me.timesByType[key]),
                            avgCalcTime: me.round(me.timesByType[key] / me.calcsByType[key])
                        });
                }
            }

            calcs.sort(function (a,b) {
                return b.calcTime - a.calcTime;
            });

            calcsLength = calcs.length;
            for (i=0; i<calcsLength; i++) {
                calc = calcs[i];

                Ext.log(
                    calc.type,
                    ': ',
                    calc.total,
                    ' in ',
                    calc.calcs,
                    ' tries (',
                    calc.multiple,
                    'x) at ',
                    calc.calcTime,
                    ' msec (avg ',
                    calc.avgCalcTime,
                    ' msec)'
                );
            }

            return ret;
        },

        runCycle: function () {
            if (this.logOn.runCycle) {
                Ext.log('>>> Cycle ', this.cycleCount, ' (queue length: ', this.layoutQueue.length, ')');
            }

            return this.callParent(arguments);
        },

        runLayout: function (layout) {
            var me = this,
                type = layout.type,
                accum = me.accumByType[type],
                frame, ret, time;

            if (me.logOn.calculate) {
                Ext.log('-- calculate ', this.getLayoutName(layout));
            }

            frame = accum && accum.enter();

            time = Ext.perf.getTimestamp();
            ret = me.callParent(arguments);
            time = Ext.perf.getTimestamp() - time;
            if (accum) {
                frame.leave();
            }

            me.calcsByType[type] = (me.calcsByType[type] || 0) + 1;
            me.timesByType[type] = (me.timesByType[type] || 0) + time;

            /*  add a / to the front of this line to enable layout completion logging
            if (layout.done) {
                var ownerContext = me.getCmp(layout.owner),
                    props = ownerContext.props;

                if (layout.isComponentLayout) {
                    Ext.log('complete ', layout.owner.id, ':', type, ' w=',props.width, ' h=', props.height);
                } else {
                    Ext.log('complete ', layout.owner.id, ':', type, ' cw=',props.contentWidth, ' ch=', props.contentHeight);
                }
            }**/

            return ret;
        }
    });