
eBPFトレースポイントを使用して、Linuxホスト上で発生するすべてのシグナルをリアルタイムでストリーミングし、送信元、ターゲット、処理方法、ハンドラレイテンシ、およびシステムコール割り込みを表示するライブカーネルシグナル観測ツール。
sigwire
tail -fシグナル用。 マシン上の任意のプロセスが発生させるすべてのシグナル — 誰が送ったか、誰が受けたか、どのシグナルか、どのように発生したか(kill(2)、カーネル、POSIX タイマー)、ターゲットがそれを捕捉したかどうかとハンドラの実行時間、ブロックされたシステムコールをEINTRで引き裂いたかどうか — カーネルのシグナルトレースポイントからデコードし、ライブで端末にストリーミングします。1つのPIDにstrace -fする必要も、ptraceも、関係するプロセスからの協力も不要です。
sigwire はカーネルのシグナル機構をライブパッチベイに変えます: 各行は sender ──SIGNAL──▶ target で、深刻度で色分けされ、発生方法でタグ付けされ、ターゲットが 捕捉 したか(およびハンドラの実行時間)、ブロックされたシステムコールを中断したか(↯ EINTR read)、スパム時に ×N に折りたたまれ、真の殺傷打撃の場合は ☠ でマークされます。 サイドレールは回線上を飛び交うものを集計します; 一時停止して行を選択すると全体像を検査できます — 処置、ハンドラアドレス、sigaction フラグ、その瞬間にターゲットがブロックしていたシグナル。
カーネルの トレースポイント をフックするため、特定のプロセスではなく、1回の実行でホスト上のすべてのシグナルを一度に監視します — あなたのアプリ、スーパーバイザー、カーネル自身のフォールト機構 — それらがトレースされていることに気づかれることなく。
[!TIP] すべてのシグナルには2つの側面があります。 sigwireは
signal:signal_generate(送信者 の視点 — 誰が何を発生させたか、スイッチボード回線)とsignal:signal_deliver(ターゲット の視点 — 捕捉したか、どのハンドラとフラグで、何をブロックしていたか、システムコールを中断したか)の両方を監視します。さらに2つのフック —rt_sigreturn(2)とシステムコール終了トレースポイント — がハンドラの時間を計測し、EINTRを捕捉します。これらはすべて1つの行に相関付けられます。この分割が、☠ 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)
[Manual install guide](https://yeet.cx/docs/manual-installation) | Linux only
設定は不要です — シグナルはあらゆるマシンで常時バックグラウンドトラフィックとして発生するため、行はすぐに上部に表示され始めます。自分でシグナルを発生させたい場合は? `kill -USR1 <pid>`、フォアグラウンドジョブを `Ctrl-C`、またはマネージドランタイムを起動して、そのGC/スケジューラが自身のスレッドにpingを送るのを確認してください(`↯ EINTR futex` が流れていきます)。
## コントロール
デフォルトではフィードは最新のシグナルを追跡します。行を選択するか一時停止すると、その位置で固定され、その間もデータは下に流れ続けます。
| キー | アクション |
| --- | ------ |
| `p` · `Space` | フィードの一時停止/再開(読み込みのために固定する) |
| `↑`/`↓`, `k`/`j` | 一時停止して行を検査 — 詳細パネルを開く |
| `/` | ファジーフィルター — プロセス、pid、シグナル、ソース、処理結果に一致。一致した文字がライブでハイライト表示されます |
| `e` | **中断されたシステムコールのみ**にフィルター(`↯ EINTR` / `↺ restarted`) |
| `s` | **シグナルピッカー**を開く — 任意のシグナルをミュートまたは表示、ライブで |
| `Esc` | 1階層戻る — フィルターをクリア/ピッカーを閉じる/選択を解除、その後終了 |
| `q` | 終了 |
## 表示内容
各行は生成された1つのシグナルであり、新しいものほど上に表示されます。```
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
各行は1つのブロックです。送信者 → 対象 は comm・pid(送信者はシグナルを上げたプロセス、current は現在のプロセス、対象はそのシグナルの宛先)で、中央の ワイヤ は重要度に応じて色付けされたシグナル名を表示し、×N は同じシグナルが連続したものを1行にまとめます。右側の 注釈 は発生源、システムコールの割り込み、そして処理方法を示します。
各行は配信が解決した瞬間に固定され、その後は変わりません。つまり、バーストはちらつく集約ではなく、安定したログとしてスクロールしていきます。
ワイヤは重要度に応じて色付けされ、UIの他の部分と同じ256色パレットを使用します。
| 重要度 | シグナル | 色 |
|---|---|---|
| kill | SIGKILL | ホットレッド |
| fatal (コアダンプ) | SEGV BUS ABRT ILL FPE TRAP SYS QUIT | レッド |
| terminating | TERM INT HUP PIPE ALRM … | アンバー |
| ジョブ制御 | STOP TSTP TTIN TTOU | イエロー |
| continue | CONT | グリーン |
| user | USR1 USR2 | シアン |
| リアルタイム | SIGRTMIN+n | バイオレット |
| housekeeping | CHLD URG WINCH … | グレー |
注釈 は発生源(kill(2)、tgkill、sigqueue、timer、kernel、fault)です。次に、ブロックされたシステムコールを割り込んだ場合、↯ EINTR read(または SA_RESTART で自動再開された場合は ↺ restarted read)が続きます。そして、処理方法(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`** 配送 — キャッチ不可、無視不可、常に致命的;**または**
- **カーネル自身が発生させた**(ユーザー空間の`kill`ではなく同期的フォールト)**コアダンプシグナル**(`SEGV`/`BUS`/`ABRT`/`ILL`/`FPE`/`TRAP`/`SYS`/`QUIT`)
その他すべて — `systemd`からの`SIGTERM`、Ctrl-Cからの`SIGINT`、ランタイムが自身のスレッドに送る`SIGPWR` — は表示され色付けされますが、死亡としてはカウントされません。おそらく死亡ではないからです。
## シグナルピッカー(ライブカーネルノブ)
どのビジーボックスでも3つのシグナルは純粋なバックグラウンドノイズです: `SIGCHLD`(すべての子プロセスの回収)、`SIGURG`(Goの非同期プリエンプションのハートビート)、`SIGWINCH`(ターミナルサイズ変更、フォアグラウンドプロセスすべてにブロードキャスト)。sigwireはデフォルトでこれら3つを**カーネル内で**ミュートし、フィードが興味深いトラフィックになるようにします — ただし、どのシグナルがノイズかはあなたの判断です。
`s`を押して**シグナルピッカー**を開きます: 各シグナルのライブ重大度色と表示回数をリストしたモーダルで、それぞれ`shown`と`muted`を切り替えられます。矢印で移動するか(または**数字を入力** — `1`、`5` → 15にジャンプ)、`space`を押すと、そのシグナルが即座に切り替わります。`a`は**すべて**を一度に切り替えます。タイトルバーの`muted`カウントは隠されている数を追跡します。
これがデモの双方向部分です: ミュートマスクは実行中のBPFプログラムの`.data`セクションにある`__u64`グローバルで、行を切り替えるとプログラムが実行し続けている間に`DataSec.patch()`を通じて対応するビットが修正されます。カーネルはミュートされたシグナルをリングバッファに到達する前にドロップするため、ミュートは何もコストがかからず — アンミュートするとリロードなしでシグナルがストリームに戻ります。
## 仕組み
コアは [`src/bpf/sigwire.bpf.c`](https://github.com/yeet-src/sigwire/blob/master/src/bpf/sigwire.bpf.c) + [`src/bpf/deliver.bpf.c`](https://github.com/yeet-src/sigwire/blob/master/src/bpf/deliver.bpf.c)(カーネル、1つのオブジェクトにリンク)と [`src/probes/sigwire.js`](https://github.com/yeet-src/sigwire/blob/master/src/probes/sigwire.js)(ユーザー空間)です。すべては`(target tid, signal)`によって関連付けられます。
### BPF 側
2つのソースファイルが1つのロード可能オブジェクト`bin/probe.bpf.o`にリンクされ、4つのトレースポイントプログラムがあります:
| Program | Attached to | What it captures |
|---|---|---|
| `on_signal_generate` | `signal:signal_generate` | 送信者(`current`)+ ターゲット(`comm`/`pid`)、シグナル、`si_code`、`group`フラグ、`result` — ライブの`mute_mask`でシグナルのビットが設定されている場合はカーネル内でドロップ |
| `on_signal_deliver` | `signal:signal_deliver` | 送信先のdisposition(`sa_handler`)、`sa_flags`、および — `task_struct`から — その`blocked`シグセット;ハンドラタイミングのために配送をスタンプ |
| (rt_sigreturn) | `syscalls:sys_enter_rt_sigreturn` | スタンプされた配送との差分をとり、ハンドラの実行時間を計算 |
| (sys_exit) | `raw_syscalls:sys_exit` | まれな`-ERESTART*`リターンを記録し、次の`signal_deliver`がそれを`EINTR`/`restarted` + 中断されたシステムコール番号に解決できるようにする |
マップがカーネルとユーザー空間を接続します: