filtのコマンド履歴を保存できるようにした

"トライアルアンドエラーパイプ"ことfiltで、パイプに繋いだコマンドの実行履歴を保存して次回filt実行時に補完候補として利用できるようにしました

k1low.hatenablog.com

コマンド履歴保存の有効化方法

以下のコマンドを実行することで有効化できます。

$ filt config history.enable true

無効化は

$ filt config history.enable false

です。

コマンド実行を再利用したい人は有効化してみてください

"2 hours" のような文字列をパースして time.Duration を取得する duration パッケージを作った

Harvest のログ取得期間指定をより柔軟にできるように --duration オプションを追加したかったのですが、

  • 標準の time.ParseDuration() ではあまり柔軟ではない
  • 既存パッケージを探したけれども time.Duration を返すものを見つけられなかった

ので作りました。

github.com

使い方

duration.Parse()time.ParseDuration() と同じように利用できます。

time.ParseDuration() と異なる点は 4hours1minute などの単位表記も判定できるという点です。

package main

import (
    "fmt"

    "github.com/k1LoW/duration"
)

func main() {
    d, _ := duration.Parse("3 days 4 hours")
    fmt.Printf("%s", d)
    // Output: 76h0m0s
}

これだけです。

というわけで

--duration オプションを作りたいときに、ご検討ください。

debパッケージをダウンロードURLを指定して直接インストールしたい

yumコマンドならできるのに」

そんなことを思ったことが何度もありました。皆さんはどうしているんでしょうか?

シェルスクリプトで書くならどんな感じかなと思って書いてみました。

そして、そのシェルスクリプトを毎回コピペするくらいならスクリプト自体もcURLで取得して実行するようにしてみました。

github.com

使い方

$ curl -L https://git.io/dpkg-i-from-url | bash -s -- [DEB_URL]

以上です。

使ってみても全然便利になった気持ちになれないのはなんなんでしょうかね。。。?

サーバ/プロセスのメトリクスを使ったNagios/Mackerel/Consulのチェックコマンドを作るときに便利な metr を作った

Consulでちょっとしたヘルスチェックを追加したいと思ったのですが、例えば iowaitが高いかつuserは低いとき という条件を書こうとしたときに、「うっ。。!どう書けばいいんだ。。」となってしまったので、作りました。

github.com

これはなに

metr は次のような利用を想定したコマンドです

  • シェルスクリプトにホストやプロセスのメトリクスの値を使った条件を組み込む
  • Nagios pluginとして利用する
  • Mackerel check pluginにチェックコマンドとして利用する

使い方

インストールはHomebrew以外にdeb/rpmパッケージを用意しています。基本的にサーバにコマンドとしてインストールするのが良いでしょう

$ dpkg -i metr_0.5.1-1_amd64.deb

metr list

取得できるメトリクスは metr list で確認できます。また -p (--pid) でプロセスのPIDを指定すると、プロセスのメトリクスも対象にできます。

以下はMac上で実行しましたが、Linux上だともう少し取得できるメトリクスが増えます。

