High Ceph Latency тартып eBPF/BCC аркылуу ядролук патчка чейин

High Ceph Latency тартып eBPF/BCC аркылуу ядролук патчка чейин

Linux өзөктү жана тиркемелерди оңдоо үчүн көптөгөн куралдарга ээ. Алардын көбү тиркеменин иштешине терс таасирин тийгизет жана өндүрүштө колдонулушу мүмкүн эмес.

Бир-эки жыл мурун болгон дагы бир курал иштелип чыккан - eBPF. Бул өзөктү жана колдонуучу тиркемелерин аз ашыкча чыгым менен жана программаларды кайра куруунун жана ядрого үчүнчү тараптын модулдарын жүктөөнүн кереги жок байкоого мүмкүндүк берет.

eBPF колдонгон көптөгөн тиркеме утилиталары бар жана бул макалада биз китепкананын негизинде өзүңүздүн профилиңизди кантип жазууну карап чыгабыз PythonBCC. Макала реалдуу окуяларга негизделген. Биз учурдагы коммуналдык кызматтарды конкреттүү кырдаалдарда кантип колдонсо болорун көрсөтүү үчүн көйгөйдөн оңдоп чыгабыз.

Ceph жай

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

High Ceph Latency тартып eBPF/BCC аркылуу ядролук патчка чейин
Башка платформалардан айырмаланып, бул хост bcache жана жаңы Linux 4.15 ядросун колдонгон. Бул жерде бул конфигурациянын хосту биринчи жолу колдонулду. Жана ошол учурда маселенин түпкү тамыры теориялык жактан ар нерсе болушу мүмкүн экени айкын болду.

Хостту иликтөө

Келгиле, ceph-osd процессинин ичинде эмне болуп жатканын карап баштайлы. Бул үчүн биз колдонобуз перф и фламаскоп (бул тууралуу көбүрөөк окуй аласыз бул жерде):

High Ceph Latency тартып eBPF/BCC аркылуу ядролук патчка чейин
Сүрөт бул функцияны айтып берет fdatasync() функцияларга суроо-талап жөнөтүү үчүн көп убакыт коротту generic_make_request(). Бул, кыязы, көйгөйлөрдүн себеби osd демонунун өзүнөн башка жерде экенин билдирет. Бул ядро ​​же дисктер болушу мүмкүн. Иостаттын чыгарылышы bcache дисктери тарабынан суроо-талаптарды иштетүүдө жогорку кечиктирүүнү көрсөттү.

Хостту текшерүүдө биз systemd-udevd демону процессордун көп убактысын - бир нече өзөктө 20% га жакынын сарптаарын таптык. Бул кызыктай жүрүм-турум, андыктан эмне үчүн экенин билишиңиз керек. Systemd-udevd окуялар менен иштегендиктен, биз аларды карап чыгууну чечтик udevadm монитор. Бул системанын ар бир блок түзмөк үчүн өзгөртүү окуялардын көп сандагы түзүлгөн экен. Бул адаттан тыш көрүнүш, ошондуктан биз бул окуялардын бардыгын эмне жаратып жатканын карап чыгышыбыз керек.

BCC Toolkit колдонуу

Биз буга чейин билгендей, ядро ​​(жана системалык чалуудагы ceph демону) көп убакытты generic_make_request(). Келгиле, бул функциянын ылдамдыгын өлчөөгө аракет кылалы. IN BCC Буга чейин сонун пайдалуу программа бар - функлаттуулук. Биз демонду анын PID боюнча 1 секунддук интервал менен байкап, натыйжаны миллисекунд менен чыгарабыз.

High Ceph Latency тартып 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 негизиндеги куралдарды жазуу

Эң жай чалууларды көзөмөлдөө жана көрсөтүү үчүн жөнөкөй утилитаны жазууга аракет кылалы generic_make_request(). Бизди бул функция чакырылган дисктин аты да кызыктырат.

План жөнөкөй:

  • Каттоо kprobe боюнча generic_make_request():
    • Биз дисктин атын эстутумга сактайбыз, функция аргументи аркылуу жеткиликтүү;
    • Биз убакыт белгисин сактайбыз.

  • Каттоо kretprobe кайтуу үчүн generic_make_request():
    • Биз учурдагы убакыт белгисин алабыз;
    • Биз сакталган убакыт белгисин издеп, аны учурдагы менен салыштырабыз;
    • Эгерде натыйжа көрсөтүлгөндөн чоңураак болсо, анда биз сакталган дисктин атын таап, аны терминалга көрсөтөбүз.

Kprobes и kretprobes Функциянын кодун тез арада өзгөртүү үчүн токтотуу чекитинин механизмин колдонуңуз. Сиз окуй аласыз документтештирүү и жакшы бул тема боюнча макала. Эгерде сиз ар кандай утилиталардын кодун карасаңыз BCC, анда сиз алардын окшош түзүлүшкө ээ экенин көрө аласыз. Ошентип, бул макалада биз талдоо скрипт аргументтерин өткөрүп жиберип, BPF программасынын өзүнө өтөбүз.

