RubyKaigi2023 @MATZ本

2023-05-15 23:59:59 +0900

行ってきたのでまとめます。LT やったのですが、 LT の話はまた今度。

Day -1

前日は恒例の Asakusa.rbpredrinkup に行ってました。ここでは、 nobu とかとはなししたりしてすごしました。 drinkup は次の日のため終了後すぐさま帰宅し、就寝した。

Day 0

この日は、 RubyistsOnRails というすばらしいイベント列車があったのでノータイム 1 8時の会に参加申し込み。6時に起床し、7時に出発。 会ではRubyistと話しながら、山見ながら、松本に到着。 松本に到着後飯として みよ田 という蕎麦屋に直行。 蕎麦を食べた直後、松本城へ。松本城では まつもとさん 集団を見つけたので、みんなで Matz本城 背景に記念撮影。まつもとさん一行は開発者会議があったらしく記念撮影後すぐにわかれました。 われわれは、この記念撮影後松本城観光し、もう一軒蕎麦屋へ昼食へ。二軒目の蕎麦屋は 蕎麦倶楽部佐々木 へ。

そんなことしてたら宿がチェックイン可能になったので、荷物置くのと下着類を購入しひとやすみ(10min程)。

Day 0 最大のイベント、 KeebKaigi へ!ここで豪華ノベリティを大量に頂きました!!!1とくによいのが城キーキャップで松本城を3DプリントしたISO エンターキーを頂きました。しかしながらこのキー差すキーボードがないので知人に譲る予定です。

KeebKaigi 自体もたいへん面白い Kaigi で楽しめました。とくに、 t-code の紹介のLT、頭文字Vなどよかったです。 KeebKaigi 後は懇親会には参加せず、友人とかるく夕飯をして宿へ。

Day 1

ここまででも大変楽しいものでしたが、RubyKaigi本番 はこの日から。

1日目の朝食は会場行く途中でもともと聞いていた店の 栞日 さんへ。 会場開いて登録したあとノベリティの列をみてげんなりしてたので一旦スポンサーブースをうろうろしてた。 この日聞いたセッションは以下です。

Matz Keynote

毎年恒例 Matz のキーノート。

昼飯

ランチはたまたま居合わせた6人で おきな堂 へ。名物のボルガライスとリンゴジュースを。シードルがあったので一緒にいったひとへオススメしてたりした。

The future vision of Ruby Parser

コミッターの spikelaf さんによるパーサーの開発話。魔窟じゃないよーなんかいってまいましたが、難しい問題を丁寧にひとつひとつ倒していった話をしていました。

Make Regexp#match much faster

去年 ReDoS の対応をしたコミッターの発表。正規表現の可能性を話してくれています。

High-performance real-time 3D graphics with Vulkan

Valkan を Ruby で利用できるようにした話。このあたりは LT への緊張感でほとんど覚えていないです。

Power up your REPL life with types

去年の TRICK Winner で今回の話は irb を利用しているときにどういう感じで補完が出るのか、出すためにどのような型推論を行なっているのかの話だったとおもう。

LT

しゃべった

オフィシャルパーティー

つかれたのでそこそこで帰った

Day 2

朝飯は 珈琲茶房かめのや へ。ここもワンオペで配膳されるまで少し時間がかかった。というか、朝食べくるひといないのかどこもワンオペだったので Day 3 も早めに出るようにしようとなった。

On Ruby and ꝩduЯ, or How Scary are Trojan Source Attacks

昔他の言語で話題になった Ruby のコード的には正しいが、見た目ではわからない 不正な文字が入ったときの対策のはなしでした。

Build a mini Ruby debugger in under 300 lines

300 行といったなあれは嘘だ。という話。Ruby の Debugger を200 行くらいでつくったはなしでした。

昼飯

種村 で蕎麦と岩魚と日本酒。

Revisiting TypeProf - IDE support as a primary feature

Ruby に梱包されている TypeProf が遅いので速くして、また書いている途中でもエラーがでたときどうするかとかの説明をしていました。まだ利用できていないので試したくなるものだった。

Ruby Implementation of QUIC: Progress and Challenges

Unasuke 大先生のライフワーク、 Ruby の QUIC 対応のはなし。Python での実装はどうとかのはなしで今回は実装がすすんでいるようでよかった。とはいえ道程はまだまだかかりそう。

afternoon break

お昼休憩後は Jeremy Evans や Maxime の話を聞いていたが疲れてなにも覚えていない。

Leaner Drinkup at RubyKaigi 2023

この日は Leaner さん主催の drinkup へ行きました。ここでは主に日本を飲んですごしました。でここで、spikelaf が kaigi でも話してた lrama が Ruby 本体のとりこまれ、 Ruby 3.3.0-preview1 がリリースされました!なんかさわいでるなと思たらリリース作業シテイタトハ… drinkup 終了後は onk とふたりで近所のワインバーへ行き、軽くのんで宿へ帰りました。

宿帰ったら ESM さんのノベリティの Rubyメソッドかるた でみんなでわいわいしながら遊んでました。

Day 3

この日の朝食は 山山食堂 というところでしました。

Committers and the Worldのあと

今回は Matz本 ということで会場にいる「まつもとさん」一同会して写真を撮ることを目的としてあつめてました。写真は以下になります

Ruby + ADBC - A single API between Ruby and DBs

須藤さん の発表で、 ADBC で DB へ接続し、さらのその接続からのよみかきの高速化のはなしでした。あいかわらずすごいなと思いながら聞いていました。

Ruby JIT Hacking Guide

国分さん の新作 RubyJIT。PRみた感じだとやってるネタにてるなあとおもってたけど、アプローチがすこし違い、またこちらは直接ELFを書いているようなのでまだちがうようだ。とはいえ今やっていることは似ているのであとで参考にしよう。

After Party and Music Mixin

STORESさんアフターパティーpixivさんruby music mixin にいってきました。両方ともサイコーでした!!!!

Day 4

一人で野沢温泉村いって温泉はいって、ジンのんで、ビールのみいこうと一旦山おりたら温泉にしか飲むとこなかったのでもどったりして帰りました。

おわり

今回のRubyKaigi久しぶりのFull開催で大変楽しめたのですが、LTとは言えはじめての発表があり、心から楽しめたかというとあれですが、すごく楽しかったし、刺激になりました。来年の那覇は本編に出せるようになんとか精進します!!!


  1. 海外の人が登録して行ったほうがいいんじゃないかと一瞬思った 

趣味は vaporware 造りですv0.0.0

2023-03-21 13:59:59 +0900

プログラマー三大勉強はしたけど、実装はしたことないものといえば CPU、OS、コンパイラーなのです1が、先日 ruby30th 誕生日会のキーノートで matz が “Static Compiler for Ruby” という今はまだない vaporware として 挙げていたのでこの static compiler を作ろうとなりました。

katsyoshi/vaporware - GitHub

