Elastic APM 前端新增上报字段

326 阅读3分钟

注:只是俺的学习记录

transaction新增网络类型字段

 if (navigator && navigator.connection && navigator.connection.effectiveType) {
   transaction.addLabels({ 'network_type': navigator.connection.effectiveType });
 }

type为resource的span新增文件大小字段、是否命中缓存字段

* transferSize 表示资源传输总大小,包含header,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
* encodedBodySize:表示压缩之后的body大小,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
* decodedBodySize:表示解压之后的body大小,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
以上信息可得知:
跨域资源:transferSize === 0 && encodedBodySize === 0 && decodedBodySize === 0
缓存资源:transferSize === 0 && (encodedBodySize !== 0 || decodedBodySize !== 0)
for(var i = 0; i < spanList.length; i++) { // spanList从transaction里拿
    if(spanList[i].type === 'resource') { 
        if(spanList[i].context && spanList[i].context.http && spanList[i].context.http.response) {
          var target_span_res = spanList[i].context.http.response;
          if(target_span_res.transfer_size !== 'undefined' && target_span_res.encoded_body_size !== 'undefined' && target_span_res.decoded_body_size !== 'undefined') {
            spanList[i].addLabels({
              'transfer_size': target_span_res.transfer_size,
              'encoded_body_size': target_span_res.encoded_body_size,
              'decoded_body_size': target_span_res.decoded_body_size,
              'cross_origin_resource': target_span_res.transfer_size === 0 && target_span_res.encoded_body_size === 0 && target_span_res.decoded_body_size === 0,
              'hit_cache': target_span_res.transfer_size === 0 && (target_span_res.encoded_body_size !== 0 || target_span_res.decoded_body_size !== 0)
            });
          }
        }
    }
}


新增页面滚动时的fps字段

  1. fps计算: fps为每秒传输帧数,而requestAnimationFrame一般多用来实现连贯的逐帧动画,那么我们计算1s内requestAnimationFrame被调用了多少次,就能得出每秒传输帧数。
// 开始记录fps
var lastTime = null;
var animationId = null;
function startFps() {
  lastTime = performance.now(); // 记录开始执行requestAnimationFrame前的时间
  var loop = function () {
    var now =  performance.now();
    timeList.push(now); // 记录每次调用requestAnimationFrame的时间
    if(!fpsStop) {
      animationId = window.requestAnimationFrame(loop);
    } else {
      window.cancelAnimationFrame(animationId);
    }
  }
  animationId = window.requestAnimationFrame(loop);
}
  
// 滚动结束后,计算fps
function calculateFps() {
  // 1000ms内执行了多少次loop事件
  var frame = 0;
  for(var i = 0; i < timeList.length; i++) {
    frame++;
    if (timeList[i] > 1000 + lastTime) { // 执行了1000ms之后
      var fps = Math.round( ( frame * 1000 ) / ( timeList[i] - lastTime ) ); // 1000ms运行了多少次
      frame = 0;
      lastTime = timeList[i];
      fpsList.push(fps)
    };
  }
}

2、页面开始滚动时开始记录fps,并上报到apm

// 创建fpsTransaction
var fpsTransaction = apm.startTransaction('page-scroll-fps', 'user-interaction');
// 停止记录fps
function handleStopFps() {
  if(!fpsStop) {
    fpsStop = true;
    // 计算最后的fps数组
    calculateFps();
    if(fpsList.length > 0) {
      // 拿到fpsList并上报,要先创建fpsTransaction
      fpsTransaction.addLabels({ 'fps_list': fpsList });
      fpsTransaction.end();
    }
  }
}
// 处理滚动事件
function handleScroll() {
  window.removeEventListener('scroll', handleScroll); // 刚开始滚动就取消监听,下面事件执行一次
  // 开始记录fps
  startFps();
  // 6s后停止记录fps
  setTimeout( function() {
    handleStopFps();
  }, 6000);
}

// scroll事件触发fps开始记录
window.addEventListener('scroll', handleScroll);

完整代码

在这之前要初始化elasticApm

