Performance

  • Что мы меряем?
  • Какие цели?
  • Как мы меряем?

Метрики

  • Time to first byte
  • Total byte weight
  • Network request count
  • First meaningful paint
  • Speed index
  • First input delay
  • Time to interactive

Конкуренты помогут

  • Marktplaats
  • Kluswebsite
  • Casius
  • Zoofy
  • Skydreams
  • Solvari
  • Yonego
  • Offerte.nl

+20%

Типы измерений

lab
lab

continuous
integration

logotype
logotype
npm i -D lighthouse
npm i -D puppeteer
const browser = 
    await puppeteer.launch({
        args: [
        '--no-sandbox', 
        '--disable-setuid-sandbox', 
        '--headless'
        ]});
const lighthouseConfig = {
    port: (
        new URL(browser.wsEndpoint())
    ).port,
    output: 'json',
    logLevel: 'info',
};
const auditConfig = {
    extends: 'lighthouse:default',
    settings: {
    onlyCategories: ['performance'],
    onlyAudits: [
        'first-meaningful-paint',
        'speed-index-metric',
        'estimated-input-latency',
        'first-interactive',
        'consistently-interactive',
    ]}};
const {
    lhr: {
        audits
    }} = await lighthouse(
        'http://werkspot.nl/', 
        lighthouseConfig,
        auditConfig);

if (audits["speed-index"].score < 0.7) 
        process.exit(1);
process.exit(0);
# .gitlab-ci.yml
image: node:latest

stages:
- performance
lighthouse:
    stage: performance
    before_script:
        - apt-get update
        - apt-get -y install gconf-service …
        - node -v
        - npm i
    script:
        - npm run lighthouse
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget

Server
Timing

Server-Timing:
    cache;
    desc="Cache Read";
    dur=23.2
Server-Timing: cache
Chrome v60-64
Server-Timing:
    cache=23.2;
    "Cache Read"
