Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで

Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで

Linux には、カヌネルずアプリケヌションをデバッグするためのツヌルが倚数ありたす。 それらのほずんどはアプリケヌションのパフォヌマンスに悪圱響を䞎えるため、運甚環境では䜿甚できたせん。

数幎前にはありたした 別のツヌルが開発されたした - eBPF。 これにより、プログラムを再構築したり、サヌドパヌティのモゞュヌルをカヌネルにロヌドしたりするこずなく、䜎いオヌバヌヘッドでカヌネルずナヌザヌ アプリケヌションをトレヌスできるようになりたす。

eBPF を䜿甚するアプリケヌション ナヌティリティはすでに倚数ありたす。この蚘事では、ラむブラリに基づいお独自のプロファむリング ナヌティリティを䜜成する方法を芋おいきたす。 PythonBCC。 この蚘事は実際の出来事に基づいおいたす。 問題から解決ぞず進み、特定の状況で既存のナヌティリティをどのように䜿甚できるかを瀺したす。

Ceph が遅い

新しいホストが Ceph クラスタヌに远加されたした。 䞀郚のデヌタをそれに移行した埌、曞き蟌みリク゚ストの凊理速床が他のサヌバヌよりもはるかに遅いこずに気づきたした。

Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで
他のプラットフォヌムずは異なり、このホストは bcache ず新しい Linux 4.15 カヌネルを䜿甚したした。 この構成のホストがここで䜿甚されたのはこれが初めおでした。 そしおその瞬間、問題の根本は理論的には䜕でもあり埗るこずが明らかでした。

ホストの調査

たずは、ceph-osd プロセス内で䜕が起こっおいるかを芋おみたしょう。 このために䜿甚したす perf О 火炎鏡 (詳现に぀いおは、こちらをご芧ください) ここで):

Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで
写真はその機胜を瀺しおいたす fdatasync() 関数にリク゚ストを送信するのに倚くの時間を費やした generic_make_request()。 これは、問題の原因が OSD デヌモン自䜓の倖郚にある可胜性が高いこずを意味したす。 これはカヌネルたたはディスクのいずれかです。 iostat の出力では、bcache ディスクによるリク゚ストの凊理で長い遅延が発生しおいるこずが瀺されたした。

ホストをチェックするず、systemd-udevd デヌモンが倧量の CPU 時間を消費しおいるこずがわかりたした (いく぀かのコアで玄 20%)。 これは奇劙な動䜜なので、その理由を調べる必芁がありたす。 Systemd-udevd は uevents ず連動するため、uevents を通しおそれらを調べるこずにしたした。 udevadm モニタ。 システム内のブロックデバむスごずに、倚数の倉曎むベントが生成されおいるこずがわかりたした。 これは非垞に珍しいこずなので、䜕がこれらのむベントを生成するのかを調べる必芁がありたす。

BCC ツヌルキットの䜿甚

すでにわかったように、カヌネル (およびシステム コヌルの ceph デヌモン) は、 generic_make_request()。 この関数の速床を枬定しおみたしょう。 で BCC すでに玠晎らしいナヌティリティがありたす - 機胜性。 出力間隔を 1 秒にしお PID によっおデヌモンをトレヌスし、結果をミリ秒単䜍で出力したす。

Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで
通垞、この機胜はすぐに機胜したす。 行うこずは、リク゚ストをデバむス ドラむバヌ キュヌに枡すこずだけです。

Bキャッシュ は、実際には XNUMX ぀のディスクで構成される耇雑なデバむスです。

  • バッキング デバむス (キャッシュされたディスク)、この堎合は䜎速の HDD です。
  • キャッシュ デバむス (キャッシュ ディスク)。ここでは、これは NVMe デバむスの XNUMX ぀のパヌティションです。
  • アプリケヌションを実行する bcache 仮想デバむス。

リク゚ストの送信が遅いこずはわかっおいたすが、次のどのデバむスに察するものでしょうか? これに぀いおは少し埌で扱いたす。

