ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช eBPF/BCC

ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช eBPF/BCC

ืœืœื™ื ื•ืงืก ื™ืฉ ืžืกืคืจ ืจื‘ ืฉืœ ื›ืœื™ื ืœื ื™ืคื•ื™ ื‘ืื’ื™ื ื‘ืงืจื ืœ ื•ื‘ืืคืœื™ืงืฆื™ื•ืช. ืœืจื•ื‘ื ื™ืฉ ื”ืฉืคืขื” ืฉืœื™ืœื™ืช ืขืœ ื‘ื™ืฆื•ืขื™ ื”ืืคืœื™ืงืฆื™ื” ื•ืœื ื ื™ืชืŸ ืœื”ืฉืชืžืฉ ื‘ื”ื ื‘ื™ื™ืฆื•ืจ.

ืœืคื ื™ ืฉื ืชื™ื™ื ื”ื™ื” ืคื•ืชื— ื›ืœื™ ื ื•ืกืฃ - eBPF. ื–ื” ืžืืคืฉืจ ืœื”ืชื—ืงื•ืช ืื—ืจ ื”ืœื™ื‘ื” ื•ื™ื™ืฉื•ืžื™ ื”ืžืฉืชืžืฉ ืขื ืชืงื•ืจื” ื ืžื•ื›ื” ื•ืœืœื ืฆื•ืจืš ืœื‘ื ื•ืช ืžื—ื“ืฉ ืชื•ื›ื ื™ื•ืช ื•ืœื˜ืขื•ืŸ ืžื•ื“ื•ืœื™ื ืฉืœ ืฆื“ ืฉืœื™ืฉื™ ืœืชื•ืš ื”ืงืจื ืœ.

ื™ืฉ ื›ื‘ืจ ื”ืจื‘ื” ื›ืœื™ ืขื–ืจ ืœืืคืœื™ืงืฆื™ื•ืช ืฉืžืฉืชืžืฉื•ืช ื‘-eBPF, ื•ื‘ืžืืžืจ ื–ื” ื ื‘ื—ืŸ ื›ื™ืฆื“ ืœื›ืชื•ื‘ ืืช ื›ืœื™ ื”ืคืจื•ืคื™ืœ ืฉืœืš ืขืœ ืกืžืš ื”ืกืคืจื™ื™ื” PythonBCC. ื”ืžืืžืจ ืžื‘ื•ืกืก ืขืœ ืื™ืจื•ืขื™ื ืืžื™ืชื™ื™ื. ื ืขื‘ื•ืจ ืžื‘ืขื™ื” ืœืชื™ืงื•ืŸ ื›ื“ื™ ืœื”ืจืื•ืช ื›ื™ืฆื“ ื ื™ืชืŸ ืœื”ืฉืชืžืฉ ื‘ื›ืœื™ ืขื–ืจ ืงื™ื™ืžื™ื ื‘ืžืฆื‘ื™ื ืกืคืฆื™ืคื™ื™ื.

Ceph ื”ื•ื ืื™ื˜ื™

ืžืืจื— ื—ื“ืฉ ื ื•ืกืฃ ืœืืฉื›ื•ืœ Ceph. ืœืื—ืจ ื”ืขื‘ืจืช ื—ืœืง ืžื”ื ืชื•ื ื™ื ืืœื™ื•, ืฉืžื ื• ืœื‘ ืฉืžื”ื™ืจื•ืช ืขื™ื‘ื•ื“ ื‘ืงืฉื•ืช ื”ื›ืชื™ื‘ื” ืขืœ ื™ื“ื• ื ืžื•ื›ื” ื‘ื”ืจื‘ื” ืžืืฉืจ ื‘ืฉืจืชื™ื ืื—ืจื™ื.

ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช eBPF/BCC
ื‘ื ื™ื’ื•ื“ ืœืคืœื˜ืคื•ืจืžื•ืช ืื—ืจื•ืช, ืžืืจื— ื–ื” ื”ืฉืชืžืฉ ื‘-bcache ื•ื‘ืงืจื ืœ ื”ื—ื“ืฉ ืฉืœ ืœื™ื ื•ืงืก 4.15. ื–ื• ื”ื™ื™ืชื” ื”ืคืขื ื”ืจืืฉื•ื ื” ืฉื‘ื” ื ืขืฉื” ืฉื™ืžื•ืฉ ื‘ืžืืจื— ืฉืœ ืชืฆื•ืจื” ื–ื•. ื•ื‘ืื•ืชื• ืจื’ืข ื”ื™ื” ื‘ืจื•ืจ ืฉืฉื•ืจืฉ ื”ื‘ืขื™ื” ื™ื›ื•ืœ ืœื”ื™ื•ืช ืชื™ืื•ืจื˜ื™ืช ื›ืœ ื“ื‘ืจ.