$ metr list
cpu (now:33.084577 %): Percentage of cpu used.
mem (now:66.468358 %): Percentage of RAM used.
swap (now:875823104 bytes): Amount of memory that has been swapped out to disk (bytes).
user (now:18.610422 %): Percentage of CPU utilization that occurred while executing at the user level.
system (now:14.143921 %): Percentage of CPU utilization that occurred while executing at the system level.
idle (now:67.245658 %): Percentage of time that CPUs were idle and the system did not have an outstanding disk I/O request.
nice (now:0.000000 %): Percentage of CPU utilization that occurred while executing at the user level with nice priority.
load1 (now:3.640000 ): Load avarage for 1 minute.
load5 (now:4.210000 ): Load avarage for 5 minutes.
load15 (now:4.600000 ): Load avarage for 15 minutes.
numcpu (now:8 ): Number of logical CPUs.
(metric measurement interval: 500 ms)
$ metr list -p `pgrep -n docker`
proc_cpu (now:1.820857 %): Percentage of the CPU time the process uses.
proc_mem (now:1.264739 %): Percentage of the total RAM the process uses.
proc_rss (now:217280512 bytes): Non-swapped physical memory the process uses (bytes).
proc_vms (now:7010299904 bytes): Amount of virtual memory the process uses (bytes).
proc_swap (now:0 bytes): Amount of memory that has been swapped out to disk the process uses (bytes).
proc_connections (now:0 ): Amount of connections(TCP, UDP or UNIX) the process uses.
cpu (now:22.000000 %): Percentage of cpu used.
mem (now:59.768772 %): Percentage of RAM used.
swap (now:781451264 bytes): Amount of memory that has been swapped out to disk (bytes).
user (now:14.925373 %): Percentage of CPU utilization that occurred while executing at the user level.
system (now:6.467662 %): Percentage of CPU utilization that occurred while executing at the system level.
idle (now:78.606965 %): Percentage of time that CPUs were idle and the system did not have an outstanding disk I/O request.
nice (now:0.000000 %): Percentage of CPU utilization that occurred while executing at the user level with nice priority.
load1 (now:1.360000 ): Load avarage for 1 minute.
load5 (now:1.610000 ): Load avarage for 5 minutes.
load15 (now:1.490000 ): Load avarage for 15 minutes.
numcpu (now:8 ): Number of logical CPUs.
(metric measurement interval: 500 ms)

metr cond ( alias: metr test )

metr condシェルスクリプトなどでメトリクスを条件に使いたいときなどに利用できます。

metr condtest コマンドと同じように、条件にマッチすると終了ステータス0 、マッチしないと 1 で終了します。

$ metr cond 'cpu < 20 and mem < 50' || somecommand

上記の場合、ホストのCPU使用率が20%以上かホストのメモリ使用率が50%以上のときに somecommand が実行されます。

ちなみに、利用できるオペレーターは

+, -, *, /, ==, !=, <, >, <=, >=, not, and, or, !, &&, || などです。

metr check

metr checkNagiosプラグインMackerelのチェック監視 とほぼ同じ終了ステータスの仕様になっています。

終了ステータス 意味
0 OK
1 WARNING
2 CRITICAL
3 UNKNOWN
$ metr check -w 'cpu > 10 or mem > 50' -c 'cpu > 50 and mem > 90'
METR WARNING: w(cpu > 10 or mem > 50) c(cpu > 50 and mem > 90)

上記の場合、ホストのCPU使用率が10%を超えたかメモリ使用率を50%を超えたときに WARNING(終了ステータス 1)、CPU使用率が50%を超えた、かつメモリ使用率を90%を超えたときに CRITICAL (終了ステータス 2)となります。

というわけで

簡単にメトリクスを利用したチェックコマンドが作れます。楽したいときなど、是非使ってみてください。


以下、余談です。

vmstatの1行目は何を表しているか( metr--interval オプション)

RedHatのCustmer Portalの「Vmstat の出力結果はどのように解釈すれば良いですか?」という質問の回答によると

このレポートの最初の行には、コンピューターが最後に再起動してからの平均値が記載されます。

とあります。

procps-ngのvmstatのソースコードをみてみます。値はgetstat関数で取得しているのですが、具体的には /proc/stat の値を読み取っています。

https://gitlab.com/procps-ng/procps/blob/master/proc/sysinfo.c#L532

そしてmain loopに入る前に1行出力しています。

https://gitlab.com/procps-ng/procps/blob/master/vmstat.c#L306-361

以降はmain loop内で差分をsleep前後の値の差分を利用しています。

つまり、1行目と2行目以降で値の特性が違うということになります。そして大抵欲しいのは2行目以降の値になります。