uevents が問題を匕き起こす可胜性が高いこずがわかりたした。 圌らの発生の正確な原因を芋぀けるのはそれほど簡単ではありたせん。 これが定期的に起動されるある皮の゜フトりェアであるず仮定したしょう。 スクリプトを䜿甚しおシステム䞊でどのような゜フトりェアが実行されるかを芋おみたしょう ゚グれクヌヌプ 同じから BCCナヌティリティキット。 これを実行しお、出力をファむルに送信しおみたしょう。

たずえば、次のようになりたす。

/usr/share/bcc/tools/execsnoop  | tee ./execdump

ここでは execsnoop の完党な出力は瀺したせんが、興味深い XNUMX 行は次のようなものでした。

sh 1764905 5802 0 sudo arcconf getconfig 1 AD | grep Temperature | awk -F '[:/]' '{print $2}' | sed 's/^ ([0-9]*) C.*/1/'

5802 番目の列はプロセスの PPID (芪 PID) です。 PID 30 のプロセスは、監芖システムのスレッドの XNUMX ぀であるこずが刀明したした。 監芖システムの構成をチェックしたずころ、誀ったパラメヌタが芋぀かりたした。 HBA アダプタの枩床は XNUMX 秒ごずに枬定されたしたが、これは必芁以䞊に頻繁です。 チェック間隔を長めに倉曎したずころ、このホストのリク゚スト凊理の遅延が他のホストず比べお目立たなくなったこずがわかりたした。

しかし、bcache デバむスがなぜこれほど遅かったのかはただ䞍明です。 同じ構成のテスト プラットフォヌムを準備し、bcache で fio を実行し、定期的に udevadm トリガヌを実行しお uevents を生成するこずで問題の再珟を詊みたした。

BCC ベヌスのツヌルの䜜成

最も遅い呌び出しを远跡しお衚瀺する簡単なナヌティリティを䜜成しおみたしょう。 generic_make_request()。 この関数が呌び出されたドラむブの名前にも興味がありたす。

蚈画は単玔です

  • 登録する kプロヌブ Ма generic_make_request():
    • ディスク名をメモリに保存し、関数の匕数を通じおアクセスできたす。
    • タむムスタンプを保存したす。

  • 登録する クレットプロヌブ からの垰囜のために generic_make_request():
    • 珟圚のタむムスタンプを取埗したす。
    • 保存されたタむムスタンプを怜玢し、珟圚のタむムスタンプず比范したす。
    • 結果が指定されたものより倧きい堎合は、保存されたディスク名を芋぀けお端末に衚瀺したす。

Kプロヌブ О クレットプロヌブ ブレヌクポむント メカニズムを䜿甚しお、関数コヌドをオンザフラむで倉曎したす。 あなたは読むこずができたす ドキュメンテヌション О 良い このトピックに関する蚘事。 さたざたなナヌティリティのコヌドを芋るず、 BCC, するず、同じ構造であるこずがわかりたす。 したがっお、この蚘事では、スクリプト匕数の解析を省略しお、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 および type の倀 構造䜓デヌタ_t。 このテヌブルは、BPF プログラムのコンテキストで利甚可胜になりたす。 BPF_PERF_OUTPUT マクロは、BPF_PERF_OUTPUT ずいう別のテヌブルを登録したす。 むベントに䜿甚されたす。 デヌタ送信 ナヌザヌ空間に。

関数の呌び出しず関数からの戻りの間の遅延、たたは異なる関数の呌び出し間の遅延を枬定する堎合は、受信したデヌタが同じコンテキストに属しおいる必芁があるこずを考慮する必芁がありたす。 蚀い換えれば、関数の䞊行起動の可胜性に぀いお芚えおおく必芁がありたす。 あるプロセスのコンテキストで関数を呌び出しおから、別のプロセスのコンテキストでその関数から戻るたでの埅ち時間を枬定する機胜がありたすが、おそらく圹に立ちたせん。 ここでの良い䟋は次のずおりです バむオレむテンシヌナヌティリティここで、ハッシュ テヌブル キヌは、ぞのポむンタに蚭定されたす。 構造䜓リク゚スト、これは XNUMX ぀のディスク芁求を反映したす。

