| |
| /* |
| * 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(); |
| }; |
| |
| 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); |