"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Saya sarankan Anda membaca transkrip laporan Roman Khavronenko “ExendedPromQL”

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Secara singkat tentang saya. Nama saya Romawi. Saya bekerja di CloudFlare dan tinggal di London. Namun saya juga pengelola VictoriaMetrics.
Dan akulah penulisnya Plugin ClickHouse untuk Grafana dan ClickHouse-proxy adalah proksi kecil untuk ClickHouse.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Kita akan mulai dengan bagian pertama, yang disebut “Kesulitan Penerjemahan” dan di dalamnya saya akan berbicara tentang fakta bahwa bahasa apa pun atau bahkan bahasa komunikasi saja sangatlah penting. Karena ini adalah cara Anda menyampaikan pemikiran Anda kepada orang atau sistem lain, cara Anda merumuskan permintaan. Orang-orang di Internet berdebat tentang bahasa mana yang lebih baik - java atau lainnya. Bagi saya sendiri, saya memutuskan bahwa saya harus memilih sesuai dengan tugas, karena semua ini spesifik.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Mari kita mulai dari awal. Apa itu PromQL? PromQL adalah Bahasa Kueri Prometheus. Beginilah cara kami membentuk kueri di Prometheus untuk mendapatkan data deret waktu.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Apa itu data deret waktu? Secara harfiah, ini adalah tiga parameter.

Ini adalah:

  • Apa yang kita lihat?
  • Ketika kita melihatnya.
  • Dan nilai apa yang ditunjukkannya?

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Jika Anda melihat bagan ini (bagan ini berasal dari ponsel saya yang menunjukkan statistik langkah saya), bagan ini dapat menjawab pertanyaan-pertanyaan ini dengan cepat.

Kami melihat langkah-langkahnya. Kita melihat maknanya dan kita melihat waktunya ketika kita melihatnya. Artinya, dengan melihat diagram ini, Anda dapat dengan mudah mengatakan bahwa pada hari Minggu saya berjalan sekitar 15 langkah. Ini adalah data deret waktu.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Sekarang mari kita "membagi" (mengubahnya) menjadi model data lain dalam bentuk tabel. Di sini kami juga memiliki apa yang sedang kami lihat. Di sini saya menambahkan sedikit data tambahan, yang kita sebut metadata, yaitu bukan saya yang mengalaminya, tetapi dua orang, misalnya Jay dan Silent Bob. Inilah yang sedang kami lihat; apa yang ditunjukkannya dan kapan menunjukkan nilai tersebut.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko
Sekarang mari kita coba menyimpan semua data ini dalam database. Misalnya, saya mengambil sintaks ClickHouse. Dan di sini kita membuat satu tabel yang disebut “Langkah”, yaitu apa yang kita lihat. Ada saatnya kita melihatnya; apa yang ditampilkannya dan beberapa meta data tempat kami akan menyimpan siapa itu: Jay dan Silent Bob.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan untuk mencoba memvisualisasikan semua ini, kita akan menggunakan Grafana karena, pertama-tama, itu indah.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Kami juga akan menggunakan plugin ini. Ada dua alasan untuk ini. Yang pertama adalah karena saya menulisnya. Dan saya tahu persis betapa sulitnya mengambil data deret waktu dari ClickHouse untuk ditampilkan di Grafana.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Kami akan menampilkannya di Panel Grafik. Ini adalah panel paling populer di Grafana, yang menunjukkan ketergantungan suatu nilai terhadap waktu, jadi kita hanya memerlukan dua parameter.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko
Mari kita tulis kueri paling sederhana - cara menampilkan statistik langkah di Grafana, menyimpan data ini di ClickHouse, di tabel yang kita buat. Dan kami menulis permintaan sederhana ini. Kami memilih dari langkah-langkah. Kami memilih nilai dan memilih waktu dari nilai-nilai ini, yaitu tiga parameter yang sama yang kita bicarakan.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan hasilnya kita akan mendapatkan grafik seperti ini. Siapa yang tahu kenapa dia begitu aneh?

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Itu benar, kita perlu mengurutkan berdasarkan waktu.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan pada akhirnya kita akan mendapatkan jadwal yang lebih baik, namun tetap aneh. Siapa yang tahu kenapa? Benar sekali, pesertanya ada dua, dan kami di Grafana membagikan dua deret waktu, karena jika dilihat kembali model datanya, maka setiap deret waktu merupakan kombinasi unik dari nama dan semua label nilai kunci.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Oleh karena itu, kita perlu memilih orang tertentu. Kami memilih Jay.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan mari menggambar lagi. Sekarang grafiknya tampak seperti kebenaran. Sekarang ini adalah jadwal normal dan semuanya berjalan dengan baik.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan Anda mungkin tahu cara melakukan hal yang kurang lebih sama, tetapi di Prometheus melalui PromQL. Sesuatu seperti ini. Sedikit lebih sederhana. Dan mari kita uraikan semuanya. Kami mengambil Langkah. Dan filter menurut Jay. Kami tidak menentukan di sini bahwa kami perlu mendapatkan nilai dan kami tidak memilih waktu.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Sekarang mari kita coba menghitung kecepatan gerak Jay atau Silent Bob. Di ClickHouse kita perlu melakukan runningDifference, yaitu menghitung selisih antara pasangan titik dan membaginya dengan waktu untuk mendapatkan kecepatan yang tepat. Permintaannya akan terlihat seperti ini.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan itu akan menunjukkan kira-kira nilai-nilai ini, yaitu Silent Bob atau Jay mengambil sekitar 1,8 langkah per detik.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan di Prometheus Anda juga tahu cara melakukan ini. Jauh lebih mudah dari sebelumnya.

"ExtendedPromQL" - transkrip laporan Roman KhavronenkoDan agar mudah dilakukan di Grafana, saya menambahkan wrapper ini, yang tampilannya sangat mirip dengan PromQL. Ini disebut Nilai Makro atau apa pun sebutannya. Di Grafana Anda cukup menulis "nilai", tetapi jauh di lubuk hati itu berubah menjadi permintaan besar ini. Dan Anda bahkan tidak perlu melihatnya, itu ada di suatu tempat, tetapi Anda menghemat banyak waktu, karena menulis query SQL sebesar itu selalu mahal. Anda dapat dengan mudah membuat kesalahan dan kemudian tidak memahami apa yang terjadi dalam waktu lama.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan ini adalah permintaan yang bahkan tidak muat dalam satu slide dan saya bahkan harus membaginya menjadi dua kolom. Ini juga merupakan permintaan di ClickHouse, yang membuat tarif yang sama, tetapi untuk kedua rangkaian waktu: Silent Bob dan Jay, sehingga kami memiliki dua rangkaian waktu di panel. Dan ini sudah sangat sulit menurut saya.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan menurut Prometheus itu akan menjadi jumlah (rate). Untuk ClickHouse, saya membuat makro terpisah bernama RateColumns, yang terlihat seperti kueri di Prometheus.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Kami melihatnya dan sepertinya PromQL sangat keren, tetapi tentu saja ada keterbatasannya.

Ini adalah:

  • PILIH Terbatas.
  • GABUNG Garis Batas.
  • Tidak MEMILIKI dukungan.

Dan jika Anda sudah lama menggunakannya, maka Anda tahu bahwa terkadang sangat sulit untuk melakukan sesuatu di PromQL, tetapi di SQL Anda dapat melakukan hampir semuanya, karena semua opsi yang baru saja kita bicarakan dapat dilakukan di SQL . Tapi apakah nyaman menggunakannya? Dan ini membuat saya berpikir bahwa bahasa yang paling canggih belum tentu merupakan bahasa yang paling nyaman.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Oleh karena itu, terkadang Anda perlu memilih bahasa untuk suatu tugas. Ini seperti Batman melawan Superman. Jelas Superman lebih kuat, namun Batman mampu mengalahkannya karena lebih praktis dan tahu persis apa yang dia lakukan.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan bagian selanjutnya adalah Memperluas PromQL.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Sekali lagi tentang VictoriaMetrics. Apa itu VictoriaMetrics? Ini adalah database deret waktu, di OpenSource, kami mendistribusikan versi tunggal dan clusternya. Menurut tolok ukur kami, ini lebih cepat dari apa pun yang ada di pasaran saat ini dan kompresinya serupa, yaitu orang-orang nyata melaporkan kompresi sekitar 0,4 byte per titik, sedangkan Prometheus adalah 1,2-1,4.

Kami mendukung lebih dari sekedar Prometheus. Kami mendukung InfluxDB, Grafit, OpenTSDB.

Anda dapat “menulis” kepada kami, yaitu Anda dapat mentransfer data lama.

Dan kami juga bekerja sempurna dengan Prometheus dan Grafana, yaitu kami mendukung mesin PromQL. Dan di Grafana Anda cukup mengubah titik akhir Prometheus ke VictoriaMetrics dan semua dasbor Anda akan berfungsi sebagaimana mestinya.

Namun Anda juga dapat menggunakan fitur tambahan yang disediakan VictoriaMetrics.

Kami akan segera memeriksa fitur-fitur yang telah kami tambahkan.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Hilangkan parameter interval – Anda dapat menghilangkan parameter interval di Grafana. Jika Anda tidak ingin mendapatkan grafik aneh saat memperbesar/memperkecil panel, disarankan untuk menggunakan variabel $__interval. Ini adalah perubahan internal Grafana dan memilih rentang datanya sendiri. Dan VictoriaMetrics sendiri dapat memahami kisaran ini seharusnya. Dan Anda tidak perlu memperbarui semua permintaan Anda. Ini akan jauh lebih mudah.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Fungsi kedua adalah referensi interval. Anda dapat menggunakan interval ini dalam ekspresi Anda. Anda dapat mengalikan, membagi, mentransfer, merujuknya.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Berikutnya adalah keluarga fungsi rollup. Fungsi Rollup mengubah rangkaian waktu Anda menjadi tiga rangkaian waktu terpisah. Ini adalah min, maks, dan rata-rata. Menurut saya ini sangat berguna karena terkadang dapat menunjukkan beberapa penyimpangan dan ketidakakuratan.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan jika Anda hanya melakukan kemarahan atau penilaian, Anda mungkin akan melewatkan beberapa kasus di mana deret waktu tidak berperilaku seperti yang Anda harapkan. Dengan fungsi ini lebih mudah untuk melihatnya, misalkan max sangat jauh dari rata-rata.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Berikutnya adalah variabel default. Default - ini berarti nilai apa yang perlu kita ambil di Grafana jika kita tidak memiliki deret waktu saat ini. Kapan ini terjadi? Katakanlah Anda mengekspor beberapa metrik kesalahan. Dan Anda memiliki aplikasi yang sangat keren sehingga ketika Anda memulai, Anda tidak mengalami kesalahan dan bahkan tidak ada kesalahan selama tiga jam atau bahkan satu hari berikutnya. Dan Anda memiliki dasbor yang menunjukkan hubungan antara keberhasilan dan kesalahan. Dan mereka tidak akan menunjukkan apa pun kepada Anda karena Anda tidak memiliki metrik kesalahan. Dan secara default Anda dapat menentukan apa saja.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Keep_last_Value – menyimpan nilai terakhir metrik jika tidak ada. Jika Prometheus tidak menemukannya dalam waktu 5 menit setelah pengikisan berikutnya, maka di sini kami akan mengingat nilai terakhirnya dan grafik Anda tidak akan rusak lagi.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Scrape_interval – menunjukkan seberapa sering Prometheus mengumpulkan data pada metrik Anda, dan dengan frekuensi berapa. Di sini Anda dapat melihat izinnya, misalnya.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko
Penggantian label adalah fitur yang populer. Namun menurut kami ini sedikit rumit karena memerlukan argumen yang utuh. Dan Anda tidak hanya perlu mengingat 5 argumen, tetapi juga mengingat urutannya.
"ExtendedPromQL" - transkrip laporan Roman Khavronenko
Oleh karena itu, mengapa tidak membuatnya lebih sederhana? Artinya, memecahnya menjadi fungsi-fungsi kecil dengan sintaksis yang dapat dimengerti.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan sekarang bagian yang menyenangkan. Mengapa menurut kami ini adalah PromQL yang diperluas? Karena kami mendukung Ekspresi Tabel Umum. Anda dapat mengikuti kode QR (https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL), lihat tautan dengan contoh, dari taman bermain, tempat Anda dapat menjalankan kueri langsung di VictoriaMetrics tanpa menginstalnya hanya di browser.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan apa ini? Permintaan di atas merupakan permintaan yang cukup populer. Saya rasa di dasbor mana pun di banyak perusahaan Anda menggunakan filter yang sama untuk semuanya. Biasanya begitu. Namun ketika Anda perlu menambahkan beberapa filter baru, Anda harus memperbarui setiap panel, atau mengunduh dasbor, membukanya di JSON, mencari pengganti, yang juga membutuhkan waktu. Mengapa tidak menyimpan nilai ini dalam variabel dan menggunakannya kembali? Menurut pendapat saya, ini terlihat lebih sederhana dan jelas.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Misalnya, ketika saya perlu memperbarui filter di Grafana di semua permintaan, dan dasbornya bisa sangat besar atau bahkan ada beberapa. Dan bagaimana saya ingin menyelesaikan masalah ini di Grafana?

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Saya memecahkan masalah ini dengan cara ini: Saya membuat commonFilter dan mendefinisikan filter ini di dalamnya, dan kemudian menggunakannya kembali dalam kueri. Namun jika Anda melakukan hal yang sama sekarang, itu tidak akan berhasil karena Grafana tidak mengizinkan Anda menggunakan variabel di dalam variabel kueri. Dan itu agak aneh.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Jadi saya membuat opsi yang memungkinkan Anda melakukan ini. Dan jika Anda tertarik atau menginginkan fitur seperti itu, dukung atau tidak suka jika Anda tidak menyukai ide ini. https://github.com/grafana/grafana/pull/16694

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Lebih lanjut tentang PromQL diperluas. Di sini kita mendefinisikan tidak hanya variabel, tetapi seluruh fungsi. Dan kami menyebutnya ru (penggunaan sumber daya). Dan fungsi ini menerima sumber daya gratis, batasan sumber daya, dan filter. Sintaksnya tampaknya sederhana. Dan sangat mudah untuk menggunakan fungsi ini dan menghitung persentase memori bebas yang kita miliki. Artinya, berapa banyak memori yang kita miliki, apa batasannya dan bagaimana cara memfilternya. Tampaknya jauh lebih nyaman jika Anda menulis semuanya, menggunakan kembali filter yang sama, karena itu akan berubah menjadi kueri yang sangat besar.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Dan inilah contoh permintaan yang sangat besar. Itu dari dashboard resmi NodeExporter untuk Grafana. Tapi saya hampir tidak mengerti apa yang terjadi di sini. Tentu saja saya mengerti jika Anda perhatikan lebih dekat, tetapi banyaknya tanda kurung dapat langsung mengurangi motivasi untuk memahami apa yang terjadi di sini. Dan mengapa tidak membuatnya lebih sederhana dan jelas?

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Misalnya seperti ini, memisahkan hal atau bagian penting menjadi variabel. Dan kemudian lakukan perhitungan dasar Anda. Ini lebih seperti pemrograman, inilah yang ingin saya lihat di masa depan di Grafana.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Berikut adalah contoh kedua bagaimana kita dapat membuat hal ini lebih mudah jika kita sudah memiliki fungsi ru ini, dan fungsi tersebut sudah ada langsung di VictoriaMetrics. Dan Anda kemudian cukup meneruskan nilai cache yang Anda nyatakan di CTE.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Saya sudah membahas betapa pentingnya menggunakan bahasa pemrograman yang tepat. Dan, mungkin, setiap perusahaan di Grafana mempunyai sesuatu yang berbeda. Dan Anda mungkin juga memberikan akses ke Grafana kepada pengembang Anda, dan pengembang melakukan hal mereka sendiri. Dan mereka semua melakukannya dengan cara yang berbeda. Tetapi saya ingin hal itu tetap sama, yaitu menguranginya menjadi standar umum.