ื—ื•ืงืจืช ืืช ื”ืžืืจื—

ื ืชื—ื™ืœ ื‘ื”ืกืชื›ืœื•ืช ืขืœ ืžื” ืฉืงื•ืจื” ื‘ืชื•ืš ืชื”ืœื™ืš ื”-ceph-osd. ืœืฉื ื›ืš ื ืฉืชืžืฉ perf ะธ ืคืœืžืกืงื•ืค (ืขื•ื“ ืขืœื™ื• ืชื•ื›ืœ ืœืงืจื•ื ื›ืืŸ):

ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช eBPF/BCC
ื”ืชืžื•ื ื” ืื•ืžืจืช ืœื ื• ืฉื”ืคื•ื ืงืฆื™ื” fdatasync() ื”ืฉืงื™ืข ื–ืžืŸ ืจื‘ ื‘ืฉืœื™ื—ืช ื‘ืงืฉื” ืœืคื•ื ืงืฆื™ื•ืช generic_make_request(). ื–ื” ืื•ืžืจ ืฉื›ื›ืœ ื”ื ืจืื” ื”ืกื™ื‘ื” ืœื‘ืขื™ื•ืช ื”ื™ื ืื™ืคืฉื”ื• ืžื—ื•ืฅ ืœื“ืžื•ืŸ ื”-OSD ืขืฆืžื•. ื–ื” ื™ื›ื•ืœ ืœื”ื™ื•ืช ื”ืงืจื ืœ ืื• ื”ื“ื™ืกืงื™ื. ืคืœื˜ ื”-iostat ื”ืจืื” ื—ื‘ื™ื•ืŸ ื’ื‘ื•ื” ื‘ืขื™ื‘ื•ื“ ื‘ืงืฉื•ืช ืขืœ ื™ื“ื™ ื“ื™ืกืงื™ bcache.

ื›ืฉื‘ื“ืงื ื• ืืช ื”ืžืืจื—, ื’ื™ืœื™ื ื• ืฉื”ื“ืžื•ืŸ systemd-udevd ืฆื•ืจืš ื›ืžื•ืช ื’ื“ื•ืœื” ืฉืœ ื–ืžืŸ CPU - ื›-20% ื‘ืžืกืคืจ ืœื™ื‘ื•ืช. ื–ื• ื”ืชื ื”ื’ื•ืช ืžื•ื–ืจื”, ืื– ืืชื” ืฆืจื™ืš ืœื‘ืจืจ ืœืžื”. ืžืื– Systemd-udevd ืขื•ื‘ื“ ืขื uevents, ื”ื—ืœื˜ื ื• ืœื”ืกืชื›ืœ ืขืœื™ื”ื ืฆื’ udevadm. ืžืกืชื‘ืจ ืฉื ื•ืฆืจื• ืžืกืคืจ ืจื‘ ืฉืœ ืื™ืจื•ืขื™ ืฉื™ื ื•ื™ ืขื‘ื•ืจ ื›ืœ ื”ืชืงืŸ ื‘ืœื•ืง ื‘ืžืขืจื›ืช. ื–ื” ื“ื™ ื—ืจื™ื’, ืื– ื ืฆื˜ืจืš ืœื”ืกืชื›ืœ ืžื” ืžื—ื•ืœืœ ืืช ื›ืœ ื”ืื™ืจื•ืขื™ื ื”ืืœื”.

ืฉื™ืžื•ืฉ ื‘ืขืจื›ืช ื”ื›ืœื™ื ืฉืœ BCC

