In short, there are three reasons:

It is the nature of engineers to focus on performance. Page performance is critical to the user experience. It is not convincing to rely only on the test data of the equipment developed by engineers to improve page performance for each reconstruction, and a large number of real data are needed for verification. Resources hang up, loading abnormal, can not always rely on users to complain after the awareness, need to take the initiative to alarm. One-time refactoring, with gigabit speeds and ten thousand devices, may improve page load time by only 0.1%, but such numbers are not representative. Network environments and hardware devices vary widely. For mid – and low-end devices, the subjective experience of performance improvement is more obvious, and corresponding data changes are more representative. Many projects upload resources to the CDN. However, when some CDN nodes have problems, they generally cannot accurately tell “xx, your XX resource is down”, so we need to take the initiative to monitor. According to Google data, when a page loads more than 10 seconds, users get desperate and often leave the current page, and most likely never come back.

With what surveillance?

In terms of front-end Performance metrics, the W3C defines a powerful Performance API, It also includes High Resolution Time, Frame Timing, Navigation Timing, Performance Timeline, Resource Timing, and User Timing And many specific standards.

This article mainly involves Navigation Timing and Resource Timing. As of mid-2018, all major browsers have completed the basic implementation.

One of the many functions of the Performance API is to record the Performance (time details) of the page itself and the resources on the page. All we have to do is query and use.

You can enter Performance in the browser console to view the API. Next, we’ll implement a simple front-end performance monitoring tool using the window.performance object provided by the browser (a concrete implementation of the Performance API).

5 minutes for a front-end performance monitoring tool

First line of code

The tool is named pMonitor, which means Performance Monitor.

Const pMonitor = {} copies the code

What indicators to monitor

Since it is the “5 minutes to achieve a XXX” series, there is a trade-off. Therefore, this paper only selects the two most important indicators for monitoring:

Page load time

Resource request time

Look at the time, has passed 4 minutes, xiaobian said emotional stability, without a trace of fluctuation.

Page load

Performance metrics for page loading can be found in Navigation Timing. Navigation Timing includes details of all steps from the time the page is requested to the time the page is loaded.

You can obtain the specific contents of Navigation Timing in the following ways:

Const navTimes = performance. GetEntriesByType (‘ navigation ‘) duplicate code

GetEntriesByType is one way we get performance data. Performance also provides getEntries and getEntriesByName. Due to time constraints, the result is an array with elements like the following structure: {“connectEnd”: 64.15495765894057, “connectStart”: 64.15495765894057, “domainLookupEnd”: 64.15495765894057, “domainLookupStart”: 64.15495765894057, “domComplete” : 2002.5385066728431, “domContentLoadedEventEnd” : 2001.7384263440083, “domContentLoadedEventStart” : 2001.2386167400286, “domInteractive” : 1988.638474368076, “domLoading”: 271.75174283737226, “Duration “: 2002.9385468372606, “entryType”: “Navigation “, “fetchStart”: 64.15495765894057, “loadEventEnd”: 2002.9385468372606, “loadEventStart”: 2002.7383663540235, “name”: “document”, “navigationStart”: 0, “redirectCount”: 0, “redirectEnd”: 0, “redirectStart”: 0, “requestStart”: 65.28225608537441; 271.75174283737226, “startTime”: 0, “type”: “navigate”, “unloadEventEnd”: 0, “unloadEventStart”: 0, “workerStart”: 0.9636893776343863} Copy code

Navigation Timing Level 2 provides a detailed description of the time of each field:

As you can see, details abound. Therefore, the content that can be calculated is very rich, such as DNS query time, TLS handshake time and so on. It can be said that only unexpected, can not do ~

Since we are concerned with page loading, it is natural to read domComplete:

Const [{domComplete}] = performance. GetEntriesByType copying code (‘ navigation ‘)

Define a method to get domComplete:

pMonitor.getLoadTime = () => { const [{ domComplete }] = performance.getEntriesByType(‘navigation’) return domComplete } Copy the code

At this point, we have an accurate page load time.

Resource to load

Static resources have a Navigation Timing as well.

The answer is yes, and it’s called Resource Timing. It contains the time details of each link in the page from sending the request to completing the loading, similar to Navigation Timing.

The keyword for obtaining the load time of a resource is ‘resource’. The specific method is as follows:

Performance. GetEntriesByType (‘ resource ‘) duplicate code

Predictably, the result is usually a very long array, because it contains the load information for all the resources on the page.

The specific structure of each message is as follows:

