text: 'use strict';
  
  let searching = 0;
  let zoomclick = null;
  
  // 以下是公共方法
  function find_child(parent, name, attr) {
      let children = parent.childNodes;
      for (let i = 0; i < children.length; i++) {
          if (children[i].tagName == name)
              return (attr != undefined) ? children[i].attributes[attr].value : children[i];
      }
      return;
  }
  
  function orig_load(e, attr) {
      if (e.attributes["_orig_" + attr] == undefined) return;
      e.attributes[attr].value = e.attributes["_orig_" + attr].value;
      e.removeAttribute("_orig_" + attr);
  }
  
  function orig_save(e, attr, val) {
      if (e.attributes["_orig_" + attr] != undefined) return;
      if (e.attributes[attr] == undefined) return;
      if (val == undefined) val = e.attributes[attr].value;
      e.setAttribute("_orig_" + attr, val);
  }
  
  function g_to_text(e) {
      var text = find_child(e, "title").firstChild.nodeValue;
      return (text)
  }
  
  function g_to_func(e) {
      var func = g_to_text(e);
      if (func != null)
          func = func.replace(/ .*/, "");
      return (func);
  }
  
  function zoom_child(e, x, ratio) {
      if (e.attributes != undefined) {
          if (e.attributes["x"] != undefined) {
              orig_save(e, "x");
              e.attributes["x"].value = (parseFloat(e.attributes["x"].value) - x - 10) * ratio + 10;
              if (e.tagName == "text") e.attributes["x"].value = find_child(e.parentNode, "rect", "x") + 3;
          }
          if (e.attributes["width"] != undefined) {
              orig_save(e, "width");
              const index = e.attributes['node-index'].value;
              const width_origin = e.attributes["width"].value;
              e.attributes["width"].value = parseFloat(width_origin) * ratio;
              e.attributes["width"].value = e.attributes["width"].value;
          }
      }
  
      if (e.childNodes == undefined) return;
      for (let i = 0, c = e.childNodes; i < c.length; i++) {
          zoom_child(c[i], x - 10, ratio);
      }
  }
  
  function zoom_parent(e, svg) {
      if (e.attributes) {
          if (e.attributes["x"] != undefined) {
              orig_save(e, "x");
              e.attributes["x"].value = 10;
          }
          if (e.attributes["width"] != undefined) {
              orig_save(e, "width");
              e.attributes["width"].value = parseFloat(svg.width.baseVal.value) - (10 * 2);
          }
      }
      if (e.childNodes == undefined) return;
      for (let i = 0, c = e.childNodes; i < c.length; i++) {
          zoom_parent(c[i], svg);
      }
  }
  
  function update_text(e) {
      let r = find_child(e, "rect");
      let t = find_child(e, "text");
      let w = parseFloat(r.attributes["width"].value) - 3;
      // let txt = find_child(e, "title").textContent.replace(/\([^(]*\)/, "");
      let txt = find_child(e, "title").textContent.split(' (')[0];
      t.attributes["x"].value = parseFloat(r.attributes["x"].value) + 3;
  
      // Smaller than this size won't fit anything
      if (w < 2 * 12 * 0.59) {
          t.textContent = "";
          return;
      }
  
      t.textContent = txt;
      // Fit in full text width
      if (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)
          return;
  
      for (let x = txt.length - 2; x > 0; x--) {
          if (t.getSubStringLength(0, x + 2) <= w) {
              t.textContent = txt.substring(0, x) + "..";
              return;
          }
      }
      t.textContent = "";
  }
  
  function zoom_reset(e) {
      if (e.attributes != undefined) {
          orig_load(e, "x");
          orig_load(e, "width");
      }
      if (e.childNodes == undefined) return;
      for (let i = 0, c = e.childNodes; i < c.length; i++) {
          zoom_reset(c[i]);
      }
  }
  
  function search(term, matchedtxt, searchbtn) {
      let re = new RegExp(term);
      let el = document.getElementsByTagName("g");
      let matches = new Object();
      let maxwidth = 0;
      for (let i = 0; i < el.length; i++) {
          let e = el[i];
          if (e.attributes["class"].value != "func_g")
              continue;
          let func = g_to_func(e);
          let rect = find_child(e, "rect");
          if (rect == null) {
              // the rect might be wrapped in an anchor
              // if nameattr href is being used
              if (rect = find_child(e, "a")) {
                  rect = find_child(r, "rect");
              }
          }
          if (func == null || rect == null)
              continue;
  
          // Save max width. Only works as we have a root frame
          let w = parseFloat(rect.attributes["width"].value);
          if (w > maxwidth)
              maxwidth = w;
  
          if (func.match(re)) {
              // highlight
              let x = parseFloat(rect.attributes["x"].value);
              orig_save(rect, "fill");
              rect.attributes["fill"].value =
                  "rgb(230,0,230)";
  
              // remember matches
              if (matches[x] == undefined) {
                  matches[x] = w;
              } else {
                  if (w > matches[x]) {
                      // overwrite with parent
                      matches[x] = w;
                  }
              }
              searching = 1;
          }
      }
      if (!searching) {
          matchedtxt.style["opacity"] = "1.0";
          matchedtxt.firstChild.nodeValue = "Matched: none";
          return;
      }
  
      searchbtn.style["opacity"] = "1.0";
      searchbtn.firstChild.nodeValue = "Reset Search"
  
      // calculate percent matched, excluding vertical overlap
      let count = 0;
      let lastx = -1;
      let lastw = 0;
      let keys = Array();
      for (let k in matches) {
          if (matches.hasOwnProperty(k))
              keys.push(k);
      }
      // sort the matched frames by their x location
      // ascending, then width descending
      keys.sort(function (a, b) {
          return a - b;
          if (a < b || a > b)
              return a - b;
          return matches[b] - matches[a];
      });
      // Step through frames saving only the biggest bottom-up frames
      // thanks to the sort order. This relies on the tree property
      // where children are always smaller than their parents.
      for (let k in keys) {
          let x = parseFloat(keys[k]);
          let w = matches[keys[k]];
          if (x >= lastx + lastw) {
              count += w;
              lastx = x;
              lastw = w;
          }
      }
      // display matched percent
      matchedtxt.style["opacity"] = "1.0";
      let pct = 100 * count / maxwidth;
      if (pct == 100)
          pct = "100"
      else
          pct = pct.toFixed(1)
      matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
  }
  
  function reset_search() {
      var el = document.getElementsByTagName("rect");
      for (var i = 0; i < el.length; i++) {
          orig_load(el[i], "fill")
      }
  }
  
  // flamegraph 颜色
  const colorMap = (function () {
      function scalarReverse(s) {
          return s.split('').reverse().join('')
      }
  
      function nameHash(name) {
          let vector = 0
          let weight = 1
          let max = 1
          let mod = 10
          let ord
  
          name = name.replace(/.(.*?)`/, '')
          let splits = name.split('')
          for (let i = 0; i < splits.length; i++) {
              ord = splits[i].charCodeAt(0) % mod
              vector += (ord / (mod++ - 1)) * weight
              max += weight
              weight *= 0.70
              if (mod > 12) break
          }
  
          return (1 - vector / max)
      }
  
      function color(type, hash, name) {
          let v1, v2, v3, r, g, b
          if (!type) return 'rgb(0, 0, 0)'
  
          if (hash) {
              v1 = nameHash(name)
              v2 = v3 = nameHash(scalarReverse(name))
          } else {
              v1 = Math.random() + 1
              v2 = Math.random() + 1
              v3 = Math.random() + 1
          }
  
          switch (type) {
              case 'hot':
                  r = 205 + Math.round(50 * v3);
                  g = 0 + Math.round(230 * v1);
                  b = 0 + Math.round(55 * v2);
                  return `rgb(${r}, ${g}, ${b})`;
              case 'mem':
                  r = 0
                  g = 190 + Math.round(50 * v2)
                  b = 0 + Math.round(210 * v1)
                  return `rgb(${r}, ${g}, ${b})`
              case 'io':
                  r = 80 + Math.round(60 * v1)
                  g = r
                  b = 190 + Math.round(55 * v2)
                  return `rgb(${r}, ${g}, ${b})`
              default:
                  throw new Error('Unknown type ' + type)
          }
      }
  
      function colorMap(paletteMap, colorTheme, hash, func) {
          if (paletteMap[func]) return paletteMap[func]
          paletteMap[func] = color(colorTheme, hash, func)
          return paletteMap[func]
      }
  
      return colorMap;
  })();
  
  // 获取渲染的 context 信息
  const contextify = (function () {
  
      function oneDecimal(x) {
          return (Math.round(x * 10) / 10)
      }
  
      function htmlEscape(s) {
          return s
              .replace(/&/g, '&amp;')
              .replace(/</g, '&lt;')
              .replace(/>/g, '&gt;')
      }
  
      function contextify(parsed, opts) {
          let each = parsed.each;
          let time = parsed.time
          let timeMax = opts.timemax
          let ypadTop = opts.fontsize * 4           // pad top, include title
          let ypadBottom = opts.fontsize * 2 + 10      // pad bottom, include labels
          let xpad = 10
          let xpad2 = opts.imagewidth * 0.82
          let xpad3 = opts.imagewidth * 0.92
          let depthMax = 0
          let frameHeight = opts.frameheight
          let paletteMap = {}
  
          if (timeMax < time && timeMax / time > 0.02) {
              console.error('Specified timemax %d is less than actual total %d, so it will be ignored', timeMax, time)
              timeMax = Infinity
          }
  
          timeMax = Math.min(time, timeMax)
  
          let widthPerTime = (opts.imagewidth - 2 * xpad) / timeMax
          let minWidthTime = opts.minwidth / widthPerTime
  
          function markNarrowBlocks(nodes) {
              function mark(k) {
                  let val = parsed.nodes[k]
                  if (typeof val.stime !== 'number') throw new Error('Missing start for ' + k)
                  if ((val.etime - val.stime) < minWidthTime) {
                      val.narrow = true
                      return
                  }
  
                  val.narrow = false
                  depthMax = Math.max(val.depth, depthMax)
              }
  
              Object.keys(nodes).forEach(mark)
          }
  
          // NodeProcessor proto
          function processNode(node, index) {
              let func = node.func
              let depth = node.depth
              let etime = node.etime
              let stime = node.stime
              let factor = opts.factor
              let countName = opts.countname
              let isRoot = !func.length && depth === 0
  
              if (isRoot) etime = timeMax
  
              let samples = Math.round((etime - stime * factor) * 10) / 10
              let costTime = samples * each
              let samplesTxt = samples.toLocaleString()
              let pct
              let pctTxt
              let escapedFunc
              let name
              let sampleInfo
  
              if (isRoot) {
                  name = 'all'
                  sampleInfo = `(${samplesTxt} ${countName} ${utils.formatTime(costTime)}, 100%)`
              } else {
                  pct = Math.round((100 * samples) / (timeMax * factor) * 10) / 10
                  pctTxt = pct.toLocaleString()
                  escapedFunc = htmlEscape(func)
  
                  name = escapedFunc
                  sampleInfo = `(${samplesTxt} ${countName} ${utils.formatTime(costTime)}), ${pctTxt}%)`
              }
  
              let x1 = oneDecimal(xpad + stime * widthPerTime)
              let x2 = oneDecimal(xpad + etime * widthPerTime)
              let y1 = oneDecimal(imageHeight - ypadBottom - (depth + 1) * frameHeight + 1)
              let y2 = oneDecimal(imageHeight - ypadBottom - depth * frameHeight)
  
              let chars = (x2 - x1) / (opts.fontsize * opts.fontwidth)
              let showText = false
              let text
  
              if (chars >= 3) { // enough room to display function name?
                  showText = true
                  text = func.slice(0, chars)
                  if (chars < func.length) text = text.slice(0, chars - 2) + '..'
                  text = htmlEscape(text)
              }
  
              let rect_w = x2 - x1;
              let rect_h = y2 - y1;
  
              return {
                  name: name
                  , index: index
                  , search: name.toLowerCase()
                  , samples: sampleInfo
                  , rect_x: x1
                  , rect_y: y1
                  , rect_w: rect_w
                  , rect_h: rect_h
                  , rect_fill: colorMap(paletteMap, opts.colors, opts.hash, func)
                  , text: text
                  , text_x: x1 + (showText ? 3 : 0)
                  , text_y: 3 + (y1 + y2) / 2
                  , narrow: node.narrow
                  , func: htmlEscape(func)
              }
          }
  
          function processNodes(nodes) {
              let keys = Object.keys(nodes)
              let acc = new Array(keys.length)
  
              for (let i = 0; i < keys.length; i++) {
                  acc[i] = processNode(nodes[keys[i]], i)
              }
  
              return acc
          }
  
          markNarrowBlocks(parsed.nodes)
  
          let imageHeight = (depthMax * frameHeight) + ypadTop + ypadBottom
          let ctx = Object.assign({}, opts, {
              imageheight: imageHeight
              , xpad: xpad
              , xpad2: xpad2
              , xpad3: xpad3
              , titleX: opts.imagewidth / 2
              , detailsY: imageHeight - (frameHeight / 2)
              , viewbox: `0 0 ${opts.imagewidth} ${imageHeight}`
          })
  
          ctx.nodes = processNodes(parsed.nodes)
          return ctx
      }
  
      return contextify;
  })();
  
  const utils = (function () {
  
      function formatTime(ts) {
          ts = !isNaN(ts) && ts || 0;
          let str = '';
          if (ts < 1e3) {
              str = `${ts.toFixed(2)} ms`;
          } else if (ts < 1e3 * 60) {
              str = `${(ts / 1e3).toFixed(2)} s`;
          } else if (ts < 1e3 * 60 * 60) {
              str = `${(ts / (1e3 * 60)).toFixed(2)} min`;
          } else if (ts < 1e3 * 60 * 60 * 60) {
              str = `${(ts / (1e3 * 60 * 60)).toFixed(2)} h`;
          } else {
              str = `${ts.toFixed(2)} ms`;
          }
  
          return str;
      }
      return { formatTime };
  })();
  
  function narrowify(context, opts) {
      function processNode(n) {
          n.class = n.narrow ? 'hidden' : ''
      }
  
      function filterNode(n) {
          return !n.narrow
      }
  
      if (opts.removenarrows) context.nodes = context.nodes.filter(filterNode)
      else context.nodes.forEach(processNode)
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: mounted
   * @descript: 计算 svg 渲染数据
   */
  function mounted() {
      // 计算 svg 真实宽度
      const imagewidth = this.$refs.svg.clientWidth;
      this.flamegraphData.fconfig.imagewidth = imagewidth;
      // 传输过程中 timemax 会丢失
      this.flamegraphData.fconfig.timemax = Infinity;
      // 计算 svg 其余渲染数据
      const context = contextify(this.flamegraphData.parsed, this.flamegraphData.fconfig);
      narrowify(context, this.flamegraphData.fconfig);
  
      this.data = context;
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 展示函数详情
   */
  function s(info) {
      this.show = 'Function: ' + info;
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 展示默认值
   */
  function c() {
      this.show = 'Easy-Monitor';
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 放大点击处
   */
  function zoom(event) {
      let node = event.currentTarget;
      let index = node.attributes['node-index'].value;
      zoomclick = index;
      if (this.need_unzoom[index]) {
          unzoom.call(this);
      }
      let svg = document.getElementsByTagName("svg")[0];
      let attr = find_child(node, "rect").attributes;
      let width = parseFloat(attr["width"].value);
      let xmin = parseFloat(attr["x"].value);
      let xmax = parseFloat(xmin + width);
      let ymin = parseFloat(attr["y"].value);
      // 计算倍率使用原来的 width
      let ratio = (svg.width.baseVal.value - 2 * 10) / parseFloat(attr["width"].value);
      let fudge = 0.0001;
  
      let unzoombtn = this.$refs.unzoom;
      unzoombtn.style["opacity"] = "1.0";
  
      let el = document.getElementsByTagName("g");
      let upstack;
      for (let i = 0; i < el.length; i++) {
          let e = el[i];
          let ei = e.attributes['node-index'].value;
          let a = find_child(e, "rect").attributes;
          let ex = parseFloat(a["x"].value);
          let ew = parseFloat(a["width"].value);
          if (0 == 0) {
              upstack = parseFloat(a["y"].value) > ymin;
          } else {
              upstack = parseFloat(a["y"].value) < ymin;
          }
          if (upstack) {
              if (ex <= xmin && (ex + ew + fudge) >= xmax) {
                  e.style["opacity"] = "0.5";
                  zoom_parent(e, svg);
                  this.need_unzoom[e.attributes['node-index'].value] = true;
                  update_text(e);
              }
              else
                  e.style["display"] = "none";
          }
          else {
              if (ex < xmin || ex + fudge >= xmax) {
                  e.style["display"] = "none";
              }
              else {
                  zoom_child(e, xmin, ratio);
                  this.need_unzoom[e.attributes['node-index'].value] = false;
                  update_text(e);
              }
          }
      }
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 缩小点击
   */
  function unzoom() {
      let unzoombtn = this.$refs.unzoom;
      unzoombtn.style["opacity"] = "0.0";
  
      let el = document.getElementsByTagName("g");
      for (let i = 0; i < el.length; i++) {
          el[i].style["display"] = "block";
          el[i].style["opacity"] = "1";
          zoom_reset(el[i]);
          update_text(el[i]);
      }
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 鼠标置于搜索框上
   */
  function searchover(e) {
      this.$refs.search.style["opacity"] = "1.0";
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: methods
   * @descript: 鼠标移开搜索框
   */
  function searchout(e) {
      let searchbtn = this.$refs.search;
      if (searching) {
          searchbtn.style["opacity"] = "1.0";
      } else {
          searchbtn.style["opacity"] = "0.1";
      }
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: search_prompt
   * @descript: 开始搜索
   */
  function search_prompt() {
      let searchbtn = this.$refs.search;
      let matchedtxt = this.$refs.matched;
      if (!searching) {
          let term = prompt("请输入需要查询的函数名: (允许输入正则表达式，例如: ^ext4_)", "");
          if (term != null) {
              search(term, matchedtxt, searchbtn);
          }
      } else {
          reset_search();
          searching = 0;
          searchbtn.style["opacity"] = "0.1";
          searchbtn.firstChild.nodeValue = "Search"
          matchedtxt.style["opacity"] = "0.0";
          matchedtxt.firstChild.nodeValue = "";
      }
  }
  
  function set_parent_inf() {
      this.showBigPic
          ? this.$emit('hidePic')
          : this.$emit('changPic');
  
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: computed
   * @descript: 函数节点展示
   */
  function nodes() {
      return this.data.nodes || [];
  }
  
  /**
   * @component: views/common/profiler/flamegraph.vue
   * @vue-data: watch
   * @descript: 缩放时进行处理
   */
  function showBigPic() {
      // 计算 svg 真实宽度
      const imagewidth = this.$refs.svg.clientWidth;
      this.flamegraphData.fconfig.imagewidth = imagewidth;
      // 传输过程中 timemax 会丢失
      this.flamegraphData.fconfig.timemax = Infinity;
      // 计算 svg 其余渲染数据
      const context = contextify(this.flamegraphData.parsed, this.flamegraphData.fconfig);
      narrowify(context, this.flamegraphData.fconfig);
      this.data = context;
  
      unzoom.call(this);
  }
  
  //导出 flamegraph.vue 所需
  export default {
      mounted,
      methods: { s, c, zoom, unzoom, searchover, searchout, search_prompt, set_parent_inf },
      computed: { nodes },
      watch: { showBigPic }
  }