NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name 性能指标自动记录器 // @namespace http://tampermonkey.net/ // @version 0.3 // @description performance api recorder! // @author kibbon,GxTongX // @include *//*.snssdk.com/feoffline/novel/* // @match https://codeday.me/bug/20180703/191609.html // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const storageKeys = { active: '__perf_active', max: '__perf_max', remain: '__perf_remain', record: '__perf_record', } const raf = window.requestAnimationFrame; const caf = window.cancelAnimationFrame; let firstScreen = 0; let list = []; const domObserver = new MutationObserver(count); domObserver.observe(document, { childList: true, subtree: true, }); function count() { const duration = performance.now(); const body = document.querySelector('body'); if (body) { list.push({ score: getScore(body, 1, false), time: duration, }); } else { list.push({ score: 0, time: duration, }); } } function getScore(element, depth, exist) { let score = 0; const tagName = element.tagName; if ( tagName !== 'SCRIPT' && tagName !== 'STYLE' && tagName !== 'META' && tagName !== 'HEAD' ) { const childrenLength = element.children ? element.children.length : 0; if (childrenLength > 0) { const children = element.children; for (let length = childrenLength - 1; length >= 0; length--) { score += getScore(children[length], depth + 1, score > 0); } } // 在以下几种情况下,将当前节点得分设置为0。这个元素没有任何视觉变化的权重 if (score <= 0 && !exist) { // 这个元素没有getBouncingClient方法,比如text? if ( !element.getBoundingClientRect || !typeof element.getBoundingClientRect === 'function' ) { return 0; } // 没有获取到boundingClient对象 const boundingClientRect = element.getBoundingClientRect(); if (!boundingClientRect) { return 0; } // 当前元素不可见 if (boundingClientRect.top > window.innerHeight) { return 0; } // 当前元素没有大小 if (boundingClientRect.height <= 0) { return 0; } } // 其他情况下, 为自身加一分,并且按照深度再加0.5倍 score += 1 + 0.5 * depth; } return score; } // 实际计算fmp的操作 function getFmpInternal() { domObserver.disconnect(); if (!list.length) { return 0; } let target = { time: list[0].time, rate: 0, }; // 求出最大变动list元素 for (let s = 1; s < list.length; s++) { if (list[s].time >= list[s - 1].time) { const diff = list[s].score - list[s - 1].score; if (target.rate < diff) { target = { time: list[s].time, rate: diff, }; } } } return target.time; } function firstScreenMonitor() { const getOffsetTop = function (ele) { if (!ele) { return 0; } let offsetTop = ele.offsetTop; if (ele.offsetParent) { offsetTop += getOffsetTop(ele.offsetParent); } return offsetTop; }; const getWinHeight = function () { if (window.innerHeight) { return window.innerHeight; } if (document.documentElement && document.documentElement.clientHeight) { return document.documentElement.clientHeight; } } let isFirstScreen = false; const firstScreenHeight = getWinHeight() || 0; const firstScreenImgs = []; let isFindLastImg = false; let allImgLoaded = false; let timerId; function step() { let i; let img; if (isFindLastImg) { if (firstScreenImgs.length && !allImgLoaded) { for (i = 0; i < firstScreenImgs.length; i++) { img = firstScreenImgs[i]; if (!img.complete) { allImgLoaded = false; break; } else { allImgLoaded = true; } } } else { allImgLoaded = true; } if (allImgLoaded) { if (!isFirstScreen) { isFirstScreen = true; firstScreen = performance.now(); // firstScreen = Date.now() - performance.timing.navigationStart; } caf(timerId); return; } } else { const imgs = document.querySelectorAll('img'); for (i = 0; i < imgs.length; i++) { img = imgs[i]; const imgOffsetTop = getOffsetTop(img); if (imgOffsetTop > firstScreenHeight) { isFindLastImg = true; break; } else if (imgOffsetTop <= firstScreenHeight && !img.hasPushed) { img.hasPushed = 1; firstScreenImgs.push(img); } } } timerId = raf(step); } timerId = raf(step); if (document.readyState === 'complete') { allImgLoaded = true; isFindLastImg = true; caf(timerId); step(); return; } document.addEventListener('DOMContentLoaded', function() { const imgs = document.querySelectorAll('img'); if (!imgs.length) { isFindLastImg = true; caf(timerId); step(); } }); window.addEventListener( 'load', function () { allImgLoaded = true; isFindLastImg = true; caf(timerId); step(); }, false ); } firstScreenMonitor(); let lcp = 0; function autoRecorder() { const t = window.performance.timing; let fid = 0; if (performance.getEntriesByType('first-input')[0]) { fid = performance.getEntriesByType('first-input')[0].processingStart - performance.getEntriesByType('first-input')[0].startTime; } const data = { blank: t.responseEnd - t.navigationStart, // 白屏 domready: t.domInteractive - t.navigationStart, // 可交互 fp: performance.getEntriesByType('paint').filter(function(p) { return p.name === 'first-paint'; })[0].startTime, // FP fcp: performance.getEntriesByType('paint').filter(function(p) { return p.name === 'first-contentful-paint'; })[0].startTime, // FCP lcp, // LCP fmp: getFmpInternal(), firstScreen, // 首屏 load: t.loadEventEnd - t.navigationStart, // 完全加载 fid // FID }; let remain = parseInt(localStorage.getItem(storageKeys.remain)); let record = JSON.parse(localStorage.getItem(storageKeys.record)); record.push([remain, data.blank, data.domready, data.fp, data.fcp, data.fmp, data.lcp, data.firstScreen, data.load, data.fid === 0 ? "-" : data.fid]); remain += 1; localStorage.setItem(storageKeys.record, JSON.stringify(record)); const maxium = parseInt(localStorage.getItem(storageKeys.max)); if (remain > maxium) { for (let key in storageKeys) { localStorage.removeItem(storageKeys[key]); } const max = ['最大']; const min = ['最小']; const avg = ['平均']; const len = record[0].length; for (let i = 1; i < len; i += 1) { let count = 0; avg.push(0); max.push(0); min.push(1e9); for (let j = 0; j < maxium; j += 1) { if (record[j][i] === "-") { continue; } count++; avg[i] += record[j][i]; min[i] = Math.min(record[j][i], min[i]); max[i] = Math.max(record[j][i], max[i]); } avg[i] /= count; if (!count) { min[i] = "-"; max[i] = "-"; avg[i] = "-"; } } record = [avg, max, min].concat(record); let recordStr = ',白屏,可交互,FP,FCP,FMP,LCP,首屏,完全加载,FID\n'; for (let j = 0; j < record.length; j += 1) { recordStr += record[j].join(',') + '\n'; } const aLink = document.createElement("a"); aLink.setAttribute("href", "data:text/csv;charset=utf-8," + encodeURIComponent(recordStr)); aLink.setAttribute("download", "record_" + location.hostname + location.pathname + ".csv"); aLink.click(); alert("记录完成!"); return; } localStorage.setItem(storageKeys.remain, remain); location.reload(); } const observer = new PerformanceObserver(function(list) { for (const entry of list.getEntries()) { if (entry.entryType === 'largest-contentful-paint') { lcp = entry.renderTime || entry.loadTime; checkLoad(); } } }); const checkLoad = function () { const t = performance.timing; if (t.loadEventEnd > t.navigationStart) { autoRecorder(); } else { window.addEventListener('load', function() { setTimeout(autoRecorder, 0); }, false); } } if (localStorage.getItem(storageKeys.active)) { if (lcp) { checkLoad(); } else { observer.observe({ entryTypes: ['paint', 'first-input', 'largest-contentful-paint'], buffered: true, }); } } else { const maxium = Number(window.prompt("刷新次数?", "10")); if (maxium) { for (let k in storageKeys) { localStorage.removeItem(storageKeys[k]); } localStorage.setItem(storageKeys.active, true); localStorage.setItem(storageKeys.remain, 1); localStorage.setItem(storageKeys.max, maxium); localStorage.setItem(storageKeys.record, JSON.stringify([])); location.reload(); } } })();