Note: This article is partially referenced

  1. Introduction to Web Performance Metrics and Core Web Vitals — The definition and design concept of modern front-end Performance Metrics
  2. 【 MDN 】 the Performance

This article combined with their own learning and understanding, summed up a small part. A lot of things are not said. Just study the text. Do not practice this.

The code for the monitoring section references code from a large front-end team. Assault delete.

The code logic please digest, here but more verbose.

Start ~

data

Window.performance gets information about page performance. Its properties and methods are as follows:

/ / property
// Here are the real numbers
// Navigation related. PerformanceNavigation type
navigation: {
    // 0: The current page is done by clicking on links, bookmarking, and form submission, or by scripting, or by entering the address directly in the URL
    // 1: Click the refresh page button or the page displayed using the location.reload () method
    // 2: the page is accessed through history and backwards and forwards
    // 255: Any other way
    type: 1.// Indicates how many times the page was redirected before reaching it.
    redirectCount: 0,},// Memory related. MemoryInfo type. This property is a non-standard property. This works only in Chrome. Should not be used in a production environment.
memory: {
    // The maximum size of the heap available in context, in bytes.
    jsHeapSizeLimit: 4294705152
    // Allocated heap volume, in bytes.
    totalJSHeapSize: 651152055
    // The size of the active segment of the current JS heap, in bytes.
    usedJSHeapSize: 613122587
},
// Start time of performance measurement (high precision timestamp)
timeOrigin: 1618392583547.822.// Delay related performance information. PerformanceTiming type
timing: {
    // UNIX timestamp at the end of unload from the previous document in the same browser context. If there is no one document, this value will and PerformanceTiming fetchStart are the same.
    navigationStart: 1618392583547.// UNIX timestamp when an unload event is thrown. This value returns 0 if there is no previous document, or if the previous document or one of the required redirects does not belong to the same source.
    unloadEventStart: 1618392583584.// Unload The UNIX timestamp at the completion of event processing. This value returns 0 if there is no previous document, or if the previous document or one of the redirects required does not belong to the same source.
    unloadEventEnd: 1618392583584.// The UNIX timestamp when the first HTTP redirect started. If there is no redirect, or a different source in the redirect, this value returns 0.
    redirectStart: 0.// The UNIX timestamp when the last HTTP redirect was completed (that is, when the last bit of the HTTP response was received directly). If there is no redirect, or a different source in the redirect, this value returns 0.
    redirectEnd: 0.// The browser is ready to fetch the UNIX timestamp of the document using HTTP requests. This point in time is before any application caches are checked.
    fetchStart: 1618392583551.// The UNIX timestamp from which domain queries start. If you are using the persistent connections (persistent connection), or the information stored in the cache or on local resources, the value will and PerformanceTiming fetchStart consistent.
    domainLookupStart: 1618392583551.// The UNIX timestamp of the end of the domain name query. If you are using the persistent connections (persistent connection), or the information stored in the cache or on local resources, the value will and PerformanceTiming fetchStart consistent.
    domainLookupEnd: 1618392583551.// The Unix millisecond timestamp when the HTTP request starts to be sent to the server. If persistent Connection is used, the return value is equal to the value of the fetchStart attribute.
    connectStart: 1618392583551.// The Unix millisecond timestamp when the connection between the browser and the server is established. If a persistent connection is established, the return value is the same as the value of the fetchStart attribute. Connection establishment refers to the completion of all handshake and authentication processes.
    connectEnd: 1618392583551.// The Unix millisecond timestamp when the browser and server begin the handshake for secure links. If the current page does not require a secure connection, 0 is returned.
    secureConnectionStart: 0.// The Unix millisecond timestamp when the browser makes an HTTP request to the server (or starts reading the local cache).
    requestStart: 1618392583574.// The Unix millisecond timestamp when the browser receives the first byte from the server (or reads it from the local cache). If the transport layer fails after initiating the request and the connection is reopened, this property will be counted as the corresponding initiation time of the new request.
    responseStart: 1618392583578.// The Unix millisecond timestamp when the browser receives (or reads from the local cache, or from a local resource) the last byte from the server (or closes if the HTTP connection has been closed before).
    responseEnd: 1618392583580.// The Unix millisecond timestamp when the DOM structure of the current web page starts parsing (i.e., when the document. readyState property becomes "loading" and the corresponding readyStatechange (en-us) event is triggered).
    domLoading: 1618392583618.// The Unix millisecond timestamp when the DOM structure of the current web page ends parsing and the embedded resource starts loading (i.e., when the document. readyState property changes to "interactive" and the corresponding readyStatechange (en-US) event is triggered).
    domInteractive: 1618392588369.// The Unix millisecond timestamp when the parser sends the DOMContentLoaded (en-us) event when all scripts that need to be executed have been parsed.
    domContentLoadedEventStart: 1618392588369.// The Unix millisecond timestamp when all scripts that need to be executed immediately have been executed (regardless of the order in which they are executed).
    domContentLoadedEventEnd: 1618392588374.// The Unix millisecond timestamp when the current Document is parsed, i.e. Document.readyState becomes 'complete' and the corresponding readyStatechange (en-us) is triggered.
    domComplete: 1618392606809.// The Unix millisecond timestamp when the LOAD (en-us) event was sent under this document. If the event has not already been sent, its value will be 0.
    loadEventStart: 1618392606809.// The Unix millisecond timestamp when the Load (en-us) event ends. If the event has not yet been sent or completed, its value will be 0.
    loadEventEnd: 1618392606809

},
Copy the code