Goal

Ruby のコードを static compile できるようにする。

コンパイル先のターゲットは x86 とします。 ARMRISC-V などは今回の実装ではターゲットにしないです。

とはいえすべての Ruby の機能を実装すると時間がかかりすぎるので個人で無理のない範囲 で作ろうとします。無理のない実装範囲は以下なのかなと

  1. 四則演算
  2. 変数
  3. メソッド
  4. 制御構文
  5. プリミティブ型

この5つの機能を実装する予定です。

実現する機能以外のことについて

5つの機能を実現すること以上のことはやらない予定です。 やらないこととしては 最適化GC 、 外部ファイルで定義したメソッドやクラスの読み込みは実装しない予定です。 実装しない個人的意見を以下に書いていきます。

最適化は Ruby のコードを単純に 機械語 におとしただけでは現在の RubyVM より速くならないと考えているからです(要確認)。 LLVM IR などへの変換ではなく、 機械語 なのは LLVM をインストールする必要があるなどして 面倒なのが大きいです2。あとバージョン毎に LLVM IR が異なるのも現状では対応しにくい点となっています。

GC (Garbage Collection: ガベージコレクション) についてはそもそもクラスをサポートできない、 変数などのメモリを確保しておく時間が長いプログラムを対象としないので今回はスコープ外としています。

外部ファイル読み込みについてですが、外部ファイルの読み込みして コンパイル するだけなら そこまで問題にはならないと考えていますが、外部ファイルで定義された メソッドクラス を 事前に コンパイル して最後に リンク するのは型が不定になるのでサポートするのは難しいと考えています。

実装方針

Goal までの実装は 低レイヤを知りたい人のためのCコンパイラ作成入門 を参考にすすめていきます。 最初に コンパイラー を実装するものとして C 言語 がたぶん勉強してきてうかぶと思います3C 言語 だと 機械語VM のバイトコードへ落とすことのできる資料が多いので選択しています。 とは言っても Ruby からの脳内変換はある程度必要なので慣れているというのもあります。

実装としては AST(Abstract Syntax Tree: 構文抽象木) から愚直に x86 アセンブラ をファイルへ書き出し、 そこから C コンパイラー (gcc or clang) をつかって機械語へ コンパイル します。 フルフルの Ruby を実装するわけじゃないので依存する gem の依存も極力減らしたいです。

実装環境

  • CPU: Ryzen Thread Ripper 1950x
  • gcc: 12.2.1
  • clang: 16.0.0
  • OS: Gentoo Linux
  • Linux Kernel: 6.2 系

パーサー

まず、AST を得るために パーサー が必要なのですが、 Ruby の構文は複雑なのでここは頑張らないようにします。ここをどうやって解決するのかというと RubyVM::AbstractSyntaxTreeRipper をつかうのか、parser.gem をつかうのかを決めるひつようがあります。今回というかしばらくは parser.gem を利用して AST を得ることにします。 ゆくゆくは RubyVM::AbstractSyntaxTree への置き替えはするよていです。

ここはそのまま parser.gem のチュートリアルどおりにすれば AST が得られます。

require "parser/current"

puts Parser::CurrentRuby.parse("(1 + 2) * 3 / (5 - 4)")

きょうはここまで

とりあえず手を動かしはじめましたが、ななななんと、似たような機能が実は Ruby 3.3 向けに JIT として入ったようです4。 ということでねこの話はね、勉強の話しかないんですが一旦 Goal まで作ってみましょうね。


  1. 個人差があります。 

  2. Gentoo Linux 使っているので毎回コンパイルしているので本当にめんどう。バイナリあるよとかそういう正論は受け付けていないです。 

  3. 時代によって変わるかも。Java だったり、Lisp だったりする人がいるかも。 

  4. https://github.com/ruby/ruby/pull/7448 

Update rubygems using Rust for Ruby 3.2

2023-01-03 23:59:59 +0900

以前このブログRust を利用して rubygems を作成した rust_uuidRuby 3.2 がリリース によりコンパイルできなくなったのでその修正顛末。

環境

system failed version succeed version
ruby 3.2.0 3.2.0
gem 3.4.1 3.4.2
bundler 2.3.19 2.4.2
rb-sys 0.9.29 0.9.53
magnus 0.3.2 0.4.4

what’s happened?

Ruby 3.2 が出てたのでアップデートして試してみるかーとおもいコマンドを実行!!!!

$ bundle exec rake build
.
.
...
error[E0425]: cannot find value `RUBY_ABI_VERSION` in the crate root
  --> /path/to/cargo/dir/registry/src/github.com-1ecc6299db9ec823/rb-sys-0.9.29/src/ruby_abi_version.rs:14:73
   |
14 | pub const __RB_SYS_RUBY_ABI_VERSION: std::os::raw::c_ulonglong = crate::RUBY_ABI_VERSION as _;
   |                                                                         ^^^^^^^^^^^^^^^^ not found in the crate root

For more information about this error, try `rustc --explain E0425`.
error: could not compile `rb-sys` due to previous error
warning: build failed, waiting for other jobs to finish...
gmake: *** [Makefile:564: target/release/librust_uuid.so] エラー 101
rake aborted!
Command failed with status (2): [/usr/bin/gmake...]
/path/to/rbenv/versions/3.2.0/bin/bundle:25:in `load'
/path/to/rbenv/versions/3.2.0/bin/bundle:25:in `<main>'
Tasks: TOP => build => compile => compile:x86_64-linux => compile:rust_uuid:x86_64-linux => copy:rust_uuid:x86_64-linux:3.2.0 => tmp/x86_64-linux/rust_uuid/3.2.0/rust_uuid.so
(See full trace by running task with --trace)
bundle exec rake build  63.55s user 10.61s system 541% cpu 13.705 total

おーなるほどなるほど、 cargorb-sysRuby 3.2 に対応していないバージョンつかってるんだなと理解。cargo を更新っするぞい。

うpだて cargo ぱっけーじ

ということで cargo build が通るようにパッケージを更新するぞい。 cargobundler 同様、 cargo update でいい感じにアップデートしてくれます。

$ cd ext/rust_uuid
$ cargo update
$ cargo build
   Compiling magnus v0.3.2
error[E0432]: unresolved import `crate::ruby_sys::ruby_rstring_consts`
  --> /path/to/cargo/dir/registry/src/github.com-1ecc6299db9ec823/magnus-0.3.2/src/r_string.rs:23:47
   |
23 | use crate::ruby_sys::{rb_str_to_interned_str, ruby_rstring_consts::RSTRING_EMBED_LEN_SHIFT};
   |                                               ^^^^^^^^^^^^^^^^^^^ could not find `ruby_rstring_consts` in `ruby_sys`

error[E0599]: no variant or associated item named `RSTRING_EMBED_LEN_MASK` found for enum `ruby_rstring_flags` in the current scope
   --> /path/to/cargo/dir/registry/src/github.com-1ecc6299db9ec823/magnus-0.3.2/src/r_string.rs:368:38
    |