{“connectEnd”: 462.95008929525244, “connectStart”: 462.95008929525244, “domainLookupEnd”: 462.95008929525244, “domainLookupStart”: 462.95008929525244, “Duration “: 0.9620853673520173, “entryType”: “Resource “, “fetchStart”: 462.95008929525244, “initiatorType”: “img”, “name”: “cn.bing.com/sa/simg/Sha…” “, “nextHopProtocol”: “”, “redirectEnd”: 0, “redirectStart”: 0, “requestStart”: 463.91217466260445, “responseEnd”: 463.91217466260445, “responseStart”: 463.91217466260445, “startTime”: 462.95008929525244, “workerStart”: 0

Above for July 7, 2018, under the cn.bing.com searching for the test, the performance. The getEntriesByType (” resource “) returns the second result. We are concerned about the loading time of resources, which can be obtained in the following form:

const [{ startTime, ResponseEnd}] = performance. GetEntriesByType () ‘resource’ const loadTime = responseEnd – startTime copy code

StartTime, fetchStart, connectStart, requestStart Resource Timing Level 2

Not all resource load times need attention, but the ones that load too slowly need attention.

For the sake of simplification, 10s is defined as the timeout threshold, so the method to obtain the timeout resource is as follows:

const SEC = 1000 const TIMEOUT = 10 * SEC const setTime = (limit = TIMEOUT) => time => time >= limit const isTimeout = setTime() const getLoadTime = ({ startTime, responseEnd }) => responseEnd – startTime const getName = ({ name }) => name const resourceTimes = performance.getEntriesByType(‘resource’) const getTimeoutRes = resourceTimes .filter(item => IsTimeout (getLoadTime(item)).map(getName) copies the code

In this way, we get a list of all the resources that have timed out.

In a nutshell:

const SEC = 1000 const TIMEOUT = 10 * SEC const setTime = (limit = TIMEOUT) => time => time >= limit const getLoadTime = ({ requestStart, responseEnd }) => responseEnd – requestStart const getName = ({ name }) => name pMonitor.getTimeoutRes = (limit = TIMEOUT) => { const isTimeout = setTime(limit) const resourceTimes = performance.getEntriesByType(‘resource’) return Resourcetimes.filter (item => isTimeout(getLoadTime(item))).map(getName)} Copies the code

Reported data

After obtaining data, the following information needs to be reported to the server:

Const convert2FormData = (data = {}) => object.entries (data).reduce((last, [key, value]) => { if (Array.isArray(value)) { return value.reduce((lastResult, item) => { lastResult.append(${key}[], item) return lastResult }, last) } last.append(key, value) return last }, New FormData()) const makeItStr = (data = {}) => object.entries (data).map(([k, V]) = > ${k} = ${n}). Join (‘ & ‘) / / report data pMonitor log = (url, data = {}, type = ‘POST’) => { const method = type.toLowerCase() const urlToUse = method === ‘get’ ? ${url}? ${makeItStr(data)} : url const body = method === ‘get’ ? {} : { body: convert2FormData(data) } const option = { method, … Body} fetch(urlToUse, option).catch(e => console.log(e))} Copy code

Go back to the initialization

Details of data upload urls, timeout times, etc., vary from project to project, so an initialization method needs to be provided:

// Let config = {} /**

  • @param {object} option
  • @param {string} option.url Indicates the address for reporting page load data
  • @param {string} option.timeoutUrl Specifies the address for reporting a page resource timeout
  • @param {string=} [option.method=’POST’] Request mode
  • @param {number=} [option.timeout=10000] */ pMonitor.init = option => { const { url, timeoutUrl, method = ‘POST’, Timeout = 10000} = option config = {url, timeoutUrl, method, timeout} // bindEvent used to trigger reporting data pmonitor.bindevent ()} copy code

When the trigger

Performance monitoring is only an auxiliary function and should not block page loading, so data is retrieved and reported only after the page is loaded (in fact, the necessary information is not retrieved until the page is loaded) :

Pmonitor.logpackage = () => {const {url, timeoutUrl, method } = config const domComplete = pMonitor.getLoadTime() const timeoutRes = pMonitor.getTimeoutRes(config.timeout) Pmonitor. log(url, {domeComplete}, method) if (timeoutres.length) {pmonitor. log(timeoutUrl, timeoutUrl) { timeoutRes }, Pmonitor.bindevent = () => {const oldOnload = window.onload window.onload = e => {if (oldOnload && typeof oldOnload = = = ‘function’) {oldOnload (e)} / / main thread does not affect the page as far as possible the if (window. RequestIdleCallback) { Window. RequestIdleCallback (pMonitor. LogPackage)} else {setTimeout (pMonitor. LogPackage)}}} to copy code

summary

At this point, a complete front-end performance monitoring tool is complete ~ the complete code is as follows:

const base = { log() {}, logPackage() {}, getLoadTime() {}, getTimeoutRes() {}, bindEvent() {}, Init () {}} const PM = (function() { window.performance) return base const pMonitor = { … base } let config = {} const SEC = 1000 const TIMEOUT = 10 * SEC const setTime = (limit = TIMEOUT) => time => time >= limit const getLoadTime = ({ startTime, ResponseEnd}) => responseEnd – startTime const getName = ({name}) => name // Const convert2FormData = (data = {}) => Object.entries(data).reduce((last, [key, value]) => { if (Array.isArray(value)) { return value.reduce((lastResult, item) => { lastResult.append(${key}[], item) return lastResult }, last) } last.append(key, value) return last }, New FormData()) const makeItStr = (data = {}) => object.entries (data).map(([k, v]) => ${k}=${v}) .join(‘&’) pMonitor.getLoadTime = () => { const [{ domComplete }] = performance.getEntriesByType(‘navigation’) return domComplete } pMonitor.getTimeoutRes = (limit = TIMEOUT) => { const isTimeout = setTime(limit) const resourceTimes = performance.getEntriesByType(‘resource’) return resourceTimes .filter(item => isTimeout(getLoadTime(item))).map(getName)} pMonitor. Log = (url, data = {}, type = ‘POST’) => { const method = type.toLowerCase() const urlToUse = method === ‘get’ ? ${url}? ${makeItStr(data)} : url const body = method === ‘get’ ? {} : { body: convert2FormData(data) } const init = { method, … Body} fetch(urlToUse, init).catch(e => console.log(e))} pMonitor. LogPackage = () => {const {url, timeoutUrl, method } = config const domComplete = pMonitor.getLoadTime() const timeoutRes = pMonitor.getTimeoutRes(config.timeout) Pmonitor. log(url, {domeComplete}, method) if (timeoutres.length) {pmonitor. log(timeoutUrl, timeoutUrl) { timeoutRes }, Pmonitor.bindevent = () => {const oldOnload = window.onload window.onload = e => {if (oldOnload && typeof oldOnload = = = ‘function’) {oldOnload (e)} / / main thread does not affect the page as far as possible the if (window. RequestIdleCallback) { window.requestIdleCallback(pMonitor.logPackage) } else { setTimeout(pMonitor.logPackage) } } } /**

  • @param {object} option
  • @param {string} option.url Indicates the address for reporting page load data
  • @param {string} option.timeoutUrl Specifies the address for reporting a page resource timeout
  • @param {string=} [option.method=’POST’] Request mode
  • @param {number=} [option.timeout=10000] */ pMonitor.init = option => { const { url, timeoutUrl, method = ‘POST’, timeout = 10000 } = option config = { url, timeoutUrl, method, PMonitor. BindEvent ()} return pMonitor})() export default PM Copies the code

How to? Is it not complicated? It’s even a little easy

I looked at the time again, 5 minutes or something, but I don’t care about the details orz

added

call

Monitoring tools should not take up JavaScript parsing time for the main thread while the page is loading if you want to catch faults. Therefore, it is best to load the page asynchronously after the onload event is triggered:

// At the bottom of the project entry file const log = async () => {const pMonitor = await import(‘/path/to/ pmonitor.js ‘) pmonitor.init ({url: ‘xxx’, timeoutUrl: } const oldOnload = window.onload window.onload = e => {if (oldOnload && typeof oldOnload = = = ‘string’) {oldOnload (e)} / / main thread does not affect the page as far as possible the if (window. RequestIdleCallback) { Window. RequestIdleCallback (log)} else {setTimeout (log)}} to copy code

Cross-domain request issues

The tool does not consider cross-domain issues when reporting data, nor does it handle both GET and POST situations.

I don’t need a bike in 5 minutes! If necessary, you can override the pMonitor.logPackage method and create it dynamically instead

What about the cops? There was no alarm? !

This still needs the cooperation of the server side.

It can be a different reporting URL for each item or a unified set of urls, and items are assigned unique ids for differentiation.

When the number of timeout times exceeds the specified threshold within the specified time, the system notifies the developer by email or SMS.

fine-grained

Now only a simple count is made for timeout resources, but no specific timeout cause is reported (DNS? TCP? The request? The response? , this is left to the reader to optimize, try ~

The next step

This article introduces performance monitoring in terms of page loading. In addition, parsing + execution of JavaScript code is also an important factor restricting the speed of the first screen rendering (especially for single-page applications). Next, we will explore the Performance Timeline Level 2 to enable more Performance monitoring for JavaScript runtime