Linux-та ядро мен қолданбаларды жөндеуге арналған көптеген құралдар бар. Олардың көпшілігі қолданбаның өнімділігіне теріс әсер етеді және өндірісте қолданыла алмайды.
Бір-екі жыл бұрын болған
eBPF пайдаланатын көптеген қолданбалы утилиталар бар және осы мақалада біз кітапхана негізінде өзіңіздің профильдеу утилитаңызды қалай жазу керектігін қарастырамыз.
Цеф баяу
Ceph кластеріне жаңа хост қосылды. Деректердің кейбірін оған көшіргеннен кейін біз оның жазу сұрауларын өңдеу жылдамдығы басқа серверлерге қарағанда әлдеқайда төмен екенін байқадық.
Басқа платформалардан айырмашылығы, бұл хост bcache және жаңа linux 4.15 ядросын пайдаланды. Бұл конфигурацияның хосты мұнда алғаш рет пайдаланылды. Міне, сол сәтте мәселенің түбірі теориялық тұрғыдан кез келген нәрсе болуы мүмкін екені белгілі болды.
Хостты зерттеу
Ceph-osd процесінде не болатынын қарастырудан бастайық. Ол үшін біз қолданамыз
Сурет бізге функцияны айтады fdatasync() функцияларға сұрау жіберуге көп уақыт жұмсады generic_make_request(). Бұл, ең алдымен, проблемалардың себебі osd демонының өзінен тыс жерде болуы мүмкін дегенді білдіреді. Бұл ядро немесе дискілер болуы мүмкін. Iostat шығысы bcache дискілері арқылы сұрауларды өңдеуде жоғары кідіріс көрсетті.
Хостты тексеру кезінде біз systemd-udevd демоны процессордың көп уақытын жұмсайтынын анықтадық - бірнеше ядроларда шамамен 20%. Бұл біртүрлі мінез-құлық, сондықтан оның себебін анықтау керек. Systemd-udevd оқиғалармен жұмыс істейтіндіктен, біз оларды қарап шығуды жөн көрдік udevadm мониторы. Жүйедегі әрбір блок құрылғысы үшін өзгерту оқиғаларының үлкен саны жасалғаны белгілі болды. Бұл өте ерекше, сондықтан біз осы оқиғалардың барлығын не тудыратынын қарастыруымыз керек.
BCC құралдар жинағын пайдалану
Біз бұрыннан белгілі болғандай, ядро (және жүйелік қоңыраудағы ceph демоны) көп уақытты generic_make_request(). Осы функцияның жылдамдығын өлшеуге тырысайық. 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 құрылғысының неге соншалықты баяу болғаны әлі белгісіз. Біз бірдей конфигурациясы бар сынақ платформасын дайындадық және оқиғаларды жасау үшін udevadm триггерін мерзімді түрде іске қосып, bcache жүйесінде fio іске қосу арқылы мәселені қайта шығаруға тырыстық.
BCC негізіндегі құралдарды жазу
Ең баяу қоңырауларды қадағалау және көрсету үшін қарапайым қызметтік бағдарламаны жазуға тырысайық generic_make_request(). Бізді бұл функция шақырылған дискінің аты да қызықтырады.
Жоспар қарапайым:
- Тіркелу kprobe туралы generic_make_request():
- Функция аргументі арқылы қол жеткізуге болатын диск атауын жадқа сақтаймыз;
- Уақыт белгісін сақтаймыз.
- Тіркелу кретпроб қайтып оралу үшін generic_make_request():
- Біз ағымдағы уақыт белгісін аламыз;
- Біз сақталған уақыт белгісін іздейміз және оны ағымдағымен салыстырамыз;
- Егер нәтиже көрсетілгеннен үлкен болса, біз сақталған диск атауын тауып, оны терминалда көрсетеміз.
Kprobes и кретпробтар функция кодын жылдам өзгерту үшін тоқтау нүктесі механизмін пайдаланыңыз. Сіз оқи аласыз
Python сценарийіндегі 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 және түрдің мәні struct 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():
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. Біз шақыратын функция
Терминалға шығарған кезде біз қазіргі уақытта ағынға қызығушылық танытпаймыз, бірақ бізді негізгі процесс қызықтырады. Алынған кідірісті берілген шекпен салыстырғаннан кейін біз құрылымымызды өтеміз мәліметтер кесте арқылы пайдаланушы кеңістігіне оқиғалар, содан кейін біз жазбаны жоямыз 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")
Біз де анықтауымыз керек struct 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()
Сценарийдің өзі мына жерден қол жетімді
Ақырында! Енді біз тоқтап тұрған bcache құрылғысы сияқты көрінетін нәрсе шынымен тоқтап тұрған қоңырау екенін көреміз generic_make_request() кэштелген диск үшін.
Ядроны қазыңыз
Сұранысты жіберу кезінде нақты не баяулайды? Біз кідіріс сұрауды есепке алу басталғанға дейін орын алатынын көреміз, яғни. ол бойынша статистиканы одан әрі шығаруға арналған нақты сұрауды есепке алу (/proc/diskstats немесе iostat) әлі басталған жоқ. Мұны мәселені шешу кезінде iostat іске қосу арқылы оңай тексеруге болады немесе
функциясына қарасақ generic_make_request(), содан кейін сұрауды есепке алу басталғанға дейін тағы екі функция шақырылатынын көреміз. Бірінші - generic_make_request_checks(), диск параметрлеріне қатысты сұраудың заңдылығын тексеруді жүзеге асырады. Екінші -
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 ережелері өте сирек өзгереді және әдетте бұл бақыланатын түрде болады. Сонымен, біз қазірдің өзінде орнатылған мәндерді қолданудың өзі сұрауды қолданбадан дискіге тасымалдаудың кешігуіне әкелетінін көреміз. Әрине, диск конфигурациясында өзгерістер болмаған кезде (мысалы, құрылғы орнатылмаған/ажыратылған) udev оқиғаларын жасау жақсы тәжірибе емес. Дегенмен, біз ядроға қажетсіз жұмыстарды жасамауға көмектесе аламыз және қажет болмаса, сұрау кезегін тоқтата аламыз.
қорытынды
eBPF - өте икемді және қуатты құрал. Мақалада біз бір практикалық жағдайды қарастырдық және не істеуге болатынын аз ғана бөлігін көрсеттік. Егер сіз BCC утилиталарын дамытуға қызығушылық танытсаңыз, оны қарастырған жөн
eBPF негізінде басқа да қызықты жөндеу және профильдеу құралдары бар. Олардың біреуі -
Ақпарат көзі: www.habr.com