368 |             f &= ruby_rstring_flags::RSTRING_EMBED_LEN_MASK as VALUE;
    |                                      ^^^^^^^^^^^^^^^^^^^^^^ variant or associated item not found in `ruby_rstring_flags`

error[E0599]: no variant or associated item named `RSTRING_EMBED_LEN_MASK` found for enum `ruby_rstring_flags` in the current scope
   --> /path/to/cargo/dir/registry/src/github.com-1ecc6299db9ec823/magnus-0.3.2/src/r_string.rs:968:42
    |
968 |                 f &= ruby_rstring_flags::RSTRING_EMBED_LEN_MASK as VALUE;
    |                                          ^^^^^^^^^^^^^^^^^^^^^^ variant or associated item not found in `ruby_rstring_flags`

Some errors have detailed explanations: E0432, E0599.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `magnus` due to 3 previous errors

cargo update しましたが、やっぱり駄目でしたね。今度は magnus が駄目そう。 Cargo.toml を見てみると、 magnus = { version = "0.3", features = ["rb-sys-interop"] } と指定してあり、 version 0.3 系が駄目そうということが類推されます。ということで公式ページを見ると新しいバージョンが出ているのでこちらにします。

--- a/ext/rust_uuid/Cargo.toml
+++ b/ext/rust_uuid/Cargo.toml
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
 [dependencies]
 rb-sys = "0.9"
 rb-allocator = "0.9"
-magnus = { version = "0.3", features = ["rb-sys-interop"] }
+magnus = { version = "0.4", features = ["rb-sys-interop"] }

 [dependencies.uuid]
 version = "1.1.2"

再度ビルド!

$ cargo update
$ cargo build
   Compiling rust_uuid v0.1.0 (/home/katsyoshi/Program/Ruby/rust_uuid/ext/rust_uuid)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s

おおっ通りました!やったね!

build

ということ cargo build 通ったので gem install しましよう。

$ cd /path/to/rust_uuid
$ bundle exec rake build
cd tmp/x86_64-linux/rust_uuid/3.2.0
/usr/bin/gmake
generating target/release/librust_uuid.so (release)
cargo rustc --target-dir target --manifest-path ../../../../ext/rust_uuid/Cargo.toml --lib --release -- -C linker=gcc -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/home/katsyoshi/.local/lib:-L/home/katsyoshi/.local/lib: -C link-arg=-lm -l pthread
   Compiling libc v0.2.139
   Compiling proc-macro2 v1.0.49
   Compiling quote v1.0.23
   Compiling unicode-ident v1.0.6
   Compiling clang-sys v1.4.0
   Compiling regex-syntax v0.6.28
   Compiling syn v1.0.107
   Compiling rb-sys-env v0.1.1
   Compiling libloading v0.7.4
   Compiling nom v7.1.2
   Compiling aho-corasick v0.7.20
   Compiling magnus v0.4.4
   Compiling bindgen v0.60.1
   Compiling getrandom v0.2.8
   Compiling uuid v1.2.2
   Compiling cexpr v0.6.0
   Compiling regex v1.7.0
   Compiling magnus-macros v0.3.0
   Compiling rb-sys-build v0.9.53
   Compiling rb-sys v0.9.53
   Compiling rb-allocator v0.9.6
   Compiling rust_uuid v0.1.0 (/path/to/rust_uuid/ext/rust_uuid)
    Finished release [optimized] target(s) in 12.03s
cd -
mkdir -p tmp/x86_64-linux/stage/lib/rust_uuid
/usr/bin/gmake install target_prefix=
generating target/release/librust_uuid.so (release)
cargo rustc --target-dir target --manifest-path ../../../../ext/rust_uuid/Cargo.toml --lib --release -- -C linker=gcc -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/home/katsyoshi/.local/lib:-L/home/katsyoshi/.local/lib: -C link-arg=-lm -l pthread
    Finished release [optimized] target(s) in 0.02s
installing rust_uuid.so to /path/to/rust_uuid/lib/rust_uuid
/usr/bin/install -c -m 0755 rust_uuid.so /path/to/rust_uuid/lib/rust_uuid
cp tmp/x86_64-linux/rust_uuid/3.2.0/rust_uuid.so tmp/x86_64-linux/stage/lib/rust_uuid/rust_uuid.so
rake aborted!
Running `gem build -V /path/to/rust_uuid/rust_uuid.gemspec` failed with the following output:

WARNING:  description and summary are identical
WARNING:  open-ended dependency on benchmark-ips (>= 0, development) is not recommended
  use a bounded requirement, such as '~> x.y'
WARNING:  open-ended dependency on rake (>= 13.0.0, development) is not recommended
  if rake is semantically versioned, use:
    add_development_dependency 'rake', '~> 13.0', '>= 13.0.0'
WARNING:  open-ended dependency on rake-compiler (>= 0, development) is not recommended
  use a bounded requirement, such as '~> x.y'
WARNING:  open-ended dependency on rb_sys (>= 0, development) is not recommended
  use a bounded requirement, such as '~> x.y'
WARNING:  open-ended dependency on rspec (>= 0, development) is not recommended
  use a bounded requirement, such as '~> x.y'
WARNING:  See https://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec.

/path/to/rbenv/versions/3.2.0/bin/bundle:25:in `load'
/path/to/rbenv/versions/3.2.0/bin/bundle:25:in `<main>'
Tasks: TOP => build
(See full trace by running task with --trace)
bundle exec rake build  32.87s user 4.33s system 291% cpu 12.741 total

なるほど? Cargo.lock ファイルもあるし問題なさそうだな。よくわからんので rubygems のリポジトリでエラーメッセージを探してみます。

すると以下のようなコード

  def validate_rust_extensions(builder) # :nodoc:
    rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder }
    missing_cargo_lock = !@specification.files.include?("Cargo.lock")

    error <<-ERROR if rust_extension && missing_cargo_lock
You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec.
    ERROR
  end

なろほどなろほど、プロジェクトの root 直下に置いておく必要があるのね。でもそのファイルどういうものなの?

gem, bundler update

ってなわけで、今度は正式に rubygems で正式にサポートされ、 bundler でも gem を作成するときにも bundle gem --ext=rust gem_nameスケルトンが作成されるようになりました。ということでこれを利用して Cargo.toml を作ってみましょう

$ bundle gem --mit --ext=rust gem_name
ERROR: "bundle gem" was called with arguments ["rust", "gem_name"]
Usage: "bundle gem NAME [OPTIONS]"

ok, ok, これは bundler のバージョンが古いな

$ bundle version
Bundler version 2.3.19 (2022-07-27 commit 4f496f93e6)

