لینکس کے پاس کرنل اور ایپلیکیشنز کو ڈیبگ کرنے کے لیے ٹولز کی ایک بڑی تعداد ہے۔ ان میں سے بیشتر کا اطلاق کی کارکردگی پر منفی اثر پڑتا ہے اور اسے پیداوار میں استعمال نہیں کیا جا سکتا۔
سال کے ایک جوڑے پہلے تھا
پہلے سے ہی بہت ساری ایپلیکیشن یوٹیلیٹیز ہیں جو eBPF استعمال کرتی ہیں، اور اس آرٹیکل میں ہم دیکھیں گے کہ لائبریری کی بنیاد پر آپ کی اپنی پروفائلنگ یوٹیلیٹی کیسے لکھی جائے۔
سیف سست ہے۔
سیف کلسٹر میں ایک نیا میزبان شامل کیا گیا ہے۔ کچھ ڈیٹا کو اس میں منتقل کرنے کے بعد، ہم نے دیکھا کہ اس کے ذریعے تحریری درخواستوں پر کارروائی کی رفتار دوسرے سرورز کے مقابلے میں بہت کم تھی۔
دوسرے پلیٹ فارمز کے برعکس، اس میزبان نے bcache اور نئے linux 4.15 کرنل کا استعمال کیا۔ یہ پہلا موقع تھا جب اس ترتیب کا میزبان یہاں استعمال کیا گیا تھا۔ اور اس وقت یہ واضح تھا کہ مسئلے کی جڑ نظریاتی طور پر کچھ بھی ہو سکتی ہے۔
میزبان سے تفتیش کر رہا ہے۔
آئیے یہ دیکھ کر شروع کریں کہ سیف او ایس ڈی کے عمل کے اندر کیا ہوتا ہے۔ اس کے لیے ہم استعمال کریں گے۔
تصویر ہمیں بتاتی ہے کہ فنکشن fdatasync() فنکشنز کو درخواست بھیجنے میں کافی وقت گزارا۔ generic_make_request(). اس کا مطلب یہ ہے کہ ممکنہ طور پر مسائل کی وجہ خود osd ڈیمون سے باہر ہے۔ یہ یا تو دانا یا ڈسک ہو سکتا ہے۔ iostat آؤٹ پٹ نے bcache ڈسک کے ذریعہ درخواستوں پر کارروائی کرنے میں ایک اعلی تاخیر کا مظاہرہ کیا۔
میزبان کو چیک کرتے وقت، ہم نے پایا کہ systemd-udevd ڈیمون CPU وقت کی ایک بڑی مقدار استعمال کرتا ہے - کئی کوروں پر تقریباً 20%۔ یہ عجیب رویہ ہے، لہذا آپ کو اس کی وجہ جاننے کی ضرورت ہے۔ چونکہ Systemd-udevd uevents کے ساتھ کام کرتا ہے، ہم نے ان کو دیکھنے کا فیصلہ کیا۔ udevadm مانیٹر. یہ پتہ چلتا ہے کہ سسٹم میں ہر بلاک ڈیوائس کے لیے بڑی تعداد میں تبدیلی کے واقعات پیدا کیے گئے تھے۔ یہ بہت غیر معمولی ہے، لہذا ہمیں یہ دیکھنا پڑے گا کہ ان تمام واقعات کو کیا پیدا ہوتا ہے.
BCC ٹول کٹ کا استعمال
جیسا کہ ہم پہلے ہی جان چکے ہیں، دانا (اور سسٹم کال میں سیف ڈیمون) بہت زیادہ وقت گزارتا ہے۔ generic_make_request(). آئیے اس فنکشن کی رفتار کو ماپنے کی کوشش کرتے ہیں۔ میں
یہ خصوصیت عام طور پر تیزی سے کام کرتی ہے۔ یہ صرف یہ کرتا ہے کہ درخواست کو ڈیوائس ڈرائیور کی قطار میں منتقل کیا جائے۔
Bcache ایک پیچیدہ ڈیوائس ہے جو اصل میں تین ڈسکوں پر مشتمل ہے:
- بیکنگ ڈیوائس (کیشڈ ڈسک)، اس معاملے میں یہ ایک سست HDD ہے۔
- کیشنگ ڈیوائس (کیشنگ ڈسک)، یہاں یہ NVMe ڈیوائس کا ایک پارٹیشن ہے۔
- bcache ورچوئل ڈیوائس جس کے ساتھ ایپلیکیشن چلتی ہے۔
ہم جانتے ہیں کہ درخواست کی ترسیل سست ہے، لیکن ان آلات میں سے کس کے لیے؟ ہم تھوڑی دیر بعد اس سے نمٹیں گے۔
اب ہم جانتے ہیں کہ euvents سے مسائل پیدا ہونے کا امکان ہے۔ ان کی نسل کی اصل وجہ کیا ہے اس کا پتہ لگانا اتنا آسان نہیں ہے۔ فرض کریں کہ یہ کوئی ایسا سافٹ ویئر ہے جو وقتاً فوقتاً لانچ کیا جاتا ہے۔ آئیے دیکھتے ہیں کہ اسکرپٹ کا استعمال کرتے ہوئے سسٹم پر کس قسم کا سافٹ ویئر چلتا ہے۔ 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 چلا کر، وقتاً فوقتاً udevadm ٹرگر چلا کر uevents بنانے کے لیے مسئلہ کو دوبارہ پیدا کرنے کی کوشش کی۔
بی سی سی پر مبنی ٹولز لکھنا
آئیے سب سے سست کالوں کو ٹریس اور ڈسپلے کرنے کے لیے ایک سادہ یوٹیلیٹی لکھنے کی کوشش کرتے ہیں۔ generic_make_request(). ہم اس ڈرائیو کے نام میں بھی دلچسپی رکھتے ہیں جس کے لیے یہ فنکشن بلایا گیا تھا۔
منصوبہ آسان ہے:
- رجسٹر کریں۔ kprobe پر generic_make_request():
- ہم ڈسک کا نام میموری میں محفوظ کرتے ہیں، فنکشن آرگومنٹ کے ذریعے قابل رسائی۔
- ہم ٹائم اسٹیمپ کو بچاتے ہیں۔
- رجسٹر کریں۔ kretprobe سے واپسی کے لیے 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_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 اور ٹائم اسٹیمپ کا پتہ لگاتے ہیں، لیکن نئے ڈیٹا ڈھانچے کے لیے میموری مختص نہیں کرتے ہیں۔ اس کے بجائے، ہم کلید == موجودہ پی آئی ڈی کا استعمال کرتے ہوئے پہلے سے موجود ڈھانچے کے لیے ہیش ٹیبل تلاش کرتے ہیں۔ اگر ڈھانچہ مل جاتا ہے، تو ہم چلنے والے عمل کا نام تلاش کرتے ہیں اور اسے اس میں شامل کرتے ہیں.
ہم یہاں جو بائنری شفٹ استعمال کرتے ہیں وہ تھریڈ GID حاصل کرنے کے لیے درکار ہے۔ وہ مرکزی عمل کی پی آئی ڈی جس نے تھریڈ شروع کیا جس کے تناظر میں ہم کام کر رہے ہیں۔ فنکشن جسے ہم کہتے ہیں۔
ٹرمینل پر آؤٹ پٹ کرتے وقت، ہم فی الحال دھاگے میں دلچسپی نہیں رکھتے، لیکن ہم مرکزی عمل میں دلچسپی رکھتے ہیں۔ ایک دی گئی حد کے ساتھ نتیجے میں تاخیر کا موازنہ کرنے کے بعد، ہم اپنی ساخت کو پاس کرتے ہیں۔ اعداد و شمار ٹیبل کے ذریعے صارف کی جگہ میں واقعات، جس کے بعد ہم اندراج کو حذف کر دیتے ہیں۔ 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 افادیت کو تیار کرنے میں دلچسپی رکھتے ہیں، تو اس پر ایک نظر ڈالنے کے قابل ہے۔
ای بی پی ایف پر مبنی دیگر دلچسپ ڈیبگنگ اور پروفائلنگ ٹولز ہیں۔ ان میں سے ایک -
ماخذ: www.habr.com