Bootstrap

自建开发工具系列-Webkit内存动量监控UI(四)

本期将 UI 部分分为计算核心部分和输出 UI 的部分, 分类规划逻辑与实践

计算核心部分使用的纯函数,状态其实没有存储,而是使用 DOM 的数据进行状态增补。

  const nodeUI = document.createElement('div');
  nodeUI.id = 'nodeUI';
  nodeUI.style.cssText = `position:relative;width:120px;height:${TOOLS_HEIGHT}px;background-color:lightyellow`;
  panel.appendChild(nodeUI);

  // 刷新后增加新的内存值和GC状态
  while (nodeUI.children.length < 120) {
    const bar = document.createElement('span');
    bar.style.cssText = `width:1px;height:${TOOLS_HEIGHT}px;float:left;background-color:black`;
    nodeUI.appendChild(bar);
  }

现在先制定一个panel,然后将内存记录按照柱状条更新在panel中,结合GC,需要合并两种信息。

  const updateData = function (dom, height, color) {
    const child = dom.appendChild(dom.firstChild);
    child.style.height = `${height}px`;
    if (color) child.style.backgroundColor = color;
  };

  const refreshGraph = function (dom, oHFactor, hFactor) {
    [].forEach.call(dom.children, (c) => {
      const cHeight = c.style.height.substring(0, c.style.height.length - 2);

      // 转换到 MB
      const newVal = TOOLS_HEIGHT - ((TOOLS_HEIGHT - cHeight) / oHFactor) * hFactor;
      c.style.height = `${newVal}px`;
    });
  };

然后再输出刷新函数

  return {
    domElement: toolsDom,
    update() {
      // 每秒刷新
      if (Date.now() - lastTime < 1000 / 1) return;
      lastTime = Date.now();

      delta = performance.memory.usedJSHeapSize - lastUsedHeap;
      lastUsedHeap = performance.memory.usedJSHeapSize;

      // GC 判定,如果是清空内存,则使用绿色标识
      color = delta < 0 ? 'green' : 'black';

      ms = lastUsedHeap;
      memoryBottom = Math.min(memoryBottom, ms);
      memoryTop = Math.max(memoryTop, ms);
      noticeLabel.textContent = window.performance.memory.jsHeapSizeLimit ? `内存使用:   ${bytesToMB(ms)}` : 'Only Webkit';

      mbValue = ms / (1024 * 1024);

      if (mbValue > MemoryLimitValue) {
        factor = (mbValue - (mbValue % TOOLS_HEIGHT)) / TOOLS_HEIGHT;
        newThreshold = TOOLS_HEIGHT * (factor + 1);
        refreshGraph(nodeUI, TOOLS_HEIGHT / MemoryLimitValue, TOOLS_HEIGHT / newThreshold);
        MemoryLimitValue = newThreshold;
      }

      updateData(nodeUI, TOOLS_HEIGHT - mbValue * (TOOLS_HEIGHT / MemoryLimitValue), color);
    }
  };

完成体的代码为,放在目录的 utils.js 里

const displayPanel = function () {
  let memoryBottom = 100;
  let memoryTop = 0;
  const TOOLS_HEIGHT = 30;
  let MemoryLimitValue = TOOLS_HEIGHT;

  const toolsDom = document.createElement('div');
  toolsDom.id = 'twm';
  toolsDom.className = 'tools';
  toolsDom.style.cssText = 'width:130;height:48px;opacity:0.9;cursor:pointer;overflow:hidden;z-index:10000;will-change:transform;';

  const panel = document.createElement('div');
  panel.id = 'ms';
  panel.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:black;';
  toolsDom.appendChild(panel);

  const noticeLabel = document.createElement('div');
  noticeLabel.id = 'noticeLabel';
  noticeLabel.style.cssText = 'color:white;font-family:Arial;font-size:9px;font-weight:bold;line-height:15px';
  noticeLabel.innerHTML = '';
  panel.appendChild(noticeLabel);

  const nodeUI = document.createElement('div');
  nodeUI.id = 'nodeUI';
  nodeUI.style.cssText = `position:relative;width:120px;height:${TOOLS_HEIGHT}px;background-color:lightyellow`;
  panel.appendChild(nodeUI);

  // 刷新后增加新的内存值和GC状态
  while (nodeUI.children.length < 120) {
    const bar = document.createElement('span');
    bar.style.cssText = `width:1px;height:${TOOLS_HEIGHT}px;float:left;background-color:black`;
    nodeUI.appendChild(bar);
  }

  const updateData = function (dom, height, color) {
    const child = dom.appendChild(dom.firstChild);
    child.style.height = `${height}px`;
    if (color) child.style.backgroundColor = color;
  };

  const refreshGraph = function (dom, oHFactor, hFactor) {
    [].forEach.call(dom.children, (c) => {
      const cHeight = c.style.height.substring(0, c.style.height.length - 2);

      // 转换到 MB
      const newVal = TOOLS_HEIGHT - ((TOOLS_HEIGHT - cHeight) / oHFactor) * hFactor;

      c.style.height = `${newVal}px`;
    });
  };

  // 避免没有 window.performance.memory 的浏览器崩溃
  if (window.performance && !performance.memory) {
    performance.memory = { usedJSHeapSize: 0, totalJSHeapSize: 0 };
  }

  let precision;
  let i;

  function bytesToMB(bytes) {
    precision = Math.pow(10, 0);
    i = Math.floor(Math.log(bytes) / Math.log(1024));
    return `${Math.round((bytes * precision) / Math.pow(1024, i)) / precision} MB`;
  }

  let lastTime = Date.now();
  let lastUsedHeap = performance.memory.usedJSHeapSize;
  let delta = 0;
  let color = 'black';
  let ms = 0;
  let mbValue = 0;
  let factor = 0;
  let newThreshold = 0;

  return {
    domElement: toolsDom,
    update() {
      // 每秒刷新
      if (Date.now() - lastTime < 1000 / 1) return;
      lastTime = Date.now();

      delta = performance.memory.usedJSHeapSize - lastUsedHeap;
      lastUsedHeap = performance.memory.usedJSHeapSize;

      // GC 判定,如果是清空内存,则使用绿色标识
      color = delta < 0 ? 'green' : 'black';

      ms = lastUsedHeap;
      memoryBottom = Math.min(memoryBottom, ms);
      memoryTop = Math.max(memoryTop, ms);
      noticeLabel.textContent = window.performance.memory.jsHeapSizeLimit ? `内存使用:   ${bytesToMB(ms)}` : 'Only Webkit';

      mbValue = ms / (1024 * 1024);

      if (mbValue > MemoryLimitValue) {
        factor = (mbValue - (mbValue % TOOLS_HEIGHT)) / TOOLS_HEIGHT;
        newThreshold = TOOLS_HEIGHT * (factor + 1);
        refreshGraph(nodeUI, TOOLS_HEIGHT / MemoryLimitValue, TOOLS_HEIGHT / newThreshold);
        MemoryLimitValue = newThreshold;
      }

      updateData(nodeUI, TOOLS_HEIGHT - mbValue * (TOOLS_HEIGHT / MemoryLimitValue), color);
    }

  };
};

export default displayPanel;

制作UI 需要将组件以一种简单的方式输出,以便加载时简单引用,这部分用react来封装

import * as React from 'react';
import './App.css';
import displayPanel from './utils';


class App extends React.Component {
  
  constructor(props) {
    super(props);
    this.state = {
      twm: displayPanel()
    };
  }

  componentDidMount() {
    const updateLoop = () => {
      if (this.refs.toolsNode.appendChild) {
        this.refs.toolsNode.appendChild(this.state.twm.domElement);
      }
      if (this.state.twm) {
        this.state.twm.update();
      }
      if (window.performance.memory.jsHeapSizeLimit) {
        window.setTimeout(updateLoop, 1000)
      }
    }
    window.setTimeout(updateLoop, 1000)
  }

  render() {
    return 
; } } export default App;

然后咱们来看一下在Create React App生成的项目中简单的引用

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

下期继续更新完善这个工具