最近RustでCLIを作るのに突然ハマってきたのと、ISUCONが近づいていることもあって、以下のCLIを作ってみました。
使い方
現状はnginxのlog_formatをJSONとした上で特定のキーを持っていることを想定しています。 (こういう感じの設定をしておく👇)
log_format json escape=json '{''"time":"$time_iso8601",''"host":"$remote_addr",''"method":"$request_method",''"uri":"$request_uri",''"status":"$status",''"body_bytes":"$body_bytes_sent",''"referer":"$http_referer",''"ua":"$http_user_agent",''"request_time":"$request_time",''"response_time":"$upstream_response_time"''}';
この状態で吐かれたアクセスログを、 seki
にパイプして食わせるのみです。以下のような形のJSONが返ってきます。*1
$ cat access.log | seki [{"method": "GET", "uri": "/hello", "count": 1, "status_code": {"status_1xx": 0, "status_2xx": 0, "status_3xx": 1, "status_4xx": 0, "status_5xx": 0}, "response_time": {"min": 0.0, "max": 0.113, "avg": 0.113, "sum": 0.113, "p50": 0.113, "p75": 0.113, "p90": 0.113, "p95": 0.113, "p99": 0.113}}]
このJSONをどう使うかはユーザに委ねる、という感じです。
思想
tkuchiki/alpや matsuu/kataribeなど便利ツールが世の中にあるので新たに作る必要は特にないですが*2、思想としてはJSONとして返しておけば色々と加工したり独自のスクリプトに組み込んであれこれしやすそうではないか?と思って作ってみています。
そのため、 seki
自体が人間にみやすいフォーマットへの出力オプションを持つ予定はないです。
private-isu でちょっと使ってみた様子
実際に catatsuy/private-isuでも使ってみました。AMIを利用してEC2サーバを建てた上で実施しています。
まず seki
をインストールします。今回は Release v0.1.2 · stefafafan/seki · GitHubに載っている curlを使う手順を使います。
$ curl --proto'=https'--tlsv1.2-LsSf https://github.com/stefafafan/seki/releases/download/v0.1.2/seki-installer.sh | sh
PATHを通したかったら source
してねと言われるのでします。
$ source$HOME/.cargo/env
nginx.conf の log_format
を書き換えた上でベンチマークを回します。終わったら早速 seki
を使ってみます。
$ sudo cat /var/log/nginx/access.log | seki
これを何気なく実行すると結果が長すぎることがわかります。 /posts/12667
みたいなエンドポイントが沢山個別にならんでいるのでグルーピングします。
一旦エンドポイントの一覧をざっとみてみましょう。 jq
コマンドで .uri
のみ抽出します。
$ sudo cat /var/log/nginx/access.log | seki | jq '.[] | .uri'"/posts/12667""/posts/2005""/image/6908.jpg""/image/9785.jpg""/image/9228.jpg""/posts/556""/posts/4366""/image/1149.jpg""/posts/7523""/posts/5208""/posts/1797""/posts/3376""/@lindsay""/@violet""/image/1855.jpg""/image/6059.jpg""/posts/7627" ...
/posts/\d+
と /images/.+
と /@[a-zA-Z0-9]+
みたいなのが沢山ありそうですね。 config.toml
にグルーピングの設定を書きましょう。 リポジトリにもサンプルがあります。*3
github.com
$ vim config.toml $ cat config.toml [[grouping]]regexp='^/posts/\d+$'[[grouping]]regexp='^/@[a-zA-Z0-9]+$'name='/@:username'[[grouping]]regexp='^/image/.+'
regexp
に正規表現を書きます。 name
の指定はオプショナルですが、名前をつけたいときに書いておくとURLが並んだ時に見やすくなります。
今一度先ほどのコマンドを実行してみます。 seki
は同じ階層にある config.toml
をデフォルトで読み込みますが、 --config
で明示的にパスを指定することもできます。
$ sudo cat /var/log/nginx/access.log | seki | jq '.[] | .uri'"/login""/posts""/""/@:username""/admin/banned""/image/.+""/comment""/initialize""/js/main.js""/register""/""/css/style.css""^/posts/\\d+$""/favicon.ico""/login""/js/timeago.min.js""/logout"
いい感じにグルーピングができたので、あとは好きな形に加工してベンチの結果を確認してみましょう。今回は response_time.sum
順にしつつ、 column
コマンドで整形して出力してみます。
$ sudo cat /var/log/nginx/access.log | seki | \ jq -r"sort_by(-.response_time.sum) | \ .[] | \ [.method, \ .uri, \ .status_code.status_2xx, \ .status_code.status_3xx, \ .status_code.status_4xx, \ .status_code.status_5xx, \ .response_time.sum] | \ @tsv" | column -t GET / 3939000198.79800000000128 GET /posts 159000095.20300000000013 GET ^/posts/\\d+$ 282900093.72100000000103 POST /login 018720078.4420000000002 GET /@:username 48200040.025 GET /login 69400019.445999999999984 POST / 0162162017.529000000000003 GET /image/.+ 2977906910016.31000000000001 POST /register 02630012.548999999999992 GET /logout 0347009.674999999999988 POST /comment 0244007.659999999999998 GET /admin/banned 0026307.20899999999999 GET /initialize 10000.369 GET /css/style.css 55040000.0 GET /js/timeago.min.js 55040000.0 GET /favicon.ico 55040000.0 GET /js/main.js 55040000.0
GET /
が遅そうなので、ここの改善をしていきましょう。
作ってみての感想
当初思っていたよりもサクッとCLIが作れてよかった、 閉包テーブルによる階層構造をPlantUMLやMermaid形式で可視化するツールを作った - stefafafan の fa は3つですの時と同様に clap-rs/clapがやっぱり便利なのと、それ以外にも各種バイナリの用意やリリースのためのツール群 (crate-ci/cargo-releaseと axodotdev/cargo-dist) もとても助かりました。
そして実装の際に alp のこと改めて調べていましたが、めちゃくちゃ機能充実していてすごいなと思ったりしました。実装にあたってalpとkataribeのインターフェースはとても参考にしました、ありがとうございます🙏 複雑なjqのクエリを書くのは大変なのでこれら既存のツールを使うのが一番手っ取り早いなと改めて思いました(が自分でも作るのは楽しかったです)。
次は pt-query-digest代わりのCLIも作りたいです (?)。