د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته

د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته

لینکس د کرنل او غوښتنلیکونو ډیبګ کولو لپاره لوی شمیر وسیلې لري. ډیری یې د غوښتنلیک فعالیت باندې منفي اغیزه لري او په تولید کې نشي کارول کیدی.

څو کاله وړاندې هلته وه بله وسیله جوړه شوې ده - eBPF. دا د دې امکان رامینځته کوي چې د کرنل او کارونکي غوښتنلیکونه د ټیټ سر سره تعقیب کړي او پرته له دې چې د برنامو بیا جوړولو ته اړتیا ولري او د دریمې ډلې ماډلونه په کرنل کې بار کړي.

دمخه د غوښتنلیک ډیری اسانتیاوې شتون لري چې eBPF کاروي ، او پدې مقاله کې به موږ وګورو چې څنګه د کتابتون پراساس ستاسو د پروفایل کولو وړتیا ولیکئ PythonBCC. مقاله د حقیقي پیښو پراساس ده. موږ به له ستونزې څخه د حل کولو لپاره لاړ شو ترڅو وښیو چې څنګه موجوده اسانتیاوې په ځانګړو شرایطو کې کارول کیدی شي.

سیف ورو دی

د سیف کلستر ته یو نوی کوربه اضافه شوی. دې ته د ځینو معلوماتو له لیږدولو وروسته، موږ ولیدل چې د دې لخوا د لیکلو غوښتنو پروسس کولو سرعت د نورو سرورونو په پرتله خورا ټیټ و.

د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته
د نورو پلیټ فارمونو برخلاف ، دې کوربه bcache او نوي لینکس 4.15 کرنل کارولی. دا لومړی ځل و چې د دې ترتیب کوربه دلته کارول کیده. او په هغه وخت کې دا روښانه وه چې د ستونزې ریښه په نظرياتي توګه هر څه کیدی شي.

د کوربه پلټنه

راځئ چې د سیف-osd پروسې دننه څه پیښ شي په لټه کې پیل وکړو. د دې لپاره موږ به وکاروو کامل и فلیمسکوپ (نور چې تاسو یې لوستلی شئ دلته):

د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته
انځور موږ ته وایي چې فعالیت fdatasync() فعالیتونو ته د غوښتنې لیږلو ډیر وخت تیر کړ عمومي_جوړولو_غوښتنه(). دا پدې مانا ده چې ډیری احتمال د ستونزو لامل پخپله د osd ډیمون څخه بهر دی. دا کیدای شي د کرنل یا ډیسک وي. د iostat محصول د bcache ډیسکونو لخوا د غوښتنو پروسس کولو کې لوړ ځنډ ښودلی.

کله چې د کوربه معاینه کول، موږ وموندله چې سیسټمډ - udevd ډیمون د CPU وخت لوی مقدار مصرفوي - په څو کورونو کې شاوخوا 20٪. دا عجیب چلند دی، نو تاسو اړتیا لرئ چې معلومه کړئ ولې. څرنګه چې Systemd-udevd د uevents سره کار کوي، موږ پریکړه وکړه چې د دوی له لارې وګورو udevadm څارونکی. دا معلومه شوه چې په سیسټم کې د هر بلاک وسیلې لپاره د بدلون لوی شمیر پیښې رامینځته شوي. دا خورا غیر معمولي ده، نو موږ باید وګورو چې دا ټولې پیښې څه رامینځته کوي.

د BCC Toolkit کارول

لکه څنګه چې موږ دمخه موندلي ، دانه (او د سیسټم کال کې سیف ډیمون) ډیر وخت تیروي عمومي_جوړولو_غوښتنه(). راځئ هڅه وکړو چې د دې فعالیت سرعت اندازه کړو. IN بي سي سي لا دمخه یو په زړه پوری افادیت شتون لري - فعالیت. موږ به ډیمون د هغې د PID په واسطه د 1 ثانیې وقفې سره د محصولاتو ترمینځ تعقیب کړو او پایله به په ملی ثانیو کې تولید کړو.

د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته
دا فیچر معمولا په چټکۍ سره کار کوي. ټول دا کوي چې غوښتنه د وسیلې ډرایور کتار ته انتقالوي.

Bcache یو پیچلی وسیله ده چې په حقیقت کې درې ډیسکونه لري:

  • د ملاتړ وسیله (کیچ شوی ډیسک) ، پدې حالت کې دا یو ورو HDD دی؛
  • د کیشینګ وسیله (کیشینګ ډیسک)، دلته دا د NVMe وسیله یوه برخه ده؛
  • د bcache مجازی وسیله چې ورسره غوښتنلیک چلیږي.

موږ پوهیږو چې د غوښتنې لیږد ورو دی، مګر د کومو وسیلو لپاره؟ موږ به لږ وروسته له دې سره معامله وکړو.

موږ اوس پوهیږو چې د پیښو احتمال د ستونزو لامل کیږي. د هغه څه موندل چې واقعیا د دوی نسل لامل کیږي دومره اسانه ندي. راځئ فرض کړو چې دا یو ډول سافټویر دی چې په دوره توګه په لاره اچول کیږي. راځئ وګورو چې کوم ډول سافټویر په سیسټم کې د سکریپټ په کارولو سره چلیږي execsnoop له همدې څخه د BCC یوټیلیټ کټ. راځئ چې دا چل کړو او محصول فایل ته واستوو.

د مثال په توګه:

/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 ټریګر چلولو لپاره د euvents رامینځته کولو لپاره.

د BCC پر بنسټ د وسیلو لیکل

راځئ هڅه وکړو چې یو ساده افادیت ولیکئ ترڅو ورو ورو تلیفونونه تعقیب او ښکاره کړئ عمومي_جوړولو_غوښتنه(). موږ د ډرایو نوم سره هم علاقه لرو د کوم لپاره چې دا فنکشن ویل شوی و.

پلان ساده دی:

  • راجستر kprobe په عمومي_جوړولو_غوښتنه():
    • موږ د ډیسک نوم په حافظه کې خوندي کوو، د فنکشن دلیل له لارې د لاسرسي وړ؛
    • موږ د مهال ویش خوندي کوو.

  • راجستر kretprobe د بیرته راستنیدو لپاره عمومي_جوړولو_غوښتنه():
    • موږ اوسنی مهال ویش ترلاسه کوو؛
    • موږ د خوندي شوي مهال ویش په لټه کې یو او د اوسني سره پرتله کوو؛
    • که پایله له ټاکل شوي څخه لویه وي، نو موږ د خوندي شوي ډیسک نوم پیدا کوو او په ټرمینل کې یې ښکاره کوو.

Kprobes и kretprobes په الوتنه کې د فعالیت کوډ بدلولو لپاره د وقفې نقطې میکانیزم وکاروئ. تاسو لوستلی شئ اسناد и ښه په دې موضوع مقاله. که تاسو د مختلفو اسانتیاوو کوډ وګورئ بي سي سي، بیا تاسو لیدلی شئ چې دوی ورته جوړښت لري. نو پدې مقاله کې به موږ د سکریپټ دلیلونو تحلیل پریږدو او پخپله د BPF برنامې ته لاړ شو.

د python سکریپټ دننه د eBPF متن داسې ښکاري:

bpf_text = “”” # Here will be the bpf program code “””

د دندو تر منځ د معلوماتو د تبادلې لپاره، د eBPF پروګرامونه کاروي د هش میزونه. موږ به هم همداسې وکړو. موږ به د پروسې PID د کلیدي په توګه وکاروو، او جوړښت به د ارزښت په توګه تعریف کړو:

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);
}

دلته د ویل شوي فنکشن لومړی دلیل به د دویم دلیل په توګه بدل شي عمومي_جوړولو_غوښتنه(). له دې وروسته، موږ د پروسې PID په هغه شرایطو کې ترلاسه کوو چې موږ یې کار کوو، او اوسنی مهال ویش په نانو ثانیو کې. موږ دا ټول په نوي ټاکل شوي کې لیکو struct data_t ډاټا. موږ د جوړښت څخه د ډیسک نوم ترلاسه کوو بایو، کوم چې د زنګ وهلو پر مهال تیریږي عمومي_جوړولو_غوښتنه()، او په ورته جوړښت کې یې خوندي کړئ معلومات. وروستی ګام د هش میز ته د ننوتلو اضافه کول دي چې مخکې یې یادونه وشوه.

لاندې فنکشن به د بیرته راستنیدو پرمهال ویل کیږي عمومي_جوړولو_غوښتنه():

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 چې موضوع یې په هغه شرایطو کې پیل کړې چې موږ یې کار کوو. هغه فعالیت چې موږ یې وایو bpf_get_current_pid_tgid() د تار GID او PID دواړه په یو واحد 64-bit ارزښت کې بیرته راګرځوي.

کله چې ټرمینل ته محصول ورکوو، موږ اوس مهال تار سره علاقه نه لرو، مګر موږ د اصلي پروسې سره علاقه لرو. وروسته له دې چې د پایلې ځنډ د ورکړل شوي حد سره پرتله کړو، موږ خپل جوړښت تیر کړو معلومات د میز له لارې د کارونکي ځای ته پېښې، له هغې وروسته موږ د ننوتلو څخه حذف کوو p.

په python سکریپټ کې چې دا کوډ به پورته کړي، موږ اړتیا لرو چې MIN_US او فاکتور د ځنډ حد او وخت واحدونو سره ځای په ځای کړو، کوم چې موږ به د دلیلونو څخه تیر کړو:

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 پروګرام له لارې چمتو کړو 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()

سکریپټ پخپله شتون لري GITHub. راځئ چې دا د ازموینې پلیټ فارم کې چلولو هڅه وکړو چیرې چې fio روانه ده، bcache ته لیکل، او udevadm مانیټر ته زنګ ووهئ:

د eBPF/BCC په کارولو سره د لوړ سیف لیټینسي څخه کرنل پیچ ته
په پای کې! اوس موږ ګورو چې هغه څه چې د سټلانګ bcache وسیلې په څیر ښکاري په حقیقت کې یو سټیلینګ کال دی عمومي_جوړولو_غوښتنه() د زیرمه شوي ډیسک لپاره.

په دانه کې کیندل

د غوښتنې لیږد په جریان کې په ریښتیا څه ورو کیږي؟ موږ ګورو چې ځنډ حتی د غوښتنې محاسبې له پیل څخه دمخه پیښیږي ، د بیلګې په توګه. په دې باندې د احصایې د نورو محصولاتو لپاره د ځانګړي غوښتنې محاسبه (/proc/diskstats یا iostat) لا نه ده پیل شوې. دا په اسانۍ سره د iostat په چلولو سره تایید کیدی شي پداسې حال کې چې ستونزه بیا تولیدوي، یا د BCC سکریپټ بیولوټینسی، کوم چې د غوښتنې حساب ورکولو پیل او پای پورې اړه لري. د دې اسانتیاو څخه هیڅ یو به د کیش شوي ډیسک ته د غوښتنو لپاره ستونزې ونه ښیې.

که موږ فعالیت ته وګورو عمومي_جوړولو_غوښتنه()، بیا به موږ وګورو چې مخکې له دې چې غوښتنې حساب پیل کړي ، دوه نور افعال بلل کیږي. لومړی - عمومي_میک_غوښتنه_چیکونه()، د ډیسک ترتیباتو په اړه د غوښتنې مشروعیت چیک کوي. دوهم - blk_queue_enter()، کوم چې په زړه پورې ننګونه لري انتظار_پېښه_مداخله کوونکی():

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    	|                                    	|

داسې ښکاري چې موږ یو حل ته نږدې یو. هغه فنکشنونه چې د قطار منجمد / غیر منجمد کولو لپاره کارول کیږي عبارت دي له blk_mq_freeze_queue и blk_mq_unfreeze_queue. دوی کارول کیږي کله چې د غوښتنې قطار تنظیماتو بدلولو لپاره اړین وي، کوم چې په دې کتار کې د غوښتنو لپاره احتمالي خطرناک دي. کله چې زنګ ووهي blk_mq_freeze_queue() فعالیت blk_freeze_queue_start() کاونټر زیات شوی دی q->mq_freeze_depth. له دې وروسته، دانه د قطار د خالي کولو لپاره انتظار کوي blk_mq_freeze_queue_wait().

هغه وخت چې د دې کتار پاکولو لپاره وخت نیسي د ډیسک ځنډ سره مساوي دی ځکه چې کرنل د ټولو قطار عملیاتو بشپړیدو ته انتظار باسي. یوځل چې قطار خالي وي ، د تنظیماتو بدلونونه پلي کیږي. له هغې وروسته ویل کیږي blk_mq_unfreeze_queue()د کاونټر کمول کنګل_ ژوره.

اوس موږ د وضعیت سمولو لپاره کافي پوهیږو. د udevadm ټریګر کمانډ د دې لامل کیږي چې د بلاک آلې لپاره تنظیمات پلي شي. دا ترتیبات د udev قواعدو کې تشریح شوي. موږ موندلی شو چې کوم ترتیبات د sysfs له لارې د بدلولو هڅه کولو یا د کرنل سرچینې کوډ په کتلو سره قطار کنګل کوي. موږ کولی شو د BCC افادیت هم هڅه وکړو ټیسټ، کوم چې به ټرمینل ته د هرې زنګ لپاره د کرنل او یوزر سپیس سټیک ټریسونه تولید کړي blk_freeze_queueد بیلګې په توګه:

~# /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 پراساس نور په زړه پوري ډیبګ کولو او پروفایل کولو وسیلې شتون لري. ېو د هغو څخه - bpftrace، کوم چې تاسو ته اجازه درکوي د awk په څیر ژبه کې قوي یو لاینر او کوچني برنامې ولیکئ. بل - ebpf_exporter، تاسو ته اجازه درکوي د ټیټې کچې ، لوړ ریزولوشن میټریکونه په مستقیم ډول ستاسو پرومیتیس سرور کې راټول کړئ ، د دې وړتیا سره چې وروسته د ښکلي لیدونو او حتی خبرتیاو ترلاسه کولو وړتیا سره.

سرچینه: www.habr.com

Add a comment