Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл

Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл

Линукс нь цөм болон програмуудыг дибаг хийх олон тооны хэрэгсэлтэй. Тэдгээрийн ихэнх нь хэрэглээний гүйцэтгэлд сөргөөр нөлөөлдөг тул үйлдвэрлэлд ашиглах боломжгүй байдаг.

Хэдэн жилийн өмнө байсан өөр хэрэгсэл боловсруулсан - eBPF. Энэ нь програмыг дахин бүтээх, гуравдагч талын модулиудыг цөмд ачаалах шаардлагагүйгээр цөм болон хэрэглэгчийн програмуудыг хянах боломжийг олгодог.

eBPF ашигладаг олон програмын хэрэгслүүд аль хэдийн байгаа бөгөөд энэ нийтлэлд бид номын сан дээр үндэслэн өөрийн профайл үүсгэх хэрэгслийг хэрхэн бичих талаар авч үзэх болно. PythonBCC. Нийтлэлийг бодит үйл явдлууд дээр үндэслэсэн болно. Бид одоо байгаа хэрэгслүүдийг тодорхой нөхцөл байдалд хэрхэн ашиглаж болохыг харуулахын тулд асуудлаас засч залруулах болно.

Цеф удаан

Ceph кластерт шинэ хост нэмэгдэв. Зарим өгөгдлийг түүн рүү шилжүүлсний дараа түүний бичих хүсэлтийг боловсруулах хурд бусад серверүүдээс хамаагүй бага байгааг бид анзаарсан.

Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл
Бусад платформуудаас ялгаатай нь энэ хост нь bcache болон шинэ Linux 4.15 цөмийг ашигласан. Энэ тохиргооны хостыг энд анх удаа ашигласан. Тэгээд тэр мөчид асуудлын үндэс нь онолын хувьд юу ч байж болох нь тодорхой болсон.

Хөтлөгчийг шалгаж байна

Ceph-osd процессын дотор юу болж байгааг харж эхэлцгээе. Үүний тулд бид ашиглах болно perf и фламскоп (энэ талаар дэлгэрэнгүй уншиж болно энд):

Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл
Зураг нь функцийг бидэнд хэлдэг fdatasync() функцууд руу хүсэлт илгээхэд маш их цаг зарцуулсан ерөнхий_хийх_хүсэлт(). Энэ нь асуудлын шалтгаан нь osd демоноос өөр газар байж магадгүй гэсэн үг юм. Энэ нь цөм эсвэл диск байж болно. Iostat гаралт нь bcache дискээр хүсэлтийг боловсруулахад их хоцролттой байгааг харуулсан.

Хостыг шалгаж үзэхэд systemd-udevd демон нь CPU-ийн маш их цаг зарцуулдаг болохыг олж мэдсэн - хэд хэдэн цөм дээр ойролцоогоор 20%. Энэ бол хачирхалтай зан авир тул та шалтгааныг олж мэдэх хэрэгтэй. Systemd-udevd нь uevents-тэй ажилладаг тул бид тэдгээрийг нарийвчлан судлахаар шийдсэн udevadm монитор. Систем дэх блок төхөөрөмж бүрт олон тооны өөрчлөлтийн үйл явдлууд үүссэн нь харагдаж байна. Энэ бол ер бусын зүйл тул бид эдгээр бүх үйл явдлыг юу үүсгэдэгийг харах хэрэгтэй болно.

BCC Toolkit ашиглах

Бидний олж мэдсэнээр цөм (мөн системийн дуудлагын ceph дэмон) маш их цаг зарцуулдаг. ерөнхий_хийх_хүсэлт(). Энэ функцийн хурдыг хэмжихийг хичээцгээе. IN BCAC Гайхамшигтай хэрэгсэл аль хэдийн байна - функциональ байдал. Бид дэмоныг гаралтын хооронд 1 секундын интервалтайгаар PID-ээр нь мөрдөж, үр дүнг миллисекундээр гаргана.

Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл
Энэ функц нь ихэвчлэн хурдан ажилладаг. Үүний хийх зүйл бол төхөөрөмжийн драйверын дараалалд хүсэлтийг дамжуулах явдал юм.

Bcache Энэ нь үнэндээ гурван дискээс бүрддэг нарийн төвөгтэй төхөөрөмж юм:

  • нөөц төхөөрөмж (кэштэй диск), энэ тохиолдолд энэ нь удаан HDD юм;
  • кэш төхөөрөмж (кэш хийх диск), энд NVMe төхөөрөмжийн нэг хэсэг байна;
  • програм ажиллаж байгаа bcache виртуал төхөөрөмж.

