経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

スナップショットテストのテストにスナップショットを使う

github.com

testing/snapshot.tsgreen(bold(`\n > ${updated} snapshots updated.`))の出力に影響する部分のリファクタリングをする際、異なるディレクトリにあるスナップショットテストの実行結果をテストで保証したかった。

既存のコードを読んだら、スナップショットテストのテストにスナップショットを使っていたのでおもしろかったので、追加したテストについてメモしておく。


まず、一時的なディレクトリを作成し、受け取った関数を実行し、実行後に一時的なディレクトリを削除する関数を用意する。

https://github.com/denoland/deno_std/blob/b7e6dc0914aeaa93b6af3f9db666eaaf7615f456/testing/snapshot_test.ts#L45-L62

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 });
    }
  };
}


実行される関数についてみていく。

引数に、スナップショットテストを記述した文字列を渡して関数を実行している。

https://github.com/denoland/deno_std/blob/b7e6dc0914aeaa93b6af3f9db666eaaf7615f456/testing/snapshot_test.ts#L629-L660

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,
        ]);
      });`,
    );
...


実行される関数は、スナップショットテストを記述した文字列を一時的なディレクトリにあるファイルに書き込んで、スナップショットテストをコマンドで実行している。
その結果が返り値となっている。

https://github.com/denoland/deno_std/blob/b7e6dc0914aeaa93b6af3f9db666eaaf7615f456/testing/snapshot_test.ts#L594-L618

    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),
      };
    }


その結果を、スナップショットテストする。

https://github.com/denoland/deno_std/blob/b7e6dc0914aeaa93b6af3f9db666eaaf7615f456/testing/snapshot_test.ts#L663-L665

    await assertSnapshot(t, formatTestOutput(result1.output), {
      name: "Snapshot Test - Different Dir - New snapshot",
    });

スナップショットが生成される。
これをみて、green(bold(`\n > ${updated} snapshots updated.`))の出力に影響がないか確認する。

https://github.com/denoland/deno_std/blob/b7e6dc0914aeaa93b6af3f9db666eaaf7615f456/testing/__snapshots__/snapshot_test.ts.snap#L517-L550

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になっているのでテスト成功。

About Deno 1.34 API changes - Deno.FileInfo

Deno 1.34 was released in May 26, 2023

deno.com

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つのチームでお世話になりました。

はてなブログ(2020年8月~2021年7月)

面接を受けたのは学部2年の夏。
その頃にはコロナが猛威を奮っていて、大学は動画を提供する施設になっていました。
1浪までして入った社会学部ですが、社会に興味が持てず、情報学の独学に励み、評価されず、楽しみや評価されたことは忘れ、理想のスケールとの乖離に陰鬱となり、後戻りはできぬと吐きそうになっていた頃に、憧れていたはてなに入れることになりました。
家では集中できなかったのと、三条あたりを歩くのが好きだったので、週3回フルタイムでオフィスに出社していました。
キャンパスの喧騒より、御池ビルの静閑が心地よく、オフィスに行った回数がキャンパスのそれに迫ろうとしていて、その事実が、自分はエンジニアであるという矜持をより強固にしたのでしょう。
ある時期までかなり美味しいお弁当が提供されていて、体制の変更で提供されなくなってからは、高級感漂う松屋ホテルユニゾ京都烏丸御池店に通っていました。

まずは普通の学生では触れないであろう言語、Perlの入門から始まりました。
もともとJavaScript,Rubyあたりを触っていたのですが、Perlは衝撃的な言語でした。
はてなブログのコードはそれまでの経験からするととてつもなく精巧かつ巨大で、最初の頃はメンターさんにかなり面倒を見てもらいました。
特にコードレビューは深く正確で、これ以上のレビューを受けることはそうそうないだろうと今でも思っています。
フロントエンドとバックエンドの境なくいろいろやっていて、多かったのが機能修正・改善でした。
表に出ているのだと、例えばこの中のいくつかをやっていました。
staff.hatenablog.com
半年ほど経てば、荒削りだった思考がましになったと自覚するまでになり、数多くのタスクに手をつけていました。
アルバイトは手数が勝負だと思っていたので、多いときは1日に10件くらいのissueをちょっとづつ進めてまたレビューに投げるというような状態でした。
そのうち数ヶ月単位のプロジェクトにも携わるようになり、企画やデザイナーの方と密に関わったり、デプロイやらキャッシュやらを考慮するエンジニアにもなれました。
staff.hatenablog.com
日々自分が開発しているブログを読み、書き、反応も貰える充足感。
その一方で、エンジニアとしての実力にまだ飢えていました。
時間があって余裕がなかったからか、他人にできて自分にできないことが許せなくて、興味ないことは認めないことで排除するスタンスをとっていた。

シスプラ(2021年11月~2022年4月)

他社のインターン等の事情で8月から10月末まではてなを離れていました。
OSS活動に熱中していたようです。
wafuwafu13.hatenadiary.com
はてなブログのチームではインフラを触る機会があまりなかったので、異動したいと申し出ました。
心残りがあったので、引き続きはてなブログにも顔を出していました。
developer.hatenastaff.com
インフラ寄りのタスクは必要な思考が異なり、タスクがあっても前提知識が足りずアプローチの仕方が分からない状態で、ここでもメンターさんに面倒を見てもらいました。
CI/CDの整備や構築、そして普通の学生では叩かないであろうAkamaiAPIであれこれするタスクなどをこなし、アプリケーションの開発と異なる面白さが発見できました。
その頃には家が集中できる環境になっていたので、リモートしか受け付けない体になりました。
3月にオフィスが移転されたのですが、最終出社以外1度も訪れることはありませんでした。
余裕と惰性を感じさせない日々を貫く緊張感と共に過ごすことは特別でも日常でもなかった。

Mackerel(2022年4月~2023年2月)

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
急いで繕った夢が叶った。



振り返れば大学生の半分を超える期間をはてなで過ごしてきました。
はてなに関わった方が積み上げた土台の上でひたすら自分のやりたいことだけやらせていただきました。インターンもバイトもずっと続いてほしいです。
大変お世話になりました。ありがとうございました。

Rustで『Database Design and Implementation』の3章を実装する

約1年前にDenoでチャレンジをしていたが、4章でbufferを扱えきれなくてScalaに乗り換えた。

wafuwafu13.hatenadiary.com
github.com

scala-simpledbのログによると12章まで進んでいたようだが、CIが落ちているので、どこかしらで進めなくなったらしい。
また、Scalaでも結局import java.***していたのでほとんど元のSimpleDBの写経になっていたような記憶がある。
というわけで、心機一転Rustで挑戦してみた結果、3章までなんとか実装できたのでメモしておく。

github.com

1番の難所はbufferの扱いだが、bytebufferという神Crateのおかげで乗り越えられた。
set_rposset_wposをサポートしているので、直感的に思った位置に書き込める。
欲を言えばその位置もデバッグ出来れば楽だが、get_rposget_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はVSCodeHex Editorを使えばデバッグしやすい。

abcdefghijklmと345(=101011001=(00000001, 01011001=(Y)))

CIはQuickstart CI workflowをそのまま使った。

手応え的に4章も実装できそうな気がしている。

ArduinoでDHT11を使って室温を取得し、Mackerelに投稿する

ELEGOO Arduino用UNO R3スターターキットに同梱されている、気温と湿度を測れるやつ(DHT11 Temperature and Humidity module)で取得した気温をMackerelに投稿してみた。

まずは説明書通り簡単な回路を組んでArdunio IDEで実行する。

Elegoo Uno R3 + DHT11 + F-M wires

スターターキットに同梱されているサンプルコードの出力を、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

M1Macで「ゼロからのOS自作入門」(みかん本)を完走した

day1~day30のログはここに。
scrapbox.io

環境構築はこの記事を参考にしたらできたが、
zenn.dev

このコメント通りに修正する必要があった。
https://zenn.dev/link/comments/3860a03795708b


day4で発生した/usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not foundを解決するために、stdint.hを使わないようにした。
https://scrapbox.io/wafuwafuoss/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AEOS%E8%87%AA%E4%BD%9C%E5%85%A5%E9%96%80#635c8a98b96bbf0000ab84b8
osbook_day30fでこの差分を元に戻しても起動できた。原因はよくわかってない。

vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/elf.hpp
diff --git a/kernel/elf.hpp b/kernel/elf.hpp
index 99ab80b..4f05cbc 100644
--- a/kernel/elf.hpp
+++ b/kernel/elf.hpp
@@ -1,14 +1,12 @@
 #pragma once
 
-#include <stdint.h>
-
-typedef uintptr_t Elf64_Addr;
-typedef uint64_t  Elf64_Off;
-typedef uint16_t  Elf64_Half;
-typedef uint32_t  Elf64_Word;
-typedef int32_t   Elf64_Sword;
-typedef uint64_t  Elf64_Xword;
-typedef int64_t   Elf64_Sxword;
+typedef unsigned long Elf64_Addr;
+typedef unsigned long  Elf64_Off;
+typedef unsigned char  Elf64_Half;
+typedef unsigned int  Elf64_Word;
+typedef int Elf64_Sword;
+typedef unsigned long  Elf64_Xword;
+typedef long Elf64_Sxword;
 
 #define EI_NIDENT 16
vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/frame_buffer_config.hpp 
diff --git a/kernel/frame_buffer_config.hpp b/kernel/frame_buffer_config.hpp
index 0ce8035..c9bb380 100644
--- a/kernel/frame_buffer_config.hpp
+++ b/kernel/frame_buffer_config.hpp
@@ -1,16 +1,14 @@
 #pragma once
 
-#include <stdint.h>
-
 enum PixelFormat {
   kPixelRGBResv8BitPerColor,
   kPixelBGRResv8BitPerColor,
 };
 
 struct FrameBufferConfig {
-  uint8_t* frame_buffer;
-  uint32_t pixels_per_scan_line;
-  uint32_t horizontal_resolution;
-  uint32_t vertical_resolution;
+  unsigned char* frame_buffer;
+  unsigned int pixels_per_scan_line;
+  unsigned int horizontal_resolution;
+  unsigned int vertical_resolution;
   enum PixelFormat pixel_format;
 };
vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/memory_map.hpp 
diff --git a/kernel/memory_map.hpp b/kernel/memory_map.hpp
index ef61348..e3b68d6 100644
--- a/kernel/memory_map.hpp
+++ b/kernel/memory_map.hpp
@@ -1,22 +1,20 @@
 #pragma once
 
-#include <stdint.h>
-
 struct MemoryMap {
   unsigned long long buffer_size;
   void* buffer;
   unsigned long long map_size;
   unsigned long long map_key;
   unsigned long long descriptor_size;
-  uint32_t descriptor_version;
+  unsigned int descriptor_version;
 };
 
 struct MemoryDescriptor {
-  uint32_t type;
-  uintptr_t physical_start;
-  uintptr_t virtual_start;
-  uint64_t number_of_pages;
-  uint64_t attribute;
+  unsigned int type;
+  unsigned long physical_start;
+  unsigned long virtual_start;
+  unsigned long number_of_pages;
+  unsigned long attribute;
 };
 
 #ifdef __cplusplus
@@ -39,11 +37,11 @@ enum class MemoryType {
   kEfiMaxMemoryType
 };
 
-inline bool operator==(uint32_t lhs, MemoryType rhs) {
-  return lhs == static_cast<uint32_t>(rhs);
+inline bool operator==(unsigned int lhs, MemoryType rhs) {
+  return lhs == static_cast<unsigned int>(rhs);
 }
 
-inline bool operator==(MemoryType lhs, uint32_t rhs) {
+inline bool operator==(MemoryType lhs, unsigned int rhs) {
   return rhs == lhs;
 }

osbook_day15dの「描画の高速化」をすると、Failed to allocate pages: NotFoundが出てしまったので、DrawAreaを使わないようにした。
https://scrapbox.io/wafuwafuoss/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AEOS%E8%87%AA%E4%BD%9C%E5%85%A5%E9%96%80#63801fb5b96bbf0000092981
osbook_day30fでこの差分を元に戻しても起動できた。原因はよくわかってない。

vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/message.hpp 
diff --git a/kernel/message.hpp b/kernel/message.hpp
index 8798623..148e82e 100644
--- a/kernel/message.hpp
+++ b/kernel/message.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 enum class LayerOperation {
-  Move, MoveRelative, Draw, DrawArea
+  Move, MoveRelative, Draw
 };
 
 struct Message {
@@ -37,7 +37,6 @@ struct Message {
       LayerOperation op;
       unsigned int layer_id;
       int x, y;
-      int w, h;
     } layer;
 
     struct {
vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/layer.hpp 
diff --git a/kernel/layer.hpp b/kernel/layer.hpp
index 5f616da..427ff29 100644
--- a/kernel/layer.hpp
+++ b/kernel/layer.hpp
@@ -69,8 +69,6 @@ class LayerManager {
   void Draw(const Rectangle<int>& area) const;
   /** @brief 指定したレイヤーに設定されているウィンドウの描画領域内を再描画する。 */
   void Draw(unsigned int id) const;
-  /** @brief 指定したレイヤーに設定されているウィンドウ内の指定された範囲を再描画する。 */
-  void Draw(unsigned int id, Rectangle<int> area) const;
 
   /** @brief レイヤーの位置情報を指定された絶対座標へと更新する。再描画する。 */
   void Move(unsigned int id, Vector2D<int> new_pos);
@@ -123,17 +121,5 @@ extern std::map<unsigned int, uint64_t>* layer_task_map;
 void InitializeLayer();
 void ProcessLayerMessage(const Message& msg);
 
-constexpr Message MakeLayerMessage(
-    uint64_t task_id, unsigned int layer_id,
-    LayerOperation op, const Rectangle<int>& area) {
-  Message msg{Message::kLayer, task_id};
-  msg.arg.layer.layer_id = layer_id;
-  msg.arg.layer.op = op;
-  msg.arg.layer.x = area.pos.x;
-  msg.arg.layer.y = area.pos.y;
-  msg.arg.layer.w = area.size.x;
-  msg.arg.layer.h = area.size.y;
-  return msg;
-}
 
 Error CloseLayer(unsigned int layer_id);
vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/layer.cpp
diff --git a/kernel/layer.cpp b/kernel/layer.cpp
index 1188b71..f4d3978 100644
--- a/kernel/layer.cpp
+++ b/kernel/layer.cpp
@@ -89,20 +89,12 @@ void LayerManager::Draw(const Rectangle<int>& area) const {
 }
 
 void LayerManager::Draw(unsigned int id) const {
-  Draw(id, {{0, 0}, {-1, -1}});
-}
-
-void LayerManager::Draw(unsigned int id, Rectangle<int> area) const {
   bool draw = false;
   Rectangle<int> window_area;
   for (auto layer : layer_stack_) {
     if (layer->ID() == id) {
       window_area.size = layer->GetWindow()->Size();
       window_area.pos = layer->GetPosition();
-      if (area.size.x >= 0 || area.size.y >= 0) {
-        area.pos = area.pos + window_area.pos;
-        window_area = window_area & area;
-      }
       draw = true;
     }
     if (draw) {
@@ -304,9 +296,6 @@ void ProcessLayerMessage(const Message& msg) {
   case LayerOperation::Draw:
     layer_manager->Draw(arg.layer_id);
     break;
-  case LayerOperation::DrawArea:
-    layer_manager->Draw(arg.layer_id, {{arg.x, arg.y}, {arg.w, arg.h}});
-    break;
   }
 }
vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/terminal.cpp 
diff --git a/kernel/terminal.cpp b/kernel/terminal.cpp
index 8762f72..1f4665d 100644
diff --git a/kernel/terminal.cpp b/kernel/terminal.cpp
index 8762f72..1f4665d 100644
--- a/kernel/terminal.cpp
+++ b/kernel/terminal.cpp
@@ -639,27 +639,18 @@ void Terminal::Print(const char* s, std::optional<size_t> len) {
   }
 
   DrawCursor(true);
-  const auto cursor_after = CalcCursorPos();
-
-  Vector2D<int> draw_pos{ToplevelWindow::kTopLeftMargin.x, cursor_before.y};
-  Vector2D<int> draw_size{window_->InnerSize().x,
-                          cursor_after.y - cursor_before.y + 16};
-
-  Rectangle<int> draw_area{draw_pos, draw_size};
-
-  Message msg = MakeLayerMessage(
-      task_.ID(), LayerID(), LayerOperation::DrawArea, draw_area);
+  Message msg{Message::kLayer, task_.ID()};
+  msg.arg.layer.layer_id = LayerID();
+  msg.arg.layer.op = LayerOperation::Draw;
   __asm__("cli");
   task_manager->SendMessage(1, msg);
   __asm__("sti");
 }
 
 void Terminal::Redraw() {
-  Rectangle<int> draw_area{ToplevelWindow::kTopLeftMargin,
-                           window_->InnerSize()};
-
-  Message msg = MakeLayerMessage(
-      task_.ID(), LayerID(), LayerOperation::DrawArea, draw_area);
+  Message msg{Message::kLayer, task_.ID()};
+  msg.arg.layer.layer_id = LayerID();
+  msg.arg.layer.op = LayerOperation::Draw;
   __asm__("cli");
   task_manager->SendMessage(1, msg);
   __asm__("sti");
@@ -743,9 +734,10 @@ void TaskTerminal(uint64_t task_id, int64_t data) {
     case Message::kTimerTimeout:
       add_blink_timer(msg->arg.timer.timeout);
       if (show_window && window_isactive) {
-        const auto area = terminal->BlinkCursor();
-        Message msg = MakeLayerMessage(
-            task_id, terminal->LayerID(), LayerOperation::DrawArea, area);
+        terminal->BlinkCursor();
+        Message msg{Message::kLayer, task_id};
+        msg.arg.layer.layer_id = terminal->LayerID();
+        msg.arg.layer.op = LayerOperation::Draw;
         __asm__("cli");
         task_manager->SendMessage(1, msg);
         __asm__("sti");
@@ -757,8 +749,9 @@ void TaskTerminal(uint64_t task_id, int64_t data) {
                                              msg->arg.keyboard.keycode,
                                              msg->arg.keyboard.ascii);
         if (show_window) {
-          Message msg = MakeLayerMessage(
-              task_id, terminal->LayerID(), LayerOperation::DrawArea, area);
+          Message msg{Message::kLayer, task_id};
+          msg.arg.layer.layer_id = terminal->LayerID();
+          msg.arg.layer.op = LayerOperation::Draw;
           __asm__("cli");
           task_manager->SendMessage(1, msg);
           __asm__("sti");

osbook_day24aの「ターミナルを増やす」でF2キーが分からなかったのでZにした。

vscode ➜ /workspaces/mikanos-devcontainer/mikanos (tags/osbook_day30f ✗) $ git diff kernel/main.cpp 
diff --git a/kernel/main.cpp b/kernel/main.cpp
index 40e67ed..e483fcf 100644
--- a/kernel/main.cpp
+++ b/kernel/main.cpp
@@ -211,7 +211,7 @@ extern "C" void KernelMainNewStack(
           InputTextWindow(msg->arg.keyboard.ascii);
         }
       } else if (msg->arg.keyboard.press &&
-                 msg->arg.keyboard.keycode == 59 /* F2 */) {
+                 msg->arg.keyboard.keycode == 29 /* Z */) {
         task_manager->NewTask()
           .InitContext(TaskTerminal, 0)
           .Wakeup();


自分の場合、順番に進めるにあたって変更する箇所ができたが、osbook_day30fでは差分なしで起動できた。素晴らしいコンテナのおかげでMacでも問題なく開発できる。
意味がわからず進めていた箇所が多いのでこれから改造して理解を進めていきたい。

etcdのgRPCコードリーディング

Do'er Advent Calendar 2022の22日目です。

etcdとは「a strongly consistent, distributed key-value store that provides a reliable way to store data that needs to be accessed by a distributed system or cluster of machines」であり、KubernetesやRookで使われていて、Quickstartを参考にすれば手元で動かせます。

いくつかコミットをしていますが、etcdではgRPCが使われているのでいろいろと思い出す必要がありました。
2年前くらいにやったThe complete gRPC courseをもう一度やったらなんとなく読めるようになったのでメモ書きをします。



コードリーディングの対象は命名からして簡単そうなclient.gocheckVersionにする。
Statusの返り値であるresp.Versionを使って処理がされているので、これがどこから来た何なのかを知りたい。

etcd/client.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

func (c *Client) checkVersion() (err error) {
...
   resp, rerr := c.Status(ctx, e)
   ...
   vs := strings.Split(resp.Version, ".")
...

Statusの定義ジャンプをすると、maintenance.goに飛ぶ。
etcd/maintenance.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) {
...
   resp, err := remote.Status(ctx, &pb.StatusRequest{}, m.callOpts...)
...

さらにStatusの定義ジャンプをすると、proto定義から生成されるrpc.pb.goに飛ぶ。
etcd/rpc.pb.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

type MaintenanceClient interface {
	// Alarm activates, deactivates, and queries alarms regarding cluster health.
	Alarm(ctx context.Context, in *AlarmRequest, opts ...grpc.CallOption) (*AlarmResponse, error)
	// Status gets the status of the member.
	Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
...

rpc.protoを見ると、関連しているmessageserviceが定義されている。
StatusResponseには目的のversionがある。

etcd/rpc.proto at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

message StatusRequest {
  option (versionpb.etcd_version_msg) = "3.0";
}

message StatusResponse {
  option (versionpb.etcd_version_msg) = "3.0";

  ResponseHeader header = 1;
  // version is the cluster protocol version used by the responding member.
  string version = 2;
...
}

service Maintenance {
...
  // Status gets the status of the member.
  rpc Status(StatusRequest) returns (StatusResponse) {
      option (google.api.http) = {
        post: "/v3/maintenance/status"
        body: "*"
    };
  }

RESTっぽいパスが定義されているし、scripts/genproto.sh--grpc-gateway_outがあったのでgRPC-Gatewayを使っていそう。だが完全に忘れているのでエンドポイントの関数がどこにあるのかわからない。Statusでコード検索をかけるとserver/etcdserver/api/v3rpc/maintenance.goという、いかにもそれっぽいパスに関数が定義されていた。
定数のversion.Versionが返り値であるrespに含まれている。

func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	...
	resp := &pb.StatusResponse{
		Header:           hdr,
		Version:          version.Version,

...

という感じでなんとなく読めたのでおしまい。