有关前端监控系统中pv指标的改进 | 青训营笔记

319 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第2天

pv(Page View)指标是前端性能监控系统中的一个必不可少的项,在过去的介绍中很多只介绍了通过onload来记录上传这项指标,但是进这种方法是不能满足实际需要的

什么是pv指标,意义何在

PV是page view的缩写,即页面浏览量,通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标。页面浏览数常作为网站流量统计的主要指标。pv记录的准确性,是业务员分析当前产品或其它的有效性的重要指标。 在前端性能监控系统中,一种最简单的做法就是监听onload事件,在页面加载完成后就进行一次页面信息的上报,作为一次pv。但根据本人在探索前端监控系统如何实现的过程中认为存在两点必须改进的地方:

  • 当前前端SPA应用的数量逐渐增加,SPA通过前端路由实现页面切换,onload是捕获不到这样的pv的
  • 实际用户使用中存在误点击(页面停留时间极短)、不关闭tab长时间离去再次返回(这应该改算作新的pv)的情况,onload事件存在局限性

针对SPA的SDK改进

SPA应用是通过前端路由进行页面切换的,在路由切换时并不会刷新页面,即不会重新出发onload事件,这使得onload页面在整个使用场景中只会触发一次

根据前端路由原理进行改进

已知的前端路由实现原理主要是依赖hash或者history进行路由切换。

  • 针对hash,只需要而外监听hashchange事件,在进行pv上报即可
  • 针对history,浏览器对于history的变化提供了onpopstate,当使用history进行前后跳转history.back() history.forward() history.go()会触发该事件,但pushState replaceState无法触发,可以对这两个进行如下简单的重写,也可以参考 js如何实现history路由监听实现一个订阅发布模式
history.pushState = function (...args) {
    let path = args[2]
            ? args[2].startsWith("#")
                    ? args[2].slice(1)
                    : args[2]
            : "";
    sendPv(path, "");
    pushState.apply(history, args);
};
history.replaceState = function (...args) {
    let path = args[2]
            ? args[2].startsWith("#")
                    ? args[2].slice(1)
                    : args[2]
            : "";
    sendPv(path, "");
    replaceState.apply(history, args);
};

在sdk中还添加了一个enableSPA = 'hash' | 'history'让用户配置是否开启某种路由监听

提供pv上传的接口

考虑的用户实际实现应用的复杂性和不可控性,sdk中暴露了sendPV这个手动进行pv上传的接口,用户可以在自己需要上报的地方进行pv上报

多种场景下的pv上报

参考:为什么你统计 PV 的方式是错的? 大家在使用浏览的过程中肯定有过这样的经历,将一个可能常用的页面固定在浏览器上,不关闭浏览器只是最小化下次直接打开继续使用。在下次继续使用前可能已经过去的很长时间,那么这算不算是一次新的pv呢?那是肯定的,但是页面未刷新onload事件是不会进行pv上报的 这里我们通过Page Visibility API来监听这种事件。

The Page Visibility API provides events you can watch for to know when a document becomes visible or hidden, as well as features to look at the current visibility state of the page.

一个简短的使用如下Page Visibility API 教程--阮一峰

document.addEventListener('visibilitychange', function () {
  // 用户离开了当前页面
  if (document.visibilityState === 'hidden') {
    document.title = '页面不可见';
  }

  // 用户打开或回到页面
  if (document.visibilityState === 'visible') {
    document.title = '页面可见';
  }
});

在我们api的实现中,就是通过该api取代了onload,部分代码如下:

let startTime = Date.now();
let isLeave = true;
// visibilitychange 替代 load
let hiddenTime = Date.now();
const handleVisibilityChange = () => {
    let visibleTimer = null;
    let limit = 1000 * 60 * 10; // 用户如果离开页面超过10min,再次有效返回时会发送pv
    let leaveTimer = null;

    return function () {
        // 用户打开或回到页面
        if (document.visibilityState === "visible") {
            leaveTimer && clearTimeout(leaveTimer);
            if (isLeave) {
                startTime = Date.now();
                // 用户在当前页面停留3s以上,才算是有效pv
                visibleTimer && clearTimeout(visibleTimer);
                visibleTimer = setTimeout(() => {
                    sendPv();
                    isLeave = false;
                }, 3000);
            }
        }

        // 用户离开了当前页面
        if (document.visibilityState === "hidden") {
            visibleTimer && clearTimeout(visibleTimer);
            if (!isLeave) {
                hiddenTime = Date.now();
                leaveTimer && clearTimeout(leaveTimer);
                // 超出limit,判定为离开页面
                leaveTimer = setTimeout(() => {
                    isLeave = true;
                    let log = {
                        category: "business",
                        type: "stayTime",
                        stayTime: hiddenTime - startTime,
                    };
                    tracker.send(log);
                }, limit);
            }
        }
    };
};
document.addEventListener('visibilitychange', handleVisibilityChange())

主要是实现了如下效果:

  • 当进入页面停留时间超过3s再发送pv,是做有效浏览
  • 页面隐藏超过10min,是为用户离开,发送staytime指标
  • 用户长时间离开后,页面再次可见时会发送pv

本次搭建前端性能监控项目sdk针对pv指标主要就是做了以上两点改进,初次接触如有问题请指正 以上