Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC

Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC

Linux ina idadi kubwa ya zana za kurekebisha kernel na programu. Wengi wao wana athari mbaya juu ya utendaji wa programu na hawawezi kutumika katika uzalishaji.

Miaka michache iliyopita kulikuwa chombo kingine kimetengenezwa - eBPF. Inafanya uwezekano wa kufuatilia kernel na programu za mtumiaji kwa uendeshaji wa chini na bila ya haja ya kujenga upya programu na kupakia moduli za tatu kwenye kernel.

Tayari kuna huduma nyingi za matumizi zinazotumia eBPF, na katika nakala hii tutaangalia jinsi ya kuandika matumizi yako ya wasifu kulingana na maktaba. PythonBCC. Nakala hiyo inategemea matukio halisi. Tutatoka kwenye tatizo hadi kurekebisha ili kuonyesha jinsi huduma zilizopo zinaweza kutumika katika hali mahususi.

Ceph ni polepole

Mwenyeji mpya ameongezwa kwenye nguzo ya Ceph. Baada ya kuhamisha baadhi ya data kwake, tuliona kwamba kasi ya usindikaji wa maombi ya kuandika nayo ilikuwa chini sana kuliko kwenye seva nyingine.

Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC
Tofauti na majukwaa mengine, mwenyeji huyu alitumia bcache na linux 4.15 kernel mpya. Hii ilikuwa mara ya kwanza kwa seva nyingi za usanidi huu kutumika hapa. Na wakati huo ilikuwa wazi kwamba mzizi wa tatizo unaweza kinadharia kuwa chochote.

Kuchunguza Mwenyeji

Wacha tuanze kwa kuangalia kile kinachotokea ndani ya mchakato wa ceph-osd. Kwa hili tutatumia perf ΠΈ flamescope (zaidi ambayo unaweza kusoma hapa):

Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC
Picha inatuambia kwamba kazi fdatasync() alitumia muda mwingi kutuma ombi kwa vipengele generic_make_request(). Hii ina maana kwamba uwezekano mkubwa sababu ya matatizo ni mahali fulani nje ya osd daemon yenyewe. Hii inaweza kuwa kernel au diski. Matokeo ya iostat yalionyesha utulivu wa hali ya juu katika usindikaji wa maombi na diski za bcache.

Wakati wa kuangalia seva pangishi, tuligundua kuwa daemon ya systemd-udevd hutumia wakati mwingi wa CPU - karibu 20% kwenye cores kadhaa. Hii ni tabia ya kushangaza, kwa hivyo unahitaji kujua kwanini. Kwa kuwa Systemd-udevd inafanya kazi na matukio, tuliamua kuyaangalia udevadm monitor. Inatokea kwamba idadi kubwa ya matukio ya mabadiliko yalitolewa kwa kila kifaa cha kuzuia katika mfumo. Hili ni jambo lisilo la kawaida, kwa hivyo tutalazimika kuangalia ni nini kinachozalisha matukio haya yote.

Kwa kutumia BCC Toolkit

Kama ambavyo tumegundua tayari, kernel (na ceph daemon kwenye simu ya mfumo) hutumia muda mwingi ndani. generic_make_request(). Hebu jaribu kupima kasi ya kazi hii. KATIKA BCC Tayari kuna matumizi mazuri - funclatency. Tutafuatilia daemoni kwa PID yake kwa muda wa sekunde 1 kati ya matokeo na kutoa matokeo katika milisekunde.

Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC
Kipengele hiki kawaida hufanya kazi haraka. Inachofanya ni kupitisha ombi kwa foleni ya kiendeshi cha kifaa.

Bcache ni kifaa changamano ambacho kina diski tatu:

  • kifaa cha kuunga mkono (diski iliyohifadhiwa), katika kesi hii ni HDD ya polepole;
  • kifaa cha caching (caching disk), hapa hii ni sehemu moja ya kifaa cha NVMe;
  • kifaa cha bcache ambacho programu huendesha.

Tunajua kwamba utumaji ombi ni wa polepole, lakini ni kwa kifaa gani kati ya hivi? Tutashughulikia hili baadaye kidogo.

