|  |  | 
|  | /* | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements.  See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership.  The ASF licenses this file | 
|  | * to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance | 
|  | * with the License.  You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, | 
|  | * software distributed under the License is distributed on an | 
|  | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | * KIND, either express or implied.  See the License for the | 
|  | * specific language governing permissions and limitations | 
|  | * under the License. | 
|  | */ | 
|  |  | 
|  | (function (global) { | 
|  |  | 
|  | var frameInsight = global.frameInsight = {}; | 
|  |  | 
|  | var DURATION_CHART_DURATION = 3000; | 
|  | var DURATION_CHART_PADDING_V = 0.05; | 
|  | var DURATION_CHART_PADDING_TOP = 0.05; | 
|  | var DURATION_CHART_PADDING_BOTTOM = 0.05; | 
|  | var DURATION_CHART_NORMAL_FILL = 'green'; | 
|  | var DURATION_CHART_SLOW_FILL = 'red'; | 
|  | var SLOW_THRESHOLD = 50; | 
|  |  | 
|  | var settings; | 
|  | var dpr = window && Math.max(window.devicePixelRatio || 1, 1) || 1; | 
|  | var durationChart = { | 
|  | ticks: [], | 
|  | tickTypes: [], | 
|  | tags: [] | 
|  | }; | 
|  | var original = { | 
|  | setTimeout: global.setTimeout, | 
|  | requestAnimationFrame: global.requestAnimationFrame, | 
|  | addEventListener: global.addEventListener | 
|  | }; | 
|  |  | 
|  | // var now = global.performance | 
|  | //     // performance.now has higer accuracy. | 
|  | //     ? performance.now.bind(performance) | 
|  | //     : function () { | 
|  | //         return +new Date(); | 
|  | //     }; | 
|  |  | 
|  | // performance.now is not mocked in the visual regression test. Always use Date.now | 
|  | var now = function () { | 
|  | return Date.now(); | 
|  | }; | 
|  |  | 
|  | instrumentBase(); | 
|  |  | 
|  | /** | 
|  | * @public | 
|  | * @param {Object} echarts | 
|  | * @param {string} durationChartDom | 
|  | */ | 
|  | frameInsight.init = function (echarts, durationChartDom, dontInstrumentECharts) { | 
|  | settings = { | 
|  | echarts: echarts, | 
|  | durationChartDom: durationChartDom | 
|  | }; | 
|  |  | 
|  | !dontInstrumentECharts && instrumentECharts(); | 
|  | initDurationChart(); | 
|  | startDurationChart(); | 
|  | }; | 
|  |  | 
|  | function startDurationChart() { | 
|  | next(); | 
|  |  | 
|  | function next() { | 
|  | renderDurationChart(); | 
|  | original.requestAnimationFrame.call(global, next); | 
|  | } | 
|  | } | 
|  |  | 
|  | function instrumentBase() { | 
|  | doInstrumentRegistrar('setTimeout', 0); | 
|  | doInstrumentRegistrar('requestAnimationFrame', 0); | 
|  | doInstrumentRegistrar('addEventListenter', 1); | 
|  | } | 
|  |  | 
|  | function instrumentECharts() { | 
|  | var echarts = settings.echarts; | 
|  |  | 
|  | var dummyDom = document.createElement('div'); | 
|  | var dummyChart = echarts.init(dummyDom, null, {width: 10, height: 10}); | 
|  | var ECClz = dummyChart.constructor; | 
|  | dummyChart.dispose(); | 
|  |  | 
|  | ECClz.prototype.setOption = doInstrumentHandler(ECClz.prototype.setOption, 'setOption'); | 
|  | } | 
|  |  | 
|  | function doInstrumentRegistrar(name, handlerIndex) { | 
|  | global[name] = function () { | 
|  | var args = [].slice.call(arguments); | 
|  | args[handlerIndex] = doInstrumentHandler(args[handlerIndex], name); | 
|  | return original[name].apply(this, args); | 
|  | }; | 
|  | } | 
|  |  | 
|  | function doInstrumentHandler(orginalHandler, tag) { | 
|  | return function () { | 
|  | var start = now(); | 
|  | var result = orginalHandler.apply(this, arguments); | 
|  | var end = now(); | 
|  | addTick(start, end, tag); | 
|  | return result; | 
|  | }; | 
|  | } | 
|  |  | 
|  | function addTick(start, end, tag) { | 
|  | var ticks = durationChart.ticks; | 
|  | var tickTypes = durationChart.tickTypes; | 
|  | var tags = durationChart.tags; | 
|  |  | 
|  | // Arbitrary number. | 
|  | if (end - start > 0.3) { | 
|  |  | 
|  | // In case that setOption in event listener. | 
|  | var lastIndex = tickTypes.length - 1; | 
|  | if (tickTypes[lastIndex] === 0) { | 
|  | tickTypes[lastIndex] = end; | 
|  | tags[lastIndex] = tag; | 
|  | } | 
|  | else { | 
|  | ticks.push(start, end); | 
|  | // 0: start, 1: end | 
|  | tickTypes.push(0, 1); | 
|  | tag = tag; | 
|  | tags.push(tag, tag); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ticks.length) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | var newStart = end - DURATION_CHART_DURATION; | 
|  | var dropCount = 0; | 
|  | for (var i = 0; i < ticks.length; i++) { | 
|  | var tick = ticks[i]; | 
|  | if (tick < newStart) { | 
|  | dropCount++; | 
|  | } | 
|  | } | 
|  | if (dropCount > 0) { | 
|  | ticks.splice(0, dropCount); | 
|  | tickTypes.splice(0, dropCount); | 
|  | tags.splice(0, dropCount); | 
|  | } | 
|  | } | 
|  |  | 
|  | function initDurationChart() { | 
|  | var dom = document.getElementById(settings.durationChartDom); | 
|  | var domStyle = dom.style; | 
|  | // domStyle.border = '2px solid #333'; | 
|  | domStyle.boxShadow = '0 0 3px #000'; | 
|  | domStyle.backgroundColor = '#eee'; | 
|  | domStyle.padding = '0'; | 
|  | domStyle.height = '60px'; | 
|  | domStyle.margin = '10px 20px'; | 
|  |  | 
|  | var domWidth = getSize(dom, 0); | 
|  | var domHeight = getSize(dom, 1); | 
|  |  | 
|  | durationChart.canvas = document.createElement('canvas'); | 
|  | dom.appendChild(durationChart.canvas); | 
|  | durationChart.canvas.style.width = domWidth + 'px'; | 
|  | durationChart.canvas.style.height = domHeight + 'px'; | 
|  | durationChart.width = durationChart.canvas.width = domWidth * dpr; | 
|  | durationChart.height = durationChart.canvas.height = domHeight * dpr; | 
|  |  | 
|  | durationChart.ctx = durationChart.canvas.getContext('2d'); | 
|  |  | 
|  | var paddingV = durationChart.width * DURATION_CHART_PADDING_V; | 
|  | var paddingTop = durationChart.height * DURATION_CHART_PADDING_TOP; | 
|  | var paddingBottom = durationChart.height * DURATION_CHART_PADDING_BOTTOM; | 
|  | durationChart.bodyLeft = paddingV; | 
|  | durationChart.bodyWidth = durationChart.width - 2 * paddingV; | 
|  | durationChart.bodyTop = paddingTop; | 
|  | durationChart.bodyHeight = durationChart.height - paddingTop - paddingBottom; | 
|  |  | 
|  | durationChart.renderExtent = [durationChart.bodyLeft, durationChart.bodyLeft + durationChart.bodyWidth]; | 
|  | var extent = [0, DURATION_CHART_DURATION]; | 
|  | durationChart.slowThresholdLength = | 
|  | linearMap(SLOW_THRESHOLD, extent, durationChart.renderExtent) | 
|  | - linearMap(0, extent, durationChart.renderExtent); | 
|  | } | 
|  |  | 
|  | function renderDurationChart() { | 
|  | // var renderStart = now(); | 
|  |  | 
|  | var ticks = durationChart.ticks; | 
|  | var tickTypes = durationChart.tickTypes; | 
|  | var ctx = durationChart.ctx; | 
|  | var timeEnd = now(); | 
|  | var timeExtent = [timeEnd - DURATION_CHART_DURATION, timeEnd]; | 
|  | var slowThresholdLength = durationChart.slowThresholdLength; | 
|  |  | 
|  | ctx.clearRect(0, 0, durationChart.width, durationChart.height); | 
|  |  | 
|  | ctx.fillStyle = DURATION_CHART_NORMAL_FILL; | 
|  |  | 
|  | var x; | 
|  | var slowRects = []; | 
|  |  | 
|  | if (tickTypes[0] === 1) { | 
|  | x = durationChart.bodyLeft; | 
|  | } | 
|  |  | 
|  | for (var i = 0; i < ticks.length; i++) { | 
|  | var tick = ticks[i]; | 
|  | var tickType = tickTypes[i]; | 
|  |  | 
|  | var tickCoord = linearMap(tick, timeExtent, durationChart.renderExtent); | 
|  | if (tickType === 0) { | 
|  | x = tickCoord; | 
|  | } | 
|  | else if (tickType === 1) { | 
|  | var width = Math.max(tickCoord - x, 0.5); | 
|  | ctx.fillRect(x, durationChart.bodyTop, width, durationChart.bodyHeight); | 
|  | if (width > slowThresholdLength) { | 
|  | slowRects.push( | 
|  | x + slowThresholdLength, | 
|  | durationChart.bodyTop, | 
|  | width - slowThresholdLength, | 
|  | durationChart.bodyHeight | 
|  | ); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (slowRects.length) { | 
|  | for (var i = 0; i < slowRects.length;) { | 
|  | var x = slowRects[i++]; | 
|  | var y = slowRects[i++]; | 
|  | var width = slowRects[i++]; | 
|  | var height = slowRects[i++]; | 
|  | var canvasGradient = ctx.createLinearGradient(x, y, x + width, y); | 
|  | canvasGradient.addColorStop(0, DURATION_CHART_NORMAL_FILL); | 
|  | canvasGradient.addColorStop(1, DURATION_CHART_SLOW_FILL); | 
|  | ctx.fillStyle = canvasGradient; | 
|  | ctx.fillRect(x, y, width, height); | 
|  | } | 
|  | } | 
|  |  | 
|  | // var renderDuration = now() - renderStart; | 
|  | // if (renderDuration > 1) { | 
|  | // console.warn(renderDuration); | 
|  | // } | 
|  | } | 
|  |  | 
|  | function linearMap(val, domain, range) { | 
|  | var subDomain = domain[1] - domain[0]; | 
|  | var subRange = range[1] - range[0]; | 
|  |  | 
|  | if (val <= domain[0]) { | 
|  | return range[0]; | 
|  | } | 
|  | if (val >= domain[1]) { | 
|  | return range[1]; | 
|  | } | 
|  |  | 
|  | return (val - domain[0]) / subDomain * subRange + range[0]; | 
|  | } | 
|  |  | 
|  | function getSize(root, whIdx) { | 
|  | var wh = ['width', 'height'][whIdx]; | 
|  | var cwh = ['clientWidth', 'clientHeight'][whIdx]; | 
|  | var plt = ['paddingLeft', 'paddingTop'][whIdx]; | 
|  | var prb = ['paddingRight', 'paddingBottom'][whIdx]; | 
|  |  | 
|  | // IE8 does not support getComputedStyle, but it use VML. | 
|  | var stl = document.defaultView.getComputedStyle(root); | 
|  |  | 
|  | return ( | 
|  | (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh])) | 
|  | - (parseInt10(stl[plt]) || 0) | 
|  | - (parseInt10(stl[prb]) || 0) | 
|  | ) | 0; | 
|  | } | 
|  |  | 
|  | function parseInt10(val) { | 
|  | return parseInt(val, 10); | 
|  | } | 
|  |  | 
|  | })(window); |