Линукс нь цөм болон програмуудыг дибаг хийх олон тооны хэрэгсэлтэй. Тэдгээрийн ихэнх нь хэрэглээний гүйцэтгэлд сөргөөр нөлөөлдөг тул үйлдвэрлэлд ашиглах боломжгүй байдаг.
Хэдэн жилийн өмнө байсан
eBPF ашигладаг олон програмын хэрэгслүүд аль хэдийн байгаа бөгөөд энэ нийтлэлд бид номын сан дээр үндэслэн өөрийн профайл үүсгэх хэрэгслийг хэрхэн бичих талаар авч үзэх болно.
Цеф удаан
Ceph кластерт шинэ хост нэмэгдэв. Зарим өгөгдлийг түүн рүү шилжүүлсний дараа түүний бичих хүсэлтийг боловсруулах хурд бусад серверүүдээс хамаагүй бага байгааг бид анзаарсан.
Бусад платформуудаас ялгаатай нь энэ хост нь bcache болон шинэ Linux 4.15 цөмийг ашигласан. Энэ тохиргооны хостыг энд анх удаа ашигласан. Тэгээд тэр мөчид асуудлын үндэс нь онолын хувьд юу ч байж болох нь тодорхой болсон.
Хөтлөгчийг шалгаж байна
Ceph-osd процессын дотор юу болж байгааг харж эхэлцгээе. Үүний тулд бид ашиглах болно
Зураг нь функцийг бидэнд хэлдэг fdatasync() функцууд руу хүсэлт илгээхэд маш их цаг зарцуулсан ерөнхий_хийх_хүсэлт(). Энэ нь асуудлын шалтгаан нь osd демоноос өөр газар байж магадгүй гэсэн үг юм. Энэ нь цөм эсвэл диск байж болно. Iostat гаралт нь bcache дискээр хүсэлтийг боловсруулахад их хоцролттой байгааг харуулсан.
Хостыг шалгаж үзэхэд systemd-udevd демон нь CPU-ийн маш их цаг зарцуулдаг болохыг олж мэдсэн - хэд хэдэн цөм дээр ойролцоогоор 20%. Энэ бол хачирхалтай зан авир тул та шалтгааныг олж мэдэх хэрэгтэй. Systemd-udevd нь uevents-тэй ажилладаг тул бид тэдгээрийг нарийвчлан судлахаар шийдсэн udevadm монитор. Систем дэх блок төхөөрөмж бүрт олон тооны өөрчлөлтийн үйл явдлууд үүссэн нь харагдаж байна. Энэ бол ер бусын зүйл тул бид эдгээр бүх үйл явдлыг юу үүсгэдэгийг харах хэрэгтэй болно.
BCC Toolkit ашиглах
Бидний олж мэдсэнээр цөм (мөн системийн дуудлагын ceph дэмон) маш их цаг зарцуулдаг. ерөнхий_хийх_хүсэлт(). Энэ функцийн хурдыг хэмжихийг хичээцгээе. IN
Энэ функц нь ихэвчлэн хурдан ажилладаг. Үүний хийх зүйл бол төхөөрөмжийн драйверын дараалалд хүсэлтийг дамжуулах явдал юм.
Bcache Энэ нь үнэндээ гурван дискээс бүрддэг нарийн төвөгтэй төхөөрөмж юм:
- нөөц төхөөрөмж (кэштэй диск), энэ тохиолдолд энэ нь удаан HDD юм;
- кэш төхөөрөмж (кэш хийх диск), энд NVMe төхөөрөмжийн нэг хэсэг байна;
- програм ажиллаж байгаа bcache виртуал төхөөрөмж.
Хүсэлт дамжуулах удаашралтай гэдгийг бид мэднэ, гэхдээ эдгээр төхөөрөмжүүдийн аль нь вэ? Бид үүнийг хэсэг хугацааны дараа шийдвэрлэх болно.
Үйл явдал нь асуудал үүсгэж болзошгүйг бид одоо мэдэж байна. Тэдний үүслийн шалтгааныг олох нь тийм ч амар биш юм. Энэ бол үе үе гарч ирдэг програм хангамж юм гэж бодъё. Скрипт ашиглан ямар төрлийн програм хангамж систем дээр ажиллаж байгааг харцгаая execsnoop ижилээс
Жишээ нь иймэрхүү:
/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 и кретпробууд Функцийн кодыг шууд өөрчлөхийн тулд таслах цэгийн механизмыг ашиглана уу. Та уншиж болно
Питон скрипт доторх eBPF текст дараах байдалтай байна.
bpf_text = “”” # Here will be the bpf program code “””
Функцуудын хооронд өгөгдөл солилцохын тулд eBPF програмуудыг ашигладаг
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);
}
Энд дуудагдсан функцийн эхний аргументыг хоёр дахь аргумент болгон орлуулах болно
Дараах функц буцаж ирэхэд дуудагдах болно ерөнхий_хийх_хүсэлт():
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. Бидний дууддаг функц
Терминал руу гаргахдаа бид одоогоор утсыг сонирхохгүй байгаа ч үндсэн процессыг сонирхож байна. Үүссэн саатлыг өгөгдсөн босготой харьцуулсны дараа бид бүтцээ дамжуулдаг мэдээ хүснэгтээр дамжуулан хэрэглэгчийн орон зайд үйл явдал, үүний дараа бид оруулгыг устгана 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 хөтөлбөрийг дамжуулан бэлтгэх хэрэгтэй
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()
Скриптийг өөрөө эндээс авах боломжтой
Эцэст нь! Одоо бид зогссон bcache төхөөрөмж шиг харагдаж байсан зүйл нь үнэндээ зогссон дуудлага болохыг харж байна ерөнхий_хийх_хүсэлт() кэштэй дискний хувьд.
Цөм рүү ухах
Хүсэлт дамжуулах явцад яг юу удааширч байна вэ? Хүсэлтийн нягтлан бодох бүртгэл эхлэхээс өмнө саатал гарч байгааг бид харж байна. үүнтэй холбоотой статистик мэдээллийг (/proc/diskstats эсвэл iostat) цаашид гаргах тусгай хүсэлтийн бүртгэл хараахан эхлээгүй байна. Асуудлыг дахин үүсгэх үед iostat ажиллуулснаар үүнийг хялбархан шалгаж болно, эсвэл
Хэрэв бид функцийг харвал ерөнхий_хийх_хүсэлт(), дараа нь хүсэлтийг нягтлан бодох бүртгэл эхлэхээс өмнө өөр хоёр функц дуудагдахыг бид харах болно. Эхлээд - ерөнхий_хүсэлтийн_шалгалт(), дискний тохиргоотой холбоотой хүсэлтийн хууль ёсны эсэхийг шалгадаг. Хоёрдугаарт -
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 | |
Бид шийдэлд ойртсон бололтой. Дарааллыг хөлдөөх/тайлахад ашигладаг функцууд нь
Цөм дараалалд орсон бүх үйлдлүүдийг дуусгахыг хүлээж байгаа тул энэ дарааллыг арилгахад шаардагдах хугацаа нь дискний хоцролттой тэнцүү байна. Дараалал хоосон болмогц тохиргооны өөрчлөлтүүд хэрэгжинэ. Үүний дараа үүнийг дууддаг
Одоо бид нөхцөл байдлыг засах хангалттай зүйлийг мэдэж байна. Udevadm триггер команд нь блок төхөөрөмжийн тохиргоог ашиглахад хүргэдэг. Эдгээр тохиргоог udev дүрэмд тайлбарласан болно. Бид sysfs-ээр дамжуулан өөрчлөхийг оролдох эсвэл цөмийн эх кодыг харах замаар дарааллыг царцааж буй тохиргоог олж мэдэх боломжтой. Бид мөн BCC хэрэгслийг туршиж үзэж болно
~# /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 дээр суурилсан бусад сонирхолтой дибаг хийх, профайл хийх хэрэгслүүд байдаг. Тэдний нэг -
Эх сурвалж: www.habr.com