As you can see, the Timing property contains the time from the last document to all nodes when the current document is loaded. With simple arithmetic, we can get the performance data we want. Such as:

// Here is the load of the page itself (no external resources included)
let positiveDiff = (para1, para2) = > {
    let diff = para1 - para2;
    return diff % 1! = =0 ? diff.toFixed(3) : diff;
};
// DNS query time:
positiveDiff(timing.domainLookupEnd, timing.domainLookupStart);

// TCP three-way handshake takes time
positiveDiff(timing.connectEnd, timing.connectStart);

// The request document takes time
positiveDiff(timing.responseStart, timing.requestStart);

// It takes time to receive documents
// The responseEnd value on iOS may be abnormally large
let responseEnd = timing.responseEnd > timing.domLoading ? timing.domLoading : timing.responseEnd;
positiveDiff(responseEnd, timing.responseStart);

// Start parsing dom until dom tree parsing is complete
positiveDiff(timing.domInteractive, timing.domLoading);

// Defer's JS download is complete
positiveDiff(timing.domContentLoadedEventStart, timing.domInteractive);

// The HTML and all resources (such as images) are loaded
positiveDiff(timing.domComplete, timing.domContentLoadedEventEnd);

// Load Event time
positiveDiff(timing.loadEventEnd, timing.loadEventStart);
Copy the code

Let’s look at the prototype approach.

Perform.getentries (): This method returns the performance properties of all resources (including the page itself and external resources).

Performance. GetEntriesByName (name) : the name of the filter (name). The timing object above is a subset of the attributes of the object passed in to the current page URI.

Performance. GetEntriesByType (type) : filter types (entry type)

Performance.mark (name): Adds a property and value to the above object when this function is executed. The property name is the parameter passed in and the value is the time at which the function is executed.

Performance.clearmarks (name): Removes this property from the browser’s performance input buffer.

performance.measure(name, startMark, endMark)

To illustrate:

// Start with a logo.
performance.mark('mySetTimeout-start');

// Wait some time.
setTimeout(function () {
    // Mark the end of time.
    performance.mark('mySetTimeout-end');

    // Measure two different flags.
    performance.measure('mySetTimeout'.'mySetTimeout-start'.'mySetTimeout-end');

    // Get all measurement output.
    // In this case there is only one.
    var measures = performance.getEntriesByName('mySetTimeout');
    var measure = measures[0];
    console.log('setTimeout milliseconds:', measure.duration);

    // Clear the stored flag bit
    performance.clearMarks();
    performance.clearMeasures();
}, 1000);
Copy the code

Performance. ClearResourceTimings () : from the browser’s performance data in the buffer is remove all entryType “resource” performance entries.

Performance.now (): Returns a number of milliseconds since the time of the performance measurement.

Performance.tojson (): Returns the JSON object of the Performance object.

monitoring

Using the various apis above we can get performance data for all resources on the page. Let’s take a look at how we can use this data for performance monitoring of our pages. (Click on the first screen to report).

First we need to map performance monitoring libraries on every page.

import performance from 'performance';
performance();
Copy the code

We need to report the time from navigationStart to loadEventEnd when the page loads. But we can’t just use the difference between these two values. Because it is possible for any one value to be zero to result in a huge deviation in the whole data. We need to add up each of these little chunks of time. In this way, the time is basically accurate.

We need to make some preparations first:

// Calculate the time difference between two points in time
let positiveDiff = (para1, para2) = > {
    let diff = para1 - para2;
    return diff % 1! = =0 ? diff.toFixed(3) : diff;
};

// Assign times
let reduceData = (value, obj, key) = > {
    if(value) { obj[key] = value; }};/ * * *@gap_ns_DNS navigationStart and DNS interval *@dns         DNS query time *@gap_dns_tcp Indicates the interval between DNS and TCP@tcp         The TCP three-way handshake takes *@gap_tcp_req Indicates the interval between TCP and REQ *@req         Document request time *@res         Receiving documents takes *@gap_res_p Indicates the interval between res and process *@p1 Start parsing the DOM until the DOM tree parsing is complete (no embedded resources have been loaded) *@pThe JS download of 2 defer has been completed *@p3 DOMContentLoaded Event time *@pHTML and all resources (such as images) are loaded *@gap_p_load domComplete immediately triggers the load event *@load        The Load event takes */
let getPerformanceData = () = > {
    if(! performance.timing) {return null;
    }

    let timing = performance.timing;

    // The responseEnd value on iOS may be abnormally large
    let responseEnd = timing.responseEnd > timing.domLoading ? timing.domLoading : timing.responseEnd;

    let times = {};

    reduceData(positiveDiff(timing.domainLookupStart, timing.navigationStart), times, 'gap_ns_dns');
    reduceData(positiveDiff(timing.domainLookupEnd, timing.domainLookupStart), times, 'dns');
    reduceData(positiveDiff(timing.connectStart, timing.domainLookupEnd), times, 'gap_dns_tcp');
    reduceData(positiveDiff(timing.connectEnd, timing.connectStart), times, 'tcp');
    reduceData(positiveDiff(timing.requestStart, timing.connectEnd), times, 'gap_tcp_req');
    reduceData(positiveDiff(timing.responseStart, timing.requestStart), times, 'req');
    reduceData(positiveDiff(responseEnd, timing.responseStart), times, 'res');
    reduceData(positiveDiff(timing.domLoading, responseEnd), times, 'gap_res_p');
    reduceData(positiveDiff(timing.domInteractive, timing.domLoading), times, 'p1');
    reduceData(positiveDiff(timing.domContentLoadedEventStart, timing.domInteractive), times, 'p2');
    reduceData(positiveDiff(timing.domContentLoadedEventEnd, timing.domContentLoadedEventStart), times, 'p3');
    reduceData(positiveDiff(timing.domComplete, timing.domContentLoadedEventEnd), times, 'p4');
    reduceData(positiveDiff(timing.loadEventStart, timing.domComplete), times, 'gap_p_load');
    reduceData(positiveDiff(timing.loadEventEnd, timing.loadEventStart), times, 'load');

    return times;
};

// step by step
let getThresholdData = (performanceData) = > {
    if(! performanceData) {return Infinity;
    }

    // Minimum process performance data key
    let perfKeys = [
        'gap_ns_dns'.'dns'.'gap_dns_tcp'.'tcp'.'gap_tcp_req'.'req'.'res'.'gap_res_p'.'p1'.'p2'.'p3'.'p4'.'gap_p_load'.'load',];// Page load completion time
    let load2 = 0;

    for (let key in perfKeys) {
        let _value = performanceData[perfKeys[key]];
        if (typeof_value ! = ='undefined') { load2 += _value; }}return load2;
};
Copy the code

With the above preparation, we can now calculate the entire time.

Referenced resources outside of the page itself also need to be monitored.

const SLOW_SOURCE_MAX = 10 * 1000; // A performance value greater than 10 seconds is treated as a slow resource
const NO_VALUE = -1; // The default value when the data is not available

