Linuxek nukleoa eta aplikazioak arazketarako tresna ugari ditu. Gehienek eragin negatiboa dute aplikazioaren errendimenduan eta ezin dira produkzioan erabili.
Duela urte pare bat zegoen
Dagoeneko badaude eBPF erabiltzen duten aplikazio-utilitate asko, eta artikulu honetan liburutegian oinarrituta zure profil-erabilgarritasuna nola idatzi aztertuko dugu.
Ceph Motela Da
Ostalari berri bat gehitu da Ceph clusterra. Datu batzuk horretara migratu ondoren, ohartu gara haren idazketa-eskaerak prozesatzeko abiadura beste zerbitzarietan baino askoz txikiagoa zela.
Beste plataforma batzuek ez bezala, ostalari honek bcache eta linux 4.15 kernel berria erabiltzen zituen. Konfigurazio honen ostalari bat hemen erabiltzen zen lehen aldia izan zen. Eta momentu horretan argi zegoen arazoaren erroa teorikoki edozer izan zitekeela.
Ostalaria ikertzen
Has gaitezen zer gertatzen den ceph-osd prozesuaren barruan. Horretarako erabiliko dugu
Irudiak funtzioa esaten digu fdatasync() denbora asko eman zuen eskaera bat funtzioetara bidaltzen generic_make_request(). Horrek esan nahi du ziurrenik arazoen kausa osd deabrutik kanpo dagoela nonbait. Hau nukleoa edo diskoak izan daitezke. Iostat irteerak latentzia handia erakutsi zuen bcache diskoen eskaerak prozesatzeko.
Ostalaria egiaztatzean, systemd-udevd deabruak CPU denbora asko kontsumitzen duela ikusi dugu -% 20 inguru hainbat nukleotan. Jokaera arraroa da, beraz, zergatik jakin behar duzu. Systemd-udevd-ek uevents-ekin lan egiten duenez, haiek aztertzea erabaki dugu udevadm monitorea. Bihurtzen da sistemako bloke-gailu bakoitzeko aldaketa-gertaera ugari sortu zirela. Hau nahiko ezohikoa da, beraz, gertaera horiek guztiak zerk sortzen dituen aztertu beharko dugu.
BCC Toolkit erabiliz
Dagoeneko jakin dugunez, nukleoak (eta sistema-deian dagoen ceph daemonak) denbora asko ematen du. generic_make_request(). Saia gaitezen funtzio honen abiadura neurtzen. IN
Ezaugarri honek normalean azkar funtzionatzen du. Egiten duen bakarra eskaera gailuaren kontrolatzaile ilarara pasatzea da.
Bcache gailu konplexu bat da, benetan hiru diskoz osatuta:
- babeskopia gailua (cachean gordetako diskoa), kasu honetan HDD motela da;
- caching gailua (caching diskoa), hemen hau NVMe gailuaren partizio bat da;
- aplikazioa exekutatzen duen bcache gailu birtuala.
Badakigu eskaeraren transmisioa motela dela, baina gailu hauetako zeinentzat? Geroxeago jorratuko dugu honi.
Orain badakigu gertakariek arazoak sor ditzaketela. Haien belaunaldia zerk eragiten duen aurkitzea ez da hain erraza. Demagun hau aldizka abiarazten den software mota bat dela. Ikus dezagun zer nolako softwarea exekutatzen den sisteman script bat erabiliz execsnoop beretik
Adibidez, honela:
/usr/share/bcc/tools/execsnoop | tee ./execdump
Ez dugu hemen execsnoop-en irteera osoa erakutsiko, baina guretzako interes-lerro bat honelakoa zen:
sh 1764905 5802 0 sudo arcconf getconfig 1 AD | grep Temperature | awk -F '[:/]' '{print $2}' | sed 's/^ ([0-9]*) C.*/1/'
Hirugarren zutabea prozesuaren PPID (guraso PID) da. PID 5802-rekin egindako prozesua gure monitorizazio sistemaren harietako bat izan zen. Monitorizazio sistemaren konfigurazioa egiaztatzean, parametro okerrak aurkitu dira. HBA egokigailuaren tenperatura 30 segundoz behin hartu zen, hau da, beharrezkoa baino askoz maizago. Egiaztatzeko tartea luzeago batera aldatu ondoren, ikusi dugu ostalari honetan eskaerak prozesatzeko latentzia ez zela nabarmentzen beste ostalariekin alderatuta.
Baina oraindik ez dago argi zergatik bcache gailua hain motela izan zen. Konfigurazio berdineko proba-plataforma bat prestatu genuen eta arazoa erreproduzitzen saiatu ginen bcache-n fio exekutatuz, aldian-aldian udevadm trigger exekutatuz uevents sortzeko.
BCCn oinarritutako tresnak idaztea
Saia gaitezen dei motelenak trazatzeko eta bistaratzeko utilitate sinple bat idazten generic_make_request(). Funtzio hori deitu zitzaion unitatearen izena ere interesatzen zaigu.
Plana erraza da:
- Izena eman kzunda on generic_make_request():
- Diskoaren izena memorian gordetzen dugu, funtzioaren argumentuaren bidez eskuragarria;
- Denbora-zigilua gordetzen dugu.
- Izena eman kretsonda itzultzeko generic_make_request():
- Uneko denbora-zigilua lortzen dugu;
- Gordetako denbora-zigilua bilatzen dugu eta unekoarekin alderatzen dugu;
- Emaitza zehaztutakoa baino handiagoa bada, gordetako diskoaren izena aurkituko dugu eta terminalean bistaratuko dugu.
Kprobes ΠΈ kretzundak erabili eten-puntuaren mekanismo bat funtzio-kodea aldatzeko. Irakur dezakezu
Python script-en barruan dagoen eBPF testua honelakoa da:
bpf_text = βββ # Here will be the bpf program code βββ
Funtzioen artean datuak trukatzeko, eBPF programek erabiltzen dute
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);
Hemen izeneko hash taula bat erregistratzen dugu p, giltza-motarekin u64 eta motaren balio bat struct data_t. Taula gure BPF programaren testuinguruan egongo da eskuragarri. BPF_PERF_OUTPUT makroak izeneko beste taula bat erregistratzen du gertaerak, horretarako erabiltzen dena
Funtzio bati deitzearen eta bertatik itzultzearen arteko atzerapenak neurtzean, edo funtzio ezberdinetarako deien artean, kontuan izan behar duzu jasotako datuak testuinguru berekoak izan behar direla. Beste era batera esanda, funtzioen abiarazte paralelo posibleari buruz gogoratu behar duzu. Funtzio bat prozesu baten testuinguruan deitzearen eta beste prozesu baten testuinguruan funtzio horretatik itzultzearen arteko latentzia neurtzeko gaitasuna dugu, baina hori alferrikakoa izango da ziurrenik. Hemen adibide ona litzateke
Ondoren, aztergai dugun funtzioa deitzen denean exekutatzen den kodea idatzi behar dugu:
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);
}
Hemen deitutako funtzioaren lehen argumentua bigarren argumentu gisa ordezkatuko da
Hurrengo funtzioari dei egingo zaio bueltan 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);
}
}
Funtzio hau aurrekoaren antzekoa da: prozesuaren PID eta denbora-zigilua aurkitzen ditugu, baina ez dugu memoriarik esleitzen datu-egitura berrirako. Horren ordez, hash taulan lehendik dagoen egitura bat bilatzen dugu == uneko PID gakoa erabiliz. Egitura aurkitzen bada, orduan martxan dagoen prozesuaren izena aurkituko dugu eta horri gehituko diogu.
Hemen erabiltzen dugun aldaketa bitarra GID haria lortzeko beharrezkoa da. horiek. Lantzen ari garen testuinguruan haria hasi duen prozesu nagusiaren PID. Deitzen dugun funtzioa
Terminalera irtetean, momentuz ez zaigu haria interesatzen, baina prozesu nagusia interesatzen zaigu. Sortutako atzerapena atalase jakin batekin alderatu ondoren, gure egitura pasatzen dugu datuak erabiltzailearen espaziora taula bidez gertaerak, ondoren, sarrera ezabatuko dugu p.
Kode hau kargatuko duen python script-ean, MIN_US eta FACTOR ordeztu behar ditugu atzerapen atalaseak eta denbora-unitateekin, argumentuetatik pasatuko ditugunak:
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"
Orain BPF programaren bidez prestatu behar dugu
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")
Guk ere zehaztu beharko dugu struct data_t gure gidoian, bestela ezingo dugu ezer irakurri:
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)]
Azken urratsa datuak terminalera ateratzea da:
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()
Gidoia bera hemen dago eskuragarri
Azkenean! Orain ikusten dugu gelditzen den bcache gailu bat zirudiena benetan gelditzen den dei bat dela generic_make_request() cachean gordetako disko baterako.
Zulatu Kernelean
Zer da zehazki moteltzen eskaeraren transmisioan? Atzerapena eskaeraren kontabilitatea hasi baino lehen ere gertatzen dela ikusten dugu, hau da. Estatistikak gehiago ateratzeko eskaera zehatz baten kontabilitatea (/proc/diskstats edo iostat) oraindik ez da hasi. Hau erraz egiazta daiteke iostat exekutatuz arazoa erreproduzitzen duzun bitartean, edo
Funtzioari erreparatzen badiogu generic_make_request(), orduan ikusiko dugu eskaera kontabilitateari ekin aurretik, beste bi funtzio deitzen direla. Lehenengoa - generic_make_request_checks(), diskoaren ezarpenei buruzko eskaeraren zilegitasuna egiaztatzen du. Bigarrena -
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));
Bertan, nukleoa ilara desizoztu arte itxarongo du. Neurtu dezagun atzerapena 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 | |
Konponbidetik gertu gaudela dirudi. Ilara bat izozteko/desizozteko erabiltzen diren funtzioak hauek dira
Ilara hau garbitzeko behar den denbora diskoaren latentziaren baliokidea da nukleoak ilaran dauden eragiketa guztiak amaitu arte itxaroten baitu. Ilara hutsik dagoenean, ezarpenen aldaketak aplikatzen dira. Horren ostean deitzen da
Orain nahikoa dakigu egoera zuzentzeko. udevadm trigger komandoak bloke-gailuaren ezarpenak aplikatzea eragiten du. Ezarpen hauek udev arauetan deskribatzen dira. Ilara izozten duten ezarpenak aurki ditzakegu sysfs bidez aldatzen saiatuz edo nukleoaren iturburu-kodea begiratuz. BCC utilitatea ere proba dezakegu
~# /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 arauak oso gutxitan aldatzen dira eta normalean hori modu kontrolatuan gertatzen da. Beraz, ikusten dugu lehendik ezarritako balioak aplikatuta ere eskaera aplikaziotik diskora transferitzeko atzerapenaren gorakada bat eragiten duela. Jakina, diskoaren konfigurazioan aldaketarik ez dagoenean udev gertaerak sortzea (adibidez, gailua ez dago muntatuta/deskonektatuta) ez da praktika ona. Hala ere, nukleoari lagundu diezaiokegu alferrikako lana ez egiten eta eskaera-ilara izozten, beharrezkoa ez bada.
Ondorioa
eBPF tresna oso malgua eta indartsua da. Artikuluan kasu praktiko bat aztertu dugu eta egin daitekeenaren zati txiki bat erakutsi dugu. BCC utilitateak garatzea interesatzen bazaizu, merezi du begirada bat ematea
Badira eBPFn oinarritutako arazketa eta profilak egiteko beste tresna interesgarri batzuk. Haietako bat -
Iturria: www.habr.com