ื›ืคื™ ืฉื›ื‘ืจ ื’ื™ืœื™ื ื•, ื”ืงืจื ืœ (ื•ื”ื“ืžื•ืŸ ื”-ceph ื‘ืงืจื™ืืช ื”ืžืขืจื›ืช) ืžื‘ืœื” ื–ืžืŸ ืจื‘ ื‘ generic_make_request(). ื‘ื•ืื• ื ื ืกื” ืœืžื“ื•ื“ ืืช ื”ืžื”ื™ืจื•ืช ืฉืœ ืคื•ื ืงืฆื™ื” ื–ื•. IN BCC ื™ืฉ ื›ื‘ืจ ื›ืœื™ ืขื–ืจ ื ืคืœื - ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช. ื ืขืงื•ื‘ ืื—ืจ ื”ื“ืžื•ืŸ ืœืคื™ ื”-PID ืฉืœื• ืขื ืžืจื•ื•ื— ืฉืœ ืฉื ื™ื™ื” ืื—ืช ื‘ื™ืŸ ื”ืคืœื˜ื™ื ื•ื ื•ืฆื™ื ืืช ื”ืชื•ืฆืื” ื‘ืืœืคื™ื•ืช ืฉื ื™ื•ืช.

ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช 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 ื”ื™ื” ื›ืœ ื›ืš ืื™ื˜ื™. ื”ื›ื ื• ืคืœื˜ืคื•ืจืžืช ื‘ื“ื™ืงื” ืขื ืชืฆื•ืจื” ื–ื”ื” ื•ื ื™ืกื™ื ื• ืœืฉื—ื–ืจ ืืช ื”ื‘ืขื™ื” ืขืœ ื™ื“ื™ ื”ืคืขืœืช fio ืขืœ bcache, ื”ืคืขืœืช udevadm ื˜ืจื™ื’ืจ ืžืขืช ืœืขืช ืœื™ืฆื™ืจืช uevents.

ื›ืชื™ื‘ืช ื›ืœื™ื ืžื‘ื•ืกืกื™ BCC

ื‘ื•ืื• ื ื ืกื” ืœื›ืชื•ื‘ ื›ืœื™ ืขื–ืจ ืคืฉื•ื˜ ืœืžืขืงื‘ ื•ื”ืฆื’ื” ืฉืœ ื”ืฉื™ื—ื•ืช ื”ืื™ื˜ื™ื•ืช ื‘ื™ื•ืชืจ generic_make_request(). ืื ื• ืžืชืขื ื™ื™ื ื™ื ื’ื ื‘ืฉื ื”ื›ื•ื ืŸ ืฉืขื‘ื•ืจื• ื ืงืจืื” ืคื•ื ืงืฆื™ื” ื–ื•.

ื”ืชื•ื›ื ื™ืช ืคืฉื•ื˜ื”:

  • ื”ื™ืจืฉื kprobe ืขืœ generic_make_request():
    • ืื ื• ืฉื•ืžืจื™ื ืืช ืฉื ื”ื“ื™ืกืง ื‘ื–ื™ื›ืจื•ืŸ, ื ื’ื™ืฉ ื“ืจืš ืืจื’ื•ืžื ื˜ ื”ืคื•ื ืงืฆื™ื”;
    • ืื ื—ื ื• ืฉื•ืžืจื™ื ืืช ื—ื•ืชืžืช ื”ื–ืžืŸ.

  • ื”ื™ืจืฉื kretprobe ืœื”ื—ื–ืจื” ืž generic_make_request():
    • ืื ื• ืžืงื‘ืœื™ื ืืช ื—ื•ืชืžืช ื”ื–ืžืŸ ื”ื ื•ื›ื—ื™ืช;
    • ืื ื• ืžื—ืคืฉื™ื ืืช ื—ื•ืชืžืช ื”ื–ืžืŸ ื”ืฉืžื•ืจื” ื•ืžืฉื•ื•ื™ื ืื•ืชื” ืœื–ื• ื”ื ื•ื›ื—ื™ืช;
    • ืื ื”ืชื•ืฆืื” ื’ื“ื•ืœื” ืžื”ืชื•ืฆืื” ืฉืฆื•ื™ื ื”, ืื ื• ืžื•ืฆืื™ื ืืช ืฉื ื”ื“ื™ืกืง ื”ืฉืžื•ืจ ื•ืžืฆื™ื’ื™ื ืื•ืชื• ื‘ืžืกื•ืฃ.