Python скриптинин ичиндеги 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 жана түрдүн мааниси структура data_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);
}

Бул жерде чакырылган функциянын биринчи аргументи экинчи аргумент катары алмаштырылат generic_make_request(). Андан кийин биз иштеп жаткан процесстин PID кодун жана наносекунддагы учурдагы убакыт белгисин алабыз. Анын баарын жаңы тандалганга жазабыз struct data_t data. Дисктин атын структурадан алабыз био, чалуу учурунда өткөрүлөт generic_make_request(), жана аны ошол эле структурада сактаңыз маалымат. Акыркы кадам - ​​буга чейин айтылган хэш таблицага жазуу кошуу.

Төмөнкү функция кайтып келгенден кийин чакырылат 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);
    }
}

Бул функция мурункуга окшош: биз процесстин PID жана убакыт белгисин табабыз, бирок жаңы маалымат структурасы үчүн эстутумду бөлбөйбүз. Анын ордуна, == учурдагы PID ачкычын колдонуп, хэш таблицасында мурунтан эле бар структураны издейбиз. Эгерде структура табылса, анда биз иштеп жаткан процесстин атын таап, ага кошобуз.

Бул жерде биз колдонгон бинардык жылдыруу жип 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")

Биз да аныкташыбыз керек структура data_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 мониторду чакырган сыноо платформасында иштетүүгө аракет кылалы:

High Ceph Latency тартып eBPF/BCC аркылуу ядролук патчка чейин
Акыры! Эми биз токтоп калган bcache түзмөгү сыяктуу көрүнгөн нерсе чындыгында токтоп калган чалуу экенин көрүп жатабыз generic_make_request() кэштелген диск үчүн.

Ядрону казыңыз

Сурам жөнөтүү учурунда так эмне жайлатып жатат? Биз кечиктирүү суроо-талапты эсепке алуу башталганга чейин пайда болгонун көрөбүз, б.а. ал боюнча статистиканы андан ары чыгаруу үчүн атайын суроо-талапты эсепке алуу (/proc/diskstats же iostat) баштала элек. Бул көйгөйдү кайра чыгарууда iostat иштетүү менен оңой текшерилиши мүмкүн, же BCC скриптинин биолатенттиги, бул суроо-талаптын бухгалтердик эсепке алуунун башталышы жана аягына негизделген. Бул утилиталардын бири да кэштелген дискке суроо-талаптар үчүн көйгөйлөрдү көрсөтпөйт.

Функцияны карасак generic_make_request(), анда биз суроо-талап бухгалтердик эсепке алуу башталганга чейин дагы эки функциянын чакырылышын көрөбүз. Алгачкы - generic_make_request_checks(), диск орнотууларына байланыштуу суроо-талаптын мыйзамдуулугун текшерет. Экинчи - blk_queue_enter(), бул кызыктуу чакырык бар 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));

Анда ядро ​​кезектин тоңушун күтөт. Кечикти өлчөп көрөлү 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 эрежелери өтө сейрек өзгөрөт жана, адатта, бул көзөмөлдөнгөн түрдө болот. Ошентип, биз буга чейин коюлган маанилерди колдонуу да өтүнүчтү тиркемеден дискке өткөрүп берүүнүн кечиктирилишине алып келерин көрүп жатабыз. Албетте, диск конфигурациясында эч кандай өзгөрүүлөр болбогондо (мисалы, аппарат орнотулган эмес/ажыратылган эмес) udev окуяларын түзүү жакшы практика эмес. Бирок, биз ядрого керексиз иштерди жасабоого жардам бере алабыз жана эгер зарыл болбосо, суроо кезегин тоңдурабыз. үч кичинекей жасоо кырдаалды оңдоо.

жыйынтыктоо

eBPF абдан ийкемдүү жана күчтүү курал болуп саналат. Макалада биз бир практикалык ишти карап чыктык жана эмне кылса болорун бир аз бөлүгүн көрсөттүк. Эгер сиз BCC утилиталарын иштеп чыгууга кызыксаңыз, анда аны карап чыгуу керек расмий окуу куралы, бул негиздерин жакшы сүрөттөйт.

eBPF негизинде башка кызыктуу мүчүлүштүктөрдү оңдоо жана профилдөө куралдары бар. Алардын бирөөсү - bpftrace, бул сизге awk сыяктуу тилде күчтүү бир лайнерлерди жана чакан программаларды жазууга мүмкүндүк берет. Башка - ebpf_exporter, төмөнкү деңгээлдеги, жогорку чечилиштеги метрикаларды түздөн-түз прометей сервериңизге чогултууга мүмкүндүк берет, кийинчерээк кооз визуализацияларды жана ал тургай эскертүүлөрдү алуу мүмкүнчүлүгү бар.

Source: www.habr.com

Комментарий кошуу