கர்னல் மற்றும் பயன்பாடுகளை பிழைத்திருத்துவதற்கு லினக்ஸில் ஏராளமான கருவிகள் உள்ளன. அவற்றில் பெரும்பாலானவை பயன்பாட்டின் செயல்திறனில் எதிர்மறையான தாக்கத்தை ஏற்படுத்துகின்றன மற்றும் உற்பத்தியில் பயன்படுத்த முடியாது.
ஓரிரு வருடங்களுக்கு முன்பு இருந்தது
eBPF ஐப் பயன்படுத்தும் பல பயன்பாட்டு பயன்பாடுகள் ஏற்கனவே உள்ளன, மேலும் இந்த கட்டுரையில் நூலகத்தின் அடிப்படையில் உங்கள் சொந்த விவரக்குறிப்பு பயன்பாட்டை எவ்வாறு எழுதுவது என்பதைப் பார்ப்போம்.
செஃப் மெதுவாக உள்ளது
Ceph கிளஸ்டரில் ஒரு புதிய ஹோஸ்ட் சேர்க்கப்பட்டுள்ளது. சில தரவுகளை அதற்கு மாற்றிய பிறகு, அதன் மூலம் எழுதும் கோரிக்கைகளை செயலாக்கும் வேகம் மற்ற சேவையகங்களை விட மிகக் குறைவாக இருப்பதைக் கவனித்தோம்.
மற்ற தளங்களைப் போலல்லாமல், இந்த ஹோஸ்ட் bcache மற்றும் புதிய linux 4.15 கர்னலைப் பயன்படுத்தியது. இந்த கட்டமைப்பின் ஹோஸ்ட் இங்கு பயன்படுத்தப்படுவது இதுவே முதல் முறை. பிரச்சினையின் வேர் கோட்பாட்டளவில் எதுவாகவும் இருக்கலாம் என்பது அந்த நேரத்தில் தெளிவாகத் தெரிந்தது.
தொகுப்பாளரிடம் விசாரணை
ceph-osd செயல்முறைக்குள் என்ன நடக்கிறது என்பதைப் பார்ப்பதன் மூலம் ஆரம்பிக்கலாம். இதற்காக நாம் பயன்படுத்துவோம்
செயல்பாடு என்று படம் சொல்கிறது fdatasync() செயல்பாடுகளுக்கு கோரிக்கையை அனுப்புவதற்கு நிறைய நேரம் செலவிட்டார் generic_make_request(). இதன் பொருள் பெரும்பாலும் பிரச்சனைகளுக்கு காரணம் osd டீமனுக்கு வெளியே எங்காவது இருக்கலாம். இது கர்னல் அல்லது வட்டுகளாக இருக்கலாம். iostat வெளியீடு bcache வட்டுகள் மூலம் கோரிக்கைகளை செயலாக்குவதில் அதிக தாமதத்தைக் காட்டியது.
ஹோஸ்டைச் சரிபார்க்கும் போது, systemd-udevd டீமான் அதிக அளவு CPU நேரத்தைப் பயன்படுத்துவதைக் கண்டறிந்தோம் - பல கோர்களில் சுமார் 20%. இது ஒரு விசித்திரமான நடத்தை, எனவே நீங்கள் ஏன் கண்டுபிடிக்க வேண்டும். Systemd-udevd uevents உடன் வேலை செய்வதால், அவற்றைப் பார்க்க முடிவு செய்தோம் udevadm மானிட்டர். கணினியில் உள்ள ஒவ்வொரு தொகுதி சாதனத்திற்கும் அதிக எண்ணிக்கையிலான மாற்ற நிகழ்வுகள் உருவாக்கப்பட்டன என்று மாறிவிடும். இது மிகவும் அசாதாரணமானது, எனவே இந்த நிகழ்வுகள் அனைத்தையும் உருவாக்குவதை நாம் பார்க்க வேண்டும்.
BCC கருவித்தொகுப்பைப் பயன்படுத்துதல்
நாம் ஏற்கனவே கண்டுபிடித்தபடி, கர்னல் (மற்றும் கணினி அழைப்பில் உள்ள செஃப் டீமான்) நிறைய நேரம் செலவிடுகிறது 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 சாதனம் ஏன் மிகவும் மெதுவாக இருந்தது என்பது இன்னும் தெளிவாகத் தெரியவில்லை. ஒரே மாதிரியான உள்ளமைவுடன் ஒரு சோதனை தளத்தை நாங்கள் தயார் செய்து, bcache இல் fio ஐ இயக்குவதன் மூலம் சிக்கலை மீண்டும் உருவாக்க முயற்சித்தோம், uevents உருவாக்க udevadm தூண்டுதலை அவ்வப்போது இயக்குகிறோம்.
BCC-அடிப்படையிலான கருவிகளை எழுதுதல்
மெதுவான அழைப்புகளைக் கண்டறிந்து காண்பிக்க எளிய பயன்பாட்டை எழுத முயற்சிப்போம் generic_make_request(). இந்த செயல்பாடு அழைக்கப்பட்ட இயக்ககத்தின் பெயரிலும் நாங்கள் ஆர்வமாக உள்ளோம்.
திட்டம் எளிது:
- பதிவு kprobe மீது generic_make_request():
- வட்டு பெயரை நினைவகத்தில் சேமிக்கிறோம், செயல்பாடு வாதம் மூலம் அணுகலாம்;
- நேர முத்திரையைச் சேமிக்கிறோம்.
- பதிவு kretprobe இருந்து திரும்புவதற்கு generic_make_request():
- தற்போதைய நேர முத்திரையைப் பெறுகிறோம்;
- சேமித்த நேர முத்திரையைத் தேடி, தற்போதைய நேரத்துடன் ஒப்பிடுகிறோம்;
- முடிவு குறிப்பிடப்பட்டதை விட அதிகமாக இருந்தால், சேமித்த வட்டு பெயரைக் கண்டுபிடித்து டெர்மினலில் காண்பிப்போம்.
Kprobes и kretprobes பறக்கும்போது செயல்பாட்டுக் குறியீட்டை மாற்ற பிரேக்பாயிண்ட் பொறிமுறையைப் பயன்படுத்தவும். நீங்கள் படிக்கலாம்
பைதான் ஸ்கிரிப்ட்டின் உள்ளே இருக்கும் 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.
இந்தக் குறியீட்டை ஏற்றும் பைதான் ஸ்கிரிப்ட்டில், 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) இன்னும் தொடங்கப்படவில்லை. சிக்கலை மீண்டும் உருவாக்கும்போது ஐயோஸ்டாட்டை இயக்குவதன் மூலம் இதை எளிதாகச் சரிபார்க்கலாம் அல்லது
செயல்பாட்டைப் பார்த்தால் generic_make_request(), பின்னர் கோரிக்கை கணக்கியல் தொடங்கும் முன், மேலும் இரண்டு செயல்பாடுகள் அழைக்கப்படுகின்றன என்று பார்ப்போம். முதலில் - பொதுவான_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