Katakanlah Anda tidak hanya memiliki insinyur sistem, mungkin Anda bahkan memiliki pakar, pengembang, atau SRE. Mungkin Anda mempunyai pakar yang mengetahui apa itu pemantauan, yang mengetahui apa itu Grafana, yaitu mereka telah bekerja dengannya selama bertahun-tahun dan mereka tahu persis bagaimana melakukannya dengan benar. Dan mereka telah menulis ini 100 kali dan menjelaskannya kepada semua orang, tetapi untuk beberapa alasan tidak ada yang mendengarkan.

Bagaimana jika mereka dapat menerapkan pengetahuan ini langsung ke Grafana sehingga pengguna lain dapat menggunakan kembali fitur-fitur tersebut? Dan jika mereka perlu menghitung persentase memori bebas, mereka cukup menerapkan fungsinya. Bagaimana jika pembuat eksportir, bersama dengan produknya, juga menyediakan serangkaian fungsi tentang cara menggunakan metrik mereka, karena mereka tahu persis apa itu metrik dan cara menghitungnya dengan benar?

Ini sebenarnya tidak ada. Inilah yang saya lakukan sendiri. Ini adalah dukungan perpustakaan di Grafana. Katakanlah orang yang membuat NodeExporter melakukan apa yang saya bicarakan. Dan mereka juga menyediakan serangkaian fungsi.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Artinya, tampilannya seperti ini. Anda menghubungkan perpustakaan ini ke Grafana, Anda masuk ke pengeditan dan ditulis dengan sangat sederhana di JSON cara bekerja dengan metrik ini. Yaitu, serangkaian fungsi, deskripsinya, dan apa jadinya.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Saya rasa ini bisa bermanfaat, karena di Grafana Anda akan menulis begitu saja. Dan Grafana “memberi tahu” Anda bahwa ada fungsi ini dan itu dari perpustakaan ini dan itu - mari kita gunakan. Menurutku itu akan sangat keren.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Sedikit tentang VictoriaMetrics. Kami melakukan banyak hal menarik. Baca artikel kami tentang kompresi, tentang persaingan kami dengan aplikasi data deret waktu lainnya, penjelasan kami tentang cara bekerja dengan PromQL, karena masih banyak pemula dalam hal ini, serta tentang skalabilitas vertikal dan tentang konfrontasi dengan Thanos.

