Ext.define('PageAnalyzer.Console', { extend: 'Ext.panel.Panel', requires: [ 'Ext.chart.*', 'Ext.grid.Panel', 'Ext.grid.feature.Grouping', 'Ext.layout.container.Border', 'PageAnalyzer.models.BasicTimeData' ], layout: 'border', border: false, initComponent: function () { var me = this; me.addEvents('rawdataupdate'); me.pathTpl = new Ext.XTemplate('{test} ({env} - {build})'); me.samples = []; me.store = Ext.create('Ext.data.Store', { model: 'PageAnalyzer.models.BasicTimeData', sorters: [{ property: 'avgTime', direction: 'DESC' }], groupField: 'path' }); me.detailStore = Ext.create('Ext.data.Store', { model: 'PageAnalyzer.models.BasicTimeData', sorters: { property: 'avgTime', direction: 'DESC' } }); me.singleSampleStore = Ext.create('Ext.data.Store', { model: 'PageAnalyzer.models.BasicTimeData' }); me.items = [ { region: 'north', title: 'Trend', itemId: 'trendPanel', layout: 'fit', split: true, collapsible: true, collapsed: false, flex: 4, items: [] }, { region: 'center', title: 'Data', flex: 4, layout: 'border', border: false, items: [ { region: 'center', xtype: 'tabpanel', tabPosition: 'bottom', flex: 5, items: [ me.makeDataGrid({ title: 'Grid', border: false }), { region: 'east', title: 'Raw', layout: 'fit', border: false, tbar: [ { text: 'Load', handler: me.onUpdateFromRawData, scope: me } ], items: [ { xtype: 'textarea', style: 'margin:0', itemId: 'rawData', emptyText: 'Raw data goes here...', selectOnFocus: true } ] }, { title: 'Accumulators', xtype: 'textarea', style: 'margin:0', itemId: 'accumulatorCfg' } ] }, me.makeDetailsChart({ region: 'east' }) ] } ]; me.callParent(); }, addSample: function (perfData) { var me = this, last = perfData; if (Ext.isArray(perfData)) { me.samples.push.apply(me.samples, perfData); last = last[last.length-1]; } else { // perfData = { env: '..', test: '..', build: '..', data: Ext.Perf.getData() } me.samples.push(perfData); } me.update(); var rec = me.store.findRecord('path', me.makePath(last)); me.select(rec); }, clearSamples: function() { var me = this; me.samples.length = 0; me.update(); }, makeDataGrid: function (config) { var me = this; return Ext.apply({ xtype: 'grid', store: me.store, itemId: 'dataGrid', flex: 5, features: [ { ftype:'grouping', hideGroupedHeader: true } ], columns: [ { text: 'Environment', dataIndex: 'environment', hidden: true }, { text: 'Build', dataIndex: 'build', hidden: true }, { text: 'Test', dataIndex: 'test', hidden: true }, { text: 'Path', dataIndex: 'path', sortable: true }, { text: 'Name', dataIndex: 'measure', sortable: true }, { text: 'Time', dataIndex: 'avgTime', sortable: true }, { text: 'Time/Call', dataIndex: 'avgTimePerCall', sortable: true }, { text: 'Samples', dataIndex: 'numSamples', sortable: true }, { text: 'Min Calls', dataIndex: 'minCalls', sortable: true }, { text: 'Max Calls', dataIndex: 'maxCalls', sortable: true } ], listeners: { selectionchange: function (model, selection) { me.onSelect(selection[0]); } } }, config); }, makePath: function (perfData) { return this.pathTpl.apply(perfData); }, makeDetailsChart: function (config) { var me = this; return Ext.apply({ xtype: 'panel', title: 'Details', layout: 'fit', flex: 3, split: true, collapsible: true, preventHeader: true, items: [{ xtype: 'chart', store: me.detailStore, itemId: 'measuresPieChart', theme: 'Base:gradients', series: [{ type: 'pie', field: 'avgTime', label: { field: 'measure', display: 'rotate', font: '12px Arial' } }] }] }, config); }, makeTrendChart: function () { var me = this, panel = me.down('#trendPanel'), builds = {}, trendData = [], fields = [], measures = {}, measureNames = [], modelName, chart; panel.removeAll(); chart = { xtype: 'chart', style: 'background:#fff', animate: false, shadow: true, flex: 5, theme: 'Category1', legend: { position: 'right' }, axes: [{ type: 'Numeric', minimum: 0, position: 'left', fields: [], title: 'Time (ms)', minorTickSteps: 1, grid: { odd: { opacity: 1, fill: '#ddd', stroke: '#bbb', 'stroke-width': 0.5 } } }, { type: 'Category', position: 'bottom', fields: ['build'], title: 'Build' }], series: [] }; me.store.each(function (r) { var rd = r.data; var build = builds[rd.build]; if (!build) { builds[rd.build] = build = { build: rd.build }; trendData.push(build); } build[rd.measure] = rd.avgTime; if (rd.measure in measures) { return; } fields.push(measures[rd.measure] = { name: rd.measure, type: 'float' }); measureNames.push(rd.measure); chart.series.push({ type: 'line', highlight: { size: 7, radius: 7 }, axis: 'left', xField: 'build', yField: rd.measure, markerConfig: { type: 'circle', size: 4, radius: 4, 'stroke-width': 0 } }); }); chart.axes[0].fields = measureNames; measureNames.sort(); var name = 'Trend_' + measureNames.join('$'); name = name.replace(/\./g, '_'); modelName = 'PageAnalyzer.models.' + name; var mfields = [{ name: 'build', type: 'int' }]; fields.push({name: 'build', type: 'int'}); if (!PageAnalyzer.models[name]) { Ext.define(modelName, { extend: 'Ext.data.Model', fields: fields }); } chart.store = Ext.create('Ext.data.Store', { model: modelName, sorters: { property: 'build', direction: 'ASC' } }); if (trendData.length == 1) { trendData.unshift({ build: '0', avgTime: null }); } chart.store.loadData(trendData); chart.store.sort(); panel.add(chart); }, onSelect: function (rec) { var me = this, records = [], store = me.detailStore; if (rec) { me.store.each(function (r) { if (r.data.path == rec.data.path) { records.push(r.copy()); } }); } store.removeAll(); if (records.length) { store.add(records); store.sort(); } }, onUpdateFromRawData: function () { var me = this, rawData = me.down('#rawData').getValue(); me.samples = Ext.decode(rawData); me.update(); }, select: function (rec) { if (rec) { var me = this, grid = me.down('#dataGrid'); grid.getSelectionModel().select(rec); } }, update: function () { var me = this, map = {}, // map[env+test+build][measure] = record json = Ext.encode(me.samples), key, measures, measure, records = []; me.down('#rawData').setValue(json); me.fireEvent('rawdataupdate', me, json, me.samples); Ext.each(me.samples, function (perfData) { key = me.makePath(perfData); measures = map[key] || (map[key] = {}); Ext.Object.each(perfData.data, function (name, stats) { measure = measures[name]; if (!measure) { measures[name] = measure = new PageAnalyzer.models.BasicTimeData({ environment: perfData.env, build: perfData.build, test: perfData.test, path: key, measure: name }); } records.push(measure); measure.addSample(stats.pure.sum, stats.count); }); }); me.store.removeAll(); me.store.add(records); me.store.sort(); me.makeTrendChart(); }, setAccumulators: function (cfg) { var me = this, accData = Ext.JSON.encodeValue(cfg, '\n '); me.down('#accumulatorCfg').setValue(accData); }, getAccumulators: function () { var me = this, accData = me.down('#accumulatorCfg').getValue(); if(!accData || accData == '') return null; return Ext.decode(accData, true); } });