Mackerel アンバサダー に選ばれました。
前職は Mackerel のエンジニアとしてアルバイトをしていた (例) のですが、再び好きなサービスに関われて非常に光栄です。
これを機にまたいろいろ遊ぼうと思います。
合わせて読みたい
wafuwafu13.hatenadiary.com
本ブログの掲載内容は私自身の見解であり、必ずしも所属する企業や組織の立場、戦略、意見を代表するものではありません。
Mackerel アンバサダー に選ばれました。
前職は Mackerel のエンジニアとしてアルバイトをしていた (例) のですが、再び好きなサービスに関われて非常に光栄です。
これを機にまたいろいろ遊ぼうと思います。
合わせて読みたい
wafuwafu13.hatenadiary.com
本ブログの掲載内容は私自身の見解であり、必ずしも所属する企業や組織の立場、戦略、意見を代表するものではありません。
欧州で働いているため、給料がユーロで振り込まれる。
永住する気はさらさらないので、いづれは全て円にしなければならない。
いつすればいいの?損をしたくない!
気づけば FX をしている状態になっていた。
とはいえ、自分でコントロールできない値に一喜一憂するほど人生は長くない。
ならばせめて監視をしよう。
そうだ、Mackerel プラグインを作ろう。
というわけで完成したのがこれ。(自分以外が使用することを想定していません)
まずは API がないか調べる。
[ 為替 API 無料 ] で以下の記事がヒットした。
有料になった exchangeratesapi.io を無料の範囲内で使い倒すnpmパッケージを作ってみた #Node.js - Qiita
どうやら Exchange Rates API に無料プランがあるらしい。
他をざっとみたら有料だったので exchangeratesapi を使用する。
無料プランの注意点として、リクエストが月に 1000回 までに制限されている。
API の更新頻度が 1日 1回 なので 30回程度 叩ければ十分だが、なんとなく定期的にメトリクスを投稿したい。
なのでとりあえず余裕を持って 2時間 に 1回 リクエストを投げるようにした。
具体的には、メトリクスを投稿した時点のタイムスタンプをファイルに書き込んで、エージェントが実行する際には、ファイルの最終行を読み取り、2時間 以上経っていなければ意図的にエラーを返すようにした。
あとはよしなに式監視 alias(scale(host(4EuzHqHvEEU, custom.exchange.USD), 1), USD)
などを仕込む。
この式監視では、円高に気付けない、アラートの闘値を逐一変更する必要がある、などの問題点があるが、linearRegression()
とかで本格的に監視できる可能性を感じた。
testing/snapshot.ts
のgreen(bold(`\n > ${updated} snapshots updated.`))
の出力に影響する部分のリファクタリングをする際、異なるディレクトリにあるスナップショットテストの実行結果をテストで保証したかった。
既存のコードを読んだら、スナップショットテストのテストにスナップショットを使っていたのでおもしろかったので、追加したテストについてメモしておく。
まず、一時的なディレクトリを作成し、受け取った関数を実行し、実行後に一時的なディレクトリを削除する関数を用意する。
function testFnWithDifferentTempDir( fn: ( t: Deno.TestContext, tempDir1: string, tempDir2: string, ) => Promise<void>, ) { return async (t: Deno.TestContext) => { const tempDir1 = await Deno.makeTempDir(); const tempDir2 = await Deno.makeTempDir(); try { await fn(t, tempDir1, tempDir2); } finally { await Deno.remove(tempDir1, { recursive: true }); await Deno.remove(tempDir2, { recursive: true }); } }; }
実行される関数についてみていく。
引数に、スナップショットテストを記述した文字列を渡して関数を実行している。
testFnWithDifferentTempDir(async (t, tempDir1, tempDir2) => { ... const result1 = await runTestWithUpdateFlag( ` import { assertSnapshot } from "${SNAPSHOT_MODULE_URL}"; Deno.test("Snapshot Test - First", async (t) => { await assertSnapshot(t, [ 1, 2, ]); }); Deno.test("Snapshot Test - Second", async (t) => { await assertSnapshot(t, [ 3, 4, ]); });`, ` import { assertSnapshot } from "${SNAPSHOT_MODULE_URL}"; Deno.test("Snapshot Test - First", async (t) => { await assertSnapshot(t, [ 1, 2, ]); }); Deno.test("Snapshot Test - Second", async (t) => { await assertSnapshot(t, [ 3, 4, ]); });`, ); ...
実行される関数は、スナップショットテストを記述した文字列を一時的なディレクトリにあるファイルに書き込んで、スナップショットテストをコマンドで実行している。
その結果が返り値となっている。
const tempTestFileName = "test.ts"; const tempTestFilePath1 = join(tempDir1, tempTestFileName); const tempTestFilePath2 = join(tempDir2, tempTestFileName); async function runTestWithUpdateFlag(test1: string, test2: string) { await Deno.writeTextFile(tempTestFilePath1, test1); await Deno.writeTextFile(tempTestFilePath2, test2); const command = new Deno.Command(Deno.execPath(), { args: [ "test", "--allow-all", tempTestFilePath1, tempTestFilePath2, "--", "-u", ], }); const { stdout, stderr } = await command.output(); return { output: new TextDecoder().decode(stdout), error: new TextDecoder().decode(stderr), }; }
その結果を、スナップショットテストする。
await assertSnapshot(t, formatTestOutput(result1.output), { name: "Snapshot Test - Different Dir - New snapshot", });
スナップショットが生成される。
これをみて、green(bold(`\n > ${updated} snapshots updated.`))
の出力に影響がないか確認する。
snapshot[`Snapshot Test - Different Dir - New snapshot 1`] = ` "running 2 tests from <tempDir>/test.ts " + "Snapshot Test - First ... ok (--ms) " + "Snapshot Test - Second ... ok (--ms) " + "------- output ------- " + " " + " > 2 snapshots updated. " + "running 2 tests from <tempDir>/test.ts " + "Snapshot Test - First ...----- output end ----- " + "Snapshot Test - First ... ok (--ms) " + "Snapshot Test - Second ... ok (--ms) " + "------- output ------- " + " " + " > 2 snapshots updated. " + " " + "ok | 4 passed | 0 failed (--ms) " + " " `;
それぞれの実行結果が2 snapshots updated
になっているのでテスト成功。
Deno 1.34 was released in May 26, 2023
I implemented Deno API changes - Deno.FileInfo
https://deno.com/blog/v1.34#denofileinfo
The Deno.FileInfo interface now includes the following new fields:
- Deno.FileInfo.isBlockDevice
- Deno.FileInfo.isCharDevice
- Deno.FileInfo.isFifo
- Deno.FileInfo.isSocket
The fields are available on Linux and macOS. On Windows, they are always null.Thank you to Hirotaka Tagawa for the contribution.
PR is #19008
github.com
Versions prior to 1.34 don't have isBlockDevice, isCharDevice, isFifo, isSocket information in FileInfo
Example =>
$ touch file_test $ deno --version deno 1.33.2 (release, aarch64-apple-darwin) v8 11.4.183.1 typescript 5.0.3 > Deno.statSync('/Users/wafuwafu13/desktop/file_test') { isFile: true, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-25T20:38:52.075Z, atime: 2023-05-25T20:38:52.075Z, birthtime: 2023-05-25T20:38:52.075Z, dev: 16777232, ino: 56604379, mode: 33188, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, blocks: 0 }
In version 1.34 have isBlockDevice, isCharDevice, isFifo, isSocket information in FileInfo
Example =>
$ deno --version deno 1.34.0 (release, aarch64-apple-darwin) v8 11.5.150.1 typescript 5.0.4 > Deno.statSync('/Users/wafuwafu13/desktop/file_test') { isFile: true, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-25T20:38:52.075Z, atime: 2023-05-25T20:38:52.075Z, birthtime: 2023-05-25T20:38:52.075Z, dev: 16777232, ino: 56604379, mode: 33188, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, blocks: 0, isBlockDevice: false, isCharDevice: false, isFifo: false, isSocket: false }
Example of isBlockDevice: true
=>
> Deno.statSync('/dev/disk0') { isFile: false, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-24T20:27:08.000Z, atime: 2023-05-24T20:27:08.000Z, birthtime: 1970-01-01T00:00:00.000Z, dev: 89290492, ino: 623, mode: 24992, nlink: 1, uid: 0, gid: 5, rdev: 16777216, blksize: 2048, blocks: 0, isBlockDevice: true, isCharDevice: false, isFifo: false, isSocket: false }
Example of isCharDevice: true
=>
> Deno.statSync('/dev/tty') { isFile: false, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-24T20:27:08.000Z, atime: 2023-05-24T20:27:08.000Z, birthtime: 1970-01-01T00:00:00.000Z, dev: 89290492, ino: 334, mode: 8630, nlink: 1, uid: 0, gid: 0, rdev: 33554432, blksize: 65536, blocks: 0, isBlockDevice: false, isCharDevice: true, isFifo: false, isSocket: false }
Example of isFifo: true
=>
$ mkfifo fifo_test > Deno.statSync('/Users/wafuwafu13/desktop/fifo_test') { isFile: false, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-25T20:37:24.714Z, atime: 2023-05-25T20:37:24.714Z, birthtime: 2023-05-25T20:37:24.714Z, dev: 16777232, ino: 56604333, mode: 4516, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, blocks: 0, isBlockDevice: false, isCharDevice: false, isFifo: true, isSocket: false }
Example of isSocket: true
=>
> Deno.statSync('/var/run/filesystemui.socket') { isFile: false, isDirectory: false, isSymlink: false, size: 0, mtime: 2023-05-24T20:27:25.710Z, atime: 2023-05-24T20:27:25.710Z, birthtime: 2023-05-24T20:27:25.710Z, dev: 16777232, ino: 56514866, mode: 49572, nlink: 1, uid: 501, gid: 0, rdev: 0, blksize: 4096, blocks: 0, isBlockDevice: false, isCharDevice: false, isFifo: false, isSocket: true }
🦕
From: 株式会社はてな
To: Amazon Web Services EMEA SARL
2020年8月から2023年2月末まで、2年半くらいエンジニアとしてアルバイトをし、3つのチームでお世話になりました。
面接を受けたのは学部2年の夏。
その頃にはコロナが猛威を奮っていて、大学は動画を提供する施設になっていました。
1浪までして入った社会学部ですが、社会に興味が持てず、情報学の独学に励み、評価されず、楽しみや評価されたことは忘れ、理想のスケールとの乖離に陰鬱となり、後戻りはできぬと吐きそうになっていた頃に、憧れていたはてなに入れることになりました。
家では集中できなかったのと、三条あたりを歩くのが好きだったので、週3回フルタイムでオフィスに出社していました。
キャンパスの喧騒より、御池ビルの静閑が心地よく、オフィスに行った回数がキャンパスのそれに迫ろうとしていて、その事実が、自分はエンジニアであるという矜持をより強固にしたのでしょう。
ある時期までかなり美味しいお弁当が提供されていて、体制の変更で提供されなくなってからは、高級感漂う松屋ホテルユニゾ京都烏丸御池店に通っていました。
まずは普通の学生では触れないであろう言語、Perlの入門から始まりました。
もともとJavaScript,Rubyあたりを触っていたのですが、Perlは衝撃的な言語でした。
はてなブログのコードはそれまでの経験からするととてつもなく精巧かつ巨大で、最初の頃はメンターさんにかなり面倒を見てもらいました。
特にコードレビューは深く正確で、これ以上のレビューを受けることはそうそうないだろうと今でも思っています。
フロントエンドとバックエンドの境なくいろいろやっていて、多かったのが機能修正・改善でした。
表に出ているのだと、例えばこの中のいくつかをやっていました。
staff.hatenablog.com
半年ほど経てば、荒削りだった思考がましになったと自覚するまでになり、数多くのタスクに手をつけていました。
アルバイトは手数が勝負だと思っていたので、多いときは1日に10件くらいのissueをちょっとづつ進めてまたレビューに投げるというような状態でした。
そのうち数ヶ月単位のプロジェクトにも携わるようになり、企画やデザイナーの方と密に関わったり、デプロイやらキャッシュやらを考慮するエンジニアにもなれました。
staff.hatenablog.com
日々自分が開発しているブログを読み、書き、反応も貰える充足感。
その一方で、エンジニアとしての実力にまだ飢えていました。
時間があって余裕がなかったからか、他人にできて自分にできないことが許せなくて、興味ないことは認めないことで排除するスタンスをとっていた。
他社のインターン等の事情で8月から10月末まではてなを離れていました。
OSS活動に熱中していたようです。
wafuwafu13.hatenadiary.com
はてなブログのチームではインフラを触る機会があまりなかったので、異動したいと申し出ました。
心残りがあったので、引き続きはてなブログにも顔を出していました。
developer.hatenastaff.com
インフラ寄りのタスクは必要な思考が異なり、タスクがあっても前提知識が足りずアプローチの仕方が分からない状態で、ここでもメンターさんに面倒を見てもらいました。
CI/CDの整備や構築、そして普通の学生では叩かないであろうAkamaiのAPIであれこれするタスクなどをこなし、アプリケーションの開発と異なる面白さが発見できました。
その頃には家が集中できる環境になっていたので、リモートしか受け付けない体になりました。
3月にオフィスが移転されたのですが、最終出社以外1度も訪れることはありませんでした。
余裕と惰性を感じさせない日々を貫く緊張感と共に過ごすことは特別でも日常でもなかった。
AWSなど手段を問わず関わってみたかったサービスがあり、Mackerelもその1つでした。
そのため、就活は終了していましたが、時給交渉が成功したことも相まって、はてなを離れずMackerelチームに異動することにしました。
興味はインフラにあったのですが、AngularJSからReactへの移行プロジェクトが進んでいたので、それに加わることになりました。
フロントは1番得意な領域だったのですが、大規模な移行プロジェクトに継続的に関わったことはなかったので、学びがいろいろありました。
それがひと段落したのが9月頃で、そこからはチームが変わってOSSを7割、残りは内部の改善や構築をしていました。
この働き方が1番性に合っていて、かなり楽しかったです。
https://github.com/pulls?page=1&q=author%3Awafuwafu13+org%3Amackerelio+org%3Amackerelio-labs
急いで繕った夢が叶った。
振り返れば大学生の半分を超える期間をはてなで過ごしてきました。
はてなに関わった方が積み上げた土台の上でひたすら自分のやりたいことだけやらせていただきました。インターンもバイトもずっと続いてほしいです。
大変お世話になりました。ありがとうございました。
約1年前にDenoでチャレンジをしていたが、4章でbufferを扱えきれなくてScalaに乗り換えた。
wafuwafu13.hatenadiary.com
github.com
scala-simpledbのログによると12章まで進んでいたようだが、CIが落ちているので、どこかしらで進めなくなったらしい。
また、Scalaでも結局import java.***
していたのでほとんど元のSimpleDBの写経になっていたような記憶がある。
というわけで、心機一転Rustで挑戦してみた結果、3章までなんとか実装できたのでメモしておく。
1番の難所はbufferの扱いだが、bytebufferという神Crateのおかげで乗り越えられた。
set_rpos
やset_wpos
をサポートしているので、直感的に思った位置に書き込める。
欲を言えばその位置もデバッグ出来れば楽だが、get_rpos
、get_wpos
で取得できるので問題ない。
github.com
追記: 素早くマージしてくださった。感謝。
注意点として、set_wpos
の説明にSets the writing cursor to min(newPosition, self.len()) to prevent overflowとあるように、書き込みで指定するoffsetがbufferの長さを超えているとき、自動で確保されるのではなく切り詰められるので、事前にresize
で確保しておく必要がある。
pub fn set_i32(&mut self, offset: usize, n: i32) { if self.buffer.len() < offset { self.buffer.resize(offset + n.to_be_bytes().len()) } self.buffer.set_wpos(offset); self.buffer.write_i32(n); }
ファイルに書き込んだbufferはVSCodeのHex Editorを使えばデバッグしやすい。
CIはQuickstart CI workflowをそのまま使った。
手応え的に4章も実装できそうな気がしている。
ELEGOO Arduino用UNO R3スターターキットに同梱されている、気温と湿度を測れるやつ(DHT11 Temperature and Humidity module)で取得した気温をMackerelに投稿してみた。
まずは説明書通り簡単な回路を組んでArdunio IDEで実行する。
スターターキットに同梱されているサンプルコードの出力を、Serial.println(temperature)
だけにしてシンプルにしておいた。
~/desktop/mackerel-plugin-dht $ git diff DHT11.ino diff --git a/DHT11.ino b/DHT11.ino index c4c54ac..f80e18a 100644 --- a/DHT11.ino +++ b/DHT11.ino @@ -48,6 +48,10 @@ void loop( ) true, then a measurement is available. */ if( measure_environment( &temperature, &humidity ) == true ) { + Serial.println(temperature); - Serial.print( "T = " ); - Serial.print( temperature, 1 ); - Serial.print( " deg. C, H = " ); - Serial.print( humidity, 1 ); - Serial.println( "%" ); } }
Mackerelへの投稿は、go-mackerel-pluginを利用してカスタムメトリックプラグインを作成して行う。
シリアル通信の受信は、go.bug.st/serialを利用するとできる。
他の人が使うことを考慮していないため、ポートのパスはベタ書きした。
scanner.Scan()
した1行目の値の表記がおかしかったので、2行目の値を投稿することにした。
注意点として、killしないと受信できない。
~/desktop $ lsof | grep usbmodem serial-mo 22338 wafuwafu13 3u CHR 9,5 0t7 723 /dev/cu.usbmodem1101 ~/desktop $ kill 22338
func (u DhtPlugin) FetchMetrics() (map[string]float64, error) { mode := &serial.Mode{ BaudRate: 9600, } port, err := serial.Open("/dev/cu.usbmodem1101", mode) if err != nil { return nil, fmt.Errorf("Failed to open port: %s", err) } scanner := bufio.NewScanner(port) scanner.Split(bufio.ScanLines) var degrees []string for scanner.Scan() { degrees = append(degrees, scanner.Text()) if len(degrees) == 2 { break } } degree, _ := strconv.ParseFloat(degrees[1], 64) return map[string]float64{"degrees": degree}, nil }
ビルドして、パスを設定ファイルに追加する。
[plugin.metrics.dht]
command = "/Users/wafuwafu13/Desktop/mackerel-plugin-dht/mackerel-plugin-dht"
室温は18℃だった。ストーブに近づけたら47℃まで上がった。
使用したコードはこちら。
github.com