"ExtendedPromQL" - transkrip laporan Roman Khavronenko

Pertanyaan:

Saya akan memulai pertanyaan saya dengan kisah hidup sederhana. Saat saya pertama kali menggunakan Grafana, saya menulis kueri yang sangat menarik sepanjang 5 baris. Hasil akhirnya adalah grafik yang sangat meyakinkan. Jadwal ini hampir memasuki produksi. Namun setelah diperiksa lebih dekat, ternyata grafik ini benar-benar tidak masuk akal dan tidak ada hubungannya dengan kenyataan, meskipun angkanya berada dalam kisaran yang kita harapkan. Dan pertanyaan saya. Kami punya perpustakaan, kami punya fungsi, tapi bagaimana kami menulis tes untuk Grafana? Anda telah menulis permintaan kompleks yang menjadi dasar keputusan bisnis - untuk memesan wadah server yang sebenarnya atau tidak. Dan seperti yang kita ketahui, fungsi menggambar grafik ini mirip dengan kebenarannya. Terima kasih.

Terima kasih atas pertanyaannya. Ada dua bagian. Pertama, saya mendapat kesan, berdasarkan pengalaman saya, bahwa sebagian besar pengguna, ketika mereka melihat grafik mereka, tidak mengerti apa yang mereka tunjukkan. Untuk beberapa alasan, orang sangat pandai dalam memberikan alasan untuk setiap anomali yang terjadi pada grafik, meskipun itu adalah kesalahan dalam suatu fungsi. Dan bagian kedua - menurut saya menggunakan fungsi seperti itu akan menjadi pendekatan yang jauh lebih baik untuk menyelesaikan masalah Anda, daripada masing-masing pengembang Anda melakukan perencanaan kapasitasnya sendiri dan membuat kesalahan dengan kemungkinan tertentu.

Bagaimana cara memeriksanya?

Bagaimana cara memeriksanya? Mungkin tidak.

Sebagai ujian di Grafana.

Apa hubungannya Grafana dengan itu? Grafana menerjemahkan permintaan ini langsung ke DataSource.

Menambahkan sedikit ke parameter.

Tidak, tidak ada yang ditambahkan ke Grafana. Mungkin ada parameter GET, misalnya, langkah. Ini tidak ditentukan secara eksplisit, tetapi Anda dapat menimpanya, atau Anda tidak boleh menimpanya, tetapi ditambahkan secara otomatis. Anda tidak akan menulis tes di sini. Saya rasa kita tidak harus mengandalkan Grafana sebagai sumber kebenaran di sini.

Terima kasih atas laporannya! Terima kasih atas kompresinya! Anda menyebutkan pemetaan variabel dalam grafik, bahwa di Grafana Anda tidak dapat menggunakan variabel di dalam variabel. Apa kamu tau maksud saya?

Ya.

Ini awalnya pusing saat ingin membuat alert di Grafana. Dan di sana Anda perlu melakukan peringatan untuk setiap host secara terpisah. Benda yang Anda buat ini, apakah berfungsi untuk peringatan di Grafana?

Jika Grafana tidak mengakses variabel secara berbeda, ya, itu akan berhasil. Namun saran saya jangan gunakan alerting sama sekali di Grafana, lebih baik gunakan alertmanager.

Ya, saya menggunakannya, tapi sepertinya lebih mudah untuk mengaturnya di Grafana, tapi terima kasih atas sarannya!

Sumber: www.habr.com

Tambah komentar