blob: 493e553d77ef3a56014b2e2dfe36057368180e18 [file] [log] [blame]
/*
* 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);