
أداة مراقبة إشارات النواة الحية باستخدام نقاط التتبع eBPF لدفق كل إشارة تُرفع على مضيف Linux، مع عرض المرسل، الهدف، التصرف، زمن تأخير المعالج، وانقطاعات استدعاءات النظام في الوقت الفعلي.
sigwire
tail -fللإشارات. كل إشارة يرفعها أي عملية على الجهاز — من أرسلها، من أصيبت، أي إشارة، كيف رُفعت (kill(2)، النواة، مؤقت POSIX)، هل التقطها الهدف وكم من الوقت استغرق معالجها، هل مزقت استدعاء نظام محظورًا معEINTR— مفكوكة من نقاط تتبع إشارات النواة وتُبث حية إلى طرفيتك. لاstrace -fعلى pid واحد، لاptrace، لا تعاون من العمليات المعنية.
sigwire يحول آلية إشارات النواة إلى لوحة توصيل حية: كل سطر هو sender ──SIGNAL──▶ target، ملون حسب الخطورة، موسوم بكيفية رفعه، هل الهدف التقطها (والمدة التي استغرقها معالجه)، هل قاطع استدعاء نظام محظورًا (↯ EINTR read)، مختصر إلى ×N عندما يكون هناك تكرار، ومميز بـ ☠ عندما تكون ضربة قاتلة حقيقية. شريط جانبي يحصي ما يمر عبر السلك؛ توقف واختر صفًا لفحص الصورة الكاملة — التصرف، عنوان المعالج، أعلام sigaction، والإشارات التي كان الهدف يحظرها في تلك اللحظة.
لأنه يربط tracepoints النواة، وليس بأي عملية واحدة، فإن تشغيلًا واحدًا يراقب كل إشارة على المضيف في آن واحد — تطبيقك، مشرف، آليات الأخطاء الخاصة بالنواة — دون أن يدرك أي منها أنه مراقب.
[!TIP] جانبان لكل إشارة. يراقب sigwire كلاً من
signal:signal_generate(منظور المرسل — من رفع ماذا، خط لوحة التوصيل) وsignal:signal_deliver(منظور الهدف — هل التقطها، بأي معالج وأعلام، ماذا كان يحظر، وهل قاطع استدعاء نظام). خطّا ربط إضافيان —rt_sigreturn(2)ونقطة تتبع الخروج من استدعاء النظام syscall-exit — يقيّسان وقت المعالج ويلتقطان EINTR. كل ذلك يُربط في صف واحد. هذا الانقسام هو أيضًا السبب في أن عدد☠ fatalمتحفظ عمدًا (انظر ما يعتبر قاتلًا): التوليد يحدث قبل التسليم، لذا لا يمكن لجانب المرسل معرفة مصير الإشارة — فقط جانب التسليم يمكنه ذلك، وفقط للحالات التي يلاحظها.
curl -fsSL https://yeet.cx | sh # install the yeet daemon (one time) yeet run github:yeet-src/sigwire # run the dashboard (the daemon does the privileged BPF load)
[دليل التثبيت اليدوي](https://yeet.cx/docs/manual-installation) | لينكس فقط
لا حاجة لتكوين أي شيء — الإشارات هي حركة مرور خلفية ثابتة على أي جهاز، لذا تبدأ الصفوف في الوصول إلى الأعلى فورًا. هل تريد إنشاء بعضها بنفسك؟ `kill -USR1 <pid>`، `Ctrl-C` لجلب مهمة أمامية، أو قم بتشغيل أي بيئة تشغيل مُدارة وشاهد GC/المجدول الخاص بها وهو ينبه خيوطه الخاصة (`↯ EINTR futex scrolling by`).
## التحكم
يتابع الخلاصة الإشارة الأحدث افتراضيًا؛ عند تحديد صف أو الإيقاف المؤقت، فإنها تثبت بينما تستمر البيانات في التدفق بالأسفل.
| المفتاح | الإجراء |
| --- | ------ |
| `p` · `Space` | إيقاف مؤقت / استئناف الخلاصة (تجميدها للقراءة) |
| `↑`/`↓`, `k`/`j` | إيقاف مؤقت وتفقد صف — يفتح لوحة التفاصيل |
| `/` | تصفية غامضة — تطابق العملية، PID، الإشارة، المصدر، والتصرف؛ تتظليل الأحرف المتطابقة مباشرة |
| `e` | تصفية إلى **استدعاءات النظام المنقطعة فقط** (`↯ EINTR` / `↺ معاد تشغيلها`) |
| `s` | افتح **منتقي الإشارات** — كتم أو إظهار أي إشارة، مباشر |
| `Esc` | الرجوع طبقة واحدة — مسح التصفية / إغلاق المنتقي / إسقاط التحديد، ثم خروج |
| `q` | خروج |
## ما الذي تراه
كل صف هو إشارة مولدة، الأحدث في الأعلى:```
WHEN SENDER SIGNAL TARGET NOTE
now bash·4402──SIGINT───▶ node·8813 kill(2) ↯ EINTR read caught 41µs
1.2s systemd·1──────SIGTERM──▶ nginx·1291 kill(2) caught 1.2ms
3.4s kernel·8813──SIGSEGV──▶ chrome·8813 fault default ☠
4.1s postgres·507──SIGUSR1───▶ postgres·509 ×6 kill(2) caught 9µs
كل صف هو كتلة واحدة: المرسِل ← الهدف هما comm·pid (المرسِل هو من أرسل الإشارة، current؛ الهدف هو من تُوجَّه إليه)، السلك في المنتصف يحمل اسم الإشارة ملونًا حسب الشدة، ×N يدمج دفعة من الإشارات المتطابقة في سطر واحد، و الملاحظة على اليمين تعطي المصدر، ثم أي انقطاع في استدعاء النظام، ثم التصرف.
كل صف يُجمَّد في اللحظة التي يتحقق فيها تسليمه ولا يتغير مرة أخرى — لذا تتدفق الدفعة كسجل ثابت، لا كتجمع وامض.
السلك ملون حسب الشدة على نفس لوحة الألوان 256-color المستخدمة في باقي واجهة المستخدم:
| الشدة | الإشارات | اللون |
|---|---|---|
| قاتل | SIGKILL | أحمر حار |
| مميت (تخزين أساسي) | SEGV BUS ABRT ILL FPE TRAP SYS QUIT | أحمر |
| إنهاء | TERM INT HUP PIPE ALRM … | كهرماني |
| تحكم بالوظائف | STOP TSTP TTIN TTOU | أصفر |
| استمرار | CONT | أخضر |
| مستخدم | USR1 USR2 | سماوي |
| زمن حقيقي | SIGRTMIN+n | بنفسجي |
| صيانة | CHLD URG WINCH … | رمادي |
الملاحظة هي المصدر (kill(2), tgkill, sigqueue, timer, kernel, fault)؛ ثم إذا قطعت استدعاء نظام محظورًا، ↯ EINTR read (أو ↺ restarted read عندما استأنفه SA_RESTART تلقائيًا)؛ ثم التصرف — caught 41µs (تم تشغيل معالج، وكم استغرق)، default (لا معالج، تم تطبيق الإجراء الافتراضي)، أو ⊘ ignored. تُشير ☠ إلى ضربة قاتلة حقيقية (انظر ما يُعتبر قاتلاً).
[!NOTE]
↯ EINTRهو ما يجب مراقبته. إشارة تصل بينما يكون الخيط معلقًا في استدعاء نظام بطيء (read,poll,accept,futex,nanosleep, …) تخرجه: استدعاء النظام يُرجع-1/EINTR، وما لم يضبط المعالجSA_RESTART، فإنه لا يستأنف — يجب على التطبيق إعادة المحاولة. نسيان ذلك هو خطأ كلاسيكي، محبط، يعتمد على التوقيت ("لماذا فشلread()مرة واحدة؟"). يُظهر sigwire ذلك يحدث مباشرة، وأي استدعاء نظام تعرض للضربة. اضغطeلإخفاء كل شيء آخر ومشاهدة المقاطعات فقط.
السكة على اليمين هي العرض الإجمالي: أهم الإشارات من حيث الحجم، تفصيل حسب المصدر، وإحصاء التسليم — كم عدد الإشارات التي تم التقاطها مقابل تلك التي طبقت الوضع الافتراضي مقابل تلك التي تم تجاهلها.
اضغط ↑/↓ (أو p) لتجميد التدفق واختيار صف؛ تتحول السكة إلى لوحة تفاصيل تحتوي على كل ما يعرفه جانب التسليم عن تلك الإشارة بالضبط:```
SIGNAL
SIGUSR1 (10) user
from ctarget·3980913
to ctarget·3980913
RAISED
via tgkill
code SI_TKILL
scope thread
result delivered
DELIVERY
handled caught
syscall EINTR ← read
handler 0x55f0a1c3
ran 3.0ms
flags SA_SIGINFO
TARGET BLOCKS
SIGINT SIGQUIT SIGTERM
- **handled** — `caught` (قام بتشغيل معالج في وضع المستخدم)، `default` (→ الإجراء الافتراضي: إنهاء / تفريغ أساسي / إيقاف / تجاهل)، أو `ignored`.
- **syscall** — إذا تسبب هذه الإشارة في مقاطعة استدعاء نظام محظور: `EINTR ← read` (يرى المستخدم `EINTR`) أو `restarted read` (استأنفه `SA_RESTART` بشفافية).
- **ran** — مدة تنفيذ المعالج، مُقاسة من لحظة التسليم إلى `rt_sigreturn(2)` الذي ينهيه. (البيئات التي تكتفي بوضع علامة في المعالج بلغة C ثم تقوم بالعمل الحقيقي لاحقًا — مثل CPython, Go — تظهر وقتًا صغيرًا هنا؛ هذا يعود لهم، وليس إلى sigwire.)
- **flags** — أعلام `sigaction` الخاصة بالمعالج (`SA_RESTART`, `SA_SIGINFO`, `SA_NODEFER`, …).
- **TARGET BLOCKS** — الإشارات التي كان الهدف قد حظرها (أي `sigprocmask` الخاص به) في لحظة التسليم، مباشرة من `task_struct` الخاصة به.
`Esc` يغلق المفتش؛ `p` يستأنف البث المباشر.
## ما يُعتبر مميتًا
عداد `☠ fatal` وشارة الصف `☠` صارمان عن قصد. لأن `signal_generate` يُطلق عند *التوليد*، لا تستطيع sigwire معرفة ما إذا كان الهدف قد قام بتثبيت معالج — فقد يتم التقاط `SIGTERM` وتحويله إلى إغلاق نظيف، أو تجاهله تمامًا. لذا فهي تحسب الوفاة فقط عندما تكون لا لبس فيها:
- **تسليم `SIGKILL`** — غير قابل للالتقاط، غير قابل للتجاهل، مميت دائمًا؛ **أو**
- إشارة **تؤدي إلى تفريغ أساسي** (`SEGV`/`BUS`/`ABRT`/`ILL`/`FPE`/`TRAP`/`SYS`/`QUIT`) قامت **النواة نفسها برفعها** (خطأ متزامن، وليس `kill` من المستخدم).