Sasa tunajua kuwa matukio yanaweza kusababisha matatizo. Kupata ni nini hasa husababisha kizazi chao sio rahisi sana. Hebu tuchukulie kwamba hii ni aina fulani ya programu ambayo inazinduliwa mara kwa mara. Wacha tuone ni aina gani ya programu inayoendesha kwenye mfumo kwa kutumia hati execsnoop kutoka sawa Seti ya matumizi ya BCC. Wacha tuiendeshe na tutume matokeo kwa faili.

Kwa mfano kama hii:

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

Hatutaonyesha matokeo kamili ya execsnoop hapa, lakini safu moja ya kupendeza kwetu ilionekana kama hii:

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

Safu ya tatu ni PPID (PID mzazi) ya mchakato. Mchakato wa PID 5802 uligeuka kuwa moja ya nyuzi za mfumo wetu wa ufuatiliaji. Wakati wa kuangalia usanidi wa mfumo wa ufuatiliaji, vigezo vya makosa vilipatikana. Joto la adapta ya HBA ilichukuliwa kila sekunde 30, ambayo ni mara nyingi zaidi kuliko lazima. Baada ya kubadilisha muda wa kuangalia hadi mrefu zaidi, tuligundua kuwa muda wa kusubiri kuchakata ombi kwenye seva pangishi hii haukuwa tofauti tena ikilinganishwa na wapangishi wengine.

Lakini bado haijulikani kwa nini kifaa cha bcache kilikuwa polepole sana. Tulitayarisha jukwaa la majaribio lenye usanidi sawa na kujaribu kuzaliana tatizo kwa kutumia fio kwenye bcache, mara kwa mara tukiendesha udevadm trigger ili kuzalisha matukio.

Kuandika Vyombo vinavyotokana na BCC

Hebu tujaribu kuandika matumizi rahisi ya kufuatilia na kuonyesha simu zinazopiga polepole zaidi generic_make_request(). Pia tunavutiwa na jina la gari ambalo kazi hii iliitwa.

Mpango ni rahisi:

  • Sajili kprobe juu ya generic_make_request():
    • Tunahifadhi jina la disk kwenye kumbukumbu, kupatikana kwa njia ya hoja ya kazi;
    • Tunahifadhi muhuri wa muda.

  • Sajili kretprobe kwa kurudi kutoka generic_make_request():
    • Tunapata muhuri wa wakati wa sasa;
    • Tunatafuta muhuri wa muda uliohifadhiwa na kulinganisha na wa sasa;
    • Ikiwa matokeo ni makubwa zaidi kuliko yaliyotajwa, basi tunapata jina la disk iliyohifadhiwa na kuionyesha kwenye terminal.

Kprobes ΠΈ kretprobes tumia njia ya kuvunja ili kubadilisha msimbo wa utendakazi kwenye nzi. Unaweza kusoma nyaraka ΠΈ nzuri makala juu ya mada hii. Ukiangalia kanuni za huduma mbalimbali katika BCC, basi unaweza kuona kwamba wana muundo unaofanana. Kwa hivyo katika nakala hii tutaruka hoja za hati na kuendelea na programu ya BPF yenyewe.

Maandishi ya eBPF ndani ya hati ya python inaonekana kama hii:

bpf_text = β€œβ€β€ # Here will be the bpf program code β€œβ€β€

Ili kubadilishana data kati ya utendaji, programu za eBPF hutumia meza za hashi. Tutafanya vivyo hivyo. Tutatumia mchakato wa PID kama ufunguo, na kufafanua muundo kama thamani:

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

Hapa tunasajili meza ya hashi inayoitwa p, na aina muhimu u64 na thamani ya aina muundo data_t. Jedwali litapatikana katika muktadha wa programu yetu ya BPF. Jumla ya BPF_PERF_OUTPUT husajili jedwali lingine linaloitwa matukio, ambayo inatumika kwa usambazaji wa data kwenye nafasi ya mtumiaji.