Kprobes ะธ kretprobes ื”ืฉืชืžืฉ ื‘ืžื ื’ื ื•ืŸ ื ืงื•ื“ืช ืฉื‘ื™ืจื” ื›ื“ื™ ืœืฉื ื•ืช ืืช ืงื•ื“ ื”ืคื•ื ืงืฆื™ื” ืชื•ืš ื›ื“ื™ ืชื ื•ืขื”. ืืชื” ื™ื›ื•ืœ ืœืงืจื•ื ืชื™ืขื•ื“ ะธ ื˜ื•ื‘ ืžืืžืจ ื‘ื ื•ืฉื ื–ื”. ืื ืืชื” ืžืกืชื›ืœ ืขืœ ื”ืงื•ื“ ืฉืœ ื›ืœื™ ืขื–ืจ ืฉื•ื ื™ื ื‘ BCC, ืื– ืืชื” ื™ื›ื•ืœ ืœืจืื•ืช ืฉื™ืฉ ืœื”ื ืžื‘ื ื” ื–ื”ื”. ืื– ื‘ืžืืžืจ ื–ื” ื ื“ืœื’ ืขืœ ื ื™ืชื•ื— ืืจื’ื•ืžื ื˜ื™ื ืฉืœ ืกืงืจื™ืคื˜ ื•ื ืขื‘ื•ืจ ืœืชื•ื›ื ื™ืช BPF ืขืฆืžื”.

ื˜ืงืกื˜ eBPF ื‘ืชื•ืš ืกืงืจื™ืคื˜ python ื ืจืื” ื›ืš:

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

ื›ืืŸ ื”ืืจื’ื•ืžื ื˜ ื”ืจืืฉื•ืŸ ืฉืœ ื”ืคื•ื ืงืฆื™ื” ื”ื ืงืจืืช ื™ื•ื—ืœืฃ ื›ืืจื’ื•ืžื ื˜ ื”ืฉื ื™ generic_make_request(). ืœืื—ืจ ืžื›ืŸ, ืื ื• ืžืงื‘ืœื™ื ืืช ื”-PID ืฉืœ ื”ืชื”ืœื™ืš ื‘ืžืกื’ืจืชื• ืื ื• ืขื•ื‘ื“ื™ื, ื•ืืช ื—ื•ืชืžืช ื”ื–ืžืŸ ื”ื ื•ื›ื—ื™ืช ื‘ื ื ื•-ืฉื ื™ื•ืช. ืื ื—ื ื• ืจื•ืฉืžื™ื ืืช ื”ื›ืœ ื‘ืงื•ื‘ืฅ ืฉื ื‘ื—ืจ ื˜ืจื™ struct data_t data. ืื ื• ืžืงื‘ืœื™ื ืืช ืฉื ื”ื“ื™ืกืง ืžื”ืžื‘ื ื” ื‘ื™ื•, ืืฉืจ ืžื•ืขื‘ืจ ื‘ืขืช ื”ืชืงืฉืจื•ืช 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 ืฉืœ ื”ืชื”ืœื™ืš ื•ืืช ื—ื•ืชืžืช ื”ื–ืžืŸ, ืืš ืœื ืžืงืฆื™ื ื–ื™ื›ืจื•ืŸ ืœืžื‘ื ื” ื”ื ืชื•ื ื™ื ื”ื—ื“ืฉ. ื‘ืžืงื•ื ื–ืืช, ืื ื• ืžื—ืคืฉื™ื ื‘ื˜ื‘ืœืช ื”-hash ืžื‘ื ื” ืฉื›ื‘ืจ ืงื™ื™ื ื‘ืืžืฆืขื•ืช ื”ืžืคืชื— == 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")

ื ืฆื˜ืจืš ื’ื ืœืงื‘ื•ืข 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 monitor:

ืž-High Ceph Latency ืœ-Kernel Patch ื‘ืืžืฆืขื•ืช eBPF/BCC
ืกื•ืฃ ื›ืœ ืกื•ืฃ! ื›ืขืช ืื ื• ืจื•ืื™ื ืฉืžื” ืฉื ืจืื” ื›ืžื• ืžื›ืฉื™ืจ bcache ื ืชืงืข ื”ื•ื ืœืžืขืฉื” ืฉื™ื—ืช ื ืชืงืข generic_make_request() ืขื‘ื•ืจ ื“ื™ืกืง ืฉืžื•ืจ.

ื—ืคืจื• ืœืชื•ืš ื”ื’ืจืขื™ืŸ

