依赖 berserkJS 建立前端性能监控平台

红薯 发布于 2012/11/02 11:53
阅读 3K+
收藏 15

世界之始

某一天,你终于有幸成为了某大型互联网公司前端开发团队的一员,你是如此的兴奋,整个世界都焕然一新了。如同霞光万丈的朝阳,伴随美好与璀璨在未来等待着你。

不幸的是,你突然发现,维护这个对可靠性要求极高的站点是如此的困难!样式丢失!图片过大!渲染性能过低!网络请求异常!脚本异常!以及万恶的故障率 KPI 时刻环绕在你周围,使你几乎窒息!璀璨的天地似乎瞬间纠结在一起,成为吞噬一起的黑洞,使你深陷其中无法自拔。

天地初开

终于你再也无法忍受下去了。你发现,可以在页面中加入一些监控代码,让它们帮你监控问题,助你脱离泥潭。此时天地再次分开,沉稳的物质凝结在一起构成大地,浮夸的上升成为蓝天。你身在其中,深深的呼吸了一口久违的空气,倍感舒畅。

此后过了很长日子,大地在不断的生长,天空亦在蔓延。每日,你辛劳的不停加入监控代码,不停的上线,以此保证天地不再交汇,世界不再混沌。

神器显现

某天夜里,你做了个梦,梦中你拥有了一把神交予你的利器。它使你摆脱了每日的重复劳作,帮你监管天地。而你则可以尽情享受大地带来的果实,仅在必要 时处理天与地的问题。从睡梦中醒来,你依然沉浸在当时美好之中。作为一个本分人,你深知梦是美好的,而现实是残酷的。但是此时,你发现真的出现了神迹,一 个闪耀着微弱青蓝色光芒怪模怪样的神器正静悄悄躺在你枕边。 berserkJS ? 这是什么?你小心的捧着那个形状扭曲到极致的器物,对着上面的铭文由衷发出了疑问。

一道光线直射你双瞳,世界在眼中瞬间成为一片惨白。身体不由自主的浮动起来,压缩为一个粒子般大小,极速进入神器之中。

平行宇宙

伴随着急促的呼吸声,你的视线渐渐清晰了。这是一个硕大的办公室,有很多跟你一样的工程师穿梭忙碌着。面前有一台终端机,上面正在现实一个叫做 “微博前端性能监控平台” 的页面,里面充满了详细数据。机器前,一个明显中年发福的死胖子工程师,正在奋力的敲着你很熟悉的 javascript 代码,片刻他高声喊道:“微博 V5 页面前端自动性能检测脚本已经放到监测平台上了,1小时后看数据!”。

出于好奇与自负的心理,你凑过去问了问:“嗨,伙计,你的检测代码部署怎么没有走上线流程啊?这样很不安全,当心出线上bug哦。” 那个发福的中年胖子翘起他那已经小的不能再小的细缝眼,不屑的看着你,从牙缝里挤出一句酸不溜丢的话:“监控平台的代码是旁路监控线上页面运行情况,它不 用修改线上代码来获取相关数据。你是新来的么,这都不知道,去北边办公室,找 @教主 给你做个培训。” 说完,捧着他的大肚子一溜烟钻进旁边的会议室不见了。

“不用修改线上代码来获取相关数据?这怎么可能?鬼都不信!”你小声嘀咕着,充满着狐疑。你试图趴在终端前看个究竟,刚迈出一步,脚下被什么东西绊 到了。它是一本技术手册,上面用标准宋体二号字写着《基于berserkJS 的前端性能监控平台技术手册》。“berserkJS!?就是它把我带到这里来的!”你的内心在狂吼,耳中几乎听到了自己瞬间加速的心跳声。必然,你急待 的开始阅读本神奇的手册,希望它能诠释你至今为止遇到的所有问题。

基于berserkJS 的前端性能监控平台

berserkJS(大名:狂暴JS / 昵称:疯子JS)是微博前端的开源工具之一。它主攻页面性能分析与测试自动化方向,可用 javascript 编写自己的检测、分析规则,并自动执行。
此工具基于 QT 开发,理论上可以跨平台使用,前提是在目标平台编译并部署 QT 运行环境。用于尝试前端自动化分析页面网络请求数据,可以使用 JS 操作页面导向,获取所需数据。

使用案例

  • 无界面浏览器测试:在不依赖本地任何浏览器的情况下,运行测试框架,如 QUnit,Capybara, QUnit, Mocha, WebDriver, YUI Test, BusterJS, FuncUnit, Robot Framework 等。
  • 页面自动化:可以无障碍访问和操作网页的标准 DOM API 以及页面所用 JS 变量、对象、属性等内容。
  • 屏幕捕获:以编程方式获取网页全部或部分内容,可根据 Selector 截取指定 DOM 元素渲染情况;包括 CSS,SVG 和 Canvas。可将截取图片 base64 化,以便发送给远端服务器保存。
  • 网络监控:自动化的网络性能监控,跟踪页面所有资源加载情况并可简便的将输出结果格式化为标准HAR格式。
  • 页面性能监控:自动化的页面渲染监控,可获取 CPU、 内存使用情况数据,根据页面整体情况可简便的输出首次渲染时间、首屏渲染时间等关键数据。