Wakati wa kupima ucheleweshaji kati ya kupiga simu na kurudi kutoka kwake, au kati ya simu kwa vitendaji tofauti, unahitaji kuzingatia kwamba data iliyopokelewa lazima iwe ya muktadha sawa. Kwa maneno mengine, unahitaji kukumbuka juu ya uzinduzi unaowezekana wa kazi. Tuna uwezo wa kupima muda wa kusubiri kati ya kuita chaguo za kukokotoa katika muktadha wa mchakato mmoja na kurudi kutoka kwa kazi hiyo katika muktadha wa mchakato mwingine, lakini hii inawezekana haina maana. Mfano mzuri ungekuwa hapa matumizi ya biolatency, ambapo ufunguo wa jedwali la hashi umewekwa kwa pointer ombi la muundo, ambayo inaonyesha ombi moja la diski.

Ifuatayo, tunahitaji kuandika nambari ambayo itafanya kazi wakati kazi inayosomwa inaitwa:

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

Hapa hoja ya kwanza ya kitendakazi kinachoitwa itabadilishwa kuwa hoja ya pili generic_make_request(). Baada ya hayo, tunapata PID ya mchakato katika muktadha ambao tunafanya kazi, na muhuri wa muda wa sasa katika nanoseconds. Tunaandika yote katika toleo jipya lililochaguliwa data_t muundo. Tunapata jina la diski kutoka kwa muundo bio, ambayo hupitishwa wakati wa kupiga simu generic_make_request(), na uihifadhi katika muundo sawa data. Hatua ya mwisho ni kuongeza kiingilio kwenye jedwali la hashi ambalo lilitajwa hapo awali.

Chaguo za kukokotoa zifuatazo zitaitwa unaporudi kutoka 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);
    }
}

Kazi hii ni sawa na ile ya awali: tunapata PID ya mchakato na muhuri wa muda, lakini usitenge kumbukumbu kwa muundo mpya wa data. Badala yake, tunatafuta jedwali la hashi kwa muundo uliopo tayari kwa kutumia kitufe == PID ya sasa. Ikiwa muundo unapatikana, basi tunapata jina la mchakato wa kukimbia na kuiongeza.

Mabadiliko ya binary tunayotumia hapa inahitajika kupata thread GID. hizo. PID ya mchakato mkuu ulioanzisha uzi katika muktadha ambao tunafanyia kazi. kazi sisi wito bpf_get_current_pid_tgid() inarudisha GID ya nyuzi na PID yake kwa thamani moja ya 64-bit.

Wakati wa kutoa kwa terminal, hatuvutiwi na uzi kwa sasa, lakini tunavutiwa na mchakato kuu. Baada ya kulinganisha ucheleweshaji unaosababishwa na kizingiti fulani, tunapitisha muundo wetu data kwenye nafasi ya mtumiaji kupitia meza matukio, baada ya hapo tunafuta kiingilio kutoka p.

Katika hati ya chatu ambayo itapakia nambari hii, tunahitaji kubadilisha MIN_US na FACTOR na vizingiti vya kuchelewa na vitengo vya wakati, ambavyo tutapitia hoja:

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"

Sasa tunahitaji kuandaa mpango wa BPF kupitia BPF jumla na sampuli za usajili:

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

Pia tutalazimika kuamua muundo data_t katika hati yetu, vinginevyo hatutaweza kusoma chochote:

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

Hatua ya mwisho ni kutoa data kwa terminal:

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

Hati yenyewe inapatikana kwa GIHub. Wacha tujaribu kuiendesha kwenye jukwaa la majaribio ambapo fio inafanya kazi, ikiandika kwa bcache, na piga simu udevadm monitor:

Kutoka High Ceph Latency hadi Kernel Patch kwa kutumia eBPF/BCC
Hatimaye! Sasa tunaona kwamba kile kilichoonekana kama kifaa cha bcache kinachosimama ni simu inayositishwa generic_make_request() kwa diski iliyohifadhiwa.

Chimba kwenye Kernel

Ni nini hasa kinachopungua wakati wa kutuma ombi? Tunaona kwamba kuchelewa hutokea hata kabla ya kuanza kwa uhasibu wa ombi, i.e. uhasibu wa ombi maalum la matokeo zaidi ya takwimu juu yake (/proc/diskstats au iostat) bado haujaanza. Hii inaweza kuthibitishwa kwa urahisi kwa kuendesha iostat wakati wa kuzalisha tatizo, au Biolatency ya maandishi ya BCC, ambayo inategemea mwanzo na mwisho wa uhasibu wa ombi. Hakuna hata moja ya huduma hizi itaonyesha matatizo kwa maombi kwa diski iliyohifadhiwa.

