Linux-da kernel və proqramların sazlanması üçün çoxlu alətlər var. Onların əksəriyyəti tətbiq performansına mənfi təsir göstərir və istehsalda istifadə edilə bilməz.
Bir neçə il əvvəl var idi
Artıq eBPF-dən istifadə edən bir çox proqram yardımçıları var və bu məqalədə kitabxanaya əsaslanaraq öz profilləşdirmə yardım proqramını necə yazacağınıza baxacağıq.
Ceph Yavaşdır
Ceph klasterinə yeni host əlavə edildi. Məlumatların bir hissəsini ona köçürdükdən sonra onun yazma sorğularını emal sürətinin digər serverlərə nisbətən xeyli aşağı olduğunu gördük.
Digər platformalardan fərqli olaraq, bu host bcache və yeni linux 4.15 nüvəsindən istifadə edirdi. Bu, ilk dəfə idi ki, burada bu konfiqurasiyadan istifadə edilirdi. Və o an aydın oldu ki, problemin kökü nəzəri olaraq hər şey ola bilər.
Ev sahibinin araşdırılması
Ceph-osd prosesində baş verənlərə baxaraq başlayaq. Bunun üçün istifadə edəcəyik
Şəkil bizə funksiyanı bildirir fdatasync() funksiyalara sorğu göndərmək üçün çox vaxt sərf etdi generic_make_request(). Bu o deməkdir ki, çox güman ki, problemlərin səbəbi osd demonunun özündən kənardadır. Bu, nüvə və ya disklər ola bilər. İostat çıxışı bcache diskləri tərəfindən sorğuların emalında yüksək gecikmə göstərdi.
Hostu yoxlayarkən biz müəyyən etdik ki, systemd-udevd demonu çoxlu CPU vaxtını sərf edir - bir neçə nüvədə təxminən 20%. Bu qəribə davranışdır, ona görə də bunun səbəbini tapmaq lazımdır. Systemd-udevd hadisələrlə işlədiyi üçün biz onları nəzərdən keçirmək qərarına gəldik udevadm monitor. Belə çıxır ki, sistemdəki hər bir blok cihazı üçün çoxlu sayda dəyişiklik hadisəsi yaradılıb. Bu olduqca qeyri-adidir, ona görə də bütün bu hadisələri nəyin yaratdığına baxmaq məcburiyyətində qalacağıq.
BCC Alət dəstindən istifadə
Artıq aşkar etdiyimiz kimi, nüvə (və sistem çağırışında olan ceph demonu) çox vaxt sərf edir. generic_make_request(). Bu funksiyanın sürətini ölçməyə çalışaq. IN
Bu xüsusiyyət adətən tez işləyir. Bunun etdiyi hər şey sorğunu cihaz sürücüsü növbəsinə ötürməkdir.
Bcache əslində üç diskdən ibarət mürəkkəb bir cihazdır:
- dəstək cihazı (keşlənmiş disk), bu halda yavaş HDD-dir;
- önbelleğe alma cihazı (keşləmə diski), burada bu NVMe cihazının bir hissəsidir;
- proqramın işlədiyi bcache virtual cihazı.
Sorğunun ötürülməsinin yavaş olduğunu bilirik, lakin bu cihazlardan hansı üçün? Bununla bir az sonra məşğul olacağıq.
İndi hadisələrin problem yarada biləcəyini bilirik. Onların nəslinə tam olaraq nəyin səbəb olduğunu tapmaq o qədər də asan deyil. Tutaq ki, bu, vaxtaşırı işə salınan bir növ proqramdır. Skriptdən istifadə edərək sistemdə hansı proqram təminatının işlədiyini görək execsnoop eynidən
Məsələn, bu kimi:
/usr/share/bcc/tools/execsnoop | tee ./execdump
Burada execsnoop-un tam çıxışını göstərməyəcəyik, lakin bizi maraqlandıran bir xətt belə görünürdü:
sh 1764905 5802 0 sudo arcconf getconfig 1 AD | grep Temperature | awk -F '[:/]' '{print $2}' | sed 's/^ ([0-9]*) C.*/1/'
Üçüncü sütun prosesin PPID-idir (ana PID). PID 5802 ilə proses monitorinq sistemimizin mövzularından biri oldu. Monitorinq sisteminin konfiqurasiyası yoxlanılarkən səhv parametrlər aşkar edilmişdir. HBA adapterinin temperaturu hər 30 saniyədə götürülürdü ki, bu da lazım olduğundan daha tez-tez olur. Yoxlama intervalını daha uzun müddətə dəyişdikdən sonra biz aşkar etdik ki, bu hostda sorğunun emal gecikməsi artıq digər hostlarla müqayisədə fərqlənmir.
Ancaq bcache cihazının niyə bu qədər yavaş olduğu hələ də aydın deyil. Biz eyni konfiqurasiyaya malik test platforması hazırladıq və hadisələri yaratmaq üçün vaxtaşırı udevadm trigger-i işə salaraq, bcache-də fio işlətməklə problemi təkrar yaratmağa çalışdıq.
BCC əsaslı alətlərin yazılması
Ən yavaş zəngləri izləmək və göstərmək üçün sadə bir yardım proqramı yazmağa çalışaq generic_make_request(). Bu funksiyanın çağırıldığı sürücünün adı ilə də maraqlanırıq.
Plan sadədir:
- Qeydiyyatdan keçin kprobe haqqında generic_make_request():
- Disk adını yaddaşda saxlayırıq, funksiya arqumenti vasitəsilə əldə edilə bilər;
- Vaxt möhürünü saxlayırıq.
- Qeydiyyatdan keçin kretprobe -dən qayıtmaq üçün generic_make_request():
- Cari vaxt damğasını alırıq;
- Saxlanmış vaxt damğasını axtarırıq və onu cari ilə müqayisə edirik;
- Nəticə göstəriləndən böyükdürsə, biz saxlanan diskin adını tapırıq və onu terminalda göstəririk.
Kprobes и kretproblar funksiya kodunu tez dəyişmək üçün kəsilmə nöqtəsi mexanizmindən istifadə edin. Oxuya bilərsiniz
Python skriptindəki eBPF mətni belə görünür:
bpf_text = “”” # Here will be the bpf program code “””
Funksiyalar arasında məlumat mübadiləsi üçün eBPF proqramları istifadə edir
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);
Burada adlanan hash cədvəlini qeyd edirik p, açar növü ilə u64 və növün dəyəri struktur data_t. Cədvəl bizim BPF proqramımızın kontekstində mövcud olacaq. BPF_PERF_OUTPUT makro adlanan başqa bir cədvəli qeyd edir hadisələrüçün istifadə olunur
Funksiyaya zəng etmək və ondan qayıtmaq və ya müxtəlif funksiyalara zənglər arasında gecikmələri ölçərkən nəzərə almaq lazımdır ki, alınan məlumat eyni kontekstə aid olmalıdır. Başqa sözlə, funksiyaların mümkün paralel işə salınmasını xatırlamaq lazımdır. Bir proses kontekstində funksiyanın çağırılması ilə digər proses kontekstində həmin funksiyadan geri qayıtması arasındakı gecikməni ölçmək imkanımız var, lakin bu, çox güman ki, faydasızdır. Burada yaxşı bir nümunə olardı
Sonra, öyrənilən funksiya çağırıldıqda işləyəcək kodu yazmalıyıq:
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);
}
Burada çağırılan funksiyanın birinci arqumenti ikinci arqument kimi əvəz olunacaq
Aşağıdakı funksiya geri qayıtdıqda çağırılacaq 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);
}
}
Bu funksiya əvvəlkinə bənzəyir: biz prosesin PID-sini və vaxt damğasını tapırıq, lakin yeni məlumat strukturu üçün yaddaş ayırmırıq. Bunun əvəzinə biz == cari PID açarından istifadə edərək artıq mövcud strukturu hash cədvəlində axtarırıq. Əgər struktur tapılarsa, o zaman işləyən prosesin adını tapıb ona əlavə edirik.
Burada istifadə etdiyimiz binar shift GID ipini əldə etmək üçün lazımdır. olanlar. İşlədiyimiz kontekstdə mövzunu başlatan əsas prosesin PID-i. Zəng etdiyimiz funksiya
Terminalda çıxış edərkən, biz hazırda mövzu ilə maraqlanmırıq, lakin əsas proseslə maraqlanırıq. Yaranan gecikməni müəyyən bir həddi ilə müqayisə etdikdən sonra strukturumuzu keçirik məlumat cədvəl vasitəsilə istifadəçi sahəsinə daxil edilir hadisələr, bundan sonra girişi silirik p.
Bu kodu yükləyən python skriptində MIN_US və FACTOR-u arqumentlərdən keçəcəyimiz gecikmə hədləri və vaxt vahidləri ilə əvəz etməliyik:
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"
İndi vasitəsilə BPF proqramını hazırlamalıyıq
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")
Biz də müəyyən etməli olacağıq struktur data_t skriptimizdə, əks halda heç nə oxuya bilməyəcəyik:
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)]
Son addım məlumatları terminala çıxarmaqdır:
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()
Skriptin özü burada mövcuddur
Nəhayət! İndi görürük ki, dayanan bcache cihazı kimi görünən şey əslində dayanan bir zəngdir generic_make_request() keşlənmiş disk üçün.
Kernelə qazın
Sorğunun ötürülməsi zamanı tam olaraq nə yavaşlayır? Biz gecikmənin sorğunun uçotu başlamazdan əvvəl baş verdiyini görürük, yəni. ona dair statistik məlumatların əlavə çıxarılması üçün xüsusi sorğunun (/proc/diskstats və ya iostat) uçotu hələ başlamamışdır. Bu problemi təkrarlayarkən iostat işlətməklə asanlıqla yoxlanıla bilər və ya
Funksiyaya baxsaq generic_make_request(), onda biz sorğunun mühasibat uçotuna başlamazdan əvvəl daha iki funksiyanın çağırıldığını görəcəyik. Birinci - generic_make_request_checks(), disk parametrləri ilə bağlı sorğunun qanuniliyini yoxlayır. İkinci -
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));
Orada nüvə növbənin donmasını gözləyir. Gecikməni ölçək 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 | |
Deyəsən, həll yoluna yaxınlaşırıq. Növbənin dondurulması/açılması üçün istifadə olunan funksiyalar bunlardır
Bu növbəni təmizləmək üçün lazım olan vaxt diskin gecikməsinə bərabərdir, çünki nüvə bütün növbəli əməliyyatların tamamlanmasını gözləyir. Növbə boş olduqdan sonra parametr dəyişiklikləri tətbiq edilir. Bundan sonra çağırılır
İndi vəziyyəti düzəltmək üçün kifayət qədər məlumatımız var. Udevadm trigger əmri blok cihazı üçün parametrlərin tətbiq edilməsinə səbəb olur. Bu parametrlər udev qaydalarında təsvir edilmişdir. Hansı parametrlərin növbəni dondurduğunu sysfs vasitəsilə dəyişdirməyə cəhd etməklə və ya nüvənin mənbə koduna baxaraq tapa bilərik. BCC yardım proqramını da sınaya bilərik
~# /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 qaydaları olduqca nadir hallarda dəyişir və adətən bu, idarə olunan şəkildə baş verir. Beləliklə, görürük ki, hətta artıq təyin edilmiş dəyərlərin tətbiqi sorğunun tətbiqdən diskə ötürülməsində gecikmənin artmasına səbəb olur. Əlbəttə ki, disk konfiqurasiyasında heç bir dəyişiklik olmadıqda (məsələn, cihaz quraşdırılmayıb/sökülməyib) udev hadisələrinin yaradılması yaxşı təcrübə deyil. Bununla belə, nüvəyə lazımsız iş görməməyə kömək edə bilərik və lazım deyilsə, sorğu növbəsini dondura bilərik.
Nəticə
eBPF çox çevik və güclü vasitədir. Məqalədə biz bir praktik işə baxdıq və nə edilə biləcəyinin kiçik bir hissəsini nümayiş etdirdik. BCC utilitlərini inkişaf etdirməklə maraqlanırsınızsa, ona nəzər salmağa dəyər
eBPF-ə əsaslanan digər maraqlı sazlama və profilləşdirmə vasitələri var. Onlardan biri -
Mənbə: www.habr.com