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