Cara Membuat Kinerja Terlihat Dengan GitLab CI Dan Tudung Artefak GitLab
Diterbitkan: 2022-03-10Penurunan kinerja adalah masalah yang kita hadapi setiap hari. Kami dapat berupaya untuk membuat aplikasi ini sangat cepat, tetapi kami segera berakhir di tempat kami memulai. Ini terjadi karena fitur baru yang ditambahkan dan fakta bahwa terkadang kita tidak berpikir dua kali tentang paket yang terus kita tambahkan dan perbarui, atau pikirkan tentang kerumitan kode kita. Ini umumnya hal kecil, tapi masih tentang hal-hal kecil.
Kami tidak mampu untuk memiliki aplikasi yang lambat. Kinerja merupakan keunggulan kompetitif yang dapat mendatangkan dan mempertahankan pelanggan. Kami tidak mampu secara teratur menghabiskan waktu untuk mengoptimalkan aplikasi lagi. Itu mahal, dan rumit. Dan itu berarti bahwa terlepas dari semua manfaat kinerja dari perspektif bisnis, itu hampir tidak menguntungkan. Sebagai langkah pertama dalam menemukan solusi untuk masalah apa pun, kita perlu membuat masalahnya terlihat. Artikel ini akan membantu Anda dengan hal itu.
Catatan : Jika Anda memiliki pemahaman dasar tentang Node.js, gagasan yang kabur tentang cara kerja CI/CD Anda, dan peduli dengan kinerja aplikasi atau keuntungan bisnis yang dapat dihasilkannya, maka kami siap melakukannya.
Cara Membuat Anggaran Kinerja Untuk Proyek
Pertanyaan pertama yang harus kita tanyakan pada diri sendiri adalah:
"Apa proyek performans itu?"
“Metrik mana yang harus saya gunakan?”
“Nilai metrik mana yang dapat diterima?”
Pemilihan metrik berada di luar cakupan artikel ini dan sangat bergantung pada konteks proyek, tetapi saya sarankan Anda mulai dengan membaca Metrik Kinerja yang berpusat pada Pengguna oleh Philip Walton.
Dari sudut pandang saya, ada baiknya menggunakan ukuran perpustakaan dalam kilobyte sebagai metrik untuk paket npm. Mengapa? Yah, itu karena jika orang lain memasukkan kode Anda dalam proyek mereka, mereka mungkin ingin meminimalkan dampak kode Anda pada ukuran akhir aplikasi mereka.
Untuk situs, saya akan mempertimbangkan Time To First Byte (TTFB) sebagai metrik. Metrik ini menunjukkan berapa lama waktu yang dibutuhkan server untuk merespons sesuatu. Metrik ini penting, tetapi cukup kabur karena dapat mencakup apa saja — mulai dari waktu rendering server dan berakhir dengan masalah latensi. Jadi, bagus untuk menggunakannya bersama dengan Server Timing atau OpenTracing untuk mengetahui apa sebenarnya yang terkandung di dalamnya.
Anda juga harus mempertimbangkan metrik seperti Time to Interactive (TTI) dan First Meaningful Paint (yang terakhir akan segera diganti dengan Largest Contentful Paint (LCP)). Saya pikir kedua hal ini paling penting — dari perspektif kinerja yang dirasakan.
Namun perlu diingat: metrik selalu terkait konteks , jadi jangan anggap remeh hal ini. Pikirkan tentang apa yang penting dalam kasus spesifik Anda.
Cara termudah untuk menentukan nilai metrik yang diinginkan adalah dengan menggunakan pesaing Anda — atau bahkan diri Anda sendiri. Selain itu, dari waktu ke waktu, alat seperti Kalkulator Anggaran Kinerja mungkin berguna — cukup mainkan sedikit.
Penurunan kinerja adalah masalah yang kita hadapi sehari-hari. Kami dapat berusaha untuk membuat aplikasi ini sangat cepat, tetapi segera kami berakhir di tempat kami memulai.
“
Gunakan Pesaing Untuk Keuntungan Anda
Jika Anda pernah kebetulan melarikan diri dari beruang yang terlalu bersemangat, maka Anda sudah tahu, bahwa Anda tidak perlu menjadi juara Olimpiade dalam berlari untuk keluar dari masalah ini. Anda hanya perlu sedikit lebih cepat dari orang lain.
Jadi buatlah daftar pesaing. Jika ini adalah proyek dengan jenis yang sama, maka biasanya terdiri dari jenis halaman yang mirip satu sama lain. Misalnya, untuk toko internet, mungkin halaman dengan daftar produk, halaman detail produk, keranjang belanja, checkout, dan sebagainya.
- Ukur nilai metrik yang Anda pilih pada setiap jenis halaman untuk proyek pesaing Anda;
- Ukur metrik yang sama pada proyek Anda;
- Temukan yang terdekat lebih baik daripada nilai Anda untuk setiap metrik dalam proyek pesaing. Tambahkan 20% kepada mereka dan tetapkan sebagai tujuan Anda berikutnya.
Mengapa 20%? Ini adalah angka ajaib yang konon berarti perbedaannya akan terlihat dengan mata telanjang. Anda dapat membaca lebih lanjut tentang nomor ini di artikel Denys Mishunov "Mengapa Persepsi Kinerja Penting, Bagian 1: Persepsi Waktu".
Pertarungan Dengan Bayangan
Apakah Anda memiliki proyek yang unik? Tidak punya pesaing? Atau Anda sudah lebih baik dari mereka dalam semua hal yang mungkin? Ini bukan masalah. Anda selalu dapat bersaing dengan satu-satunya lawan yang layak, yaitu diri Anda sendiri. Ukur setiap metrik kinerja proyek Anda pada setiap jenis halaman dan kemudian buat mereka lebih baik dengan 20% yang sama.
Tes Sintetis
Ada dua cara untuk mengukur kinerja:
- Sintetis (dalam lingkungan yang terkendali)
- RUM (Pengukuran Pengguna Nyata)
Data sedang dikumpulkan dari pengguna nyata dalam produksi.
Dalam artikel ini, kami akan menggunakan pengujian sintetis dan menganggap bahwa proyek kami menggunakan GitLab dengan CI bawaannya untuk penerapan proyek.
Perpustakaan Dan Ukurannya Sebagai Metrik
Mari kita asumsikan bahwa Anda telah memutuskan untuk mengembangkan perpustakaan dan mempublikasikannya ke NPM. Anda ingin membuatnya tetap ringan — jauh lebih ringan daripada pesaing — sehingga berdampak lebih kecil pada ukuran akhir proyek yang dihasilkan. Ini menghemat lalu lintas klien — terkadang lalu lintas yang dibayar oleh klien. Ini juga memungkinkan proyek dimuat lebih cepat, yang cukup penting sehubungan dengan pangsa seluler yang berkembang dan pasar baru dengan kecepatan koneksi yang lambat dan jangkauan internet yang terfragmentasi.
Paket Untuk Mengukur Ukuran Perpustakaan
Untuk menjaga ukuran perpustakaan sekecil mungkin, kita perlu memperhatikan dengan cermat bagaimana perubahannya seiring waktu pengembangan. Tapi bagaimana Anda bisa melakukannya? Nah, kita bisa menggunakan paket Size Limit yang dibuat oleh Andrey Sitnik dari Evil Martians.
Mari kita menginstalnya.
npm i -D size-limit @size-limit/preset-small-lib
Kemudian, tambahkan ke package.json
.
"scripts": { + "size": "size-limit", "test": "jest && eslint ." }, + "size-limit": [ + { + "path": "index.js" + } + ],
Blok "size-limit":[{},{},…]
berisi daftar ukuran file yang ingin kita periksa. Dalam kasus kami, ini hanya satu file: index.js
.
size
skrip NPM hanya menjalankan paket size-limit
, yang membaca batas size-limit
blok konfigurasi yang disebutkan sebelumnya dan memeriksa ukuran file yang terdaftar di sana. Mari kita jalankan dan lihat apa yang terjadi:
npm run size