Хүсэлт дамжуулах удаашралтай гэдгийг бид мэднэ, гэхдээ эдгээр төхөөрөмжүүдийн аль нь вэ? Бид үүнийг хэсэг хугацааны дараа шийдвэрлэх болно.

Үйл явдал нь асуудал үүсгэж болзошгүйг бид одоо мэдэж байна. Тэдний үүслийн шалтгааныг олох нь тийм ч амар биш юм. Энэ бол үе үе гарч ирдэг програм хангамж юм гэж бодъё. Скрипт ашиглан ямар төрлийн програм хангамж систем дээр ажиллаж байгааг харцгаая execsnoop ижилээс BCC хэрэгслийн иж бүрдэл. Үүнийг ажиллуулаад гаралтыг файл руу илгээцгээе.

Жишээ нь иймэрхүү:

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

Бид execsnoop-ийн бүрэн гаралтыг энд харуулахгүй, гэхдээ бидний сонирхсон нэг хэсэг нь дараах байдалтай харагдсан:

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

Гурав дахь багана нь процессын PPID (эцэг эх PID) юм. PID 5802-тэй үйл явц нь манай хяналтын системийн нэг сэдвийн нэг болсон. Хяналтын системийн тохиргоог шалгахад алдаатай параметрүүд олдсон. HBA адаптерийн температурыг 30 секунд тутамд хэмждэг байсан бөгөөд энэ нь шаардлагатай хэмжээнээс хамаагүй их байдаг. Шалгах интервалыг урт болгон өөрчилсний дараа энэ хост дээрх хүсэлтийг боловсруулах хоцрогдол бусад хостуудтай харьцуулахад онцгой байхаа больсон.

Гэхдээ bcache төхөөрөмж яагаад ийм удаан байсан нь тодорхойгүй хэвээр байна. Бид ижил тохиргоотой туршилтын платформ бэлтгэсэн бөгөөд bcache дээр fio ажиллуулж, үе үе үүсгэхийн тулд udevadm триггерийг ажиллуулж асуудлыг дахин гаргахыг оролдсон.

BCC-д суурилсан хэрэгслүүд бичих

Хамгийн удаан дуудлагыг хянах, харуулах энгийн хэрэгсэл бичихийг хичээцгээе ерөнхий_хийх_хүсэлт(). Мөн бид энэ функцийг дуудсан дискний нэрийг сонирхож байна.

Төлөвлөгөө нь энгийн:

  • Бүртгүүлэх kprobe тухай ерөнхий_хийх_хүсэлт():
    • Бид дискний нэрийг санах ойд хадгалдаг бөгөөд функцийн аргументаар дамжуулан хандах боломжтой;
    • Бид цагийн тэмдгийг хадгалдаг.

  • Бүртгүүлэх кретпроб -аас буцах зорилгоор ерөнхий_хийх_хүсэлт():
    • Бид одоогийн цагийн тэмдгийг авдаг;
    • Бид хадгалсан цагийн тэмдгийг хайж, одоогийнхтой харьцуулдаг;
    • Хэрэв үр дүн нь заасан хэмжээнээс их байвал бид хадгалсан дискний нэрийг олж, терминал дээр харуулна.

Kprobes и кретпробууд Функцийн кодыг шууд өөрчлөхийн тулд таслах цэгийн механизмыг ашиглана уу. Та уншиж болно баримт бичиг и сайн энэ сэдвээр нийтлэл. Хэрэв та янз бүрийн хэрэгслийн кодыг харвал BCAC, тэгвэл тэд ижил бүтэцтэй болохыг харж болно. Тиймээс энэ нийтлэлд бид скриптийн аргументуудыг задлан шинжлэхийг алгасаад BPF програм руу шилжих болно.

Питон скрипт доторх eBPF текст дараах байдалтай байна.

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

Функцуудын хооронд өгөгдөл солилцохын тулд eBPF програмуудыг ашигладаг хэш хүснэгтүүд. Бид ч мөн адил хийх болно. Бид процессын PID-г түлхүүр болгон ашиглаж, бүтцийг дараах утга болгон тодорхойлно.

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);

Энд бид нэртэй хэш хүснэгтийг бүртгэдэг p, түлхүүрийн төрөлтэй u64 мөн төрлийн утга бүтэц өгөгдөл_t. Хүснэгтийг манай BPF хөтөлбөрийн хүрээнд авах боломжтой. BPF_PERF_OUTPUT макро нь өөр нэртэй хүснэгтийг бүртгэдэг үйл явдал, үүнд ашиглагддаг өгөгдөл дамжуулах хэрэглэгчийн орон зайд.