metr は上記の(sleepで実現している)時間間隔を -i (--interval) としてmsで指定できるようにしています(デフォルトは500msです)。

vmstatでもvmstat 1 2 | tail -1 とすれば1秒間隔の差分での計測値を取得することができます。

metr だともう少し簡単に取得できて、かつ複数のメトリクスで条件などを組みやすいという感じです。

ちなみにmackarel-agentも同じように前後の計測の差分で計測しているようです。

https://github.com/mackerelio/mackerel-agent/blob/c9db8018714023fef9c8ba7eac20471e8a1b297a/metrics/linux/cpuusage.go#L44-L70

Sheer Heart Attack のコアライブラリとして組み込んだ

私は先行して、Sheer Heart Attackというデバッグ用ツールを作っています。

k1low.hatenablog.com

両ツールはメトリクスの取得という点では同じなので、今回、 metr をコアライブラリとしてSheer Heart Attackに組み込むことで実装の共通化もしました。

[BREAKING] Use github.com/k1LoW/metr by k1LoW · Pull Request #21 · k1LoW/sheer-heart-attack · GitHub

metrを拡張していくことでSheer Heart Attackも自動でパワーアップするという寸法です。

metrの読み方

いつものように metrics を短くしただけなんですけど、なんとも読みづらい箇所で省略したなと。。

「メタァ」とか「メトロ」とかで呼んでいます。まあ、呼ぶことはないと思うので大丈夫かな。。

標準入力の指定の文字列に色をつけるツール colr

Go Conference ‘19 Summer in Fukuokaでも少し紹介したcolrです。

github.com

これはなに

まずは以下のスクリーンキャストをご覧ください

f:id:k1LoW:20190813073706g:plain

colrtail -F /path/to/access.log のようなログにカジュアルに色をつけるツールです。

$ tail -F /var/log/nginx/access.log | colr POST GET 404 500 search

のように何個でもカラーにしたい文字列を指定できます。

これだけです

色の指定もできませんし、その他のオプションもありません。 colr の後に引数としてカラーにしたい文字列を並べるだけです。

実は正規表現っぽい文字列を渡すと正規表現マッチに変更してくれますが、それでも使い方は変わりません。

これだけの機能、ただその分、何も考えずに利用できます。

是非ご利用ください。単純なツールですがなにげに重宝します。

Windowsにも対応しています!


以下余談です。

grep --color で色をつけてもパイプで繋ぐと色が消える。その仕組み

colr の紹介をすると「それ grep --color でいいんじゃ?」と言われます。「いや、私は絞込みたいわけではないんだよ」となるのですが、今回は別の話です。

例えば

$ tail -F /var/log/nginx/access.log | grep POST --color

とすると POST に色がつきますが、

$ tail -F /var/log/nginx/access.log | grep POST --color | grep search

とすると POST の色が消えてしまいます。

ちなみにこれを回避する方法は grep POST --color=always とすればいいのですが、パイプで繋ぐとなぜ色が消えるのか考えてみました。

ターミナル上で色をつける仕組み

ターミナルで色をつける仕組みは特殊なエスケープコードを使って文字列を囲むことで実現しています。

en.wikipedia.org

例えば、文字列を赤くするためには以下のような文字列を標準出力に出力します(iTerm2想定)。

fmt.Println("\x1b[31;1mHello, World!\x1b[0m")

ターミナルは、上記エスケープコード付き文字列を「色つけの指示付きの文字列」と解釈してエスケープコード部分を省いて色をつけた形で表示します。

つまり、色をつけているのはターミナルです。

エスケープコードを解釈するのがターミナルでない場合(パイプにつなげたりリダイレクトでファイルに保存する場合)、エスケープコードは「ただの文字列として解釈されてしまう = 不必要な文字列がついている状態」となるわけです。

上記を回避するために grep --color ではターミナル(擬似端末)かどうかを判断してエスケープコードの出力を制御しているようです。grep --color=always はその判定処理をスキップしているわけですね。