Kita dapat melihat ukuran file, tetapi ukuran ini sebenarnya tidak terkendali. Mari kita perbaiki dengan menambahkan limit
ke package.json
:
"size-limit": [ { + "limit": "2 KB", "path": "index.js" } ],
Sekarang jika kita menjalankan skrip itu akan divalidasi terhadap batas yang kita tetapkan.

Jika pengembangan baru mengubah ukuran file hingga melebihi batas yang ditentukan, skrip akan dilengkapi dengan kode bukan nol. Ini, selain dari hal-hal lain, berarti akan menghentikan pipeline di GitLab CI.

Sekarang kita dapat menggunakan git hook untuk memeriksa ukuran file terhadap batas sebelum setiap komit. Kami bahkan dapat menggunakan paket husky untuk membuatnya dengan cara yang bagus dan sederhana.
Mari kita menginstalnya.
npm i -D husky
Kemudian, ubah package.json
kami.
"size-limit": [ { "limit": "2 KB", "path": "index.js" } ], + "husky": { + "hooks": { + "pre-commit": "npm run size" + } + },
Dan sekarang sebelum setiap komit secara otomatis akan dieksekusi npm run size
command dan jika itu akan diakhiri dengan kode bukan nol maka komit tidak akan pernah terjadi.

Tetapi ada banyak cara untuk melewati kail (sengaja atau bahkan tidak sengaja), jadi kita tidak boleh terlalu mengandalkannya.
Juga, penting untuk dicatat bahwa kita tidak perlu membuat pemblokiran cek ini. Mengapa? Karena tidak apa-apa jika ukuran perpustakaan bertambah saat Anda menambahkan fitur baru. Kita perlu membuat perubahan terlihat, itu saja. Ini akan membantu menghindari peningkatan ukuran yang tidak disengaja karena memperkenalkan pustaka pembantu yang tidak kita perlukan. Dan, mungkin, beri pengembang dan pemilik produk alasan untuk mempertimbangkan apakah fitur yang ditambahkan sepadan dengan peningkatan ukuran. Atau, mungkin, apakah ada paket alternatif yang lebih kecil. Bundlephobia memungkinkan kita untuk menemukan alternatif untuk hampir semua paket NPM.
Jadi apa yang harus kita lakukan? Mari tunjukkan perubahan ukuran file secara langsung di permintaan penggabungan! Tetapi Anda tidak mendorong untuk menguasai secara langsung; Anda bertindak seperti pengembang dewasa, bukan?
Menjalankan Pemeriksaan Kami Di GitLab CI
Mari tambahkan artefak GitLab dari jenis metrik. Artefak adalah file, yang akan «hidup» setelah operasi pipa selesai. Jenis artefak khusus ini memungkinkan kami menampilkan widget tambahan dalam permintaan penggabungan, yang menunjukkan setiap perubahan nilai metrik antara artefak di master dan cabang fitur. Format artefak metrics
adalah format teks Prometheus. Untuk nilai GitLab di dalam artefak, itu hanya teks. GitLab tidak mengerti apa yang sebenarnya berubah dalam nilainya — hanya tahu bahwa nilainya berbeda. Jadi, apa sebenarnya yang harus kita lakukan?
- Mendefinisikan artefak dalam pipa.
- Ubah skrip sehingga membuat artefak di saluran pipa.
Untuk membuat artefak, kita perlu mengubah .gitlab-ci.yml
dengan cara ini:
image: node:latest stages: - performance sizecheck: stage: performance before_script: - npm ci script: - npm run size + artifacts: + expire_in: 7 days + paths: + - metric.txt + reports: + metrics: metric.txt
-
expire_in: 7 days
— artefak akan ada selama 7 hari. paths: metric.txt
Ini akan disimpan di katalog root. Jika Anda melewatkan opsi ini maka tidak mungkin untuk mengunduhnya.reports: metrics: metric.txt
Artefak akan memiliki jenisreports:metrics
Sekarang mari kita buat Batasan Ukuran menghasilkan laporan. Untuk melakukannya kita perlu mengubah package.json
:
"scripts": { - "size": "size-limit", + "size": "size-limit --json > size-limit.json", "test": "jest && eslint ." },
size-limit
dengan kunci --json
akan menampilkan data dalam format json:

