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
eBPF-dan foydalanadigan ko'plab amaliy yordam dasturlari allaqachon mavjud va ushbu maqolada biz kutubxona asosida o'z profilingizni qanday yozishni ko'rib chiqamiz.
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.
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
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
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
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
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
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
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
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
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
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
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
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
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 -
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
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
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
~# /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.
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
eBPF asosidagi boshqa qiziqarli disk raskadrovka va profillash vositalari mavjud. Ulardan biri -
Manba: www.habr.com