├── static
└── .gitkeep
├── .eslintignore
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── .editorconfig
├── src
├── App.vue
├── plugins
│ ├── index.js
│ ├── __performanceMonitor.js
│ └── performanceMonitor.js
├── router
│ └── index.js
├── views
│ └── index.vue
└── main.js
├── .gitignore
├── .postcssrc.js
├── index.html
├── .babelrc
├── LICENSE
├── .eslintrc.js
├── package.json
└── README.md
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | import PerformanceMonitor from './performanceMonitor';
2 |
3 | export default {
4 | install(Vue, options) {
5 | /* eslint-disable no-new */
6 | new PerformanceMonitor(options);
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueRouter from 'vue-router';
3 |
4 | Vue.use(VueRouter);
5 |
6 | export default new VueRouter({
7 | routes: [{
8 | path: '/',
9 | name: 'Index',
10 | component: () => import( /* webpackChunkName:'index' */ '../views/index.vue')
11 | }]
12 | })
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 前端性能监控
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
性能监控测试页面
4 |
5 |
6 |
7 |
14 |
15 |
22 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": [
9 | "> 1%",
10 | "last 2 versions",
11 | "not ie <= 8"
12 | ]
13 | }
14 | }
15 | ],
16 | "stage-2"
17 | ],
18 | "plugins": [
19 | "transform-vue-jsx",
20 | "transform-runtime"
21 | ]
22 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import PerformanceMonitor from './plugins/performanceMonitor';
7 |
8 | Vue.config.productionTip = false
9 |
10 | Vue.use(PerformanceMonitor, {
11 | reportUrl: "http://localhost:10300/performanceMonitor",
12 | appId: "performanceMonitor-1559318109525",
13 | appName: "performanceMonitor",
14 | env: 'dev'
15 | });
16 |
17 | /* eslint-disable no-new */
18 | new Vue({
19 | el: '#app',
20 | components: {
21 | App
22 | },
23 | template: '',
24 | router
25 | })
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 sky9102(https://github.com/sky9102)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: "babel-eslint"
7 | },
8 | env: {
9 | browser: true
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | "plugin:vue/essential",
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | "standard"
17 | ],
18 | // required to lint *.vue files
19 | plugins: ["vue"],
20 | // add your custom rules here
21 | rules: {
22 | // allow async-await
23 | "generator-star-spacing": "off",
24 | // allow debugger during development
25 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
26 | semi: 0,
27 | quotes: 0,
28 | "space-before-function-paren": 0,
29 | "no-multiple-empty-lines": 0,
30 | "comma-style": 0,
31 | "no-useless-return": 0,
32 | indent: 0,
33 | "no-unused-vars": 0,
34 | "o-undef": 0,
35 | "eol-last": 0,
36 | "comma-dangle": 0,
37 | eqeqeq: 0,
38 | "no-throw-literal": 0,
39 | "prefer-promise-reject-errors": 0,
40 | "no-trailing-spaces": 0,
41 | "no-new": 0,
42 | "operator-linebreak": 0,
43 | 'space-in-parens': 0,
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "performance-monitor",
3 | "version": "1.0.0",
4 | "description": "performance-monitor",
5 | "author": "sky9102",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "lint": "eslint --ext .js,.vue src",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "stylus": "^0.54.5",
15 | "stylus-loader": "^3.0.2",
16 | "vue": "^2.5.2",
17 | "vue-router": "^3.0.6"
18 | },
19 | "devDependencies": {
20 | "autoprefixer": "^7.1.2",
21 | "babel-core": "^6.22.1",
22 | "babel-eslint": "^8.2.1",
23 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
24 | "babel-loader": "^7.1.1",
25 | "babel-plugin-syntax-jsx": "^6.18.0",
26 | "babel-plugin-transform-runtime": "^6.22.0",
27 | "babel-plugin-transform-vue-jsx": "^3.5.0",
28 | "babel-preset-env": "^1.3.2",
29 | "babel-preset-stage-2": "^6.22.0",
30 | "chalk": "^2.0.1",
31 | "copy-webpack-plugin": "^4.0.1",
32 | "css-loader": "^0.28.0",
33 | "eslint": "^4.15.0",
34 | "eslint-config-standard": "^10.2.1",
35 | "eslint-friendly-formatter": "^3.0.0",
36 | "eslint-loader": "^1.7.1",
37 | "eslint-plugin-import": "^2.7.0",
38 | "eslint-plugin-node": "^5.2.0",
39 | "eslint-plugin-promise": "^3.4.0",
40 | "eslint-plugin-standard": "^3.0.1",
41 | "eslint-plugin-vue": "^4.0.0",
42 | "extract-text-webpack-plugin": "^3.0.0",
43 | "file-loader": "^1.1.4",
44 | "friendly-errors-webpack-plugin": "^1.6.1",
45 | "html-webpack-plugin": "^2.30.1",
46 | "node-notifier": "^5.1.2",
47 | "optimize-css-assets-webpack-plugin": "^3.2.0",
48 | "ora": "^1.2.0",
49 | "portfinder": "^1.0.13",
50 | "postcss-import": "^11.0.0",
51 | "postcss-loader": "^2.0.8",
52 | "postcss-url": "^7.2.1",
53 | "rimraf": "^2.6.0",
54 | "semver": "^5.3.0",
55 | "shelljs": "^0.7.6",
56 | "uglifyjs-webpack-plugin": "^1.1.1",
57 | "url-loader": "^0.5.8",
58 | "vue-loader": "^13.3.0",
59 | "vue-style-loader": "^3.0.1",
60 | "vue-template-compiler": "^2.5.2",
61 | "webpack": "^3.6.0",
62 | "webpack-bundle-analyzer": "^2.9.0",
63 | "webpack-dev-server": "^2.9.1",
64 | "webpack-merge": "^4.1.0"
65 | },
66 | "engines": {
67 | "node": ">= 6.0.0",
68 | "npm": ">= 3.0.0"
69 | },
70 | "browserslist": [
71 | "> 1%",
72 | "last 2 versions",
73 | "not ie <= 8"
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 10300, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-module-eval-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | cssSourceMap: true
44 | },
45 |
46 | build: {
47 | // Template for index.html
48 | index: path.resolve(__dirname, '../dist/index.html'),
49 |
50 | // Paths
51 | assetsRoot: path.resolve(__dirname, '../dist'),
52 | assetsSubDirectory: 'static',
53 | assetsPublicPath: '/',
54 |
55 | /**
56 | * Source Maps
57 | */
58 |
59 | productionSourceMap: true,
60 | // https://webpack.js.org/configuration/devtool/#production
61 | devtool: '#source-map',
62 |
63 | // Gzip off by default as many popular static hosts such as
64 | // Surge or Netlify already gzip all static assets for you.
65 | // Before setting to `true`, make sure to:
66 | // npm install --save-dev compression-webpack-plugin
67 | productionGzip: false,
68 | productionGzipExtensions: ['js', 'css'],
69 |
70 | // Run the build command with an extra argument to
71 | // View the bundle analyzer report after build finishes:
72 | // `npm run build --report`
73 | // Set to `true` or `false` to always turn it on or off
74 | bundleAnalyzerReport: process.env.npm_config_report
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 前端性能监控
2 |
3 | > 简介:前端性能监控,实时上报用户浏览站点性能数据;监控的数据有 **准备新页面时间耗时、DNS查询耗时、TCP链接耗时、Request请求耗时、解析dom树耗时、页面白屏时间、domready时间、onload执行完成时间时间**;可以持续监控、评估、上报数据,及时发现瓶颈,从而优化。
4 |
5 | * 目的:解决前端性能监控、评估、预警页面性能的状况、发现瓶颈、从而指导优化工作。
6 | * 功能:记录**前端**性能信息。
7 | * 范围:任何前端应用
8 | * 使用:两行代码搞定,使用的复杂度几乎降低到了零
9 |
10 | ## 为什么要做监控页面性能?
11 | * 1、一个页面性能差的话会影响到很多方面。在公司层面,页面性能会影响公司收益,如用户打开页面等待的太久,用户可能会直接关掉页面,或者下次不再打开了,特别是在移动端用户对页面响应延迟容忍度很低。
12 |
13 | * 2、除此之外,页面的加载速度还将直接影响页面的SEO,网页加载速度太慢,用户会直接关掉,这直接增加页面的跳出率,当搜索引擎发现页面跳出率高,搜索引擎会认为该网站对用户的价值不高,从而降低排名。2018年7月谷歌公司新规定,页面访问时间比较长,谷歌公司将会降低该页面的搜索排名。
14 |
15 | * 3、虽然性能很重要,但在开发迭代中,开发会有所忽略,性能会随着版本迭代而有所衰减,所以我们需要一个性能监控系统,持续监控,评估,预警页面性能的状况,发现瓶颈,从而指导优化工作。
16 |
17 | * 4、页面性能的评估与监控有很多成熟优秀的工具 ,比如gtmetrix 网站,他可以同时查多个分析工具的的结果,会提供许多的建议。
18 |
19 | * 5、 但这种方式与真实情况偏离,无法反馈某个地区的整体速度,慢速用户多少,无法反映性能的波动情况,另外除了白屏之类的,我们还有一些功能性的测速,比如页面可点击时间,广告展示的时间等等,这些都是无法模拟监控的。
20 |
21 | * 6、为了持续监控不同网络环境下用户访问情况与页面各功能可用情况,我们选择在页面上植入JS来监控线上真实用户数据。具体做法就是用一段代码将用户的数据上报到我们的服务器,通过一个系统将数据汇总,处理,最后图形化数据,方便我们查看各个页面等性能。
22 |
23 | ## 特点
24 | * 可拔插
25 | * 代码侵入量小
26 | * 使用灵活方便
27 |
28 | ## 快速启动
29 |
30 | ``` bash
31 | npm install -g cnpm --registry=https://registry.npm.taobao.org
32 | cnpm install
33 | npm run dev
34 | ```
35 | 此时,浏览器打开,输入网址http://localhost:10300, 进入 **性能监控测试页面**。
36 |
37 | ### 使用
38 | ```
39 | import PerformanceMonitor from './plugins/performanceMonitor';
40 |
41 | Vue.use(PerformanceMonitor, {
42 | reportUrl: "http://localhost:10300/performanceMonitor",
43 | appId: "performanceMonitor-1559318109525",
44 | appName: "performanceMonitor",
45 | env: "dev"
46 | });
47 | ```
48 |
49 | ## 配置参数 options
50 |
51 | 属性|说明|类型|默认值|是否可以为空
52 | --|:--:|--:|--:|--:
53 | reportUrl|性能上报地址|String|http://localhost:10300/performanceMonitor|N|
54 | env|环境:dev、test、uat、pro|String|dev|Y
55 | appId|项目ID|String||Y
56 | appName|项目名称|String||Y
57 | timeSpan|发送数据时的时间戳|Number|每次取当前的时间戳|Y|
58 | userAgent|userAgent|String|userAgent|Y|
59 | isSendBeacon|是否使用高级浏览器支持的 navigator.sendBeacon方法,这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发送请求,特别适合上报统计的场景。不支持时使用img的方式上报|Boolean|false|N|
60 |
61 | ## 上报数据
62 | 属性|说明
63 | --|:--:
64 | reportUrl|上报URL
65 | appId|appId
66 | env|环境:dev、test、uat、pro
67 | infoType|preformance
68 | timeSpan|当前时间戳
69 | userAgent|userAgent
70 | isSendBeacon|是否使用高级浏览器支持的 navigator.sendBeacon方法
71 | prepareNewPageTime|准备新页面时间耗时(毫秒)
72 | queryDNSTime|DNS查询耗时(毫秒)
73 | connectionTCPTime|TCP链接耗时(毫秒)
74 | requestTime|request请求耗时(毫秒)
75 | analysisDOMTime|解析dom树耗时(毫秒)
76 | whiteScreenTime|白屏时间(毫秒)
77 | domReadyTime|domready时间(毫秒)
78 | onloadSuccessTime|onload执行完成时间(毫秒)
79 | currenPagetUrl|当前页面地址
80 |
81 |
82 | ## 问题
83 | * 开发者有问题或者好的建议可以用[Issues](https://github.com/sky9102/performance-monitor/issues)反馈交流,请给出详细信息。
84 | * 如果有问题需要提问,请在提问前先完成以下过程:
85 | * 请仔细阅读本项目文档,查看能否解决;
86 | * 请提问前尽可能做一些DEBUG或者思考分析,然后提问时给出详细的错误相关信息以及个人对问题的理解。
87 |
88 | ## License
89 | [MIT](https://github.com/sky9102/performance-monitor/blob/master/LICENSE) Copyright (c) 2019 [sky9102](https://github.com/sky9102)
90 |
--------------------------------------------------------------------------------
/src/plugins/__performanceMonitor.js:
--------------------------------------------------------------------------------
1 | /*
2 | * 格式化参数
3 | */
4 | function formatParams(data = {}) {
5 | const arr = [];
6 | for (const name in data) {
7 | arr.push(
8 | encodeURIComponent(name) + "=" + encodeURIComponent(data[name])
9 | );
10 | }
11 | return arr.join("&");
12 | }
13 |
14 | /**
15 | * 性能监控
16 | */
17 | export default class PerformanceMonitor {
18 | constructor(ops) {
19 | this.options = {
20 | reportUrl: location.href, // 上报地址
21 | appId: "", // 项目ID
22 | appName: "", // 项目名称,
23 | env: "dev", // 环境:dev、test、uat、pro
24 | infoType: "preformance", // 信息类别
25 | timeSpan: Date.now(), // 发送数据时的时间戳
26 | userAgent: navigator.userAgent,
27 | isSendBeacon: false
28 | };
29 | Object.assign(this.options, ops);
30 | // 任务列表,存放所有任务
31 | this.reqDataList = [];
32 |
33 | this.init();
34 | }
35 |
36 | init() {
37 | this.listenOnLoad();
38 | }
39 |
40 | listenOnLoad() {
41 | window.addEventListener("load", () => {
42 | setTimeout(() => {
43 | this.delaySetPerformanceData({
44 | currenPagetUrl: location.href
45 | });
46 | });
47 | });
48 | }
49 |
50 | delaySetPerformanceData(obj) {
51 | this.savePerformanceData(obj);
52 | }
53 |
54 | getPerformanceData() {
55 | const {
56 | timing
57 | } = window.performance;
58 | const {
59 | navigationStart = 0, // 准备加载页面的起始时间
60 | fetchStart = 0, // 开始检查缓存或开始获取资源的时间
61 | domainLookupStart = 0, // 开始进行dns查询的时间
62 | domainLookupEnd = 0, // dns查询结束的时间
63 | connectStart = 0, // 开始建立连接请求资源的时间
64 | connectEnd = 0, // 建立连接成功的时间.
65 | responseStart = 0, // 接收到第一个字节的时间
66 | responseEnd = 0, // 接收到最后一个字节的时间.
67 | domInteractive = 0, // 文档解析结束的时间
68 | domContentLoadedEventEnd = 0, // DOMContentLoaded事件结束的时间
69 | domComplete = 0, // current document readiness被设置 complete的时间
70 | loadEventEnd = 0 // onload事件结束的时间
71 | } = timing;
72 |
73 | // 准备新页面时间耗时
74 | const prepareNewPageTime = fetchStart - navigationStart;
75 | // DNS查询耗时
76 | const queryDNSTime = domainLookupEnd - domainLookupStart;
77 | // TCP链接耗时
78 | const connectionTCPTime = connectEnd - connectStart;
79 | // request请求耗时
80 | const requestTime = responseEnd - responseStart;
81 | // 解析dom树耗时
82 | const analysisDOMTime = domComplete - domInteractive;
83 | // 白屏时间
84 | const whiteScreenTime = responseStart - navigationStart;
85 | // domready时间
86 | const domReadyTime = domContentLoadedEventEnd - navigationStart;
87 | // onload执行完成时间时间
88 | const onloadSuccessTime = loadEventEnd - navigationStart;
89 |
90 | return {
91 | prepareNewPageTime,
92 | queryDNSTime,
93 | connectionTCPTime,
94 | requestTime,
95 | analysisDOMTime,
96 | whiteScreenTime,
97 | domReadyTime,
98 | onloadSuccessTime
99 | };
100 | }
101 |
102 | async savePerformanceData(obj) {
103 | const performanceInfo = this.getPerformanceData();
104 | await Object.assign(performanceInfo, obj, {
105 | timeSpan: Date.now()
106 | });
107 | await this.reqDataList.push(
108 | Object.assign({}, this.options, performanceInfo)
109 | );
110 | await this.asyncSendReport();
111 | }
112 |
113 | asyncSendReport() {
114 | const {
115 | isSendBeacon = false, reportUrl = ""
116 | } = this.options;
117 | let repDataList = this.reqDataList;
118 |
119 | while (repDataList.length > 0) {
120 | const reqData = repDataList.shift();
121 | (data => {
122 | setTimeout(() => {
123 | this.sendReport(data, reportUrl, isSendBeacon);
124 | });
125 | })(reqData);
126 | }
127 | }
128 |
129 | /**
130 | * 高级浏览器还支持 navigator.sendBeacon方法。
131 | * 这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发,特别适合上报统计的场景。
132 | * 不支持时使用img的方式上报
133 | */
134 | sendReport(performance, reportUrl, isSendBeacon = false) {
135 | if (isSendBeacon && navigator.sendBeacon) {
136 | this.sendBeacon(performance, reportUrl);
137 | return;
138 | }
139 | this.sendImage(performance, reportUrl);
140 | }
141 |
142 | sendBeacon(data, reportUrl) {
143 | navigator.sendBeacon(reportUrl, JSON.stringify(data));
144 | }
145 |
146 | sendImage(data, reportUrl) {
147 | const image = new Image();
148 | image.src = `${reportUrl}?${formatParams(data)}`;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/plugins/performanceMonitor.js:
--------------------------------------------------------------------------------
1 | /*
2 | * 格式化参数
3 | */
4 | function formatParams(data = {}) {
5 | const arr = [];
6 | for (const name in data) {
7 | arr.push(
8 | encodeURIComponent(name) + "=" + encodeURIComponent(data[name])
9 | );
10 | }
11 | return arr.join("&");
12 | }
13 |
14 | /**
15 | * 性能监控
16 | */
17 | class PerformanceMonitor {
18 | constructor(ops) {
19 | this.options = {
20 | reportUrl: location.href, // 上报地址
21 | appId: "", // 项目ID
22 | appName: "", // 项目名称,
23 | env: "dev", // 环境:dev、test、uat、pro
24 | infoType: "preformance", // 信息类别
25 | timeSpan: Date.now(), // 发送数据时的时间戳
26 | userAgent: navigator.userAgent,
27 | isSendBeacon: false
28 | };
29 | Object.assign(this.options, ops);
30 | // 任务列表,存放所有任务
31 | this.reqDataList = [];
32 |
33 | this.init();
34 | }
35 |
36 | init() {
37 | this.listenOnLoad();
38 | }
39 |
40 | listenOnLoad() {
41 | window.addEventListener("load", () => {
42 | setTimeout(() => {
43 | this.delaySetPerformanceData({
44 | currenPagetUrl: location.href
45 | });
46 | });
47 | });
48 | }
49 |
50 | delaySetPerformanceData(obj) {
51 | this.savePerformanceData(obj);
52 | }
53 |
54 | getPerformanceData() {
55 | const { timing, memory, navigation } = window.performance;
56 | const {
57 | navigationStart = 0, // 准备加载页面的起始时间
58 | // unloadEventStart = 0, // 如果前一个文档和当前文档同源,返回前一个文档开始unload的时间
59 | // unloadEventEnd = 0, // 如果前一个文档和当前文档同源,返回前一个文档开始unload结束的时间
60 | // redirectStart = 0, // 如果有重定向,这里是重定向开始的时间.
61 | // redirectEnd = 0, // 如果有重定向,这里是重定向结束的时间.
62 | fetchStart = 0, // 开始检查缓存或开始获取资源的时间
63 | domainLookupStart = 0, // 开始进行dns查询的时间
64 | domainLookupEnd = 0, // dns查询结束的时间
65 | connectStart = 0, // 开始建立连接请求资源的时间
66 | connectEnd = 0, // 建立连接成功的时间.
67 | // secureConnectionStart = 0, // 如果是https请求.返回ssl握手的时间
68 | // requestStart = 0, // 开始请求文档时间(包括从服务器,本地缓存请求)
69 | responseStart = 0, // 接收到第一个字节的时间
70 | responseEnd = 0, // 接收到最后一个字节的时间.
71 | // domLoading = 0, // ‘current document readiness’ 设置为 loading的时间 (这个时候还木有开始解析文档)
72 | domInteractive = 0, // 文档解析结束的时间
73 | // domContentLoadedEventStart = 0, // DOMContentLoaded事件开始的时间
74 | domContentLoadedEventEnd = 0, // DOMContentLoaded事件结束的时间
75 | domComplete = 0, // current document readiness被设置 complete的时间
76 | // loadEventStart = 0, // 触发onload事件的时间
77 | loadEventEnd = 0 // onload事件结束的时间
78 | } = timing;
79 |
80 | // const {
81 | // usedJSHeapSize = 0, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize,否则可能出现内存泄漏
82 | // totalJSHeapSize = -1 // 可使用的内存
83 | // } = memory;
84 |
85 | // 准备新页面时间耗时
86 | const prepareNewPageTime = fetchStart - navigationStart;
87 | // DNS查询耗时
88 | const queryDNSTime = domainLookupEnd - domainLookupStart;
89 | // TCP链接耗时
90 | const connectionTCPTime = connectEnd - connectStart;
91 | // request请求耗时
92 | const requestTime = responseEnd - responseStart;
93 | // 解析dom树耗时
94 | const analysisDOMTime = domComplete - domInteractive;
95 | // 白屏时间
96 | const whiteScreenTime = responseStart - navigationStart;
97 | // domready时间
98 | const domReadyTime = domContentLoadedEventEnd - navigationStart;
99 | // onload执行完成时间
100 | const onloadSuccessTime = loadEventEnd - navigationStart;
101 |
102 | // 内存是否溢出
103 | // const memoryOverFlow = totalJSHeapSize > usedJSHeapSize ? 0 : 1;
104 |
105 | // 页面的加载方式
106 | // const pageLoadType = navigation.type;
107 | // const pageLoadTypeStr = this.pageLoadMethod(navigation.type);
108 |
109 | return {
110 | prepareNewPageTime,
111 | queryDNSTime,
112 | connectionTCPTime,
113 | requestTime,
114 | analysisDOMTime,
115 | whiteScreenTime,
116 | domReadyTime,
117 | onloadSuccessTime
118 | // memoryOverFlow,
119 | // pageLoadTypeStr
120 | // pageLoadType
121 | };
122 | }
123 |
124 | async savePerformanceData(obj) {
125 | const performanceInfo = this.getPerformanceData();
126 | await Object.assign(performanceInfo, obj, {
127 | timeSpan: Date.now()
128 | });
129 | await this.reqDataList.push(
130 | Object.assign({}, this.options, performanceInfo)
131 | );
132 | await this.asyncSendReport();
133 | }
134 |
135 | /**
136 | * 页面的加载方式
137 | * @param {*} type navigation.type
138 | */
139 | pageLoadMethod(type) {
140 | switch (type) {
141 | case 0:
142 | return "点击链接、地址栏输入、表单提交、脚本操作等方式加载";
143 | case 1:
144 | return "通过“重新加载”按钮或者location.reload()方法加载";
145 | case 2:
146 | return "网页通过“前进”或“后退”按钮加载";
147 | default:
148 | return "任何其他来源的加载";
149 | }
150 | }
151 |
152 | asyncSendReport() {
153 | const { isSendBeacon = false, reportUrl = "" } = this.options;
154 | let repDataList = this.reqDataList;
155 |
156 | while (repDataList.length > 0) {
157 | const reqData = repDataList.shift();
158 | (data => {
159 | setTimeout(() => {
160 | this.sendReport(data, reportUrl, isSendBeacon);
161 | });
162 | })(reqData);
163 | }
164 | }
165 |
166 | /**
167 | * 高级浏览器还支持 navigator.sendBeacon方法。
168 | * 这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发,特别适合上报统计的场景。
169 | * 不支持时使用img的方式上报
170 | */
171 | sendReport(performance, reportUrl, isSendBeacon = false) {
172 | if (isSendBeacon && navigator.sendBeacon) {
173 | this.sendBeacon(performance, reportUrl);
174 | return;
175 | }
176 | this.sendImage(performance, reportUrl);
177 | }
178 |
179 | sendBeacon(data, reportUrl) {
180 | navigator.sendBeacon(reportUrl, JSON.stringify(data));
181 | }
182 |
183 | sendImage(data, reportUrl) {
184 | const image = new Image();
185 | image.src = `${reportUrl}?${formatParams(data)}`;
186 | }
187 | }
188 |
189 | export default {
190 | install(Vue, options) {
191 | /* eslint-disable no-new */
192 | new PerformanceMonitor(options);
193 | }
194 | };
195 |
--------------------------------------------------------------------------------