ちなみに、擬似端末判定処理について、Golangだと github.com/mattn/go-isatty が有名だと思います。

github.com

colrは色をつけるツール

colr はシンプルに色をつけるためだけのツールです。色をつけなければ何もしないただリソースを消費するだけのコマンドになってしまいます。

なので、「 colr を利用するときは、どうしても色をつけたいのだろう」と考え、(利用カラーリングライブラリにはターミナル判定処理がついているのですが、)上記の擬似判定処理をスキップしています。

これで、どう使っても色がつくようになりました。

おすすめ

Goならわかるシステムプログラミング

Goならわかるシステムプログラミング

Goなどでクロスコンパイルしたワンバイナリなソフトウェアを最小工数でdeb/RPMパッケージにしてくれるNFPMが便利

みなさんはdeb/RPMパッケージを作ったことはありますか?

私はtcpdpのパッケージ作成ではじめて作りました。具体的にはここらへんです。

tcpdpはlibpcapに依存していることもあり、DockerでUbuntu/CentOSの環境を作ってその上でコンパイルして、合わせてdeb/RPMパッケージ化までするようにしています。

本来Goはクロスコンパイルができるので、コンパイルしてできたバイナリをdeb/RPMパッケージにできればいいわけです。

ただ、意外にdeb/RPMパッケージをするための前準備が面倒です。

debパッケージであればcontrolファイルを書かないといけないですし、RPMパッケージであればspecファイルを書く必要があります。それぞれ覚えているほど書く機会はないので毎回ググるはめに。

ビルド方法にもお作法があります。これもあまり覚えていませんがディレクトリ構成などいろいろ面倒だった記憶があります。。

いくらワンバイナリとはいえaptやyumで管理できるメリットは大きいので、できればdeb/RPMパッケージも用意したいのですが、上記のような面倒さから億劫になっていました。

NFPM

ここで見つけたのがNFPMです。

github.com

NFPMはGoReleaserのorg配下にあり、GoReleaser( .goreleaser.yml )からも呼び出せるようになっています。むしろGoReleaser経由の方法を紹介します。

実際にcgrpsの設定を元に説明します。

cgrps/.goreleaser.yml at master · k1LoW/cgrps · GitHub

builds ディレクティブで id を付与する

まず、builds ディレクティブでビルド方法を指定すると思いますが、こちらで id を付与して nfpms ディレクティブで指定できるようにします。

builds:
-
  id: cgrps-linux 👈
  env:
  - CGO_ENABLED=0
  goos:
    - linux
  goarch:
    - amd64

ちょっと古い .goreleaser.yml の書き方だと id の指定は必要ありませんが、これの機会に指定しておきましょう。

nfpms ディレクティブを追加する

nfpms ディレクティブを追加します。

cgrpsでは以下のようにしています。

nfpms:
  -
    id: cgrps-nfpms
    name_template: "{{ .ProjectName }}_{{ .Version }}-1_{{ .Arch }}"
    builds:
    - cgrps-linux 👈
    homepage: https://github.com/k1LoW/cgrps
    maintainer: Ken'ichiro Oyama <k1lowxb@gmail.com>
    description: cgrps is a set of commands for checking cgroups.
    license: MIT
    formats:
      - deb
      - rpm
    bindir: /usr/bin
    epoch: 1

詳しくは GoReleaserのNFPMの設定のドキュメント をご覧ください。

nfpms.builds には、先ほどの builds.id を指定します。

UbuntuCentOSで利用するdeb/RPMパッケージであれば、Linux用にコンパイルする設定を書いた builds.id を指定することになると思います。

goreleaserコマンドを実行

設定は、これで終わりです。

試しに以下のコマンドを実行すると dist/ ディレクトリにdeb/RPMパッケージが作成されていると思います。

$ goreleaser --snapshot --skip-publish --rm-dist