(function () {
  var fpsStop = false;
  var fpsList = [];
  var timeList = [];
  var lastTime = null;
  var animationId = null;
  if(typeof window === 'undefined' || !elasticApm) {
    return;
  }
  // 新建上报fps的transaction
  var fpsTransaction = elasticApm.startTransaction(name, type);
  // 新增网络类型、文件大小、是否命中缓存上报
  elasticApm.observe('transaction:end', function (transaction) {
    if(transaction.type === 'page-load') {
      var spanList = transaction && transaction.spans.length ? transaction.spans : [];
      // https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/connection
      // navigator.connection.effectiveType 获取设备的网络连接信息
      // 兼容性:pc不兼容火狐、ie、safari;m不支持safari
      if (navigator && navigator.connection && navigator.connection.effectiveType) {
        transaction.addLabels({ 'network_type': navigator.connection.effectiveType });
      }
      /**
      * https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/transferSize
      * https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API#resource_loading_timestamps
      * 兼容性:pc不支持ie、safari;m不支持safari
      * transferSize 表示资源传输总大小,包含header,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
      * encodedBodySize:表示压缩之后的body大小,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
      * decodedBodySize:表示解压之后的body大小,如果资源是从本地缓存中获取的,或者是跨源资源(没有Timing-Allow-Origin HTTP响应标头的资源),此属性返回零。
      */
      for(var i = 0; i < spanList.length; i++) {
        if(spanList[i].type === 'resource') {
          if(spanList[i].context && spanList[i].context.http && spanList[i].context.http.response) {
            var targetSpanRes = spanList[i].context.http.response;
            if(targetSpanRes.transfer_size !== 'undefined' && targetSpanRes.encoded_body_size !== 'undefined' && targetSpanRes.decoded_body_size !== 'undefined') {
              spanList[i].addLabels({
                'transfer_size': targetSpanRes.transfer_size,
                'encoded_body_size': targetSpanRes.encoded_body_size,
                'decoded_body_size': targetSpanRes.decoded_body_size,
                'cross_origin_resource': targetSpanRes.transfer_size === 0 && targetSpanRes.encoded_body_size === 0 && targetSpanRes.decoded_body_size === 0,
                'hit_cache': targetSpanRes.transfer_size === 0 && (targetSpanRes.encoded_body_size !== 0 || targetSpanRes.decoded_body_size !== 0),
              });
            }
          }
        }
      }
    }
  });
  
  // 开始记录fps
  function startFps() {
    lastTime = performance.now();
    var loop = function () {
      var now = performance.now();
      timeList.push(now);
      if(!fpsStop) {
        animationId = window.requestAnimationFrame(loop);
      } else {
        window.cancelAnimationFrame(animationId);
      }
    };
    animationId = window.requestAnimationFrame(loop);
  }

  // 滚动结束后,计算fps
  function calculateFps() {
    // 1000ms内执行了多少次loop事件
    var frame = 0;
    for(var i = 0; i < timeList.length; i++) {
      frame++;
      if (timeList[i] > 1000 + lastTime) { // 执行了1000ms之后
        var fps = Math.round((frame * 1000) / (timeList[i] - lastTime)); // 1000ms运行了多少次
        frame = 0;
        lastTime = timeList[i];
        fpsList.push(fps);
      }
    }
  }

  // 停止记录fps
  function handleStopFps() {
    if(!fpsStop) {
      fpsStop = true;
      calculateFps();
      if(fpsList.length > 0) {
        fpsTransaction.addLabels({ 'fps_list': fpsList });
        fpsTransaction.end();
      }
    }
  }

  // 处理滚动事件
  function handleScroll() {
    window.removeEventListener('scroll', handleScroll);
    startFps();
    // 6s后停止记录fps
    setTimeout(function () {
      handleStopFps();
    }, 6000);
  }

  // scroll事件触发fps开始记录
  window.addEventListener('scroll', handleScroll);
})();

参考:
developer.mozilla.org/en-US/docs/…
developer.mozilla.org/en-US/docs/…
developer.mozilla.org/zh-CN/docs/…
fed.taobao.org/blog/taofed…