背景
首先我們為什么要做前端系統呢,先看下面這張表,可以很顯然的看出,前端的性能對於產品的價值提升還是蠻有幫助的,但是這些信息如果我們能實時的采集到,並且實施以監控,讓整個產品在產品線上一直保持高效的運作,這才是我們的目的。
性能
|
收益
|
---|---|
Google 延遲 400ms | 搜索量下降 0.59% |
Bing 延遲 2s | 收入下降 4.3% |
Yahoo 延遲 400ms | 流量下降 5-9% |
Mozilla 頁面打開減少 2.2s | 下載量提升 15.4% |
Netflix 開啟 Gzip | 性能提升 13.25% 帶寬減少50% |
其次,也有利於我們發布的產品,能夠及時發現我們的錯誤。如果一個產品在新的迭代中,發生不可描述的錯誤。
開始
以上是我們需要做的一些事情。
要做監控系統,首先我們得有一個對象。我們監控的對象!對象!對象!對象。
我在我的系統寫了一個這樣的頁面,
<body> <div>2</div> <div>2</div> <div>2</div> <div>2</div> <div>2</div> <div>2</div> </body>
沒錯這就是我們要監控的頁面。這個.....真不是我懶。
然后接下來我一共設計了3塊數據
- 頁面加載時間
- 統計用戶使用設備
- 錯誤量的統計
頁面加載時間
window.logInfo = {};
//統計頁面加載時間
window.logInfo.openTime = performance.timing.navigationStart;
window.logInfo.whiteScreenTime = +
new
Date() - window.logInfo.openTime;
document.addEventListener(
'DOMContentLoaded'
,
function
(event) {
window.logInfo.readyTime = +
new
Date() - window.logInfo.openTime;
});
window.onload =
function
() {
window.logInfo.allloadTime = +
new
Date() - window.logInfo.openTime;
window.logInfo.nowTime =
new
Date().getTime();
var
timname = {
whiteScreenTime:
'白屏時間'
,
readyTime:
'用戶可操作時間'
,
allloadTime:
'總下載時間'
,
mobile:
'使用設備'
,
nowTime:
'時間'
,
};
var
logStr =
''
;
for
(
var
i
in
timname) {
console.warn(timname[i] +
':'
+ window.logInfo[i] +
'ms'
);
if
(i ===
'mobile'
) {
logStr +=
'&'
+ i +
'='
+ window.logInfo[i];
}
else
{
logStr +=
'&'
+ i +
'='
+ window.logInfo[i];
}
}
(
new
Image()).src =
'/action?'
+ logStr;
};
|
統計用戶使用設備
window.logInfo.mobile = mobileType();
function
mobileType() {
var
u = navigator.userAgent, app = navigator.appVersion;
var
type = {
// 移動終端瀏覽器版本信息
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
//ios終端
iPad: u.indexOf(
'iPad'
) > -1,
//是否iPad
android: u.indexOf(
'Android'
) > -1 || u.indexOf(
'Linux'
) > -1,
//android終端或者uc瀏覽器
iPhone: u.indexOf(
'iPhone'
) > -1 || u.indexOf(
'Mac'
) > -1,
//是否為iPhone或者QQHD瀏覽器
trident: u.indexOf(
'Trident'
) > -1,
//IE內核
presto: u.indexOf(
'Presto'
) > -1,
//opera內核
webKit: u.indexOf(
'AppleWebKit'
) > -1,
//蘋果、谷歌內核
gecko: u.indexOf(
'Gecko'
) > -1 && u.indexOf(
'KHTML'
) == -1,
//火狐內核
mobile: !!u.match(/AppleWebKit.*Mobile/i) || !!u.match(/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/),
//是否為移動終端
webApp: u.indexOf(
'Safari'
) == -1
//是否web應該程序,沒有頭部與底部
};
var
lists = Object.keys(type);
for
(
var
i = 0; i < lists.length; i++) {
if
(type[lists[i]]) {
return
lists[i];
}
}
}
|
錯誤量的統計
window.onload =
function
() {
window.logInfo.allloadTime = +
new
Date() - window.logInfo.openTime;
window.logInfo.nowTime =
new
Date().getTime();
var
timname = {
whiteScreenTime:
'白屏時間'
,
readyTime:
'用戶可操作時間'
,
allloadTime:
'總下載時間'
,
mobile:
'使用設備'
,
nowTime:
'時間'
,
};
var
logStr =
''
;
for
(
var
i
in
timname) {
console.warn(timname[i] +
':'
+ window.logInfo[i] +
'ms'
);
if
(i ===
'mobile'
) {
logStr +=
'&'
+ i +
'='
+ window.logInfo[i];
}
else
{
logStr +=
'&'
+ i +
'='
+ window.logInfo[i];
}
}
(
new
Image()).src =
'/action?'
+ logStr;
};
var
defaults = {
msg:
''
,
// 錯誤的具體信息
url:
''
,
// 錯誤所在的url
line:
''
,
// 錯誤所在的行
col:
''
,
// 錯誤所在的列
nowTime:
''
,
// 時間
};
window.onerror =
function
(msg,url,line,col,error) {
col = col || (window.event && window.event.errorCharacter) || 0;
defaults.url = url;
defaults.line = line;
defaults.col = col;
defaults.nowTime =
new
Date().getTime();
if
(error && error.stack){
// 如果瀏覽器有堆棧信息,直接使用
defaults.msg = error.stack.toString();
}
else
if
(arguments.callee){
// 嘗試通過callee拿堆棧信息
var
ext = [];
var
fn = arguments.callee.caller;
var
floor = 3;
while
(fn && (--floor>0)) {
ext.push(fn.toString());
if
(fn === fn.caller) {
break
;
}
fn = fn.caller;
}
ext = ext.join(
","
);
defaults.msg = error.stack.toString();
}
var
str =
''
for
(
var
i
in
defaults) {
// console.log(i,defaults[i]);
if
(defaults[i] ===
null
|| defaults[i] === undefined) {
defaults[i] =
'null'
;
}
str +=
'&'
+ i +
'='
+ defaults[i].toString();
}
srt = str.replace(
'&'
,
''
).replace(
'\n'
,
''
).replace(/\s/g,
''
);
(
new
Image()).src =
'/error?'
+ srt;
}
|
以上就是收集數據的全部,通過發送/action請求或者是/error請求,這些都是可以自定義的,我講的只是整個過程是如何實現的。
然后通過我的的一個后台express.js把所有的請求處理並都記錄下來,記錄好后的數據是這樣子的。
user_ip=
127.0
.
0.1
&whiteScreenTime=
185
&readyTime=
192
&allloadTime=
208
&mobile=webKit&nowTime=
1513071388941
|
數據處理
這里我是通過自己寫的一段腳本進行解析,parse.js,這里不具體講解,看源碼即可。我展現下解析好的數據。
我以cvs的數據格式儲存,因為后面圖表的需要,我也支持json格式方式導出,只不過后面就需要你自己來配置可視化的界面了。
數據是這樣的。
charts/csvData/2017-12-16time.csv
時間,白屏時間,用戶可操作時間,總下載時間
1513427051482
,
137
,
137
,
153
1513427065080
,
470
,
471
,
507
1513427080040
,
127
,
127
,
143
1513428714345
,
274
,
275
,
323
1513428733583
,
267
,
268
,
317
1513428743167
,
268
,
268
,
317
1513428754796
,
276
,
276
,
328
|
數據展示
這里我用的是highcharts.js
具體的配置我不進行講解,可以自己到官網進行查看。
下面是可視化的圖表,顯示的是每天各個時間段的信息。
環境
node >= 6.0.0
redis >= 2.6.0
在這里我說明下,因為如果這個部署在線上環境的時候,如果每次記錄都進行記錄的話,會消耗大量的內存,所以我架設了一層redis,為了防止大流量的沖擊,然后可以每隔一段時間進行存儲。
const express = require(
'express'
);
const performance = require(
'./lib/performance.js'
);
const app = express();
const router = express.Router();
router.get(
'/'
,
function
(req, res, next) {
req.url =
'./index.html'
;
next();
});
app.use(router);
app.use(performance({
time: 10,
// 秒為單位
originalDir:
'./originalData'
,
// 數據的目錄
errorDir:
'./errorData'
// 報錯的目錄
}))
app.use(express.static(
'./'
));
const server = app.listen(3000)
|
這里可以設置默認的時間,我這里以10秒為單位,為了demo的效果起見。一般我采用的是一分鍾進行一次存儲。