Ikiwa tunaangalia kazi generic_make_request(), basi tutaona kwamba kabla ya ombi kuanza uhasibu, kazi mbili zaidi zinaitwa. Kwanza - generic_make_request_checks(), hufanya hundi juu ya uhalali wa ombi kuhusu mipangilio ya disk. Pili - blk_queue_enter(), ambayo ina changamoto ya kuvutia 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));

Ndani yake, kernel inasubiri foleni ili kufungia. Hebu tupime kuchelewa 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    	|                                    	|

Inaonekana tunakaribia suluhu. Vipengele vinavyotumika kufungia/kusimamisha foleni ni blk_mq_kufungia_foleni ΠΈ blk_mq_unfreeze_foleni. Zinatumika inapohitajika kubadilisha mipangilio ya foleni ya ombi, ambayo inaweza kuwa hatari kwa maombi katika foleni hii. Wakati wa kupiga simu blk_mq_freeze_foleni() kazi blk_freeze_queue_start() counter ni incremented q->mq_kuganda_kina. Baada ya hayo, punje hungoja foleni kumwaga ndani blk_mq_freeze_queue_wait().

Muda unaochukua kufuta foleni hii ni sawa na muda wa kusubiri wa diski kwani kernel inasubiri shughuli zote zilizowekwa kwenye foleni zikamilike. Mara foleni inapokuwa tupu, mabadiliko ya mipangilio yanatumika. Baada ya hapo inaitwa blk_mq_unfreeze_queue(), kupunguza kaunta kuganda_kina.

Sasa tunajua vya kutosha kurekebisha hali hiyo. Amri ya trigger ya udevadm husababisha mipangilio ya kifaa cha kuzuia kutumika. Mipangilio hii imeelezewa katika sheria za udev. Tunaweza kupata mipangilio ambayo inafungia foleni kwa kujaribu kuibadilisha kupitia sysfs au kwa kuangalia msimbo wa chanzo wa kernel. Tunaweza pia kujaribu matumizi ya BCC kuwaeleza, ambayo itatoa alama za kernel na nafasi ya mtumiaji kwa kila simu kwenye terminal foleni_ya_kufungia, kwa mfano:

~# /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]

Sheria za Udev hubadilika mara chache na kawaida hii hufanyika kwa njia iliyodhibitiwa. Kwa hivyo tunaona kwamba hata kutumia maadili yaliyowekwa tayari husababisha mwiba katika kuchelewesha kuhamisha ombi kutoka kwa programu hadi kwenye diski. Bila shaka, kuzalisha matukio ya udev wakati hakuna mabadiliko katika usanidi wa disk (kwa mfano, kifaa hakijawekwa / kukatwa) sio mazoezi mazuri. Walakini, tunaweza kusaidia kernel isifanye kazi isiyo ya lazima na kufungia foleni ya ombi ikiwa sio lazima. Tatu ndogo kujitolea rekebisha hali hiyo.

Hitimisho

eBPF ni zana inayonyumbulika sana na yenye nguvu. Katika makala tuliangalia kesi moja ya vitendo na tukaonyesha sehemu ndogo ya kile kinachoweza kufanywa. Ikiwa ungependa kuunda huduma za BCC, inafaa kutazama mafunzo rasmi, ambayo inaelezea misingi vizuri.

Kuna zana zingine za kuvutia za utatuzi na wasifu kulingana na eBPF. Mmoja wao - bpftrace, ambayo inakuwezesha kuandika mstari mmoja wenye nguvu na programu ndogo katika lugha ya awk-kama. Mwingine - ebpf_nje, hukuruhusu kukusanya vipimo vya kiwango cha chini na vya ubora wa juu moja kwa moja kwenye seva yako ya prometheus, ikiwa na uwezo wa kupata taswira nzuri na hata arifa baadaye.

Chanzo: mapenzi.com

Kuongeza maoni