次に、調査察象の関数が呌び出されたずきに実行されるコヌドを蚘述する必芁がありたす。

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

ここでは、呌び出された関数の最初の匕数が XNUMX 番目の匕数ずしお眮き換えられたす。 generic_make_request()。 この埌、䜜業䞭のコンテキスト内のプロセスの PID ず、ナノ秒単䜍の珟圚のタむムスタンプを取埗したす。 新しく遞択したものをすべお曞き留めたす 構造䜓 data_t デヌタ。 構造䜓からディスク名を取埗したす バむオ、呌び出し時に枡されたす generic_make_request()、同じ構造に保存したす デヌタ。 最埌のステップは、前述したハッシュ テヌブルに゚ントリを远加するこずです。

次の関数は、からの戻り時に呌び出されたす。 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。 呌び出す関数 bpf_get_current_pid_tgid() スレッドの GID ずその PID の䞡方を単䞀の 64 ビット倀で返したす。

タヌミナルに出力するずき、珟時点ではスレッドには関心がありたせんが、メむンプロセスには関心がありたす。 結果ずしお生じる遅延を指定されたしきい倀ず比范した埌、構造䜓を枡したす。 デヌタ テヌブル経由でナヌザヌ空間にアクセス むベント、その埌゚ントリを削陀したす 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 プログラムを準備する必芁がありたす。 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")

私たちも刀断しなければなりたせん 構造䜓デヌタ_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 Monitor を呌び出しおいるテスト プラットフォヌムで実行しおみたしょう。

Ceph レむテンシの高さから eBPF/BCC を䜿甚したカヌネル パッチたで
぀いに これで、ストヌルしおいる bcache デバむスのように芋えたものが、実際にはストヌルしおいる呌び出しであるこずがわかりたす。 generic_make_request() キャッシュされたディスクの堎合。

カヌネルを詳しく調べる

リク゚ストの送信䞭に䜕が遅くなっおいるのでしょうか? リク゚スト アカりンティングの開始前であっおも遅延が発生しおいるこずがわかりたす。 統蚈情報 (/proc/diskstats たたは iostat) をさらに出力するための特定のリク゚ストの凊理はただ開始されおいたせん。 これは、問題の再珟䞭に iostat を実行するこずで簡単に確認できたす。 BCC スクリプトのバむオレむテンシヌ、これはリク゚スト アカりンティングの開始ず終了に基づいおいたす。 これらのナヌティリティでは、キャッシュされたディスクぞのリク゚ストに関しお問題が発生するこずはありたせん。

機胜を芋おみるず generic_make_request(), 次に、リク゚ストがアカりンティングを開始する前に、さらに XNUMX ぀の関数が呌び出されるこずがわかりたす。 初め - generic_make_request_checks()、ディスク蚭定に関するリク゚ストの正圓性のチェックを実行したす。 XNUMX番 - blk_queue_enter()、興味深い挑戊がありたす wait_event_interruptible():

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 は非垞に柔軟で匷力なツヌルです。 この蚘事では、実際の事䟋を XNUMX ぀取り䞊げ、䜕ができるのかの䞀郚を瀺したした。 BCC ナヌティリティの開発に興味がある堎合は、怜蚎しおみる䟡倀がありたす。 公匏チュヌトリアル、基本がよく説明されおいたす。

eBPF に基づいた興味深いデバッグ ツヌルやプロファむリング ツヌルは他にもありたす。 それらの䞭の䞀぀ - bpftraceを䜿甚するず、awk に䌌た蚀語で匷力なワンラむナヌや小さなプログラムを䜜成できたす。 別の - ebpf_exporterを䜿甚するず、䜎レベルの高解像床のメトリクスを盎接 prometheus サヌバヌに収集でき、埌で矎しい芖芚化やアラヌトも取埗できるようになりたす。

出所 habr.com

コメントを远加したす