eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha

eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha

Linux yadro va ilovalarni disk raskadrovka qilish uchun juda ko'p vositalarga ega. Ularning aksariyati dastur ishlashiga salbiy ta'sir ko'rsatadi va ishlab chiqarishda foydalanish mumkin emas.

Bir necha yil oldin bor edi yana bir vosita ishlab chiqildi - eBPF. Bu yadro va foydalanuvchi ilovalarini kam yuk bilan va dasturlarni qayta tiklash va yadroga uchinchi tomon modullarini yuklamasdan kuzatish imkonini beradi.

eBPF-dan foydalanadigan ko'plab amaliy yordam dasturlari allaqachon mavjud va ushbu maqolada biz kutubxona asosida o'z profilingizni qanday yozishni ko'rib chiqamiz. PythonBCC. Maqola real voqealarga asoslangan. Mavjud yordamchi dasturlardan muayyan vaziyatlarda qanday foydalanish mumkinligini ko'rsatish uchun muammoni hal qilishga o'tamiz.

Sef sekin

Ceph klasteriga yangi xost qo'shildi. Ma'lumotlarning bir qismini unga ko'chirganimizdan so'ng, biz uning yozish so'rovlarini qayta ishlash tezligi boshqa serverlarga qaraganda ancha past ekanligini payqadik.

eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha
Boshqa platformalardan farqli o'laroq, bu xost bcache va yangi Linux 4.15 yadrosidan foydalangan. Bu yerda birinchi marta ushbu konfiguratsiya xosti ishlatilgan. Va o'sha paytda muammoning ildizi nazariy jihatdan har qanday narsa bo'lishi mumkinligi aniq edi.

Xostni tekshirish

Keling, seph-osd jarayonida nima sodir bo'lishini ko'rib chiqaylik. Buning uchun biz foydalanamiz mukammal и flameskop (bu haqida ko'proq o'qishingiz mumkin shu yerda):

eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha
Rasm bizga bu funktsiyani aytadi fdatasync() funktsiyalarga so'rov yuborish uchun ko'p vaqt sarfladi generic_make_request(). Bu shuni anglatadiki, muammolarning sababi osd demonining o'zidan tashqarida bo'lishi mumkin. Bu yadro yoki disk bo'lishi mumkin. Iostat chiqishi bcache disklari tomonidan so'rovlarni qayta ishlashda yuqori kechikishni ko'rsatdi.

Xostni tekshirishda biz systemd-udevd demoni protsessor vaqtini katta miqdorda - bir nechta yadrolarda taxminan 20% iste'mol qilishini aniqladik. Bu g'alati xatti-harakat, shuning uchun nima uchun ekanligini bilib olishingiz kerak. Systemd-udevd voqealar bilan ishlaganligi sababli, biz ularni ko'rib chiqishga qaror qildik udevadm monitor. Ma'lum bo'lishicha, tizimdagi har bir blokli qurilma uchun ko'plab o'zgarishlar hodisalari yaratilgan. Bu juda g'ayrioddiy, shuning uchun biz ushbu voqealarning barchasini nima keltirib chiqarganini ko'rib chiqishimiz kerak.

BCC asboblar to'plamidan foydalanish

Biz allaqachon bilib olganimizdek, yadro (va tizim chaqiruvidagi seph daemon) ko'p vaqtini generic_make_request(). Keling, ushbu funktsiyaning tezligini o'lchashga harakat qilaylik. IN BCC Ajoyib yordamchi dastur allaqachon mavjud - funklatentlik. Biz demonni PID bo'yicha chiqishlar orasidagi 1 soniyalik interval bilan kuzatib boramiz va natijani millisekundlarda chiqaramiz.

eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha
Bu xususiyat odatda tez ishlaydi. Buning uchun so'rovni qurilma drayveri navbatiga o'tkazish kifoya.

Bcache aslida uchta diskdan iborat murakkab qurilma:

  • qo'llab-quvvatlash qurilmasi (keshli disk), bu holda bu sekin HDD;
  • keshlash qurilmasi (keshlash diski), bu erda NVMe qurilmasining bir qismi;
  • ilova ishlaydigan bcache virtual qurilmasi.

Bilamizki, so'rovni uzatish sekin, lekin bu qurilmalarning qaysi biri uchun? Bu bilan biroz keyinroq shug'ullanamiz.

Voqealarning muammolarga olib kelishi mumkinligini endi bilamiz. Ularning paydo bo'lishiga nima sabab bo'lganini topish unchalik oson emas. Faraz qilaylik, bu vaqti-vaqti bilan ishga tushiriladigan dasturiy ta'minot turi. Keling, skript yordamida tizimda qanday dasturiy ta'minot ishlashini ko'rib chiqaylik execsnoop xuddi shunday BCC yordam to'plami. Keling, uni ishga tushiramiz va natijani faylga yuboramiz.

Masalan, shunday:

/usr/share/bcc/tools/execsnoop  | tee ./execdump

Biz bu erda execsnoop-ning to'liq chiqishini ko'rsatmaymiz, lekin bizni qiziqtirgan bir yo'nalish quyidagicha ko'rindi:

sh 1764905 5802 0 sudo arcconf getconfig 1 AD | grep Temperature | awk -F '[:/]' '{print $2}' | sed 's/^ ([0-9]*) C.*/1/'

Uchinchi ustun - jarayonning PPID (ota-ona PID). PID 5802 bilan jarayon bizning monitoring tizimimizning mavzularidan biri bo'ldi. Monitoring tizimining konfiguratsiyasini tekshirishda noto'g'ri parametrlar aniqlandi. HBA adapterining harorati har 30 soniyada o'lchandi, bu zarur bo'lganidan ancha tez-tez. Tekshirish oralig'ini uzoqroqqa o'zgartirganimizdan so'ng, biz ushbu xostdagi so'rovni qayta ishlash kechikishi boshqa xostlar bilan solishtirganda farq qilmasligini aniqladik.

Lekin nima uchun bcache qurilmasi juda sekin bo'lganligi hali ham noma'lum. Biz bir xil konfiguratsiyaga ega sinov platformasini tayyorladik va muammoni bcache-da fio-ni ishga tushirish, vaqti-vaqti bilan hodisalar yaratish uchun udevadm triggerini ishga tushirish orqali qayta ishlab chiqarishga harakat qildik.

BCC-ga asoslangan asboblarni yozish

Keling, eng sekin qo'ng'iroqlarni kuzatish va ko'rsatish uchun oddiy yordamchi dastur yozishga harakat qilaylik generic_make_request(). Bizni ushbu funktsiya chaqirilgan diskning nomi ham qiziqtiradi.

Reja oddiy:

  • Roʻyxatdan oʻtish kprobe haqida generic_make_request():
    • Disk nomini xotiraga saqlaymiz, funktsiya argumenti orqali kirish mumkin;
    • Vaqt tamg'asini saqlaymiz.

  • Roʻyxatdan oʻtish kretprobe dan qaytish uchun generic_make_request():
    • Biz joriy vaqt tamg'asini olamiz;
    • Biz saqlangan vaqt tamg'asini qidiramiz va uni joriy bilan solishtiramiz;
    • Agar natija belgilanganidan katta bo'lsa, biz saqlangan disk nomini topamiz va uni terminalda ko'rsatamiz.

Kprobes и kretproblar funksiya kodini tezda o'zgartirish uchun to'xtash nuqtasi mexanizmidan foydalaning. O'qishingiz mumkin hujjatlar и yaxshi ushbu mavzu bo'yicha maqola. Agar siz turli xil yordamchi dasturlarning kodiga qarasangiz BCC, keyin ular bir xil tuzilishga ega ekanligini ko'rishingiz mumkin. Shunday qilib, ushbu maqolada biz skript argumentlarini tahlil qilishni o'tkazib yuboramiz va BPF dasturining o'ziga o'tamiz.

Python skripti ichidagi eBPF matni quyidagicha ko'rinadi:

bpf_text = “”” # Here will be the bpf program code “””

Funktsiyalar o'rtasida ma'lumot almashish uchun eBPF dasturlari foydalanadi hash jadvallari. Biz ham xuddi shunday qilamiz. Biz PID jarayonini kalit sifatida ishlatamiz va strukturani qiymat sifatida belgilaymiz:

struct data_t {
	u64 pid;
	u64 ts;
	char comm[TASK_COMM_LEN];
	u64 lat;
	char disk[DISK_NAME_LEN];
};

BPF_HASH(p, u64, struct data_t);
BPF_PERF_OUTPUT(events);

Bu erda biz xesh jadvalini ro'yxatdan o'tkazamiz p, kalit turi bilan u64 va tur qiymati struct data_t. Jadval bizning BPF dasturimiz kontekstida mavjud bo'ladi. BPF_PERF_OUTPUT makrosi chaqirilgan boshqa jadvalni qayd qiladi tadbirlaruchun ishlatiladi ma'lumotlarni uzatish foydalanuvchi maydoniga.