工具特性

  • 跨平台性:基于 Qt 开发,可跨平台编译,部署。内置基于 QtWebkit 的浏览器环境。源码需在目标系统中编译后,可产生运行于 Windows / Linux / Mac 系统的可执行文件。
  • 功能性:工具内置 webkit 浏览器内核,可响应浏览器内核事件回调、支持发送鼠标消息给浏览器、包装浏览器网络请求数据为JS数据格式、可与浏览器内JS做数据交互。
  • 开放性:工具将主要操作均包装为JS语法与数据格式,采用JS语法包装,前端工程师可根据API组装出符合各自预期的检测功能。
  • 接口性:工具本身支持命令行参数,可带参调用。API支持处理外部进程读取输出流、支持HTTP发送数据。可由 WEB 程序远程调用后获取测试的返回结果。
  • 标准性:完全真实的浏览器环境内 DOM,CSS,JavaScript,Canvas,SVG 可供使用,绝无仿真模拟。

 

代码获取:https://github.com/tapir-dream/berserkJS

API 文档:http://tapir-dream.github.com/berserkJS

前端性能监控平台是基于 berserkJS 工具建立的性能数据收集与展示平台。它基于如下方式构建:

 berserkJS 担任运行线上页面的任务,使用它内置的 WebKit 将页面所需的性能信息获取,并提交给日志服务器。由于它的可编程性,不同产品的页面可使用不同的检测脚本获取它们感兴趣的数据,并且可以任意时间获得,再 也不同担心检测代码上线下线带来的繁琐流程。

自动化脚本可以使用纯 javascript 方式编写,对前端工程师完全无压力:

(function() {
(function() {
  // 检测时间 25000 MS
  var DETECT_TIME_OUT = 25000;
  // 报告类型
  var typeList = {
    doc: 'doc',
    js: 'js',
    css: 'css',
    img: 'img',
    cpu: 'cpu',
    dom: 'dom',
    repaint: 'repaint',
    firstPaintFinished: 'firstPaintFinished',
    firstScreenFinished: 'firstScreenFinished'
  };
 
  // 初始化报告对象
  var report = {};
  report['name'] = '天猫首页';
  report['url'] = '';
  report[typeList.firstPaintFinished] = {data: {}};
  report[typeList.firstScreenFinished] = {data: {}};
  report[typeList.repaint] = {data: []};
  report[typeList.doc] = {data: []};
  report[typeList.js] = {data: []};
  report[typeList.css] = {data: []};
  report[typeList.img] = {data: []};
  report[typeList.cpu] = {data: []};
  report[typeList.dom] = {data: {}};
 
  var repaintInterval;
  var w = App.webview; 
 
  var eventHandle = {
    // 取数据
    getNetworkData: function() {
      // 收集 cpu 占用每 200 ms 收集一次
      dataCollection.cpu();
      // 收集25秒用户首页数据
      setTimeout(function() {
        // 关闭网络监听
        w.netListener(false);
        // 获取 url
        report['url'] = w.execScript(function() {
          return location.href;
        });
        // 收集doc
        // 收集js
        // 收集css
        // 收集 img
        dataCollection.doc();
        dataCollection.js();
        dataCollection.css();
        dataCollection.img();
        dataCollection.dom();
        // 发送报告
        action.sendReport();
        App.close();
      }, DETECT_TIME_OUT);
 
    },
 
    firstScreenFinished: function(timeout, url) {
      report[typeList.firstScreenFinished].data = {timeout: timeout};
    },
 
    // 首次渲染监听
    firstPaintFinished: function(timeout, url) {
      report[typeList.firstPaintFinished].data = {timeout: timeout};
      // 首次渲染后开始计算每次重绘间隔
      repaintInterval = new Date().getTime();
    },
 
    repaint: function(rect) {
      if (!repaintInterval) {
        return;
      }
      var timeout = new Date().getTime() - repaintInterval;
      var area = rect.width * rect.height;
      report[typeList.repaint].data.push({
        timeout: timeout, area: area
      });
      repaintInterval = new Date().getTime();
    }
  };
 
  var action = {
    sendReport: function() {
      // 将数据POST到远程页面
      //App.httpRequest(url, 'data=' + encodeURIComponent(JSON.stringify(report)));
      console.log(JSON.stringify(report, undefined, 2));
    }
  };
 
  var dataCollection = {
    cpu: function() {
      var max = 100;
      var addUp = 0;
      var timeId = setInterval(function() {
        if (addUp == max) {
          clearInterval(timeId);
        }
        report[typeList.cpu].data.push({
          'cpu': App.cpu()
        });
      }, 150);
    },
    doc: function() {
      var arr = App.selector.doc().get();
      for (var i = 0, c = arr.length; i < c; ++i) {
        report[typeList.doc].data.push({
          url: encodeURI(arr[i].url),
          ResponseSize: arr[i].ResponseSize,
          ResponseDuration: arr[i].ResponseDuration,
          ResponseDNSLookupDuration: arr[i].ResponseDNSLookupDuration,
          ResponseWaitingDuration: arr[i].ResponseWaitingDuration,
          ResponseDownloadDuration: arr[i].ResponseDownloadDuration
        });
      }
    },
    css: function() {
      var arr = App.selector.css().get();
      for (var i = 0, c = arr.length; i < c; ++i) {
        report[typeList.css].data.push({
          url: encodeURI(arr[i].url),
          ResponseSize: arr[i].ResponseSize,
          ResponseDuration: arr[i].ResponseDuration,
          ResponseDNSLookupDuration: arr[i].ResponseDNSLookupDuration,
          ResponseWaitingDuration: arr[i].ResponseWaitingDuration,
          ResponseDownloadDuration: arr[i].ResponseDownloadDuration
        });
      }
    },
    js: function() {
      var arr = App.selector.js().get();
      for (var i = 0, c = arr.length; i < c; ++i) {
        report[typeList.js].data.push({
          url: encodeURI(arr[i].url),
          ResponseSize: arr[i].ResponseSize,
          ResponseDuration: arr[i].ResponseDuration,
          ResponseDNSLookupDuration: arr[i].ResponseDNSLookupDuration,
          ResponseWaitingDuration: arr[i].ResponseWaitingDuration,
          ResponseDownloadDuration: arr[i].ResponseDownloadDuration
        });
      }
    },
    img: function() {
      var arr = App.selector.img().get();
      for (var i = 0, c = arr.length; i < c; ++i) {
        report[typeList.img].data.push({
          url: encodeURI(arr[i].url),
          ResponseSize: arr[i].ResponseSize,
          ResponseDuration: arr[i].ResponseDuration,
          ResponseDNSLookupDuration: arr[i].ResponseDNSLookupDuration,
          ResponseWaitingDuration: arr[i].ResponseWaitingDuration,
          ResponseDownloadDuration: arr[i].ResponseDownloadDuration,
          width: arr[i].width,
          height: arr[i].height
        });
      }
    },
    dom: function() {
      report[typeList.dom].data = {count: w.execScript(function() {
        return document.getElementsByTagName('*').length;
      })}
    }
  }
  w.setViewport({width: 1024, height: 768});
  w.netListener(true);
  eventHandle.getNetworkData();
  w.addEventListener('repaint', eventHandle.repaint);
  w.addEventListener('firstPaintFinished', eventHandle.firstPaintFinished);
  w.addEventListener('firstScreenFinished', eventHandle.firstScreenFinished);
  w.open('http://www.YourWebSite.com/');
})();

以上脚本中,获取了页面文档、JS、CSS、图片请求的具体时间构成,并且获取到了页面渲染和CPU性能相关数据。

编写好的脚本,需要使用 berserkJS –script=your scipt file path 方式运行。

每次脚本执行后,所获取的数据,提交脚本内指定的日志服务器处理。工程师在任意时刻都可以访问性能平台,在日志服务器中获取大量详实的线上性能数据。如:

该图表示明确,某页面在 10 月 26 日期间,JS 脚本请求时间过长。

你想知道是哪个文件导致的请求时间异常,只需点击问题节点,平台将会把更详细的信息展示出来。

通过对 JS 资源请求数据表进行排序,我们看到了请求时间最长为 14 秒的文件,就是这个文件导致了整体数据异常。

除此之外,性能检测平台还提高了页面绘制范围折线图以及页面的CPU占用率图。这些数据是传统线上打点监控所无法获得的,它为我们优化渲染体统了重要参考数据。

作为监控平台,最为贴心的是提供了首次渲染时间与首屏时间数据。通过这两个关键数据指标,我们可以轻易知道当前页面整体性能状况。在迭代开发上线过程中提供了有利的防前端性能退化保障。

回归本土

你轻轻合上技术手册,心中陡然明白了生活的真谛,身体不由得轻飘飘起来,眼前的一切也徒然模糊,灵魂似乎飞散了出去…… 不知多久,你突然恢复了意识。此时,你躺在一片开阔草地上,黎明时分天边正在乍现一道曙光。使劲的揉了揉眼睛,在昏暗的光线下此处的景物倍感熟悉与亲切 ——你意识到自己已经返回原来的世界了。不远处,神器静静的躺在一朵小花旁,依旧闪烁着那不同寻常的飘逸光芒。你站了起来,毫不犹豫的抓起神器,奔向广阔 天地。

原文出处:http://tech.weibo.com/?p=2150

加载中
0
osrchina
osrchina
这几个标题比较NB
0
l
log_
好文,赞
返回顶部
顶部