さっきの PR は入ったのは Ruby 3.2 リリース直前なのでまだ入ってないよなとおもったけど、実際は Gemfile.lockBUNDLED WITH2.3.19 を指定してるだけだったのです。 なので対象の 2 行を削除して bundle update を実行し、続いて rust サポートした gem を生成して、 Cargo.toml を見てみましょう。

$ bundle gem --mit --ext=rust gem_name
Creating gem 'gem_name'...
MIT License enabled in config
Changelog enabled in config
Initializing git repo in /path/to/rust_uuid/gem_name
      create  gem_name/Gemfile
      create  gem_name/lib/gem_name.rb
      create  gem_name/lib/gem_name/version.rb
      create  gem_name/sig/gem_name.rbs
      create  gem_name/gem_name.gemspec
      create  gem_name/Rakefile
      create  gem_name/README.md
      create  gem_name/bin/console
      create  gem_name/bin/setup
      create  gem_name/.gitignore
      create  gem_name/.github/workflows/main.yml
      create  gem_name/LICENSE.txt
      create  gem_name/CHANGELOG.md
      create  gem_name/Cargo.toml
      create  gem_name/ext/gem_name/Cargo.toml
      create  gem_name/ext/gem_name/extconf.rb
      create  gem_name/ext/gem_name/src/lib.rs
Gem 'gem_name' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html
$ cat Cargo.toml
# This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is
# a Rust project. Your extensions depedencies should be added to the Cargo.toml
# in the ext/ directory.

[workspace]
members = ["./ext/gem_name"]
resolver = "2"

というようなファイルが得られるので真似して作成しちゃいましょう。

[workspace]
members = ["./ext/rust_uuid"]
resolver = "2"

そうしたらもう一度ビルドしてしまいます!

$ cargo generate-lockfile
$ git add Cargo.toml Carog.lock
$ bundle exec rake build
/usr/bin/gmake install target_prefix=
generating target/release/librust_uuid.so (release)
cargo rustc --target-dir target --manifest-path ../../../../ext/rust_uuid/Cargo.toml --lib --release -- -C linker=gcc -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/home/katsyoshi/.local/lib:-L/home/katsyoshi/.local/lib: -C link-arg=-lm -l pthread
   Compiling rust_uuid v0.1.0 (/path/to/rust_uuid/ext/rust_uuid)
    Finished release [optimized] target(s) in 0.26s
installing rust_uuid.so to /path/to/rust_uuid/lib/rust_uuid
/usr/bin/install -c -m 0755 rust_uuid.so /path/to/rust_uuid/lib/rust_uuid
cp tmp/x86_64-linux/rust_uuid/3.2.0/rust_uuid.so tmp/x86_64-linux/stage/lib/rust_uuid/rust_uuid.so
rust_uuid 0.1.0 built to pkg/rust_uuid-0.1.0.gem.
$ bundle exec rake install
/usr/bin/gmake install target_prefix=
generating target/release/librust_uuid.so (release)
cargo rustc --target-dir target --manifest-path ../../../../ext/rust_uuid/Cargo.toml --lib --release -- -C linker=gcc -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/path/to/rbenv/versions/3.2.0/lib -L native=/home/katsyoshi/.local/lib:-L/home/katsyoshi/.local/lib: -C link-arg=-lm -l pthread
    Finished release [optimized] target(s) in 0.03s
installing rust_uuid.so to /path/to/rust_uuid/lib/rust_uuid
/usr/bin/install -c -m 0755 rust_uuid.so /path/to/rust_uuid/lib/rust_uuid
cp tmp/x86_64-linux/rust_uuid/3.2.0/rust_uuid.so tmp/x86_64-linux/stage/lib/rust_uuid/rust_uuid.so
$ bundle exec ruby -rrust_uuid -e 'puts RustUUID.v4'
2eb053de-8ae7-4669-853b-95f06c872300

ようやっと通ったああぁあ!!!!

conclusion

実際は11月の末あたりに head でコンパイルできないなあと気がついていたのですが、そのうち直るやろと思っててなにもしなかったのです。いざ Ruby 3.2 がリリースされたときに試して動かなかったのでやっと対応してみました。

ということで正式に Rust がサポートされるようになったのでまた YARUKI がでてきますね。

それはそうと今度は、jekyllRuby 3.2 で動かなくなった。

お名前ドットコムのメールがうざすぎたので DNS を Cloudflare に移行して快適生活

2022-10-15 23:59:50 +0900

katsyoshi.org の登録先を お名前ドットコム にしてたけど、広告のようなメールとか届くし 更新案内と広告の違いがわからない感じのメールが大量にくるのでやめようやめようと思ってたのでいいかげん変えてみた話。

準備

準備として移行先のレジストラを選定します。 移行先としては普通のレジストラとクラウド業者がやっているレジストラがあると思いますが、今回は以下3つを候補にしました。

  1. Google Domains: Google がやっているやつ。メールとか Google なんで DNS まで Google にするのは心理的抵抗が強い。
  2. Route 53: みんなつかってる AWS のサービス。仕事で利用しているので、プライベートは別のがいいかな。
  3. Cloudflare の DNS: みんなだいすき低価格 CDN 業者の Cloudflare がやってる DNS サービス。

GoogleAWS は言わずと知れた巨大企業でサービスがなくなるということはないとおもうが、 仕事で利用したり、情報全部預けたりしているところなので選択する理由が個人的には弱い。 個人利用でガンガン変えたり、 VPC でネットワーク構築するわけじゃないので Cloudflare でいいかなと。

お名前ドットコムでの作業

レジストラを移管する前に現在登録してある WHOIS 情報 を確認します。 これは移管作業で移管作業用コードが WHOIS 登録者にメールが送られてくるのでどのメールアドレスかの確認です。 ここでWHOIS 情報公開代行 を利用している場合は、 WHOIS を一旦登録時のものに変更します1

移管

移管作業としては新規レジストラで移管依頼を参考に移管依頼ページを開きます。 開いたら、旧サービスから移管コードの発行を行ないます2。 移管コードを Cloudflare 側で入力してしばし待機。

しばらくしたら、レコードが登録されるので完了です。

おわり

