text: /* globals testHelper */
  (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 = testHelper.getDom(settings.durationChartDom);
          var domStyle = dom.style;
          // domStyle.border = '2px solid #333';
          domStyle.boxShadow = '0 0 3px #000';
          domStyle.backgroundColor = '#fff';
          domStyle.padding = '0';
          domStyle.height = '80px';
          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);
  