size-limit --json
menampilkan JSON ke konsol. JSON berisi larik objek yang berisi nama dan ukuran file, serta memberi tahu kami jika melebihi batas ukuran. (Pratinjau besar) Dan redirection > size-limit.json
akan menyimpan JSON ke file size-limit.json
.
Sekarang kita perlu membuat artefak dari ini. Format bermuara pada [metrics name][space][metrics value]
. Mari kita buat skrip generate-metric.js
:
const report = require('./size-limit.json'); process.stdout.write(`size ${(report[0].size/1024).toFixed(1)}Kb`); process.exit(0);
Dan tambahkan ke package.json
:
"scripts": { "size": "size-limit --json > size-limit.json", + "postsize": "node generate-metric.js > metric.txt", "test": "jest && eslint ." },
Karena kita telah menggunakan awalan post
, perintah npm run size
akan menjalankan skrip size
terlebih dahulu, dan kemudian, secara otomatis, mengeksekusi skrip postsize
, yang akan menghasilkan pembuatan file metric.txt
, artefak kita.
Akibatnya, ketika kami menggabungkan cabang ini menjadi master, mengubah sesuatu, dan membuat permintaan penggabungan baru, kami akan melihat yang berikut:

Di widget yang muncul di halaman, pertama-tama kita melihat nama metrik ( size
) diikuti dengan nilai metrik di cabang fitur serta nilai di master dalam tanda kurung bulat.
Sekarang kita benar-benar dapat melihat bagaimana mengubah ukuran paket dan membuat keputusan yang masuk akal apakah kita harus menggabungkannya atau tidak.
- Anda mungkin melihat semua kode ini di repositori ini.
Melanjutkan
OKE! Jadi, kami telah menemukan cara untuk menangani kasus sepele. Jika Anda memiliki banyak file, cukup pisahkan metrik dengan jeda baris. Sebagai alternatif untuk Batas Ukuran, Anda dapat mempertimbangkan ukuran bundel. Jika Anda menggunakan WebPack, Anda mungkin mendapatkan semua ukuran yang Anda butuhkan dengan membangun dengan flag --profile
dan --json
:
webpack --profile --json > stats.json
Jika Anda menggunakan next.js, Anda dapat menggunakan plugin @next/bundle-analyzer. Terserah kamu!
Menggunakan Mercusuar
Lighthouse adalah standar de facto dalam analisis proyek. Mari kita menulis skrip yang memungkinkan kita mengukur kinerja, 11 tahun, praktik terbaik, dan memberi kita skor SEO.
Script Untuk Mengukur Semua Barang
Untuk memulai, kita perlu menginstal paket mercusuar yang akan melakukan pengukuran. Kita juga perlu menginstal dalang yang akan kita gunakan sebagai browser tanpa kepala.
npm i -D lighthouse puppeteer
Selanjutnya, mari buat skrip lighthouse.js
dan mulai browser kita:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); })();
Sekarang mari kita tulis fungsi yang akan membantu kita menganalisis URL yang diberikan:
const lighthouse = require('lighthouse'); const DOMAIN = process.env.DOMAIN; const buildReport = browser => async url => { const data = await lighthouse( `${DOMAIN}${url}`, { port: new URL(browser.wsEndpoint()).port, output: 'json', }, { extends: 'lighthouse:full', } ); const { report: reportJSON } = data; const report = JSON.parse(reportJSON); // … }
Besar! Kami sekarang memiliki fungsi yang akan menerima objek browser sebagai argumen dan mengembalikan fungsi yang akan menerima URL
sebagai argumen dan menghasilkan laporan setelah meneruskan URL
itu ke lighthouse
.
Kami meneruskan argumen berikut ke lighthouse
:
- Alamat yang ingin kami analisis;
- opsi
lighthouse
,port
browser pada khususnya, danoutput
(format keluaran laporan); - konfigurasi
report
danlighthouse:full
(semua yang dapat kami ukur). Untuk konfigurasi yang lebih tepat, periksa dokumentasi.
Hebat! Kami sekarang memiliki laporan kami. Tapi apa yang bisa kita lakukan dengannya? Nah, kita dapat memeriksa metrik terhadap batasan dan keluar dari skrip dengan kode bukan nol yang akan menghentikan jalur pipa:
if (report.categories.performance.score < 0.8) process.exit(1);
Tapi kami hanya ingin membuat kinerja terlihat dan non-blocking? Kemudian mari kita mengadopsi jenis artefak lain: artefak kinerja GitLab.
Artefak Performa GitLab
Untuk memahami format artefak ini, kita harus membaca kode plugin sitespeed.io. (Mengapa GitLab tidak dapat menjelaskan format artefak mereka di dalam dokumentasi mereka sendiri? Misteri. )
[ { "subject":"/", "metrics":[ { "name":"Transfer Size (KB)", "value":"19.5", "desiredSize":"smaller" }, { "name":"Total Score", "value":92, "desiredSize":"larger" }, {…} ] }, {…} ]
Artefak adalah file JSON
yang berisi array objek. Masing-masing mewakili laporan tentang satu URL
.
[{page 1}, {page 2}, …]
Setiap halaman diwakili oleh objek dengan atribut berikut:
-
subject
Pengidentifikasi halaman (cukup berguna untuk menggunakan nama jalur seperti itu); -
metrics
Array objek (masing-masing mewakili satu pengukuran yang dibuat pada halaman).
{ "subject":"/login/", "metrics":[{measurement 1}, {measurement 2}, {measurement 3}, …] }
measurement
adalah suatu benda yang memiliki sifat-sifat sebagai berikut:
-
name
Nama pengukuran, misalnyaTime to first byte
atauTime to interactive
. -
value
Hasil pengukuran numerik. -
desiredSize
Jika nilai target harus sekecil mungkin, misalnya untuk metrikTime to interactive
, maka nilainya harus lebihsmaller
. Jika harus sebesar mungkin, misalnya untukPerformance score
mercusuar , gunakanlarger
.
{ "name":"Time to first byte (ms)", "value":240, "desiredSize":"smaller" }
Mari ubah fungsi buildReport
kita sedemikian rupa sehingga mengembalikan laporan untuk satu halaman dengan metrik mercusuar standar.

const buildReport = browser => async url => { // … const metrics = [ { name: report.categories.performance.title, value: report.categories.performance.score, desiredSize: 'larger', }, { name: report.categories.accessibility.title, value: report.categories.accessibility.score, desiredSize: 'larger', }, { name: report.categories['best-practices'].title, value: report.categories['best-practices'].score, desiredSize: 'larger', }, { name: report.categories.seo.title, value: report.categories.seo.score, desiredSize: 'larger', }, { name: report.categories.pwa.title, value: report.categories.pwa.score, desiredSize: 'larger', }, ]; return { subject: url, metrics: metrics, }; }
Sekarang, ketika kita memiliki fungsi yang menghasilkan laporan. Mari kita terapkan pada setiap jenis halaman proyek. Pertama, saya perlu menyatakan bahwa process.env.DOMAIN
harus berisi domain pementasan (yang Anda perlukan untuk menyebarkan proyek Anda dari cabang fitur sebelumnya).

+ const fs = require('fs'); const lighthouse = require('lighthouse'); const puppeteer = require('puppeteer'); const DOMAIN = process.env.DOMAIN; const buildReport = browser => async url => {/* … */}; + const urls = [ + '/inloggen', + '/wachtwoord-herstellen-otp', + '/lp/service', + '/send-request-to/ww-tammer', + '/post-service-request/binnenschilderwerk', + ]; (async () => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); + const builder = buildReport(browser); + const report = []; + for (let url of urls) { + const metrics = await builder(url); + report.push(metrics); + } + fs.writeFileSync(`./performance.json`, JSON.stringify(report)); + await browser.close(); })();
- Anda dapat menemukan sumber lengkap di Intisari ini dan contoh kerja di repositori ini.
Catatan : Pada titik ini, Anda mungkin ingin menyela saya dan berteriak dengan sia-sia, “Mengapa Anda menyita waktu saya — Anda bahkan tidak dapat menggunakan Promise.all dengan benar!” Dalam pembelaan saya, saya berani mengatakan, bahwa tidak disarankan untuk menjalankan lebih dari satu instance mercusuar pada saat yang sama karena ini berdampak buruk pada keakuratan hasil pengukuran. Juga, jika Anda tidak menunjukkan kecerdikan, itu akan menyebabkan pengecualian.
Penggunaan Beberapa Proses
Apakah Anda masih melakukan pengukuran paralel? Baik, Anda mungkin ingin menggunakan kluster simpul (atau bahkan Utas Pekerja jika Anda suka bermain tebal), tetapi masuk akal untuk mendiskusikannya hanya jika saluran Anda berjalan di lingkungan dengan beberapa kor yang tersedia. Dan meskipun demikian, Anda harus ingat bahwa karena sifat Node.js Anda akan memiliki instance Node.js berat penuh yang muncul di setiap garpu proses ( alih-alih menggunakan kembali yang sama yang akan menyebabkan peningkatan konsumsi RAM). Semua ini berarti akan lebih mahal karena kebutuhan perangkat keras yang berkembang dan sedikit lebih cepat. Tampaknya permainan itu tidak sepadan dengan lilinnya.
Jika Anda ingin mengambil risiko itu, Anda perlu:
- Pisahkan larik URL menjadi potongan berdasarkan nomor inti;
- Buat garpu proses sesuai dengan jumlah inti;
- Mentransfer bagian dari array ke fork dan kemudian mengambil laporan yang dihasilkan.
Untuk membagi array, Anda dapat menggunakan pendekatan multipile. Kode berikut — ditulis hanya dalam beberapa menit — tidak akan lebih buruk dari yang lain:
/** * Returns urls array splited to chunks accordin to cors number * * @param urls {String[]} — URLs array * @param cors {Number} — count of available cors * @return {Array } — URLs array splited to chunks */ function chunkArray(urls, cors) { const chunks = [...Array(cors)].map(() => []); let index = 0; urls.forEach((url) => { if (index > (chunks.length - 1)) { index = 0; } chunks[index].push(url); index += 1; }); return chunks; }
/** * Returns urls array splited to chunks accordin to cors number * * @param urls {String[]} — URLs array * @param cors {Number} — count of available cors * @return {Array } — URLs array splited to chunks */ function chunkArray(urls, cors) { const chunks = [...Array(cors)].map(() => []); let index = 0; urls.forEach((url) => { if (index > (chunks.length - 1)) { index = 0; } chunks[index].push(url); index += 1; }); return chunks; }
Buat garpu sesuai dengan jumlah inti:
// Adding packages that allow us to use cluster const cluster = require('cluster'); // And find out how many cors are available. Both packages are build-in for node.js. const numCPUs = require('os').cpus().length; (async () => { if (cluster.isMaster) { // Parent process const chunks = chunkArray(urls, urls.length/numCPUs); chunks.map(chunk => { // Creating child processes const worker = cluster.fork(); }); } else { // Child process } })();
Mari kita transfer array potongan ke proses anak dan mengambil kembali laporan:
(async () => { if (cluster.isMaster) { // Parent process const chunks = chunkArray(urls, urls.length/numCPUs); chunks.map(chunk => { const worker = cluster.fork(); + // Send message with URL's array to child process + worker.send(chunk); }); } else { // Child process + // Recieveing message from parent proccess + process.on('message', async (urls) => { + const browser = await puppeteer.launch({ + args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], + }); + const builder = buildReport(browser); + const report = []; + for (let url of urls) { + // Generating report for each URL + const metrics = await builder(url); + report.push(metrics); + } + // Send array of reports back to the parent proccess + cluster.worker.send(report); + await browser.close(); + }); } })();
Dan, akhirnya, susun kembali laporan ke satu larik dan buat artefak.
- Lihat kode lengkap dan repositori dengan contoh yang menunjukkan cara menggunakan mercusuar dengan banyak proses.
Akurasi Pengukuran
Yah, kami memparalelkan pengukuran, yang meningkatkan kesalahan pengukuran lighthouse
yang sudah sangat disayangkan. Tapi bagaimana kita menguranginya? Nah, buatlah beberapa pengukuran dan hitung rata-ratanya.
Untuk melakukannya, kami akan menulis fungsi yang akan menghitung rata-rata antara hasil pengukuran saat ini dan yang sebelumnya.
// Count of measurements we want to make const MEASURES_COUNT = 3; /* * Reducer which will calculate an avarage value of all page measurements * @param pages {Object} — accumulator * @param page {Object} — page * @return {Object} — page with avarage metrics values */ const mergeMetrics = (pages, page) => { if (!pages) return page; return { subject: pages.subject, metrics: pages.metrics.map((measure, index) => { let value = (measure.value + page.metrics[index].value)/2; value = +value.toFixed(2); return { ...measure, value, } }), } }
Kemudian, ubah kode kita untuk menggunakannya:
process.on('message', async (urls) => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); const builder = buildReport(browser); const report = []; for (let url of urls) { + // Let's measure MEASURES_COUNT times and calculate the avarage + let measures = []; + let index = MEASURES_COUNT; + while(index--){ const metric = await builder(url); + measures.push(metric); + } + const measure = measures.reduce(mergeMetrics); report.push(measure); } cluster.worker.send(report); await browser.close(); }); }
- Lihat intinya dengan kode lengkap dan repositori dengan sebuah contoh.
Dan sekarang kita bisa menambahkan lighthouse
ke dalam pipa.
Menambahkannya ke Pipeline
Pertama, buat file konfigurasi bernama .gitlab-ci.yml
.
image: node:latest stages: # You need to deploy a project to staging and put the staging domain name # into the environment variable DOMAIN. But this is beyond the scope of this article, # primarily because it is very dependent on your specific project. # - deploy # - performance lighthouse: stage: performance before_script: - apt-get update - apt-get -y install 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 - npm ci script: - node lighthouse.js artifacts: expire_in: 7 days paths: - performance.json reports: performance: performance.json
Beberapa paket yang diinstal diperlukan untuk puppeteer
. Sebagai alternatif, Anda dapat mempertimbangkan untuk menggunakan docker
. Selain itu, masuk akal jika kami menetapkan jenis artefak sebagai kinerja. Dan, segera setelah cabang master dan fitur memilikinya, Anda akan melihat widget seperti ini di permintaan penggabungan:

Bagus?
Melanjutkan
Kami akhirnya selesai dengan kasus yang lebih kompleks. Jelas, ada beberapa alat serupa selain dari mercusuar. Misalnya, kecepatan situs.io. Dokumentasi GitLab bahkan berisi artikel yang menjelaskan cara menggunakan sitespeed
situs di saluran GitLab. Ada juga plugin untuk GitLab yang memungkinkan kita menghasilkan artefak. Tapi siapa yang lebih suka produk open-source berbasis komunitas daripada yang dimiliki oleh monster korporat?
Tidak Ada Istirahat Untuk Orang Jahat
Tampaknya kita akhirnya sampai di sana, tetapi tidak, belum. Jika Anda menggunakan versi GitLab berbayar, maka artefak dengan metrics
dan performance
jenis laporan hadir dalam paket mulai dari premium
dan silver
biaya $19 per bulan untuk setiap pengguna. Selain itu, Anda tidak bisa hanya membeli fitur tertentu yang Anda butuhkan — Anda hanya dapat mengubah paketnya. Maaf. Jadi apa yang bisa kita lakukan? Berbeda dengan GitHub dengan API Pemeriksaan dan API Statusnya, GitLab tidak mengizinkan Anda membuat sendiri widget aktual dalam permintaan penggabungan. Dan tidak ada harapan untuk mendapatkannya dalam waktu dekat.

Salah satu cara untuk memeriksa apakah Anda benar-benar memiliki dukungan untuk fitur ini: Anda dapat mencari variabel lingkungan GITLAB_FEATURES
di dalam pipeline. Jika tidak memiliki merge_request_performance_metrics
dan metrics_reports
dalam daftar, maka fitur ini tidak didukung.
GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics, elastic_search, export_issues,group_bulk_edit,group_burndown_charts,group_webhooks, issuable_default_templates,issue_board_focus_mode,issue_weights,jenkins_integration, ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees, multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users, push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board, usage_quotas,visual_review_app,wip_limits
Jika tidak ada dukungan, kita perlu membuat sesuatu. Misalnya, kami dapat menambahkan komentar ke permintaan penggabungan, komentar dengan tabel, yang berisi semua data yang kami butuhkan. Kami dapat membiarkan kode kami tidak tersentuh — artefak akan dibuat, tetapi widget akan selalu menampilkan pesan «metrics are unchanged»
.
Perilaku yang sangat aneh dan tidak jelas; Saya harus berpikir dengan hati-hati untuk memahami apa yang terjadi.
Jadi, apa rencananya?
- Kita perlu membaca artefak dari cabang
master
; - Buat komentar dalam format
markdown
; - Dapatkan pengidentifikasi permintaan penggabungan dari cabang fitur saat ini ke master;
- Tambahkan komentar.
Cara Membaca Artefak Dari Cabang Master
Jika kita ingin menunjukkan bagaimana metrik kinerja diubah antara master
dan cabang fitur, kita perlu membaca artefak dari master
. Dan untuk melakukannya, kita perlu menggunakan fetch
.
npm i -S isomorphic-fetch
// You can use predefined CI environment variables // @see https://gitlab.com/help/ci/variables/predefined_variables.md // We need fetch polyfill for node.js const fetch = require('isomorphic-fetch'); // GitLab domain const GITLAB_DOMAIN = process.env.CI_SERVER_HOST || process.env.GITLAB_DOMAIN || 'gitlab.com'; // User or organization name const NAME_SPACE = process.env.CI_PROJECT_NAMESPACE || process.env.PROJECT_NAMESPACE || 'silentimp'; // Repo name const PROJECT = process.env.CI_PROJECT_NAME || process.env.PROJECT_NAME || 'lighthouse-comments'; // Name of the job, which create an artifact const JOB_NAME = process.env.CI_JOB_NAME || process.env.JOB_NAME || 'lighthouse'; /* * Returns an artifact * * @param name {String} - artifact file name * @return {Object} - object with performance artifact * @throw {Error} - thhrow an error, if artifact contain string, that can't be parsed as a JSON. Or in case of fetch errors. */ const getArtifact = async name => { const response = await fetch(`https://${GITLAB_DOMAIN}/${NAME_SPACE}/${PROJECT}/-/jobs/artifacts/master/raw/${name}?job=${JOB_NAME}`); if (!response.ok) throw new Error('Artifact not found'); const data = await response.json(); return data; };
Membuat Teks Komentar
Kita perlu membuat teks komentar dalam format markdown
. Mari kita buat beberapa fungsi layanan yang akan membantu kita:
/** * Return part of report for specific page * * @param report {Object} — report * @param subject {String} — subject, that allow find specific page * @return {Object} — page report */ const getPage = (report, subject) => report.find(item => (item.subject === subject)); /** * Return specific metric for the page * * @param page {Object} — page * @param name {String} — metrics name * @return {Object} — metric */ const getMetric = (page, name) => page.metrics.find(item => item.name === name); /** * Return table cell for desired metric * * @param branch {Object} - report from feature branch * @param master {Object} - report from master branch * @param name {String} - metrics name */ const buildCell = (branch, master, name) => { const branchMetric = getMetric(branch, name); const masterMetric = getMetric(master, name); const branchValue = branchMetric.value; const masterValue = masterMetric.value; const desiredLarger = branchMetric.desiredSize === 'larger'; const isChanged = branchValue !== masterValue; const larger = branchValue > masterValue; if (!isChanged) return `${branchValue}`; if (larger) return `${branchValue} ${desiredLarger ? '' : '' } **+${Math.abs(branchValue - masterValue).toFixed(2)}**`; return `${branchValue} ${!desiredLarger ? '' : '' } **-${Math.abs(branchValue - masterValue).toFixed(2)}**`; }; /** * Returns text of the comment with table inside * This table contain changes in all metrics * * @param branch {Object} report from feature branch * @param master {Object} report from master branch * @return {String} comment markdown */ const buildCommentText = (branch, master) =>{ const md = branch.map( page => { const pageAtMaster = getPage(master, page.subject); if (!pageAtMaster) return ''; const md = `|${page.subject}|${buildCell(page, pageAtMaster, 'Performance')}|${buildCell(page, pageAtMaster, 'Accessibility')}|${buildCell(page, pageAtMaster, 'Best Practices')}|${buildCell(page, pageAtMaster, 'SEO')}| `; return md; }).join(''); return ` |Path|Performance|Accessibility|Best Practices|SEO| |--- |--- |--- |--- |--- | ${md} `; };
Script Yang Akan Membangun Komentar
Anda harus memiliki token untuk bekerja dengan GitLab API. Untuk membuatnya, Anda perlu membuka GitLab, masuk, buka opsi 'Pengaturan' pada menu, lalu buka 'Token Akses' yang ada di sisi kiri menu navigasi. Anda kemudian dapat melihat formulir, yang memungkinkan Anda menghasilkan token.

Juga, Anda akan memerlukan ID proyek. Anda dapat menemukannya di repositori 'Pengaturan' (di submenu 'Umum'):

Untuk menambahkan komentar ke permintaan penggabungan, kita perlu mengetahui ID-nya. Fungsi yang memungkinkan Anda memperoleh ID permintaan penggabungan terlihat seperti ini:
// You can set environment variables via CI/CD UI. // @see https://gitlab.com/help/ci/variables/README#variables // I have set GITLAB_TOKEN this way // ID of the project const GITLAB_PROJECT_ID = process.env.CI_PROJECT_ID || '18090019'; // Token const TOKEN = process.env.GITLAB_TOKEN; /** * Returns iid of the merge request from feature branch to master * @param from {String} — name of the feature branch * @param to {String} — name of the master branch * @return {Number} — iid of the merge request */ const getMRID = async (from, to) => { const response = await fetch(`https://${GITLAB_DOMAIN}/api/v4/projects/${GITLAB_PROJECT_ID}/merge_requests?target_branch=${to}&source_branch=${from}`, { method: 'GET', headers: { 'PRIVATE-TOKEN': TOKEN, } }); if (!response.ok) throw new Error('Merge request not found'); const [{iid}] = await response.json(); return iid; };
We need to get a feature branch name. You may use the environment variable CI_COMMIT_REF_SLUG
inside the pipeline. Outside of the pipeline, you can use the current-git-branch
package. Also, you will need to form a message body.
Let's install the packages we need for this matter:
npm i -S current-git-branch form-data
And now, finally, function to add a comment:
const FormData = require('form-data'); const branchName = require('current-git-branch'); // Branch from which we are making merge request // In the pipeline we have environment variable `CI_COMMIT_REF_NAME`, // which contains name of this banch. Function `branchName` // will return something like «HEAD detached» message in the pipeline. // And name of the branch outside of pipeline const CURRENT_BRANCH = process.env.CI_COMMIT_REF_NAME || branchName(); // Merge request target branch, usually it's master const DEFAULT_BRANCH = process.env.CI_DEFAULT_BRANCH || 'master'; /** * Adding comment to merege request * @param md {String} — markdown text of the comment */ const addComment = async md => { const iid = await getMRID(CURRENT_BRANCH, DEFAULT_BRANCH); const commentPath = `https://${GITLAB_DOMAIN}/api/v4/projects/${GITLAB_PROJECT_ID}/merge_requests/${iid}/notes`; const body = new FormData(); body.append('body', md); await fetch(commentPath, { method: 'POST', headers: { 'PRIVATE-TOKEN': TOKEN, }, body, }); };
And now we can generate and add a comment:
cluster.on('message', (worker, msg) => { report = [...report, ...msg]; worker.disconnect(); reportsCount++; if (reportsCount === chunks.length) { fs.writeFileSync(`./performance.json`, JSON.stringify(report)); + if (CURRENT_BRANCH === DEFAULT_BRANCH) process.exit(0); + try { + const masterReport = await getArtifact('performance.json'); + const md = buildCommentText(report, masterReport) + await addComment(md); + } catch (error) { + console.log(error); + } process.exit(0); } });
- Check the gist and demo repository.
Now create a merge request and you will get:

Melanjutkan
Comments are much less visible than widgets but it's still much better than nothing. This way we can visualize the performance even without artifacts.
Autentikasi
OK, but what about authentication? The performance of the pages that require authentication is also important. It's easy: we will simply log in. puppeteer
is essentially a fully-fledged browser and we can write scripts that mimic user actions:
const LOGIN_URL = '/login'; const USER_EMAIL = process.env.USER_EMAIL; const USER_PASSWORD = process.env.USER_PASSWORD; /** * Authentication sctipt * @param browser {Object} — browser instance */ const login = async browser => { const page = await browser.newPage(); page.setCacheEnabled(false); await page.goto(`${DOMAIN}${LOGIN_URL}`, { waitUntil: 'networkidle2' }); await page.click('input[name=email]'); await page.keyboard.type(USER_EMAIL); await page.click('input[name=password]'); await page.keyboard.type(USER_PASSWORD); await page.click('button[data-test]', { waitUntil: 'domcontentloaded' }); };
Before checking a page that requires authentication, we may just run this script. Selesai.
Ringkasan
In this way, I built the performance monitoring system at Werkspot — a company I currently work for. It's great when you have the opportunity to experiment with the bleeding edge technology.
Now you also know how to visualize performance change, and it's sure to help you better track performance degradation. But what comes next? You can save the data and visualize it for a time period in order to better understand the big picture, and you can collect performance data directly from the users.
You may also check out a great talk on this subject: “Measuring Real User Performance In The Browser.” When you build the system that will collect performance data and visualize them, it will help to find your performance bottlenecks and resolve them. Good luck with that!