ということで DNS の登録を Cloudflare へ移管しました。 お名前ドットコム はあのメールさえなければ続けたのかもしれない。 が更新警告と普通のメールの違いがあまりにもわからないので捨てることにしました。 Cloudflare で不満があったらまた変更すると思いますが、快適な生活になりました(たぶん


  1. 前に移行しようとしたとき、.org のドメインは WHOIS 情報書き変えられなくて移行失敗。現在この制限はなくなったので移行。 

  2. こいつが見つけにくく、お名前ドットコムからは見つけられずに移管レポートブログから発見。 

Communicate on IPv6 from home

2022-10-12 23:59:50 +0900

家のネットワークからインターネットへ出るとき IPoE を使ってたけど、家庭内 LAN のネットワークは IPv6 を off にしていた。 この LAN 内 IPv6 化していなかった理由としては、昔 Linux (だけじゃないかも)が IPv6 が利用できる状態だと 先に IPv6 で繋ぎにいこうとして IPv6 で通信できなかったら IPv4 にフォールバックするという挙動で、すごくストレスフルだった。 この挙動の対処として、カーネルレベルで IPv6 を off にしてました。 そうこうしているうちに契約している ISP が IPv6 オプションなしで IPoE を利用できるようになったので、 とりあえず IPoE だけを利用できるようにして、家庭内の LAN はそのままという状態にしていました。 いいかげんこの家庭内 LAN のネットワークを IPv6 化し、インターネットと IPv6 で通信できるようにした顛末をのこす。のこします。

家庭内 LAN 環境

  • ゲートウェイ: NEC IP38X/1210 (YAMAHA RTX1200)
  • PC: 6.0.0-gentoo

IPoE 化と家庭内 LAN の IPv6 化

設定は YAMAHA の設定例集に載ってあるのでそれを参考にします。 IPoE 化はこの設定例でいけるのですが、どうも設定したときにミスったらしく、 LAN の IP アドレスが IPv4 ではインターネットへ出ることができるが、 IPv6 ではインターネットへ出ることができない状態になってしまいました。

旧設定

どのような設定だったかは以下に

ip route default gateway tunnel 1
ipv6 prefix 1 ra-prefix@lan2::/64
ipv6 lan1 address ra-prefix@lan2::1/64
ipv6 lan1 prefix ra-prefix@lan2::/64
ipv6 lan1 rtadv send 1
ipv6 lan1 dhcp service server
ipv6 lan2 address auto
ipv6 lan2 secure filter in 1010 1011 1012 2000
ipv6 lan2 secure filter out 3000 dynamic 100 101 102 103 104 105 106
ipv6 lan2 dhcp service client ir=on
ngn type lan2 ntt
tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint address 2404:8e00::feed:100
 tunnel enable 1
ipv6 filter 1010 pass * * icmp6 * *
ipv6 filter 1011 pass * * tcp * ident
ipv6 filter 1012 pass * * udp * 546
ipv6 filter 2000 reject * * * * *
ipv6 filter 3000 pass * * * * *
ipv6 filter dynamic 100 * * ftp
ipv6 filter dynamic 101 * * domain
ipv6 filter dynamic 102 * * www
ipv6 filter dynamic 103 * * smtp
ipv6 filter dynamic 104 * * pop3
ipv6 filter dynamic 105 * * tcp
ipv6 filter dynamic 106 * * udp

この設定ではインターネットとは IPv4 でしか通信できていない状態でした。 でこのトラブルシュートとして知人(@n_kane, @paina) のちからを借りてどこまで通じてどこから通じないかを確認しました。

troubleshooting

とりあえず ping6traceroute6 で通じていないことを確認します。

$ ping6 -c 3 google.com

PING  google.com(nrt13s55-in-x0e.1e100.net (2404:6800:4004:824::200e)) 56 データ長(byte)

--- google.com ping 統計 ---
送信パケット数 3, 受信パケット数 0, パケット損失 100%, 時間 2067ミリ秒

$ traceroute6 google.com
traceroute to google.com, 30 hops max, 80 byte packets
 1  * * *
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  * * *
 8  * * *
 9  * * *
10  * * *
11  * * *
12  * * *
13  * * *
14  * * *
15  * * *
16  * * *
17  * * *
18  * * *
19  * * *
20  * * *
21  * * *
22  * * *
23  * * *
24  * * *
25  * * *
26  * * *
27  * * *
28  * * *
29  * * *
30  * * *

通ってないですね。 先述したように PC は IPv6 を on にしたばかりなのでクライアント側の設定でブロックしていないかを確認します。 まずクライアント側で考えられるのは iptables でのパケットフィルタリングですね。 ルーターの下にある PC なので、ここでブロックすることは低いのですが、確認します。

# ip6tables --list
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

なにもフィルタリングしていないですね。 ということで、クライアント側には問題なさそうですね。 どこまで通じてどこまで通じていないのか別のエンドポイントで確認します。

まず、手元と相手で tcpdump 利用してパケットが送っているか届いているかを確認します。

# tcpdump -nei enp8s0f0 icmp6
dropped privs to pcap
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp8s0f0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:35:41.158558 00:a0:de:69:40:9d > 33:33:ff:51:5e:63, ethertype IPv6 (0x86dd), length 86: fe80::212:e2ff:fe70:6144 > ff02::1:ff51:5e63: ICMP6, neighbor solicitation, who has 2409:10:a5c0:1f00:d903:ddb5:851:5e63, length 32
22:35:58.765783 ee:60:51:38:43:9c > 33:33:ff:00:00:01, ethertype IPv6 (0x86dd), length 86: 2409:10:a5c0:1f00:5ce3:afcb:9bb2:5c1b > ff02::1:ff00:1: ICMP6, neighbor solicitation, who has 2409:10:a5c0:1f00::1, length 32
22:36:09.249427 ee:60:51:38:43:9c > 33:33:ff:00:00:01, ethertype IPv6 (0x86dd), length 86: 2409:10:a5c0:1f00:5ce3:afcb:9bb2:5c1b > ff02::1:ff00:1: ICMP6, neighbor solicitation, who has 2409:10:a5c0:1f00::1, length 32

送れてはいるようで対向側にも届いていたらしく戻りのパケットは送ってたようですが、手元では戻ってくるパケットは見えていないですね。

ということはやはりルーターの設定が悪そうということが推察されますね。 設定のどこが悪いのかあやしいところを見ていきます。

最初に怪しいとおもったのはルーターのフィルターまわりです。一旦、no ipv6 interface secure filer in で全部無効化しましょう。しかしとくに変りはないです。これはフィルターが原因ではなさそうですね。

次にあやしい点は以下2つの設定

  1. ipv6 interface address
  2. ipv6 interface prefix

上のコマンドは ISP から自動で振ってきている IP を割り当てる設定で、下のコマンドは ISP から振ってきた IPv6 のプレフィックスを付けるようにするための設定となります。 どうもこの下のコマンドが邪魔なようです。 no ipv6 interface prefix で無効化できるので一旦外してみましょう。 すると

$ ping6 -c 3 google.com
PING google.com(nrt20s18-in-x0e.1e100.net (2404:6800:4004:81c::200e)) 56 データ長(byte)
64 バイト応答 送信元 nrt20s18-in-x0e.1e100.net (2404:6800:4004:81c::200e): icmp_seq=1 ttl=57 時間=6.53ミリ秒
64 バイト応答 送信元 nrt20s18-in-x0e.1e100.net (2404:6800:4004:81c::200e): icmp_seq=2 ttl=57 時間=2.73ミリ秒
64 バイト応答 送信元 nrt20s18-in-x0e.1e100.net (2404:6800:4004:81c::200e): icmp_seq=3 ttl=57 時間=2.81ミリ秒

--- google.com ping 統計 ---
送信パケット数 3, 受信パケット数 3, パケット損失 0%, 時間 2002ミリ秒
rtt 最小/平均/最大/mdev = 2.731/4.023/6.532/1.773ミリ秒

のように通りました!勝ったッ!第三部完!

おわり

以前中途半端に設定したおかげで IPv6 有効化に時間が掛ってしまった顛末をまとめました。 これで家のネットワークから IPv6 で通信できるようになりました。 ありがとうございました!

次回 katsyoshi.org お名前.COM やめるってよ

RubyKaigi 最高!

2022-09-11 23:59:50 +0900

RubyKaigi 2022 に行ってきました! ので感想を

DAY 0

今回も やんちゃハウス に楽をして泊まることにしてたのでなにも気にせず宿へ。 着いて他メンバーが来るまで宿で待機してたが、最寄り駅の周りが思った以上になにもなく、途方に暮れてました。

夕飯

もうひとり宿に着いたので入れ変わりで夕飯へ出かけたが、そもそも最寄り駅前にはなにもないので伊勢駅まで出ることにした。

この判断が間違えてたようで、伊勢駅に出てもとくに変らなかった。しかし幸い伊勢駅前に担々麺屋があったのでそこで食料摂取した。

DAY 1

この日の聞いたセトリと感想は以下のとおり

Ruby meets WebAssembly: presentation

この日のキーノートは Rubywasm 対応の話で wasi を通じてブラウザで Ruby が動かすまでのたのしいことをしゃべってたようです。 おなかいたくてトイレ行ってたら面白いところ聞き逃がしてしまった。

ランチ

適当に幕の内弁当を最初に選択。

Making *MaNy* threads on Ruby: presentation

笹田さんの発表で如何に Ruby で CPU をフルに使えるようにするかの話。 Ruby での Thread のはなしから Ractor を利用してフルフルに CPU 利用するための書きかたとかの話していました。

Building a Lightweight IR and Backend for YJIT: presentation

Ruby 3.1YJIT が入って Ruby 3.2 でサポートされる CPU アーキテクチャが増えたはなし。RubyVM(?) に YJITRuby IR を作成して JIT を実行するという話。 聞いてて疑問になった点としては、 Ruby IR では直接機械語に翻訳しているようだったが、このバックエンドに LLVM を利用していないのはなんでなんだろう。聞き逃がしているという話はあるので、詳しいひと教えて。

Tools for Providing rich user experience in debugger: presentation

debug.gemVS Code だけじゃなく、 Chrome のリモートデバッグ機能を利用したデバッグの話でした。 この機能自体はすごく便利だと思うのですが、そもそも両方とも宗教上の理由で利用できない体なので聞くだけでした。

TRICK 2022 (Returns): repos

いつも通り 変な エキセントリックなプログラムしか出てこなかったし、いつもの面子が賞もらってたのですごいなーという感想。

夕飯

この日は津駅があまりなさそうだったので、駅近くで飲むのではなく、宿寄りの場所松坂駅で飲むことにした。 ここで、津の夜情報を聞き込んだり、朝ごはん情報自体を仕入れてました。

DAY 2

2日目は、ネットワークが不安定になったりしてこちらの集中力が切れたりしたのであまり覚えていない。

朝ごはん

前日の夕飯に教えてもらった市場の人向け食堂が松坂駅前にあり、その食堂で食事をした。

Matz Keynote: presentation

Do Pure Ruby Dream of Encrypted Binary Protocol?: presentation

ノーコメント。

ランチ

この日も一番に弁当を取得。弁当はみんな大好き松坂牛ローストビーフ丼にした。感想としては、もう肉はいいかな。

自キKaigi

誘われたので参加しました。iPad で活動しようと思い corne 持っていってたので参加してた。

Ruby x BPF in Action: How important observability is: presentation

Hunting Production Memory Leaks with Heap Sampling: presentation

このあたりネットワークが不安定になり、セッションが中断されたりしたためあまり覚えていない。

立ち話

久し振りの対面での参加だったのでセッションあまり聞かずに受け付け前で立ち話している人たちがいたので、その立ち話に参加した。 たまたま、最近読んでる本の翻訳者がいたのでこれからの話などを聞いたりしていた。

Caching With MessagePack: presentation

Ruby Committers vs The World

夕飯

夕飯は、駅近くのお店でかるく3人で。

こんなリプライがきてたようなので一緒にいたひとにまかせた。ギリギリまで粘ってたので帰宿後すぐ就寝。

DAY 3

3日目はそもそも前2日の疲れが出たりしたのかほとんど覚えていない。あと ruby puzzle をやっていてセッションをあまり聞けていない。

朝ごはん

Megaruby - Running mruby/c programs on Sega Mega Drive: presentation

これに間に合うように出たはずだったが、間に合わず聞いていた。メガドライブすげえ。

Automatically Find Memory Leaks in Native Gems: presentation

Fast data processing with Ruby and Apache Arrow: presentation

Fixing Assignment Evaluation Order: presentation

Stories from developing YJIT: presentation

まとめ

3年ぶりの現地開催だったので参加してきました。対面での参加はやはりよいもので新しい出会いや、疑問を本人、解っている人に直接聞くことができてすばらしい体験でした。

三重について

三重県の酒のイメージは、行く前は「作」「 而今」「伊勢角谷屋麦酒」というイメージでした。 とくに日本酒は美味しいイメージがあり、知らない日本酒もあったりするんだろうなあと思っていました。 行ってみると日本酒はイメージと変わらないのですが、だいたい似た味の傾向で飽きるというかなんというか 美味しいんだけど、好きじゃないという感想になってしまった。 ビールについては瓶ビールしか飲まなかったので感想はなしで。

三重は大きな街が比較的分散しているようで、津も松坂も伊勢も鳥羽も駅前が小さく感じました。 とくに0日目の食事が大変で、調達が難しかった。

Ruby gem で Rust をつかって爆速にしたい!!!!!!11

2022-08-12 13:59:59 +0900

Ruby GemsRustNative として利用可能になった のでとりあえず UUIDv4 を生成してみた。

リポジトリ

katsyoshi/rust_uuid - GitHub

準備

Ruby 側の gem に Rust を利用する準備として rb_sysrake-compiler を利用します。この二つの gem は native compile するためにインストールしておきます。 Rust 側から Ruby へ関数を公開するために rb-sysmagnus を利用します。

gem install

とりあえず cargo で Rust のパッケージを作って Rust を書いてみます。

> bundle gem rust_uuid --mit --ext rust_uuid # --ext を指定してnative build する gem を作成
> cd rust_uuid # 作成した gem のディレクトリへ移動
> cd ext/rust_uuid # ビルドするディレクトリへ移動
> cargo init . --lib # cargo を初期化
> rm -f *.c *.h # C のファイルが生成されるので削除
> cargo add rb-sys rb-allocator
> cargo add magnus --features rb-sys-interop
> cargo add uuid --features v4 # uuid v4 を指定

ext/rust_uuid/extconf.rb を以下のように編集します。

@@ -1,5 +1,6 @@
 # frozen_string_literal: true
 
 require "mkmf"
+require "rb_sys/mkmf"
 
-create_makefile("rust_uuid/rust_uuid")
+create_rust_makefile("rust_uuid/rust_uuid")

次に ext/rust_uuid/src/lib.rs を以下の様に変更します。

use magnus::{define_module, function, prelude::*, Error};
use rb_allocator::ruby_global_allocator;
use uuid::Uuid;

ruby_global_allocator!();

// UUIDv4 を文字列として公開
fn v4() -> String {
    Uuid::new_v4().to_string()
}

#[magnus::init]
fn init() -> Result<(), Error> {
    let module = define_module("RustUUID")?;
    // RustUUID.v4 と利用するようにシングルトンメソッドを定義
    module.define_singleton_method("v4", function!(v4, 0))?;
    Ok(())
}

これまでできたら一旦 Rust をコンパイルしましょう。

> cd ext/rust_uuid
> cargo build
> git add .
> rake build
.... # install cargo dependencies and packages
cp: '/home/katsyoshi/Program/Ruby/rust_uuid/tmp/x86_64-linux/rust_uuid/3.1.2/target/release/librust_uuid.so'stat できません: そのようなファイルやディレクトリはありません
gmake: *** [Makefile:551: foo_bar.so] エラー 1
rake aborted!
Command failed with status (2): [/usr/bin/gmake...]

Tasks: TOP => build => compile => compile:x86_64-linux => compile:foo_bar:x86_64-linux => copy:rust_uuid:x86_64-linux:3.1.2 => tmp/x86_64-linux/rust_uuid/3.1.2/rust_uuid.so

とエラーになります。 これは ext/rust_uuid/Cargo.toml の設定が足りていません。そこで以下を追加してみてください。

[lib]
crate-type = ["cdylib"]

追加したら gem をビルド&&インストール&&試してみましょう!

> rake install
....
> ruby -rrust_uuid -e 'puts RustUUID.v4'
2be6f4d2-200b-4d08-9a1a-11fa523b316b

べんちまーく

以下、 SecureRandom.uuid との比較用のベンチマークコードを示します。

require "benchmark/ips"
require "securerandom"
require "rust_uuid"

Benchmark.ips do |x|
  x.report("standard") { SecureRandom.uuid }
  x.report("rust lib") { RustUUID.v4 }
  x.compare!
end

結果発表〜

Rust を利用することでだいたい 5 倍ほど速くなっています。

> ruby bentimark.rb
Warming up --------------------------------------
            standard    36.437k i/100ms
            rust lib   177.585k i/100ms
Calculating -------------------------------------
            standard    365.407k (± 1.4%) i/s -      1.858M in   5.086491s
            rust lib      1.793M (± 1.8%) i/s -      9.057M in   5.053179s

Comparison:
            rust lib:  1792925.9 i/s
            standard:   365407.3 i/s - 4.91x  (± 0.00) slower

ruby bentimark.rb  9.88s user 4.30s system 99% cpu 14.175 total

TOO HAYAI

まとめ

簡単に Rust を利用して速くしてみました。 思った以上に速くなっていたので重い処理をする場合に CC++ 以外でも簡単に利用できるようになって 選択肢が増えたのはよいことでした。

実はこの uuid crate の features に fast-rng を追加すると 10 倍速くなるんですが、 ruby 側の終了時に SEGV してしまうので載せていないです。 SEGV しないように原因を調査などはまた今度。

Hello, Wezterm

2022-03-19 18:00:00 +0900

tmux + Allacritty が疲れてきたのではてぶで流れてきた weztermsixel を利用できてよさそうだったので試してみることにした。

設定

設定ファイルが lua でカスタマイズがいろいろとできるのでまずは色を代えてみます。

local wezterm = require 'wezterm'
return {
  color_scheme = "Dracula",
}

プログラミング言語でカスタマイズができるので以下のようにアクティブなタブへの移動のキーバインドのカスタマイズができます。

local wezterm = require 'wezterm'
local move_keys = {}

for i = 1, 9 do
   table.insert(move_keys, {
      key = tostring(i),
      mods = "CTRL",
      action = wezterm.action{ ActivateTab = i - 1, },
   })
end

return {
   color_scheme = "Dracula",
   disable_default_key_bindings = true, -- 初期のキーバインドは利用しない場合
   keys = move_keys,
}

こんな感じで設定できるので便利です。

このキーバインドは任意のイベントも設定でき、任意のイベントを利用してアクションを定義できます。以下の例では、Paneを開い監視用のプログラムを開きます。

local wezterm = require 'wezterm'
wezterm.on("open-nvtop-and-ytop", function(win, pane)
   win:perform_action(wezterm.action{ SplitHorizontal = { domain = "CurrentPaneDomain", args = { "nvtop", }, }, }, pane)
   win:perform_action(wezterm.action{ SplitVertical = { domain = "CurrentPaneDomain", args = { "ytop", "-ps", }, }, }, pane)
end)

return {
   keys = { { key = "r", mods = "CTRL", action = wezterm.action{ EmitEvent = "open-nvtop-and-ytop", }, }, },
}

とすると以下のようになります。 便利!

こんな便利なものということで systemd でデーモン化しています。

[Unit]
Description=GUI Accellarated terminal
Documentation=

[Service]
Type=forking
ExecStart=/usr/local/bin/wezterm-mux-server --daemonize
Restart=on-failure

[Install]
WantedBy=default.target

で起動しておいています

問題点

と設定ファイルの例書いてみたのですが、とても大きな問題点にブチあたったので書いておきます。

wezterm には wezterm-mux-server というマルチプレクサ(tmuxのように扱うため)のサーバーモードプログラムがあるのですが、こいつがどうも wezterm とは挙動が異なり、前述した監視用のキーバインドが微妙に異なった挙動となってしまっています。サーバーモードに接続した場合の挙動は以下のようになります。

1つ目はpaneの位置が期待したとおりになっていない。2つ目は ytop が起動していないというので2つ目の方は気にしなければいいのでまあいいかと思っている。1つ目の問題は許容できていないので一旦はこのキーバインドは封印となっています。

おわり

長年利用してた tmux を捨てて wezterm を利用しはじめた。 設定が lua で書けるのが体験的にとても良いのでこれからも利用するかなと。 weztermsixel 利用した画像表示ができるようになったのが便利なので「よしっ!」

冬やすみ

2022-01-03 23:59:59 +0900

冬やすみの間、やりたいこと、やっといたほうがいいやつをやってました。 ひとつは xremap の設定ともうひとつは CO2-mini から CO2 を見える ようにした。

今回は、 mackerel で見えるようになった CO2 の値を Slack へ定期的に投げるようにします。今回も Rust を利用しています。

準備

準備として、 mackerel 1Slack 2 両アプリケーションの投稿 API 用 Token をそれぞれ用意します。 各公式ページにあるように生成、取得するだけでよいです。

mackerel 側は ホストメトリック API を利用します。 Slack 側は chat.postMessage API を利用します。 各 API に対して取得した API Token を用いて curl で確認しておきます。

実装

今回は対話式の bot ではないので、 RTM を利用せずに、HTTP クライアントだけで構成しています3RustHTTP クライアントとして hyper4 を利用します。 TLShyper_tls を利用しています。

実装とは言っても対象の mackerel の APIを叩き値を取得して、 その値を元に Slack へポストするだけです。

mackerel での値取得時に気をつける点としては、ホストメトリック API では host名 ではなく、 host id がパラメーターとなっていますので注意が必要です。 まずレスポンスを入れる構造体を定義します。

#[derive(Deserialize)]
pub struct Metric {
    pub time: i64,
    pub value: i16, // 今回は co2 の値なので i16 としている
}

#[derive(Deserialize)]
pub struct ResponseMetrics {
    pub metrics: Vec<Metric>,
}

つぎに以下のようにしてリクエストを組みたてて、値を取得しています。

let https = HttpsConnector::new();
let req = hyper::Request::builder()
    .method(hyper::Method::GET)
    .uri(url)
    .header("X-Api-Key", api_key)
    .body(hyper::Body::empty())?;
// https として request する
let client = hyper::Client::builder().build::<_, hyper::Body>(https);
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let json: ResponseMetrics = serde_json::from_reader(body.reader())?;
let metrics = json.metrics;

値を取得したら、今度は同じように Slack の方も構造体を定義します。

// リクエスト用構造体
#[derive(Serialize)]
pub struct SlackMessage {
    pub channel: String,
    pub sub_type: String,
    pub text: String,
    pub username: String,
    pub as_user: bool,
}
// レスポンス用構造体
#[derive(Deserialize)]
pub struct PostMessage {
    #[allow(unused)]
    channel: String,
}

リクエストを組みたてて、POSTします。 見てわかると思いますが、ほとんど mackerel と変わらないです。

// リクエスト body を json に変換
let json = serde_json::to_string(&SlackMessage {
    channel: "channel".to_string(),
    sub_type: "bot_message".to_string(),
    text: "message".to_string(),
    username: "botname".to_string(),
    as_user: true,
})?;

let https = hyper_tls::HttpsConnector::new();
let req = hyper::Request::builder()
    .method(hyper::Method::POST)
    .uri(url)
    .header("Content-Type", "application/json")
    .header("Authorization", format!("Bearer {}", get_env("SLACK_API_KEY")))
    .body(hyper::Body::from(json))?;

let client = hyper::Client::builder().build::<_, hyper::Body>(https);
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let _json: PostMessage = serde_json::from_reader(body.reader())?;

ポストするメッセージを作る際に2つのことをしています。 まずは timeUNIX EPOCH TIME からローカルの時間を表示するようにしています。 それと CO2 の値に依って絵文字を追加するかどうかを入れています。 -1 とか予定していない値が入ってきた場合は panic! するようにしています。

// chrono を利用して unix time からローカルの文字列へ変換
let t = chrono::Local.timestamp(time);
let v = match value {
    0..=700 => ":large_green_circle:",
    701..=1000 => ":large_yellow_circle:",
    1001.. => ":red_circle:", // なんで slack は :large_red_circle: を用意していないんだろうか
    _ => panic!("unexpected number!!"),
};

こうやってポストされたメッセージは以下のようになります。

絵文字つきでポストされましたね。

まとめ

Rustbot を作ってみました。 と言ってもただの HTTP クライアントbot なだけですけど。 一旦 Slack でも見えるようになったので今度は Nature Remo と連携して気温や湿度での自動化ができたらいいな。


  1. https://mackerel.io/ja/api-docs/ 

  2. https://slack.com/intl/ja-jp/help/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E7%94%9F%E6%88%90%E3%81%A8%E5%86%8D%E7%94%9F%E6%88%90 

  3. slack-rs という便利ライブラリがあるのですが、ちょっと触ってみたこれは必要なんだっけ?となってやめました。 

  4. 公式の README にあるようにこの場合は reqwest を利用するほうがよかったかもしれない。TLS は直接 hyper が対応していなかったりしてすこし面倒です。 

今年飲んだコーヒー豆の種類 2021

2021-12-31 23:59:59 +0900

今年もコロナでどこにも行くにしても行きにくかったし、 店もどこもあまりやってなかったようだったので今年も酒ではないまとめを。

苦いコーヒーすこし飽きてきたなーとか、あんまり大量にのむのはなーと思ってたところ1に スペシャルティコーヒー専門店が近所にできていて、そこで飲んで教えてもらったコーヒーが 気に入りよく通うように。

普段の飲む量を1日1杯のみに抑えるために多少高くてもおいしい豆で1杯で満足できる豆を買うことにした。

買ったというのがわかっている豆。

  • HAMBELA WAMENA, HEIRLOOM, Natural, エチオピア
  • Carmo De Minas Santuario Sul, Yellow Bourbon, Citrus Sweetness, ブラジル
  • NYERI KARATINA, SL28SL32RUIRU11, Fully Washed, ケニア
  • KAKAMEGA ISULU, SL28SL32RUIRU11, Natural Anaerobic, ケニア
  • WEST ARSI GORA KONE, HEIRLOOM, Washed, エチオピア
  • GEDEB WORKA, HEIRLOOM, Special Process Natural, エチオピア
  • EL PARAISO RED FRUITS, CASTILLO, DOUBLE ANAEROBIC WASHED, コロンビア
  • EL PARAISO LYCHEE, CASTILLO, DOUBLE ANAEROBIC WASHED, コロンビア
  • NYERI MAGANJO, SL28SL32RUIRU11, Fully Washed, ケニア
  • Fazenda Guariroba, Yellow Catuai, Double Fermentation Black Honey, ブラジル

感想

を書くつもりが、そもそも感想メモをほとんど残していなかった。いくつか残しているのでそれを。

  • EL PARAISO RED FRUITS: 別ロットはピーチティのような感じであったが、今回はかなりフルーティな味でライチな感じです。
  • Fazenda Guariroba: 香りが柑橘系というかレモンの香りがする。味はスッキリしてて口に含んだ酸味は少なく、後味としての酸味が強くのこって美味しい。

まとめ

1年くらい前に社内 Slack にコーヒーの感想をまとめるためのチャンネル作ったけど、 まったく活用していなくて感想がほとんど残っていない。 おいしいこと以外は思いだせるわけもなく……


  1. ここ1,2年コーヒー飲みすぎると胃が痛くなるのに気がついた。