Функцийг дуудах, түүнээс буцах, эсвэл өөр функц руу залгах хоорондох саатлыг хэмжихдээ хүлээн авсан өгөгдөл нь ижил контекст хамаарах ёстой гэдгийг анхаарч үзэх хэрэгтэй. Өөрөөр хэлбэл, функцуудыг зэрэгцээ ажиллуулах боломжтой гэдгийг санах хэрэгтэй. Бид нэг үйл явцын хүрээнд функцийг дуудах болон өөр процессын хүрээнд тухайн функцээс буцаж ирэх хоцролтыг хэмжих чадвартай боловч энэ нь ашиггүй байх магадлалтай. Сайн жишээ энд байх болно биолатентийн хэрэгсэл, хэш хүснэгтийн түлхүүрийг заагч болгон тохируулсан бүтцийн хүсэлт, энэ нь нэг дискний хүсэлтийг тусгасан.

Дараа нь бид судалж буй функцийг дуудах үед ажиллах кодыг бичих хэрэгтэй.

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);
}

Энд дуудагдсан функцийн эхний аргументыг хоёр дахь аргумент болгон орлуулах болно ерөнхий_хийх_хүсэлт(). Үүний дараа бид ажиллаж байгаа үйл явцын PID-г, наносекундэд одоогийн цагийн тэмдгийг авдаг. Бид бүгдийг шинээр сонгогдсон хэлбэрээр бичдэг бүтцийн data_t өгөгдөл. Бид бүтцээс дискний нэрийг авдаг био, дуудлага хийх үед дамжуулдаг ерөнхий_хийх_хүсэлт(), мөн ижил бүтцэд хадгална мэдээ. Сүүлийн алхам бол өмнө дурдсан хэш хүснэгтэд оруулга нэмэх явдал юм.

Дараах функц буцаж ирэхэд дуудагдах болно ерөнхий_хийх_хүсэлт():

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);
    }
}

Энэ функц нь өмнөхтэй төстэй: бид процессын PID болон цагийн тэмдгийг олж мэдэх боловч шинэ өгөгдлийн бүтцэд санах ойг хуваарилдаггүй. Үүний оронд бид хэш хүснэгтээс == одоогийн PID түлхүүрийг ашиглан аль хэдийн байгаа бүтцийг хайдаг. Хэрэв бүтэц олдвол бид ажиллаж байгаа процессын нэрийг олж, түүнд нэмнэ.

Энд бидний ашигладаг хоёртын шилжилт нь thread GID-г авахад хэрэгтэй. тэдгээр. Бидний ажиллаж буй контекст дэх хэлхээг эхлүүлсэн үндсэн процессын PID. Бидний дууддаг функц bpf_get_current_pid_tgid() нь урсгалын GID болон түүний PID хоёуланг нь 64 битийн нэг утгаар буцаана.

Терминал руу гаргахдаа бид одоогоор утсыг сонирхохгүй байгаа ч үндсэн процессыг сонирхож байна. Үүссэн саатлыг өгөгдсөн босготой харьцуулсны дараа бид бүтцээ дамжуулдаг мэдээ хүснэгтээр дамжуулан хэрэглэгчийн орон зайд үйл явдал, үүний дараа бид оруулгыг устгана p.

Энэ кодыг ачаалах python скрипт дээр бид MIN_US болон FACTOR-ыг саатлын босго болон хугацааны нэгжээр солих шаардлагатай бөгөөд бид аргументуудыг дамжуулна.

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"

Одоо бид BPF хөтөлбөрийг дамжуулан бэлтгэх хэрэгтэй BPF макро болон дээжийг бүртгэх:

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")

Бид бас тодорхойлох хэрэгтэй болно бүтэц өгөгдөл_t Манай скрипт дээр, эс тэгвээс бид юу ч унших боломжгүй болно:

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)]

Сүүлийн алхам бол өгөгдлийг терминал руу гаргах явдал юм.

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()

Скриптийг өөрөө эндээс авах боломжтой GItHub. Үүнийг fio ажиллаж байгаа тестийн платформ дээр ажиллуулж, bcache руу бичиж, udevadm monitor гэж дуудъя:

Өндөр Цефийн хоцролтоос eBPF/BCC ашиглан цөмийн нөхөөс хүртэл
Эцэст нь! Одоо бид зогссон bcache төхөөрөмж шиг харагдаж байсан зүйл нь үнэндээ зогссон дуудлага болохыг харж байна ерөнхий_хийх_хүсэлт() кэштэй дискний хувьд.

Цөм рүү ухах