あとはいつものように goreleaser コマンドを実行することでリリースが完了します。

NFPMでは何をしているのか?

では実際にNFPMでは何をしているのか気になったので、ソースコードを見てみました。

愚直にspecファイルを書いてrpmbuildコマンドを叩いていました *1

ある意味安心な実装でしたので、みなさんも是非ご利用ください。

*1:なので、実はMacではbrew install rpmが必要です

tblsがデータソースとしてCloud Spannerに対応した

約1,000円の資金を投じて作りました。

Cloud Spannerのスキーマをtblsで出力するとどのようになるのか

例えば、 https://github.com/k1LoW/tbls/blob/master/testdata/spanner.sqlSQLで作成したデータベーススキーマから以下のようなドキュメントが生成されます。

https://github.com/k1LoW/tbls/tree/master/sample/spanner

使い方

tblsの他のRDBMSと使い方は変わりません。Cloud SpannerのDSNは

spanner://[ProjectID]/[InstanceID]/[databaseID]

となっています。ADCの設定がされていて( gcloud auth application-default login していて)、データベースへのアクセス権限があれば、もう tbls doc でのドキュメント生成が可能なはずです。

サービスアカウントのクレデンシャルファイルがあれば、?creds=[GOOGLE_APPLICATION_CREDENTIALS] をDSNに付与する形での対応も可能です。

$ tbls doc spanner://[ProjectID]/[InstanceID]/[databaseID]?creds=spanner_client_secrets.json

Cloud Spannerの運用にtblsを利用すると何が嬉しいのか

「Cloud SpannerはGCP上にスキーマ情報を確認できるWebUIがあるので特にいらない」となるかと思います。

ただ、以下の点でCloud Spannerの運用にtblsを使うメリットがあると考えています。

1. .tbls.yml を使ってテーブルコメント/カラムコメントをドキュメントにでき、CIを回せる

.tbls.yml によるコメントの付与は、MySQLPostgreSQLといったRDBMSにおいては「 ALTER TABLE する必要なく、コメント運用ができる」というメリットをうたっていましたが、Cloud Spannerにはそもそもテーブルコメント/カラムコメントというものがないようです。

tblsであれば、 .tbls.yml の comments ディレクティブで各テーブル各カラムコメントを付与して、さらに tbls lint を使ってCIを回せばコメントの追加漏れがなくなる*1、など、スキーマの長期運用においてメリットがあると考えています。

2. .tbls.yml で設計上テーブル間をINTERLEAVEにしない判断をしたが、実は存在するテーブル間の関係をドキュメントにでき、CIを回せる

tblsではINTERLEAVEで親子関係になっているテーブルについて、リレーションがあると判断してその情報をドキュメントに追加します。

INTERLEAVEについては以下のドキュメントをご覧ください。

cloud.google.com

一方で、何かしらの理由でINTERLEAVEで親子関係にはしていないものの、論理的にはテーブル間に関係があるということはあります。

tblsはそれらについても .tbls.yml の relations ディレクティブでテーブル間の関係をドキュメントに追加できます。そして tbls lint を使ったチェックが可能です*2

これもMySQLPostgreSQLといったRDBMSにおいては「外部キー制約がないがテーブル間の関係があるということをドキュメント化できる」というようにメリットをうたっていたのですが、Cloud SpannerのINTERLEAVEの特性をみると外部キー制約よりも積極的にインターリーブ しない という選択がありそうなので、「制約はないけど実は関係がある」というような情報を付与するのにtblsはメリットがありそうです。

というわけで

Amazon Redshiftに続き、Cloud Spannerもちゃんとプロダクションで使ったことがないので是非フィードバックが欲しいと考えています。

既にCloud Spannerをガンガン使っている皆さま、是非使ってみてください。

*1:コメントがないカラムやテーブルに対してチェックができる requireTableComment や requireColumnComment があります

*2:リレーションのないテーブルをチェックできる unrelatedTable があります