const express = require('express');
const app = express();
app.get('/', (req, res) => {
    //…
    res.send(200).end();
});
app.listen(3000);
res.set(
    'Server-Timing',
    'cache;
     desc="Cache Read";
     dur=23.2');
// node.js v0.7.6
process.hrtime();
// integer[]
// [ seconds, nanoseconds ]
// [ 8063, 904046009 ]
const from =
    process.hrtime();
const duration =
    process.hrtime(from);
// To milliseconds
const ms = parseInt(
            duration[0] * 1e3 +
            duration[1] * 1e-6,
           10);
// node.js v10.7.0, Stage 3
const from =
    process.hrtime.bigint();
const to =
    process.hrtime.bigint();
const duration = to - from;
// To milliseconds
const ms =
    duration / 1000000n;
npm i -S server-timing-header
const serverTiming = require('server-timing-header');
const express = require('express');
const app = express();
app.use(serverTiming);
app.get('/', (req, res) => {
    //…
    res.send(200).end();
});
app.listen(3000);
const serverTiming = require('server-timing-header');
// const express = require('express');
// const app = express();
app.use(serverTiming);
// app.get('/', (req, res) => {
// …
//    res.send(200).end();
// });
// app.listen(3000);
req.serverTiming.from('db');
// fetching data from database
req.serverTiming.to('db');
req.serverTiming.add(
    'cache', // name
    'Cache Read', //description
    23.2 // value
);
req.serverTiming.add(
    'cache',
    'Element not in cache',
);
// not safari
// ff need https
performance.getEntriesByType('navigation');
performance.getEntriesByType('resource');
Timing-Allow-Origin: *

Trailer
Header

const {
    renderToNodeStream
    } = require("react-dom/server");
const React = require("react");
const express = require('express');
const app = express();
app.get('/', (req, res) => {/*…*/});
app.listen(3000);
res.writeHead(200, {
    'Content-Type': 'text/html',
    'Transfer-Encoding': 'chunked'
});
res.write("<main id='content'>");
const stream = renderToNodeStream(
    React.createElement(
        'p', null, 'XXX'));
stream.pipe(res, { end: false });
stream.on('end', () => {
    res.write("</main>").end();
});
Server-Timing?
res.writeHead(200, {
    'Trailer': 'Server-Timing',
    // …
});
stream.on('end', () => {
    res.write("</main>")
    res.addTrailers({
        'Server-Timing':
        'metric;desc="metric 2";dur=12'
    });
    res.end();
});

Resource
Hints

HTTP/1.1 200 OK
Link: <https://widget.com>; 
    rel=dns-prefetch
Link: <https://example.com>; 
    rel=preconnect
Link: <https://example.com/next-page.html>; 
    rel=prerender;
Link: <https://example.com/logo-hires.jpg>; 
    rel=prefetch; as=image;
<link rel="dns-prefetch" 
    href="//widget.com">
<link rel="preconnect" 
    href="//cdn.example.com">
<link rel="prerender" 
    href="//example.com/next-page.html">
<link rel="prefetch" 
    href="//example.com/logo-hires.jpg"
    as="image">

Preload

<link 
    rel="preload" 
    href="/goodmojo.css" 
    as="style">
HTTP/1.1 200 OK
Link: 
    </badmojo.css>; 
    rel=preload; 
    as=style
res.set('Link', [
    '</badmojo.css>; rel=preload; 
        as=style',
    '</badmojo.js>; rel=preload; 
        as=script',
    '</font.ttf>; rel=preload; 
        as=font']);

103 Early
Hints

HTTP/1.1 103 Early Hints
Link: 
    </hintmojo.css>; 
    rel=preload; 
    as=style

HTTP/1.1 200 OK
npm i -S early-hints
app.use(earlyHints([{
    path: '/hintmojo.css', 
    rel: 'preload', 
    as: 'style' 
}]));

Client
Hints

Accept-CH: 
    Save-Data,
    Downlink,
    Device-Memory,
    DPR,
    Viewport-Width,
    Width
<meta 
    http-equiv="Accept-CH" 
    content="Save-Data
        Downlink,
        Device-Memory,
        DPR,
        Viewport-Width, 
        Width" />
'save-data': 'on',
'downlink': '7.95',
'device-memory': '8',
'dpr': '1',
'viewport-width': '1306',
'width': '327',
Accept-CH-Lifetime: 86400
Vary: Device-Memory

Perfor
mance

Performance
PerformanceObserver
  • measure
  • mark
  • navigation
  • resource
  • longtask
performance.getEntriesByType(entryType)
performance.getEntriesByName(name)
performance.getEntries()
const observer 
    = new PerformanceObserver(this.handler);
observer.observe({
    entryTypes
});

High Res.
Time

performance.now()
performance.timeOrigin
const start = performance.now();
const end = performance.now();
const duration = end - start;

User
Timing

performance.mark("startTask1");
// Получение данных
const url = '/api/v1/employees';
const resource = await fetch(url);
performance.mark("endTask1");
const entries = performance
    .getEntriesByType("mark");
for (const entry of entries) {
    console.table(entry.toJSON());
}
performance.measure(
    'task1', 
    'startTask1', 
    'endTask1'
);
const entries = performance
    .getEntriesByType("measure");
for (const entry of entries) {
    console.table(entry.toJSON());
}
performance.clearMeasures('task1')
performance.measure('from start');
const entries = performance
    .getEntriesByType("measure");
for (const entry of entries) {
    console.table(entry.toJSON());
}
performance.timing
performance.navigation

Resource
Timing

const entries = performance
    .getEntriesByType("resource");
for (const entry of entries) {
    console.table(entry.toJSON());
}

Long
Task

const plannedEntries = ['longtask'];
const entryTypes = 
    plannedEntries.filter(entry => {
    return PerformanceObserver
        .supportedEntryTypes
        .includes(entry);
});
if (entryTypes.length > 0) {
    const observer = 
        new PerformanceObserver(report);
    observer.observe({ 
        entryTypes
    });
}

Beacon

const express = require('express');
const formidable = require('express-formidable');
const path = require('path');
const port = 3333;
const app = express();
app.use(formidable({
    encoding: 'utf-8',
    uploadDir: path.join(
        __dirname, 
        'uploads'),
    multiples: true,
    keepExtensions:true,
}));
app.use('/reports', 
    function (req, res) {
        res.status(204).end();
        console.log(req.fields);
});
app.use('/', 
    function (req, res) {
        res.send(`/…/`)
            .status(200)
            .end();
});
app.listen(port);
let data = new FormData();
let items = {
    some: 'data',
    more: ['words', 'aside'],
};
for ( var key in items ) {
    data.append(
        key, 
        items[key]
    );
}
navigator.sendBeacon('/reports', data);
{ 
    some: 'data', 
    more: 'words,aside' 
}
World Wide Web Consortium
QRCODE cо ссылкой

https://github.com/w3c/

Web Incubator CG
QRCODE cо ссылкой

https://github.com/WICG/

Антон Немцев

skype: ravencry

QRCODE cо ссылкой на доклад

https://github.com/SilentImp/performance