topとjqでプロセスのリモート監視

jqで遊んでいたら、なんとなく思いついたので投稿。

# 用意するもの

CentOS release 6.7

- [jq](https://stedolan.github.io/jq/)
- topコマンド

# top + jq

topのバッチモードとjqで、topの出力をjson形式に変更。

```bash
$ top -b -n 1 | head -10 | tee top.txt
top - 01:58:31 up  2:47,  4 users,  load average: 0.00, 0.02, 0.00
Tasks:  99 total,   1 running,  98 sleeping,   0 stopped,   0 zombie
Cpu(s):  2.0%us,  0.9%sy,  0.0%ni, 96.4%id,  0.5%wa,  0.3%hi,  0.0%si,  0.0%st
Mem:    502040k total,   379992k used,   122048k free,    44600k buffers
Swap:  1015804k total,        0k used,  1015804k free,   258188k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    1 root      20   0 19232 1376 1100 S  0.0  0.3   0:00.41 init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd
    3 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0

# jqでjsonに変換
# メモリの使用率が多いやつ上位10件を取得
$ cat top.txt | jq -rcsR '[ gsub("\n$";"") | splits("\n") | sub(" +$";"") | sub("^ +";"")] | .[0:5] as $titles | .[6:] as $data | {"sys": [$titles | .[] | split(",") | [.[] | sub("^ +";"")]]} as $sys |  [$data[0] | splits(" +") | sub("%";"") | sub("\\+";"")] as $row_keys | $data[1:] | [.[] | [splits(" +")]] | {"task":[.[] | [. as $r | range($row_keys|length) | {"key": $row_keys[.], "value": $r[.]}] | from_entries] | sort_by(.MEM) | reverse | .[:10]} as $task | $sys * $task'
{"sys":[["top - 01:58:31 up  2:47","4 users","load average: 0.00","0.02","0.00"],["Tasks:  99 total","1 running","98 sleeping","0 stopped","0 zombie"],["Cpu(s):  2.0%us","0.9%sy","0.0%ni","96.4%id","0.5%wa","0.3%hi","0.0%si","0.0%st"],["Mem:    502040k total","379992k used","122048k free","44600k buffers"],["Swap:  1015804k total","0k used","1015804k free","258188k cached"]],"task":[{"PID":"1","USER":"root","PR":"20","NI":"0","VIRT":"19232","RES":"1376","SHR":"1100","S":"S","CPU":"0.0","MEM":"0.3","TIME":"0:00.41","COMMAND":"init"},{"PID":"3","USER":"root","PR":"RT","NI":"0","VIRT":"0","RES":"0","SHR":"0","S":"S","CPU":"0.0","MEM":"0.0","TIME":"0:00.00","COMMAND":"migration/0"},{"PID":"2","USER":"root","PR":"20","NI":"0","VIRT":"0","RES":"0","SHR":"0","S":"S","CPU":"0.0","MEM":"0.0","TIME":"0:00.00","COMMAND":"kthreadd"}]}

# 5秒ごとにtop+jq
$ while true ; do top -b -n 1 | jq -rcsR '[ gsub("\n$";"") | splits("\n") | sub(" +$";"") | sub("^ +";"")] | .[0:5] as $titles | .[6:] as $data | {"sys": [$titles | .[] | split(",") | [.[] | sub("^ +";"")]]} as $sys |  [$data[0] | splits(" +") | sub("%";"") | sub("\\+";"")] as $row_keys | $data[1:] | [.[] | [splits(" +")]] | {"task":[.[] | [. as $r | range($row_keys|length) | {"key": $row_keys[.], "value": $r[.]}] | from_entries] | sort_by(.MEM) | reverse | .[:10]} as $task | $sys * $task'; sleep 5; done

```

topとjqでサーバーのプロセスがjsonで取得できるようになりました。

# おまけ

ブラウザ上でリアルタイムに確認したいので、websocketのサーバーとクライアントを作成。

```toml:Cargo.toml
[dependencies]
ws = "*"

[[bin]]
name = "ws-server"
path = "src/main.rs"

[[bin]]
name = "ws-client"
path = "src/bin/client.rs"
```

## サーバー
メッセージが来たらブロードキャストするだけ。

```rust:src/main.rs
extern crate ws;

fn main() {
    ws::listen("<さーばーのIP>:<ぽーと>", |out| {
        move |msg| { out.broadcast(msg) }
    }).unwrap()
}
```

## クライアント
標準入力から持ってきてサーバーに送信するだけ。

```rust:src/bin/client.rs
extern crate ws;

use std::io::Read;

fn main() {
    ws::connect("<さーばーのIP>:<ぽーと>", |out| {
        let mut buf = String::new();
        let _ = std::io::stdin().read_to_string(&mut buf).unwrap();
        out.send(buf).unwrap();
        move |_| { out.close(ws::CloseCode::Normal) }
    }).unwrap();
}
```

## ビルドして実行

```bash
$ cargo build --release

# サーバー起動
$ ./target/release/ws-server

# 別のターミナルでクライアントぐるぐる
$ while true; do top -b -n 1 | jq -rcsR '[ gsub("\n$";"") | splits("\n") | sub(" +$";"") | sub("^ +";"")] | .[0:5] as $titles | .[6:] as $data | {"sys": [$titles | .[] | split(",") | [.[] | sub("^ +";"")]]} as $sys |  [$data[0] | splits(" +") | sub("%";"") | sub("\\+";"")] as $row_keys | $data[1:] | [.[] | [splits(" +")]] | {"task":[.[] | [. as $r | range($row_keys|length) | {"key": $row_keys[.], "value": $r[.]}] | from_entries] | sort_by(.MEM) | reverse | .[:10]} as $task | $sys * $task' | ./target/release/ws-client ; sleep 10 ; done

```

10秒ごとにtopのjsonが送信されてくるので、JSの`WebSocket("ws://<サーバーのIP>:<ポート>/")`で
ごにょごにょしたらいいと思います。