Funktsiyani chaqirish va undan qaytish o'rtasidagi yoki turli funktsiyalarga qo'ng'iroqlar orasidagi kechikishlarni o'lchashda siz qabul qilingan ma'lumotlar bir xil kontekstga tegishli bo'lishi kerakligini hisobga olishingiz kerak. Boshqacha qilib aytganda, siz funktsiyalarni parallel ravishda ishga tushirish haqida eslashingiz kerak. Biz bir jarayon kontekstida funktsiyani chaqirish va boshqa jarayon kontekstida ushbu funktsiyadan qaytish o'rtasidagi kechikish vaqtini o'lchash imkoniyatiga egamiz, ammo bu foydasiz bo'lishi mumkin. Bu erda yaxshi misol bo'lardi biolatent yordam dasturi, bu erda xesh jadvali kaliti ko'rsatgichga o'rnatiladi tuzilish so'rovi, bu bitta disk so'rovini aks ettiradi.

Keyinchalik, o'rganilayotgan funksiya chaqirilganda ishlaydigan kodni yozishimiz kerak:

void start(struct pt_regs *ctx, struct bio *bio) {
	u64 pid = bpf_get_current_pid_tgid();
	struct data_t data = {};
	u64 ts = bpf_ktime_get_ns();
	data.pid = pid;
	data.ts = ts;
	bpf_probe_read_str(&data.disk, sizeof(data.disk), (void*)bio->bi_disk->disk_name);
	p.update(&pid, &data);
}

Bu erda chaqirilgan funksiyaning birinchi argumenti ikkinchi argument sifatida almashtiriladi generic_make_request(). Shundan so'ng, biz ishlayotgan jarayonning PID-ni va nanosekundlarda joriy vaqt tamg'asini olamiz. Biz hammasini yangi tanlangan holda yozamiz struct data_t ma'lumotlari. Disk nomini strukturadan olamiz bio, bu qo'ng'iroq paytida uzatiladi generic_make_request(), va uni bir xil tuzilmada saqlang ma'lumotlar. Oxirgi qadam, avval aytib o'tilgan xesh jadvaliga yozuv qo'shishdir.

Qaytishda quyidagi funksiya chaqiriladi generic_make_request():

void stop(struct pt_regs *ctx) {
    u64 pid = bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();
    struct data_t* data = p.lookup(&pid);
    if (data != 0 && data->ts > 0) {
        bpf_get_current_comm(&data->comm, sizeof(data->comm));
        data->lat = (ts - data->ts)/1000;
        if (data->lat > MIN_US) {
            FACTOR
            data->pid >>= 32;
            events.perf_submit(ctx, data, sizeof(struct data_t));
        }
        p.delete(&pid);
    }
}

Ushbu funktsiya avvalgisiga o'xshaydi: biz jarayonning PID va vaqt tamg'asini bilib olamiz, lekin yangi ma'lumotlar tuzilishi uchun xotira ajratmaymiz. Buning o'rniga, biz == joriy PID kaliti yordamida allaqachon mavjud tuzilma uchun xesh jadvalini qidiramiz. Agar struktura topilsa, biz ishlaydigan jarayonning nomini bilib olamiz va uni unga qo'shamiz.

Bu erda biz ishlatadigan ikkilik siljish GID ipini olish uchun kerak. bular. Biz ishlayotgan kontekstda ipni boshlagan asosiy jarayonning PID. Biz chaqiradigan funksiya bpf_get_current_pid_tgid() ipning GID va uning PID ni bitta 64 bitli qiymatda qaytaradi.

Terminalga chiqishda biz hozirda mavzuga qiziqmaymiz, lekin biz asosiy jarayonga qiziqamiz. Olingan kechikishni ma'lum bir chegara bilan solishtirgandan so'ng, biz tuzilmamizdan o'tamiz ma'lumotlar jadval orqali foydalanuvchi maydoniga tadbirlar, shundan so'ng biz yozuvni o'chirib tashlaymiz p.

Ushbu kodni yuklaydigan python skriptida biz MIN_US va FACTOR ni kechikish chegaralari va vaqt birliklari bilan almashtirishimiz kerak, biz ularni argumentlar orqali o'tamiz:

bpf_text = bpf_text.replace('MIN_US',str(min_usec))
if args.milliseconds:
	bpf_text = bpf_text.replace('FACTOR','data->lat /= 1000;')
	label = "msec"
else:
	bpf_text = bpf_text.replace('FACTOR','')
	label = "usec"

Endi biz orqali BPF dasturini tayyorlashimiz kerak BPF makro va namunalarni ro'yxatdan o'tkazish:

b = BPF(text=bpf_text)
b.attach_kprobe(event="generic_make_request",fn_name="start")
b.attach_kretprobe(event="generic_make_request",fn_name="stop")

Biz ham aniqlashimiz kerak bo'ladi struct data_t skriptimizda, aks holda biz hech narsani o'qiy olmaymiz:

TASK_COMM_LEN = 16	# linux/sched.h
DISK_NAME_LEN = 32	# linux/genhd.h
class Data(ct.Structure):
	_fields_ = [("pid", ct.c_ulonglong),
            	("ts", ct.c_ulonglong),
            	("comm", ct.c_char * TASK_COMM_LEN),
            	("lat", ct.c_ulonglong),
            	("disk",ct.c_char * DISK_NAME_LEN)]

Oxirgi qadam ma'lumotlarni terminalga chiqarishdir:

def print_event(cpu, data, size):
    global start
    event = ct.cast(data, ct.POINTER(Data)).contents
    if start == 0:
        start = event.ts
    time_s = (float(event.ts - start)) / 1000000000
    print("%-18.9f %-16s %-6d   %-1s %s   %s" % (time_s, event.comm, event.pid, event.lat, label, event.disk))

b["events"].open_perf_buffer(print_event)
# format output
start = 0
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

Skriptning o'zi quyidagi manzilda mavjud GItHub. Keling, uni fio ishlayotgan, bcache-ga yozadigan test platformasida ishga tushirishga harakat qilaylik va udevadm monitoriga qo'ng'iroq qilaylik:

eBPF/BCC yordamida yuqori Ceph kechikishidan yadro yamog'igacha
Nihoyat! Endi biz to'xtab qolgan bcache qurilmasiga o'xshagan narsa aslida to'xtab qolgan qo'ng'iroq ekanligini ko'ramiz generic_make_request() keshlangan disk uchun.

Yadroni qazib oling

So'rovni uzatishda aniq nima sekinlashadi? Biz kechikish so'rovni hisobga olish boshlanishidan oldin ham sodir bo'lishini ko'ramiz, ya'ni. u bo'yicha statistik ma'lumotlarni keyingi chiqarish uchun aniq so'rovni hisobga olish (/proc/diskstats yoki iostat) hali boshlanmagan. Bu muammoni qayta ishlab chiqarishda iostatni ishga tushirish orqali osongina tekshirilishi mumkin, yoki BCC skriptining biolatentligi, bu so'rovni hisobga olishning boshlanishi va oxiriga asoslanadi. Ushbu yordamchi dasturlarning hech biri keshlangan diskka so'rovlar uchun muammolarni ko'rsatmaydi.

Funktsiyaga qarasak generic_make_request(), keyin biz so'rovni hisobga olish boshlanishidan oldin yana ikkita funktsiya chaqirilishini ko'ramiz. Birinchi - generic_make_request_checks(), disk sozlamalari bo'yicha so'rovning qonuniyligini tekshiradi. Ikkinchi - blk_queue_enter(), bu qiziqarli muammoga ega wait_event_interruptible():

ret = wait_event_interruptible(q->mq_freeze_wq,
	(atomic_read(&q->mq_freeze_depth) == 0 &&
	(preempt || !blk_queue_preempt_only(q))) ||
	blk_queue_dying(q));

Unda yadro navbatning muzlashini kutadi. Keling, kechikishni o'lchaymiz blk_queue_enter():

~# /usr/share/bcc/tools/funclatency  blk_queue_enter -i 1 -m               	 
Tracing 1 functions for "blk_queue_enter"... Hit Ctrl-C to end.

 	msecs           	: count 	distribution
     	0 -> 1      	: 341  	|****************************************|

 	msecs           	: count 	distribution
     	0 -> 1      	: 316  	|****************************************|

 	msecs           	: count 	distribution
     	0 -> 1      	: 255  	|****************************************|
     	2 -> 3      	: 0    	|                                    	|
     	4 -> 7      	: 0    	|                                    	|
     	8 -> 15     	: 1    	|                                    	|

Biz yechimga yaqinmiz. Navbatni muzlatish/muzdan chiqarish uchun ishlatiladigan funksiyalar blk_mq_freeze_queue и blk_mq_unfreeze_queue. Ular ushbu navbatdagi so'rovlar uchun potentsial xavfli bo'lgan so'rovlar navbati sozlamalarini o'zgartirish zarur bo'lganda foydalaniladi. Qo'ng'iroq qilganda blk_mq_freeze_queue() funktsiya blk_freeze_queue_start() hisoblagich oshiriladi q->mq_muzlatish_chuqurligi. Shundan so'ng, yadro navbat bo'shatilguncha kutadi blk_mq_freeze_queue_wait().

Bu navbatni tozalash uchun ketadigan vaqt diskdagi kechikish vaqtiga teng, chunki yadro barcha navbatdagi operatsiyalar tugashini kutadi. Navbat bo'sh bo'lgach, sozlamalardagi o'zgarishlar qo'llaniladi. Shundan so'ng u chaqiriladi blk_mq_unfreeze_queue(), hisoblagichni kamaytirish muzlash_chuqurligi.

Endi biz vaziyatni to'g'irlash uchun etarli darajada bilamiz. Udevadm trigger buyrug'i bloklash qurilmasi sozlamalarini qo'llashga olib keladi. Ushbu sozlamalar udev qoidalarida tasvirlangan. Qaysi sozlamalar navbatni muzlatishini ularni sysfs orqali o'zgartirishga urinib yoki yadro manba kodiga qarab bilib olamiz. BCC yordam dasturini ham sinab ko'rishimiz mumkin iz, bu terminalga har bir qo'ng'iroq uchun yadro va foydalanuvchilar maydoni stek izlarini chiqaradi blk_freeze_queue, masalan:

~# /usr/share/bcc/tools/trace blk_freeze_queue -K -U
PID 	TID 	COMM        	FUNC        	 
3809642 3809642 systemd-udevd   blk_freeze_queue
    	blk_freeze_queue+0x1 [kernel]
    	elevator_switch+0x29 [kernel]
    	elv_iosched_store+0x197 [kernel]
    	queue_attr_store+0x5c [kernel]
    	sysfs_kf_write+0x3c [kernel]
    	kernfs_fop_write+0x125 [kernel]
    	__vfs_write+0x1b [kernel]
    	vfs_write+0xb8 [kernel]
    	sys_write+0x55 [kernel]
    	do_syscall_64+0x73 [kernel]
    	entry_SYSCALL_64_after_hwframe+0x3d [kernel]
    	__write_nocancel+0x7 [libc-2.23.so]
    	[unknown]

3809631 3809631 systemd-udevd   blk_freeze_queue
    	blk_freeze_queue+0x1 [kernel]
    	queue_requests_store+0xb6 [kernel]
    	queue_attr_store+0x5c [kernel]
    	sysfs_kf_write+0x3c [kernel]
    	kernfs_fop_write+0x125 [kernel]
    	__vfs_write+0x1b [kernel]
    	vfs_write+0xb8 [kernel]
    	sys_write+0x55 [kernel]
    	do_syscall_64+0x73 [kernel]
    	entry_SYSCALL_64_after_hwframe+0x3d [kernel]
    	__write_nocancel+0x7 [libc-2.23.so]
    	[unknown]

Udev qoidalari juda kamdan-kam hollarda o'zgaradi va odatda bu nazorat ostida sodir bo'ladi. Shunday qilib, biz allaqachon o'rnatilgan qiymatlarni qo'llash ham so'rovni dasturdan diskka o'tkazish kechikishiga olib kelishini ko'ramiz. Albatta, disk konfiguratsiyasida hech qanday o'zgarishlar bo'lmaganda (masalan, qurilma o'rnatilmagan/o'chirilgan) udev hodisalarini yaratish yaxshi amaliyot emas. Biroq, biz yadroga keraksiz ishlarni qilmaslikda yordam bera olamiz va agar kerak bo'lmasa, so'rovlar navbatini muzlatib qo'yamiz. Uchta kichiklar topshirmoq vaziyatni to'g'rilash.

Xulosa

eBPF juda moslashuvchan va kuchli vositadir. Maqolada biz bitta amaliy ishni ko'rib chiqdik va nima qilish mumkinligining kichik qismini ko'rsatdik. Agar siz BCC utilitlarini ishlab chiqishga qiziqsangiz, uni ko'rib chiqishga arziydi rasmiy darslik, bu asoslarni yaxshi tasvirlaydi.

eBPF asosidagi boshqa qiziqarli disk raskadrovka va profillash vositalari mavjud. Ulardan biri - bpftrace, bu sizga awk-ga o'xshash tilda kuchli bir-laynerlar va kichik dasturlarni yozish imkonini beradi. Boshqa - ebpf_exporter, past darajadagi, yuqori aniqlikdagi o'lchovlarni to'g'ridan-to'g'ri prometey serveringizga to'plash imkonini beradi, keyinchalik chiroyli vizualizatsiya va hatto ogohlantirishlarni olish imkoniyati bilan.

Manba: www.habr.com

a Izoh qo'shish