const ERROR_MINUS = -21; // exception -- the default value for negative numbers

/** * Handle the exception size value (browser difference) *@param a Number
 * @return Number* /
let abnormalSize = (arg) = > {
    if (isNaN(arg)) {
        return NO_VALUE;
    }
    if (arg < 0) {
        return ERROR_MINUS;
    }
    return arg;
};

/** * Calculates the load time of external resources *@return value
 * @rstime           Start time *@rredir           Redirection takes *@rredircache      Cache check and redirection interval *@rcache           Cache check time *@rdns             DNS resolution time *@rdnstcp          DNS and TCP interval *@rtcp             The connection between TCP and HTTPS takes *@rssl             Time between the HTTPS connection start and TCP connection completion *@rtcpreq          The interval between TCP and REQ *@rreq             Document request time *@rres             Receiving documents takes time */
let getEntryTiming = (entry) = > {
    let t = entry;
    let name = t.name;

    let redirectStart = t.redirectStart || t.startTime;
    let redirectEnd = t.redirectEnd || t.startTime;
    let responseStart = t.responseStart || t.fetchStart;
    let secureConnectionStart = t.secureConnectionStart || t.connectStart;
    If the dnslookup phase does not exist, domainLookupStart is 0
    let domainLookupStart = t.domainLookupStart || t.fetchStart;

    try {
        // gap_cache_dns cannot be calculated directly into the cache
        let times = {};
        // Load time
        times.rduration = t.duration % 1! = =0 ? t.duration.toFixed(3) : t.duration;

        reduceData(positiveDiff(t.startTime, 0), times, 'rstime');
        reduceData(positiveDiff(redirectEnd, redirectStart), times, 'rredir');
        reduceData(positiveDiff(t.fetchStart, redirectEnd), times, 'rredircache');
        reduceData(positiveDiff(domainLookupStart, t.fetchStart), times, 'rcache');
        reduceData(positiveDiff(t.domainLookupEnd, t.domainLookupStart), times, 'rdns');
        reduceData(positiveDiff(t.connectStart, t.domainLookupEnd), times, 'rdnstcp');
        reduceData(positiveDiff(secureConnectionStart, t.connectStart), times, 'rtcp');
        reduceData(positiveDiff(t.connectEnd, secureConnectionStart), times, 'rssl');
        reduceData(positiveDiff(t.requestStart, t.connectEnd), times, 'rtcpreq');
        reduceData(positiveDiff(t.responseStart, t.requestStart), times, 'rreq');
        reduceData(positiveDiff(t.responseEnd, responseStart), times, 'rres');

        if (times.rduration < SLOW_SOURCE_MAX) {
            let search = name.split('? ');
            name = search[0];
        }

        // Resource name (absolute path)
        times.rname = name;
        // Custom type
        times.rtype = t.type;

        let size = abnormalSize(t.transferSize);
        if (size) {
            times.rtransize = size;
        }
        return times;
    } catch (e) {
        console.warn(`msg:${e.msg}`);
        console.warn(`stack:${e.stack}`);
        return{}; }};Copy the code
