Pembuatan profil aplikasi Node.js melibatkan pengukuran kinerjanya dengan menganalisis CPU, memori, dan metrik runtime lainnya saat aplikasi sedang berjalan. Ini membantu mengidentifikasi hambatan, penggunaan CPU yang tinggi, kebocoran memori, atau panggilan fungsi yang lambat yang dapat memengaruhi efisiensi, responsivitas, dan skalabilitas aplikasi.
Ada banyak alat pihak ketiga yang tersedia untuk membuat profil aplikasi Node.js, tetapi, dalam banyak kasus, opsi termudah adalah menggunakan profiler bawaan Node.js. Profiler bawaan menggunakan profiler di dalam V8 yang mengambil sampel tumpukan secara berkala selama eksekusi program. Profiler mencatat hasil sampel ini, bersama dengan peristiwa pengoptimalan penting seperti kompilasi jit, sebagai serangkaian tanda centang:
code-creation,LazyCompile,0,0x2d5000a337a0,396,"bp native array.js:1153:16",0x289f644df68,~ code-creation,LazyCompile,0,0x2d5000a33940,716,"hasOwnProperty native v8natives.js:198:30",0x289f64438d0,~ code-creation,LazyCompile,0,0x2d5000a33c20,284,"ToName native runtime.js:549:16",0x289f643bb28,~ code-creation,Stub,2,0x2d5000a33d40,182,"DoubleToIStub" code-creation,Stub,2,0x2d5000a33e00,507,"NumberToStringStub"
Di masa lalu, Anda memerlukan kode sumber V8 untuk dapat menginterpretasikan tanda centang. Untungnya, berbagai alat telah diperkenalkan sejak Node.js 4.4.0 yang memudahkan penggunaan informasi ini tanpa harus membangun V8 dari sumber secara terpisah. Mari kita lihat bagaimana profiler bawaan dapat membantu memberikan wawasan tentang kinerja aplikasi.
Untuk mengilustrasikan penggunaan profiler tanda centang, kita akan bekerja dengan aplikasi Express sederhana. Aplikasi kita akan memiliki dua pengendali, satu untuk menambahkan pengguna baru ke sistem kita:
app.get('/newUser', (req, res) => {
let username = req.query.username || '';
const password = req.query.password || '';
username = username.replace(/[!@#$%^&*]/g, '');
if (!username || !password || users[username]) {
return res.sendStatus(400);
}
const salt = crypto.randomBytes(128).toString('base64');
const hash = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512');
users[username] = { salt, hash };
res.sendStatus(200);
});
dan satu lagi untuk memvalidasi upaya otentikasi pengguna:
app.get('/auth', (req, res) => {
let username = req.query.username || '';
const password = req.query.password || '';
username = username.replace(/[!@#$%^&*]/g, '');
if (!username || !password || !users[username]) {
return res.sendStatus(400);
}
const { salt, hash } = users[username];
const encryptHash = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512');
if (crypto.timingSafeEqual(hash, encryptHash)) {
res.sendStatus(200);
} else {
res.sendStatus(401);
}
});
Harap perhatikan bahwa ini BUKAN pengendali yang direkomendasikan untuk mengautentikasi pengguna di aplikasi Node.js Anda dan digunakan murni untuk tujuan ilustrasi. Anda tidak boleh mencoba merancang mekanisme autentikasi kriptografi Anda sendiri secara umum. Jauh lebih baik menggunakan solusi autentikasi yang sudah ada dan terbukti.
Sekarang asumsikan bahwa kita telah menerapkan aplikasi kita dan pengguna mengeluh tentang latensi yang tinggi pada permintaan. Kita dapat dengan mudah menjalankan aplikasi dengan profiler bawaan:
NODE_ENV=production node --prof app.js
dan memberi beberapa beban pada server menggunakan ab (ApacheBench):
curl -X GET "http://localhost:8080/newUser?username=matt&password=password"
ab -k -c 20 -n 250 "http://localhost:8080/auth?username=matt&password=password"
dan dapatkan keluaran ab:
Concurrency Level: 20 Time taken for tests: 46.932 seconds Complete requests: 250 Failed requests: 0 Keep-Alive requests: 250 Total transferred: 50250 bytes HTML transferred: 500 bytes Requests per second: 5.33 [#/sec] (mean) Time per request: 3754.556 [ms] (mean) Time per request: 187.728 [ms] (mean, across all concurrent requests) Transfer rate: 1.05 [Kbytes/sec] received ... Percentage of the requests served within a certain time (ms) 50% 3755 66% 3804 75% 3818 80% 3825 90% 3845 95% 3858 98% 3874 99% 3875 100% 4225 (longest request)
Dari keluaran ini, kita melihat bahwa kita hanya mampu melayani sekitar 5 permintaan per detik dan bahwa permintaan rata-rata memerlukan waktu kurang dari 4 detik untuk bolak-balik. Dalam contoh dunia nyata, kita dapat melakukan banyak pekerjaan dalam banyak fungsi atas nama permintaan pengguna, tetapi bahkan dalam contoh sederhana kita, waktu dapat terbuang untuk mengompilasi ekspresi reguler, menghasilkan garam acak, menghasilkan hash unik dari kata sandi pengguna, atau di dalam kerangka Express itu sendiri.
Karena kita menjalankan aplikasi kita menggunakan opsi --prof, file tick dihasilkan di direktori yang sama dengan aplikasi yang Anda jalankan secara lokal. File tersebut harus memiliki bentuk isolate-0xnnnnnnnnnnnn-v8.log (di mana n adalah digit).
Untuk memahami file ini, kita perlu menggunakan prosesor tick yang dibundel dengan biner Node.js. Untuk menjalankan prosesor, gunakan tanda --prof-process:
node --prof-process isolate-0xnnnnnnnnnnnn-v8.log > processed.txt
Membuka file .txt yang diproses di editor teks favorit Anda akan memberikan beberapa jenis informasi yang berbeda. File tersebut dipecah menjadi beberapa bagian yang kemudian dipecah lagi berdasarkan bahasa. Pertama, kita lihat bagian ringkasan dan lihat:
[Summary]:
ticks total nonlib name 79 0.2% 0.2% JavaScript 36703 97.2% 99.2% C++ 7 0.0% 0.0% GC 767 2.0% Shared libraries 215 0.6% Unaccounted
Ini memberi tahu kita bahwa 97% dari semua sampel yang dikumpulkan terjadi dalam kode C++ dan bahwa saat melihat bagian lain dari keluaran yang diproses, kita harus lebih memperhatikan pekerjaan yang dilakukan dalam C++ (bukan JavaScript). Dengan mengingat hal ini, selanjutnya kita menemukan bagian [C++] yang berisi informasi tentang fungsi C++ mana yang menghabiskan waktu CPU paling banyak dan melihat:
[C++]:
ticks total nonlib name 19557 51.8% 52.9% node::crypto::PBKDF2(v8::FunctionCallbackInfo<v8::Value> const&) 4510 11.9% 12.2% _sha1_block_data_order 3165 8.4% 8.6% _malloc_zone_malloc
Kita melihat bahwa 3 entri teratas menghabiskan 72,1% waktu CPU yang digunakan oleh program. Dari keluaran ini, kita langsung melihat bahwa sedikitnya 51,8% waktu CPU digunakan oleh fungsi yang disebut PBKDF2 yang sesuai dengan pembuatan hash dari kata sandi pengguna. Namun, mungkin tidak langsung jelas bagaimana dua entri yang lebih rendah berperan dalam aplikasi kita (atau jika memang demikian, kita akan berpura-pura sebaliknya demi contoh). Untuk lebih memahami hubungan antara fungsi-fungsi ini, selanjutnya kita akan melihat bagian [Bottom up (heavy) profile] yang menyediakan informasi tentang pemanggil utama setiap fungsi. Dengan memeriksa bagian ini, kita menemukan:
ticks parent name 19557 51.8% node::crypto::PBKDF2(v8::FunctionCallbackInfo<v8::Value> const&) 19557 100.0% v8::internal::Builtins::~Builtins() 19557 100.0% LazyCompile: ~pbkdf2 crypto.js:557:16 4510 11.9% _sha1_block_data_order 4510 100.0% LazyCompile: *pbkdf2 crypto.js:557:16 4510 100.0% LazyCompile: *exports.pbkdf2Sync crypto.js:552:30 3165 8.4% _malloc_zone_malloc 3161 99.9% LazyCompile: *pbkdf2 crypto.js:557:16 3161 100.0% LazyCompile: *exports.pbkdf2Sync crypto.js:552:30
Menguraikan bagian ini memerlukan sedikit lebih banyak pekerjaan daripada hitungan tanda centang mentah di atas. Dalam setiap "tumpukan panggilan" di atas, persentase di kolom induk memberi tahu Anda persentase sampel yang fungsi di baris di atas dipanggil oleh fungsi di baris saat ini. Misalnya, di tengah "tumpukan panggilan" di atas untuk _sha1_block_data_order, kita melihat bahwa _sha1_block_data_order muncul di 11,9% sampel, yang kita ketahui dari hitungan mentah di atas. Namun, di sini, kita juga dapat mengetahui bahwa itu selalu dipanggil oleh fungsi pbkdf2 di dalam modul kripto Node.js. Kita melihat bahwa dengan cara yang sama, _malloc_zone_malloc dipanggil hampir secara eksklusif oleh fungsi pbkdf2 yang sama. Dengan demikian, dengan menggunakan informasi dalam tampilan ini, kita dapat mengetahui bahwa perhitungan hash kita dari kata sandi pengguna tidak hanya mencakup 51,8% dari atas tetapi juga untuk semua waktu CPU dalam 3 fungsi teratas yang paling banyak diambil sampelnya karena panggilan ke _sha1_block_data_order dan _malloc_zone_malloc dilakukan atas nama fungsi pbkdf2.
Pada titik ini, sangat jelas bahwa pembuatan hash berbasis kata sandi harus menjadi target pengoptimalan kita. Untungnya, Anda telah sepenuhnya memahami manfaat pemrograman asinkron dan Anda menyadari bahwa pekerjaan untuk membuat hash dari kata sandi pengguna dilakukan secara sinkron dan dengan demikian mengikat loop peristiwa. Hal ini mencegah kita mengerjakan permintaan masuk lainnya saat menghitung hash.
Untuk mengatasi masalah ini, Anda membuat sedikit modifikasi pada pengendali di atas untuk menggunakan versi asinkron dari fungsi pbkdf2:
app.get('/auth', (req, res) => {
let username = req.query.username || '';
const password = req.query.password || '';
username = username.replace(/[!@#$%^&*]/g, '');
if (!username || !password || !users[username]) {
return res.sendStatus(400);
}
crypto.pbkdf2(
password,
users[username].salt,
10000,
512,
'sha512',
(err, hash) => {
if (users[username].hash.toString() === hash.toString()) {
res.sendStatus(200);
} else {
res.sendStatus(401);
}
}
);
});
Pengujian baru ab benchmark di atas dengan versi asinkron aplikasi Anda menghasilkan:
Concurrency Level: 20
Time taken for tests: 12.846 seconds Complete requests: 250 Failed requests: 0 Keep-Alive requests: 250 Total transferred: 50250 bytes HTML transferred: 500 bytes Requests per second: 19.46 [#/sec] (mean) Time per request: 1027.689 [ms] (mean) Time per request: 51.384 [ms] (mean, across all concurrent requests) Transfer rate: 3.82 [Kbytes/sec] received ... Percentage of the requests served within a certain time (ms) 50% 1018 66% 1035 75% 1041 80% 1043 90% 1049 95% 1063 98% 1070 99% 1071 100% 1079 (longest request)
Hore! Aplikasi Anda sekarang melayani sekitar 20 permintaan per detik, kira-kira 4 kali lebih banyak daripada saat menggunakan pembuatan hash sinkron. Selain itu, latensi rata-rata turun dari 4 detik sebelumnya menjadi lebih dari 1 detik.
Semoga, melalui penyelidikan kinerja dari contoh (yang memang dibuat-buat) ini, Anda telah melihat bagaimana prosesor tick V8 dapat membantu Anda memperoleh pemahaman yang lebih baik tentang kinerja aplikasi Node.js Anda.
Anda mungkin juga menemukan cara membuat grafik api yang bermanfaat.
0 Comments