ืžื” ื‘ื“ื™ื•ืง ืžืื˜ ื‘ืžื”ืœืš ืฉื™ื“ื•ืจ ื”ื‘ืงืฉื”? ืื ื• ืจื•ืื™ื ืฉื”ืขื™ื›ื•ื‘ ืžืชืจื—ืฉ ืขื•ื“ ืœืคื ื™ ืชื—ื™ืœืช ื—ืฉื‘ื•ื ืื•ืช ื”ื‘ืงืฉื”, ื›ืœื•ืžืจ. ืขื“ื™ื™ืŸ ืœื ื”ื—ืœ ื ื™ื”ื•ืœ ื—ืฉื‘ื•ื ืื•ืช ืฉืœ ื‘ืงืฉื” ืกืคืฆื™ืคื™ืช ืœืคืœื˜ ื ื•ืกืฃ ืฉืœ ืกื˜ื˜ื™ืกื˜ื™ืงื•ืช ืขืœื™ื” (/proc/diskstats ืื• iostat). ื ื™ืชืŸ ืœืืžืช ื–ืืช ื‘ืงืœื•ืช ืขืœ ื™ื“ื™ ื”ืคืขืœืช iostat ืชื•ืš ื›ื“ื™ ืฉื—ื–ื•ืจ ื”ื‘ืขื™ื”, ืื• ื‘ื™ื•ืœื ื˜ื™ื•ืช ืฉืœ ืกืงืจื™ืคื˜ BCC, ื”ืžื‘ื•ืกืก ืขืœ ื”ื”ืชื—ืœื” ื•ื”ืกื™ื•ื ืฉืœ ื—ืฉื‘ื•ื ืื•ืช ื‘ืงืฉื”. ืืฃ ืื—ื“ ืžื›ืœื™ ื”ืฉื™ืจื•ืช ื”ืœืœื• ืœื ื™ืฆื™ื’ ื‘ืขื™ื•ืช ืขื‘ื•ืจ ื‘ืงืฉื•ืช ืœื“ื™ืกืง ื”ืžืื•ื—ืกืŸ.

ืื ื ืกืชื›ืœ ืขืœ ื”ืคื•ื ืงืฆื™ื” generic_make_request(), ืื– ื ืจืื” ืฉืœืคื ื™ ืชื—ื™ืœืช ื ื™ื”ื•ืœ ื”ื‘ืงืฉื•ืช, ื ืงืจืื•ืช ืฉืชื™ ืคื•ื ืงืฆื™ื•ืช ื ื•ืกืคื•ืช. ืจืืฉื•ืŸ - generic_make_request_checks(), ืžื‘ืฆืข ื‘ื“ื™ืงื•ืช ืฉืœ ื—ื•ืงื™ื•ืช ื”ื‘ืงืฉื” ืœื’ื‘ื™ ื”ื’ื“ืจื•ืช ื”ื“ื™ืกืง. ืฉื ื™ืช - 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(), ืžื•ืจื™ื“ ืืช ื”ืžื•ื ื” freeze_depth.

ืขื›ืฉื™ื• ืื ื—ื ื• ื™ื•ื“ืขื™ื ืžืกืคื™ืง ื›ื“ื™ ืœืชืงืŸ ืืช ื”ืžืฆื‘. ื”ืคืงื•ื“ื” udevadm trigger ื’ื•ืจืžืช ืœื”ื—ืœืช ื”ื”ื’ื“ืจื•ืช ืฉืœ ื”ืชืงืŸ ื”ื—ืกื™ืžื”. ื”ื’ื“ืจื•ืช ืืœื• ืžืชื•ืืจื•ืช ื‘ื›ืœืœื™ 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, ื”ืžืืคืฉืจ ืœืš ืœื›ืชื•ื‘ One-Liner ืขื•ืฆืžืชื™ ื•ืชื•ื›ื ื™ื•ืช ืงื˜ื ื•ืช ื‘ืฉืคื” ื”ื“ื•ืžื” ืœ-awk. ืขื•ื“ - ebpf_exporter, ืžืืคืฉืจ ืœืš ืœืืกื•ืฃ ืžื“ื“ื™ื ื‘ืจืžื” ื ืžื•ื›ื” ื•ื‘ืจื–ื•ืœื•ืฆื™ื” ื’ื‘ื•ื”ื” ื™ืฉื™ืจื•ืช ืœืฉืจืช ื”ืคืจื•ืžืชืื•ืก ืฉืœืš, ืขื ื™ื›ื•ืœืช ืœืงื‘ืœ ืžืื•ื—ืจ ื™ื•ืชืจ ื”ื“ืžื™ื•ืช ื™ืคื•ืช ื•ืืคื™ืœื• ื”ืชืจืื•ืช.

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”