let entries = [];
let infoList = [];
let getResourceData = () = > {
    try {
        let resourceData = [];
        entries.forEach((entry) = > {
            let name = entry.name && entry.name.match(/ / / / ^? #; *)? (? : [?] ([^? #] *))? (# #] [^ *)? $/);
            let pathName = name[1];
            let fullName = name[0];

            if (entry.entryType === 'navigation') {
                // Filter the page itself
                return;
            }

            if (/\.js$/.test(pathName)) {
                entry.type = 'script';
            } else if (/\.css$/.test(pathName)) {
                entry.type = 'stylesheet';
            } else if (/\.(eot|woff|ttf|svg)$/.test(pathName)) {
                entry.type = 'font';
            } else if (/\.(bmp|gif|jpg|png|jpeg|webp)(! share)*? $/.test(pathName)) {
                entry.type = 'image';
                return;
            } else if (/\.(mp3|wma|wav|ape|flac|ogg|aac)$/.test(pathName)) {
                / / audio
                entry.type = 'media';
            } else if (/\.(mp4|avi|rmvb|mkv)$/.test(pathName)) {
                / / video
                entry.type = 'media';
            } else if (entry.initiatorType === 'iframe') {
                entry.type = 'iframe';
            } else {
                entry.type = 'others';
                return;
            }

            resourceData.push(getEntryTiming(entry));
        });

        return resourceData;
    } catch (e) {
        console.warn(`msg:${e.msg}`);
        console.warn(`stack:${e.stack}`);
        return[]; }};Copy the code
// performance.js

const FEATURE_MAX = 60 * 1000; // The performance value greater than 60 is treated as an exception
const hasLoaded = false; // Whether the page is loaded

const init = () = > {
    //
    let loadedFunc = () = > {
        // The page load performance is reported
        setTimeout(() = > {
            hasLoaded = true;
            let performanceData = getPerformanceData();
            let data = getThresholdData(performanceData);

            // If the page loading time calculated by dot-count data is longer than 60 seconds, the dot-count is not reported
            if(data < FEATURE_MAX) { sendLog(data); }});// Reports the performance of external resources on the page
        // Allow lost-deferred execution
        setTimeout(() = > {
            let resourceData = getResourceData();
            let length = resourceData.length;
            let arr = [];
            let num = 10;

            while (length > 0) {
                if (length > num) {
                    arr = resourceData.splice(0, num);
                    length = resourceData.length;
                } else {
                    arr = resourceData;
                    length = 0; } sendLog(arr); }},500);
    };

    window.addEventListener('load', loadedFunc);
};

export default init;
Copy the code

This reports the time when the page DOM has finished loading.

So how do you report the first screen time? How do you report the date of your first paint?

Let’s take a look at the time of First Paint

/* Get the first render time */
let getFirstPaint = function (decimalNum) {
    if (!window.performance || !window.performance.getEntriesByType) {
        return undefined;
    }

    decimalNum || (decimalNum = 0);

    let paintEntries = window.performance.getEntriesByType('paint');

    if (paintEntries && paintEntries.length) {
        for (let i = 0, length = paintEntries.length; i < length; i++) {
            let entry = paintEntries[i];

            if (entry && entry.name === 'first-paint') {
                return+(entry.startTime + entry.duration).toFixed(decimalNum); }}}return undefined;
};
Copy the code

Among them, the window. The performance. GetEntriesByType (‘ paint ‘) is the object shown below:

So you can also monitor first-Contentful-paint.

Again, how to choose when to report the first screen data according to the situation of our page?

Let’s expose a firstScreen method.

/** ** ** /

let pageStartTime = 0; // The start time of the first screen. The default value is 0. This value needs to be reset when switching pages in a single page application

let firstScreen = () = > {
    if (window.performance && performance.now && ! firstScreenFlag) { firstScreenFlag =true;

        let firstScreenLog = window.firstScreenTime || positiveDiff(parseInt(performance.now()), pageStartTime);
        // The first screen may load later than the page, so you need to make a judgment here
        if (hasLoaded) {
            sendLog(firstScreenLog);
        } else {
            window.addEventListener('load'.() = > {
                // Javascript executed synchronously in the onload event is counted in pageLoad with a layer of asynchrony
                setTimeout(() = >{ sendLog(firstScreenLog); }); }); }}};let init = () = > {
    // The above code is not repeated.return {
        firstScreen,
    };
}

Copy the code

FirstScreenTime can be defined in an HTML file.

.<! Above is the HTML section -->
<script>
    (function () {
        var positiveDiff = function (para1, para2) {
            var diff = para1 - para2;
            if (isNaN(diff)) {
                return -1;
            }
            if (diff > 60000) {
                return -22;
            }
            if (diff < 0) {
                return -21;
            }
            return diff % 1! = =0 ? diff.toFixed(3) : diff;
        };
        window.firstScreenTime = positiveDiff(parseInt(performance.now()), 0); }) ();</script>
Copy the code

If we hadn’t defined this property in HTML, we would have taken the current time when we called firstScreen. For example, it can be called after our business interface request completes.

indicators

Now we have a lot of performance data. So how do we judge a resource, or how well our pages are performing?

Here is a direct reference to the Byte guy’s article.

Introduction to Web Performance Metrics and Core Web Vitals — The definition and design concept of modern front-end Performance Metrics