watermint.org - Takayuki Okazaki's note

趣味のプログラム watermint toolbox と tbx

趣味とある程度の実用性を備えたプログラムとして watermint toolboxと最近始めたtbxというプロジェクトがあります。watermint toolboxは既に何度か紹介していますが、Dropbox向けのコマンドラインツールとして開発を始め、今はそれ以外にも多様なコマンドを備えるプログラムとして成長しました。執筆時点で、最初のコミットからおおよそ6年(最初のコミットが2016年11月)、toolboxとして集約する前のいくつかのサブプロジェクトも含めると6年半ほどになります。

Wicklow

プログラムの仕様想定として、WindowsやmacOSなどの環境で追加ライブラリ等を必要としない、いわゆるシングルバイナリ配布できることを重視していたのでプログラミング環境としてはGoを選択しました。当時も他の言語選択肢はありましたが、Better Cとして名高いこと、開発環境の成熟度(≒IntelliJ Goプラグインの成熟度)、学習環境(Stackoverflowや書籍等の情報源)の充実度を考慮して決めたのだと思います。

この6年間、Goでプログラムを作ってみた成果として「Goらしくプログラムする」こともそれなりに成功したと思っています。フルタイムの仕事としてプログラムをしていたときは、言語仕様を読んだり著名なライブラリ・プログラムの設計解説や、コードを読んだりして自身の設計や開発に適用したものですが、趣味としてプログラミングを継続させようとすると違ったアプローチが必要になります。

趣味として成立させるにはある程度短期的に達成できる成功体験が不可欠と考えています(仕事として成立させるにも重要ですが場合により、必須ではない)。具体的にはプログラミング言語のチュートリアルとしてHello Worldの出力に始まり、ファイル入出力、簡単なモックアップやプロトタイプ作成、少し本格的な設計の取り込み、開発規模拡大に従う課題への対応と進んでいくかと思います。この一つ一つの段階があまり飛躍し過ぎてしまうと趣味としての継続が難しいと考えています。仕事であれば、ある程度段階が飛躍したとしても時間や費用をかけ習得したり、先達の助けを借りてこの飛躍を乗り切ることもできるでしょう。

継続は力なりと言いますが、一方で継続を実現するにはある程度の成功体験を繰り返せるための計画性も必要になってくると思いますし、実際watermint toolbox開発でもそう実感しました。この実感には裏づけとなる失敗・成功を含む別例があります。

結果的には同じようなプログラムを何度も作っているのですが、2013年から2015年にかけてScalaで開発していたプロジェクトがあります。このプログラムはもともとDDDやScala言語の理解を深めるために始めたものですが、ある程度の複雑性がある具体的なユースケースが欲しいと思い日常的に必要な課題解決(チャット操作の効率化やクラウドストレージへのファイルアップロードなど)を実装したものです。DDDやScalaの理解という意味ではある程度進んだのですが、設計がやや壮大過ぎたこともありDDD・Scalaの理解がある程度進んだという最初の(やや曖昧な)ゴールを達成したことで自然消滅的にプロジェクトが終了しました。

当初ゴール達成という意味では成功なのですが、失敗だと思っているのはせっかく2弱年もかけて作ったプログラムがあまり自身の資産になっていないというところが大きな理由です。その点、watermint toolboxではプログラムとしての綺麗さやGo言語の習得という以上に、実用性をより重視して短期的な問題解決を優先したこともあり短期的に成功体験が得られ、より継続的な開発が進められるようになりました。

Dublin

今年2022年は、コロナ禍がある程度定常的なリスク・コストと認識され仕事上でも出張が再開された年でした。今年は海外へ行く機会があり、長時間フライト中は普段できない考えの整理ができるということでwatermint toolboxについても今後どうするか考えることにしました。

watermint toolboxは趣味と実用という意味ではなかなかの成功を収めたと思っています。これをさらに10年・20年とライフワーク的に開発し、資産として形成するにはどうすればよいか考えました。一つの議論はこのままGo言語で開発を進めるかということです。

Goは手軽さやエコシステムの充実といった意味で非常に優れていると思っています。一方でいくつかの理由によりある程度の大きさのプログラムを保守するのも難しそうだとも感じています。理由をある程度絞ると次の二つが挙げられます。

一つ目は型システムがJavaやScalaなどと比較しあまり充実しておらず、特にインタフェースの設計と実装ならびに保守がなかなか手間がかかることです。Go 1.18では待望のGenericsが導入されましたが、誤解を恐れず言えば適用範囲は限定的で関数の定義をマクロ的に複数型対応にコンパイル時に展開してくれる。という程度のもので、変数の宣言や構造体にGenericsのフィールドを定義できないなど型情報を資産として形成できるほどの機能はありません。このため、たとえばある型の配列から条件に合う値のみを抽出して別の配列を作成するという処理もGoでは毎回forループを書かなければなりません。そのforループにバグがあったりテストを書いたりしなければならないコストは趣味のプログラムには無視できない大きさです。

二つ目はエラー処理です。前述の型とも関連しますが現状のGo Genericsでは Javaでいうjava.util.Optional<T>・ScalaでいうOption[+A]といったnullに頼らないライブラリ群の構築ができません。Goでのエラー処理は戻り値リストの最後にerrorを返すというのが慣例です。この、errorもerrorインタフェースを実装したポインタということで、毎回型を調べてキャストしたり、別関数で判定したりと統一感もなく注意深くドキュメントを読んだとしてもエラー処理にまつわる不具合を生じやすいことが大きな問題だと感じています。

たとえばファイルが指定パスに存在するかどうかはGoでは次のように判定します。

_, err := os.Lstat("/path/to/file")
if os.IsExist(err) {
   // 存在する場合の処理
}

このLstatが返すエラーは PathErrorという構造体のものですが、このエラーの詳細を知ろうとする場合は次のようにキャストして調べる必要があります。errorは実際にはどのような型のものかドキュメントやソースを見なければ分からず、Javaでいうところの、全てjava.lang.Exceptionとして例外を扱っているようなものです。議論の余地はあるでしょうけれど、Goで6年プログラムしてみて有益と感じたことはありませんでした。

_, err := os.Lstat("/path/to/file")
if os.IsNotExist(err) { // ファイルが存在しない場合の処理
  switch e := err.(type) {
    case *fs.PathError:
      fmt.Printf("Op[%s] Path[%s] Error[%s]\n", e.Op, e.Path, e.Err)
  }
}

Goのエコシステム、プログラミング環境の充実度はすばらしく、たとえばQRコードを作るプログラムを作りたいなと思ったとき、boombuler/barcodeのようなライブラリがすぐに見つかります。 短期的な成功体験を得るという趣味のプログラムを支えるにはぴったりです。

しかし、10年後に資産となるプログラムという意味では少し言語機能が不足していると感じるのと、ある程度プログラムが大きくなってきた時に駆られる「全部書き直したい」というモチベーションを考慮して並行して新しいプロジェクトを始めることにしました。

新しいプロジェクトを始めるにあたって、6年前と比べればプログラミングの環境も大きく変わったように見えます。GraalVMやKotlin Native、Scala Nativeなどの登場・成熟で実行ファイルのバイナリ配布の敷居が下がり、選択肢が増えました。TIOBEのプログラミング言語コミュニティ指標を見てみると、Pythonがこの5〜6年で急成長しトップになり、Javaは今月発表された結果ではついにトップ3から陥落し4位になるなど様変わりしたようです。

新しいプロジェクトの言語をどれにするかは2ヶ月ほど悩んだ結果、Rustを使うことにしました。選定理由はGoではない言語にしようと考えた理由である(1) 型周りが充実していることが最重要で、(2) エコシステムがある程度大きく必要なライブラリが探せること、(3) どうせなら本格的にプログラミングで使ったことのない言語といった理由からです。

趣味のプログラムとしてRustを始めるにあたっては、バランスの問題でwatermint toolboxを始めた頃と比べて違う計画を立てました。watermint toolboxは短期的な成功体験を継続することで成長させてきましたが、新しいプロジェクトで同じことをやると新しいプロジェクト側の方が当然楽しくなってしまい、旧プロジェクトを触らなくなってしまいます。

これを避けるために新プロジェクト側はある程度長期的なゴール設定をし2つのプロジェクトを並行して進めることにしました。watermint toolboxは引き続き短期的な問題解決のために、tbxは10年後を見据えた資産にしていくことに。tbxでは、具体的な実行可能プログラムよりはライブラリ群を最初に整備していくことでRustを習得しつつ、ある程度加速的に開発できる状態までライブラリ群が成熟した段階でwatermint toolboxの機能を逐次取り込み置き換えを目指すというものです。

Rustを習得するにあたっては、いくつか順番をおって実装していくことにしました。まずは文字列操作、続いてUUIDなど今後利用するであろうライブラリの実装、乱数など外部ライブラリをラッピングしたライブラリの構築といった順番です。細かくテストできる範囲から実装することで、所有権などRustならではのコンセプトを学びます。既存の優れたライブラリをラッピングすることは漢字の書き取りのような感じで、読むだけでは思いつかないテクニックや設計が学べます。

おそらくこのようなライブラリ群で文字列、数値、時間、KVSやデータベース、ログなどを実装またはラッパーを実装することで学びを深め、1〜2年後ごろから本格的な実装をしていくという予定です。果たしてこのような計画で進めるかはわかりませんが、継続を優先し楽しく来年もコードを書いていきたいと思います。

Dublin

皆様もよいお年をお迎えください。

watermint toolboxでもっと手軽に負荷モニタリングしたい (ログ取得編)

色々と検証作業をしていると様々な情報が欲しくなる。プログラムがうまくいかなかったのがCPUを使い切ったからなのか、ネットワーク帯域が足りなかったからなのか大まかには知りたい。

こういう時、まずOSごとに装備されているモニタリングツールが違うのでWindows、macOS、Linuxを混在環境で使っているとこの時点で毎回この差異に気をつかう。データを時系列で残すのも難しくはないが、ちゃんとやるとなかなか面倒だ。かといって、本格的なモニタリングツールの導入は監視サーバを立てたり監視サーバまでのネットワーク疎通はどうしようかと悩んでいると結局そこまで準備するぐらいなら今回は諦めようとなる。

CPU負荷やネットワーク状況、ストレージIOなど欲を言えばIOPSが欲しいとか、毎秒の分解能が欲しいとか、プロセスごとのCPU・メモリ利用が欲しいとか様々欲しくなるが、まずそういった細かな情報よりも最低限の情報がささっと手軽にとれるツールが欲しい。

なければ作れば良いということで、2016年よりコツコツ開発しているwatermint toolboxというプロジェクトのコマンドの一つとして実装した。

今回はまだモニタリングして情報収集するところまでで、分析をするツールまでは作っていない。取得するデータはJSON形式データで集まるのでまずはJSON加工ツールや簡単なスクリプトを書いて分析するつもりのため、分析はまた将来的な拡張のお楽しみに。

概要

今回はutil monitor clientというコマンドとして実装した。 watermint toolboxはマルチプラットホーム対応でWindows、macOS (x64/M1)、Linux (x64/arm64)に対応したそれぞれのバイナリを配布している。シングルバイナリ動作するようコンパイルしてあるので、ほとんどの場合追加ライブラリは必要なく動作する。(なお、Alpine Linuxの場合はTBX on Alpine Linuxを参照)

このモニタリングツールはデフォルトでは10秒間隔で各種統計を取り、1時間に一度ローカルディスクに蓄積してあるデータをDropboxへ同期する。同期完了後にローカルディスクのデータは削除される。データはすべてDropboxのフォルダに集約されるので、監視サーバを立てたり、監視サーバまでの通信経路に気を遣わなくてもインターネットにさえ出られれば良い。

Dropboxに保存されたデータ

データは自動的に 対象名/yyyy-mm/yyyy-mm-dd/tbx-monitor-対象名-UNIXTIME.log.gz のようなフォルダ・ファイル名でgzip圧縮形式で保存される。また、watermint toolboxがクラッシュしてファイルがアップロードされていない場合は、再度同じように実行すれば次回実行時の同期処理時にすべてのファイルをアップロードする。なお、同期処理前はgzip圧縮が未実施のためローカルディスクの消費は1時間分で500KiB程度のサイズとなる。

ファイルサイズは概ね圧縮後で50KiB (10秒間隔・1時間分)、1日分でおおよそ1.2MB程度になる。これならば数週間といった単位で数台を監視してもさほど気にならないと思う。将来的には分析ツール部分を作る際にTSDBに格納しながらもう少しコンパクトなデータ形式で格納することになると思うが、今のフォルダ・ファイル形式でも分析までの受け渡しとしてはこの程度で良いだろうと思っている。

使い方

コマンド実行オプションなど詳細はutil monitor clientを参照いただきたいが大まかな流れは次の通り。

  1. 初回実行時にログをアップロードするDropboxアカウントへの認証を求められる
  2. 表示されたURLをブラウザに貼り付け認可
  3. その後表示されるコードをプログラムに貼り付ける

認可で取得した認証トークンは $HOME/.toolbox/secrets/secrets.db に格納される。2度目以降の実行ではここから認証トークンを取得するので再認証は不要である。

また、認証トークンを格納するファイルは -auth-database オプションでも指定可能である。たとえば次のように実行すると $HOME/Desktop/secrets.db に認証情報が保管される。

$ tbx util monitor client -auth-database $HOME/Desktop/secrets.db -name MONITOR_NAME -data-path $HOME/.toolbox/monitor -sync-path /monitor

これを応用すれば仮想マシンなどで認証操作をとばして監視を実施したい場合でも、認証データファイルと一緒にデプロイすれば良い。当然ながら、認証データファイルはパスワードと同様のものなのでアクセス権限などをしっかり設定することは前提となる。

長期間モニタリング

watermint toolboxはデバッグのために各種ログを自動的に出力するようになっている。これらのログは問題原因を探るには良いのだが、長期間モニタリングするにはストレージ容量を圧迫する懸念があるかもしれない。watermint toolboxは自動的にログローテートし一定サイズ以上にログファイルが肥大化しないように設計しているが、プログラムのクラッシュ等で回収しきれないログが残存する場合もある。

このような場合は次のように -skip-logging オプションを追加すると良い。このオプションにより、ほとんどのログがストレージへ書き込み処理なく実行されるのでログによるストレージ圧迫の懸念がなくなる。

$ tbx util monitor client -auth-database $HOME/Desktop/secrets.db -name MONITOR_NAME -data-path $HOME/.toolbox/monitor -sync-path /monitor -skip-logging

まだログの解析部分についてのプログラムがないので、負荷モニタリングという意味では道半分というところだ。今回のモニタリングプログラムについては、gopsutilプロジェクトの成果物を利用させていただいたので、ほぼ技術的な困難性はなかった。あえていうと、認証トークンが簡単にデプロイできるよう認証まわりのフレームワークを大きく書き換えたことが最も困難性が高かった。

またいつになるかはわからないが、解析編として解析プログラムが出来上がったときには紹介したいと思う。s

ブレーカー付きタップ選び

ブレーカー付きタップを複数設置しようとすると、タップの出っ張りが干渉して複数設置できないという悲しい状態になりました。 タップを綺麗に配置したかったので今回は複数のブレーカー付きタップを準備して検証しました。 残念ながら今回準備したどのタップメーカーも寸法仕様がWebページ上にないようだったので、これから購入される方の参考になれば。

比較

上図は左からオーム電機のHS-TMP2HH3-WHS-TM1AHL3-W、ヤザワのY02FUBHKS210BK、朝日電器 ELPAのA-S400Bと並べた様子。 こうして並べてみるとわかりやすいのですが、プラグ部分から上の出っ張りが各社まあまあ違います。この中ではELPAが最も出っぱっていて、他のコンセントに干渉しやすい形をしています。

なおHS-TM1AHL3-Wはブレーカーはついていない発煙ガードタップ。発煙ガードとは、オーム電機の特許で内部温度が150度以上になると自動的に通電が遮断されるヒューズのような安全装置のよう。 動画での解説を見る限りはヒューズのように使い捨てになる模様。HS-TMP2HH3-Wはブレーカーに加え、この発煙ガードが付いているよう。

さて、ヤザワ・朝日電器のものはプラグ部分からタップ上部まで出っ張りがあり、ここが干渉します。 出っ張り部分の長さを調べてみると、ヤザワ・朝日電器は17mm以上あります(下図はヤザワのY02FUBHKS210BK)。

Y02FUBHKS210BK

オーム電機のものはHS-TMP2HH3-WもHS-TM1AHL3-Wも9mm〜10mm程度。

HS-TMP2HH3-W

壁側2口コンセントの間隔を測ると24mm程度。 つまり2つ設置したい場合、出っ張りは12mm以内である必要があります。 (3口コンセントの場合はさらに狭くなります)

コンセントの間隔

このため消去法でオーム電機 HS-TMP2HH3-Wのみがブレーカー付きタップとして2つできる唯一のもののよう。

設置完了

2つ刺してみても少し余裕があってとてもレイアウトしやすいタップだということがよくわかりました。

BromptonとGarmin Varia RTL515のマウント

そろそろ乗り始めて丸6年になる折り畳み自転車Bromptonにサイクルコンピュータを導入することにしました。

昨年からiPhoneを使ってWithingsのHealth Mateアプリを使ったり、Stravaアプリを使ったりしてサイクリングの記録を残していましたが、 うまく記録が取れないことが増えてきていました。 ネットワークの調子なのかBluetoothの調子なのか、GPS信号がうまくiPhoneで取得できていないのか原因は定かではないですが 100kmぐらい走ったなかの前半50kmぐらいしか記録されていなかったり、全く記録されていなかったりと安定しません。

いろいろ検討の結果対策としてサイクルコンピュータ Garmin Edge 530 を導入しました。 今回はこのサイクルコンピュータと連動して使えるテールライト、Varia RTL515のお話。 このRTL515を使うと、レーダーで後続の車やバイクなどが接近してきたことをEdge 530と連動してアラームを出したり後方の状況が確認できます。

Garmin Edge 530とVarita RTL515

上図は実際にアラームが出ている状態です。写真では信号待ちで停車中ですが、後ろから車が接近していることを検出してアラームと表示で警告が出ています。

このように車やバイクなどが急接近すると赤色表示になって、複数台車がきていれば右側の丸印が複数表示されるという仕組みです。 最近はEVやハイブリッド車など静かに近づいてくる車も増えましたし、自転車専用道路を走っていても後ろからロードバイクなどで全速力で飛ばしてくる方もがいるのでヒヤヒヤします。 良識ある方々はスピードを落とした上で「通りま〜す」など、一声かけてくださるのですが残念ながらそういった方は少数で、強引に抜かそうとされる方も多く自衛手段を検討せざるを得ません。 こういったヒヤヒヤが少しでも緩和されるという意味ではとても価値のある製品かと思います。

さて表題の通りRTL515というテールライトのマウントについてですが、Bromptonは折り畳み自転車ということもあってマウントできる位置が限られます。 折りたたんだ後でも干渉しない位置にマウントしたかったのですが標準添付されているゴムパッドではうまく取りつきません。 どうしようか思案していたところ、面ファスナーで設置することを思いつきました。

Varita RTL515のマウント

少し強引ですがRTL515標準付属のシートポストマウントには幅1cm程度の穴が空いています。ここに面ファスナーを通して、ぐるりと回して取り付けました。 ややぐらぐらしますが、面ファスナーの安定性が高いので標準のゴムパッドで付けるよりはなんとなく面ファスナーの方が信頼性がありそうにも感じます。

面ファスナー

さて、この面ファスナーですが100円均一にちょうどいいサイズ感の物がありました。この製品の1cmの幅というのがなかなか絶妙で幅は調整することなくこのまま利用できました。 同じようにブロンプトンにRTL515をマウントさせようという方のご参考になれば幸いです。

Migrate to Cloudflare Pages

I mentioned that this site moved from GitHub Pages to Cloudflare Workers. This time, I changed the infrastructure to Cloudflare Pages. This change will not affect tracking code removal.