Хүсэлт дамжуулах явцад яг юу удааширч байна вэ? Хүсэлтийн нягтлан бодох бүртгэл эхлэхээс өмнө саатал гарч байгааг бид харж байна. үүнтэй холбоотой статистик мэдээллийг (/proc/diskstats эсвэл iostat) цаашид гаргах тусгай хүсэлтийн бүртгэл хараахан эхлээгүй байна. Асуудлыг дахин үүсгэх үед iostat ажиллуулснаар үүнийг хялбархан шалгаж болно, эсвэл BCC скриптийн биологийн нууцлал, энэ нь хүсэлтийн нягтлан бодох бүртгэлийн эхлэл ба төгсгөлд үндэслэсэн. Эдгээр хэрэгслүүдийн аль нь ч кэшд хадгалагдсан дискэнд хүсэлт гаргахад асуудал харуулахгүй.

Хэрэв бид функцийг харвал ерөнхий_хийх_хүсэлт(), дараа нь хүсэлтийг нягтлан бодох бүртгэл эхлэхээс өмнө өөр хоёр функц дуудагдахыг бид харах болно. Эхлээд - ерөнхий_хүсэлтийн_шалгалт(), дискний тохиргоотой холбоотой хүсэлтийн хууль ёсны эсэхийг шалгадаг. Хоёрдугаарт - blk_queue_enter(), энэ нь сонирхолтой сорилттой хүлээх_үйл_ тасалдал():

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));

Үүний дотор цөм нь дарааллыг тайлахыг хүлээнэ. Хоцролтыг хэмжиж үзье 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    	|                                    	|

Бид шийдэлд ойртсон бололтой. Дарааллыг хөлдөөх/тайлахад ашигладаг функцууд нь blk_mq_freeze_queue и blk_mq_unfreeze_queue. Эдгээрийг хүсэлтийн дарааллын тохиргоог өөрчлөх шаардлагатай үед ашигладаг бөгөөд энэ нь энэ дарааллын хүсэлтэд аюултай байж болзошгүй юм. Дуудлага хийх үед blk_mq_freeze_queue() функц blk_freeze_queue_start() тоолуур нэмэгдсэн байна q->mq_хөлдөлтийн_гүн. Үүний дараа цөм дарааллыг хоослохыг хүлээнэ blk_mq_freeze_queue_wait().

Цөм дараалалд орсон бүх үйлдлүүдийг дуусгахыг хүлээж байгаа тул энэ дарааллыг арилгахад шаардагдах хугацаа нь дискний хоцролттой тэнцүү байна. Дараалал хоосон болмогц тохиргооны өөрчлөлтүүд хэрэгжинэ. Үүний дараа үүнийг дууддаг blk_mq_unfreeze_queue(), тоолуурыг багасгах хөлдөх_гүн.

Одоо бид нөхцөл байдлыг засах хангалттай зүйлийг мэдэж байна. Udevadm триггер команд нь блок төхөөрөмжийн тохиргоог ашиглахад хүргэдэг. Эдгээр тохиргоог udev дүрэмд тайлбарласан болно. Бид sysfs-ээр дамжуулан өөрчлөхийг оролдох эсвэл цөмийн эх кодыг харах замаар дарааллыг царцааж буй тохиргоог олж мэдэх боломжтой. Бид мөн BCC хэрэгслийг туршиж үзэж болно ул мөр, энэ нь терминал руу залгах бүрд цөм болон хэрэглэгчийн зайны стекийн ул мөрийг гаргах болно blk_freeze_queueЖишээ нь:

~# /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 үйл явдал үүсгэх нь тийм ч сайн туршлага биш юм. Гэсэн хэдий ч бид цөмд шаардлагагүй ажил хийхгүй байх, шаардлагагүй бол хүсэлтийн дарааллыг царцаахад тусалж чадна. Гурав жижиг хийх нөхцөл байдлыг засах.

Дүгнэлт

eBPF бол маш уян хатан, хүчирхэг хэрэгсэл юм. Нийтлэлд бид нэг практик тохиолдлыг авч үзээд юу хийж болох талаар багахан хэсгийг харуулсан. Хэрэв та BCC хэрэгслүүдийг хөгжүүлэх сонирхолтой байгаа бол үүнийг анхаарч үзэх нь зүйтэй албан ёсны заавар, энэ нь үндсийг сайн дүрсэлсэн.

eBPF дээр суурилсан бусад сонирхолтой дибаг хийх, профайл хийх хэрэгслүүд байдаг. Тэдний нэг - bpftrace, энэ нь танд awk-тэй төстэй хэлээр хүчирхэг нэг давхар, жижиг программ бичих боломжийг олгодог. Өөр нэг - ebpf_exporter, бага түвшний, өндөр нарийвчлалтай хэмжигдэхүүнүүдийг өөрийн прометей серверт шууд цуглуулах боломжийг олгодог бөгөөд дараа нь үзэсгэлэнтэй дүрслэл, тэр байтугай сэрэмжлүүлэг авах боломжтой.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх