Linux සතුව කර්නලය සහ යෙදුම් නිදොස් කිරීම සඳහා මෙවලම් විශාල ප්රමාණයක් ඇත. ඒවායින් බොහොමයක් යෙදුම් කාර්ය සාධනය කෙරෙහි ඍණාත්මක බලපෑමක් ඇති කරන අතර නිෂ්පාදනයේදී භාවිතා කළ නොහැක.
අවුරුදු කීපයකට කලින් තිබුණා
eBPF භාවිතා කරන බොහෝ යෙදුම් උපයෝගිතා දැනටමත් ඇති අතර, මෙම ලිපියෙන් අපි පුස්තකාලය මත පදනම්ව ඔබේම පැතිකඩ උපයෝගීතාවයක් ලියන්නේ කෙසේදැයි බලමු.
Ceph මන්දගාමී වේ
Ceph පොකුරට නව සත්කාරකයක් එක් කර ඇත. සමහර දත්ත එයට සංක්රමණය කිරීමෙන් පසුව, එය මඟින් ලිවීමේ ඉල්ලීම් සැකසීමේ වේගය අනෙකුත් සේවාදායකයන්ට වඩා බෙහෙවින් අඩු බව අපි දුටුවෙමු.
අනෙකුත් වේදිකා මෙන් නොව, මෙම ධාරකය bcache සහ නව linux 4.15 කර්නලය භාවිතා කළේය. මෙම වින්යාසයේ ධාරකයක් මෙහි භාවිතා කළ පළමු අවස්ථාව මෙය විය. ගැටලුවේ මූලය න්යායාත්මකව ඕනෑම දෙයක් විය හැකි බව ඒ මොහොතේ පැහැදිලි විය.
සත්කාරක සමාගම විමර්ශනය කිරීම
අපි පටන් ගනිමු ceph-osd ක්රියාවලිය ඇතුලේ මොකද වෙන්නේ කියලා. මේ සඳහා අපි භාවිතා කරන්නෙමු
පින්තූරය අපට පවසන්නේ කාර්යය බවයි fdatasync() කාර්යයන් සඳහා ඉල්ලීමක් යැවීමට බොහෝ කාලයක් ගත විය Generic_make_request(). මෙයින් අදහස් කරන්නේ බොහෝ විට ගැටළු වලට හේතුව osd ඩීමන් වලින් පිටත කොහේ හරි බවයි. මෙය කර්නලය හෝ තැටි විය හැක. iostat ප්රතිදානය bcache තැටි මඟින් ඉල්ලීම් සැකසීමේ ඉහළ ප්රමාදයක් පෙන්නුම් කරයි.
ධාරකය පරීක්ෂා කිරීමේදී, systemd-udevd ඩීමන් CPU කාලය විශාල ප්රමාණයක් පරිභෝජනය කරන බව අපට පෙනී ගියේය - මධ්ය කිහිපයක 20% පමණ. මෙය අමුතු හැසිරීමකි, එබැවින් ඔබ එයට හේතුව සොයා බැලිය යුතුය. Systemd-udevd uevents සමඟ ක්රියා කරන බැවින්, අපි ඒවා හරහා බැලීමට තීරණය කළෙමු udevadm මොනිටරය. පද්ධතියේ එක් එක් බ්ලොක් උපාංගය සඳහා විශාල වෙනස්කම් සිදුවීම් ජනනය කර ඇති බව පෙනී යයි. මෙය තරමක් අසාමාන්ය ය, එබැවින් මෙම සියලු සිදුවීම් උත්පාදනය කරන්නේ කුමක් දැයි බැලීමට අපට සිදුවනු ඇත.
BCC මෙවලම් කට්ටලය භාවිතා කිරීම
අප දැනටමත් සොයාගෙන ඇති පරිදි, කර්නලය (සහ පද්ධති ඇමතුමේ ඇති ceph daemon) බොහෝ කාලයක් ගත කරයි Generic_make_request(). මෙම කාර්යයේ වේගය මැනීමට උත්සාහ කරමු. තුල
මෙම විශේෂාංගය සාමාන්යයෙන් ඉක්මනින් ක්රියා කරයි. එය කරන්නේ උපාංගයේ ධාවක පෝලිමට ඉල්ලීම යැවීමයි.
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 ධාවනය කිරීමෙන්, uevens උත්පාදනය කිරීමට udevadm ප්රේරකය වරින් වර ධාවනය කිරීමෙන් ගැටලුව ප්රතිනිෂ්පාදනය කිරීමට උත්සාහ කළෙමු.
BCC-පාදක මෙවලම් ලිවීම
මන්දගාමී ඇමතුම් සොයා ගැනීමට සහ සංදර්ශන කිරීමට සරල උපයෝගීතාවයක් ලිවීමට උත්සාහ කරමු Generic_make_request(). මෙම කාර්යය හැඳින්වූ ධාවකයේ නම ගැනද අපි උනන්දු වෙමු.
සැලැස්ම සරලයි:
- අපි ලියාපදිංචි වෙනවා kprobe මත Generic_make_request():
- අපි තැටියේ නම මතකයට සුරකිමු, ක්රියාකාරී තර්කය හරහා ප්රවේශ විය හැකිය;
- අපි කාල මුද්රාව සුරකිමු.
- අපි ලියාපදිංචි වෙනවා kretprobe සිට ආපසු පැමිණීම සඳහා Generic_make_request():
- අපි වත්මන් වේලා මුද්රාව ලබා ගනිමු;
- අපි සුරකින ලද වේලා මුද්රාව සොයන අතර එය වත්මන් එක සමඟ සසඳන්න;
- ප්රති result ලය නියමිත ප්රමාණයට වඩා වැඩි නම්, අපි සුරකින ලද තැටි නම සොයාගෙන එය ටර්මිනලයේ ප්රදර්ශනය කරමු.
Kprobes и kretprobes පියාසර කරන විට ක්රියාකාරී කේතය වෙනස් කිරීමට බ්රේක්පොයින්ට් යාන්ත්රණයක් භාවිතා කරන්න. ඔබට කියවිය හැකිය
පයිතන් ස්ක්රිප්ටය තුළ ඇති ඊබීපීඑෆ් පෙළ මේ ආකාරයට පෙනේ:
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() හැඹිලි තැටියක් සඳහා.
කර්නලය හාරා
ඉල්ලීම සම්ප්රේෂණය කිරීමේදී මන්දගාමී වීම යනු කුමක්ද? ඉල්ලීම් ගිණුම්කරණය ආරම්භ වීමට පෙර පවා ප්රමාදය සිදුවන බව අපට පෙනේ, i.e. එය (/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 trigger විධානය මඟින් බ්ලොක් උපාංගයේ සිටුවම් යෙදීමට හේතු වේ. මෙම සැකසුම් 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