Linux 生コンソールで日本語を表示する

こんにちは、masm11 です。

暇に任せて Linux の生コンソール (window system が動いていない黒背景の画面を私は勝手にこう呼んでいます。window system が動いていようが、コンソールはコンソールなので…) に日本語を表示する設定をしたので、今回はそれをご紹介します。

KMSCON をインストール、起動する

https://wiki.archlinux.jp/index.php/KMSCON

archlinux を使っているので↑このページを参考にしました。

fbdev とか drm2d とか drm3d とか書いてあって、「…?」となりましたが、 自分の PC で何が動いているのかの確認方法も書かれていないので、 とりあえず無視して進めました。

まず kmscon をインストールします。

sudo pacman -S kmscon

「tty1 で」と書いてありますが、OS が起動して最初の tty は tty1 です。 何かあると詰んでしまう可能性があります。tty2 で動かしてみることにします。

sudo systemctl disable getty@tty2.service
sudo systemctl enable kmsconvt@tty2.service

tty2 に切り替えると、いつもと様子の違う画面が表示されました。 無事動いているようです。 fbdev 云々の条件もクリアしていたのでしょう。

ちょっと確認してみる

…気になりません? 私は気になりました。tty を確認してみます。

% tty
/dev/pts/0

なんと、疑似端末 (pseudo-tty, pty) を使用しているのですね。

上記ページの最初に書いてある、

カーネルの VT 実装をユーザスペースのコンソールで置き換えようという試みです。

これは、こういう意味なのでしょう。

イメージとしては、端末エミュレータを全画面で使用しているようなものでしょうか。 window system のサーバはいませんけど。

設定を続ける

ちょっと操作しづらいので、設定をしてみました。

/etc/systemd/system/kmsconvt@tty2.service.d/cmd.conf に以下のように書きました。

[Service]
ExecStart=
ExecStart=/usr/bin/kmscon "--vt=%I" --seats=seat0 --no-switchvt --xkb-repeat-rate=20 --xkb-options=ctrl:swapcaps --sb-size=100000 --font-size=16

下側の ExecStart=... は、--no-switchvt までは元のコマンドそのままで、 その後ろを追加しました。 ただ、それだけだと ExecStart= の追加になってしまうので、 その上の行に ExecStart= のみを書くことで、一旦クリアしています。

追加した各オプションの意味は以下のとおりです。

  • --xkb-repeat-rate=20

    キーリピートが遅かったので 2.5倍にしました。

  • --xkb-options=ctrl:swapcaps

    ctrl と capslock を入れ替えました。私はいつもこれです。

  • --sb-size=100000

    バックスクロールができるんですね。ただデフォルトの1000行では少ないので、100倍しておきました。

  • --font-size=16

    文字を大きくしました。もう歳でして…

さて、これを反映させるには、以下を実行します。

sudo systemctl restart kmsconvt@tty2.service

フォントを設定

なんかフォントが意図したものと違うな、と思ったら、 いつものフォントは ~/.config/fontconfig/fonts.conf に設定してあるので、ユーザ用の設定でした。 kmscon はシステムの一部として動いているので、システム用に設定する必要があります。

~/.config/fontconfig/fonts.conf を /etc/fonts/local.conf にコピーして、 それを反映させるために以下のコマンドを実行しました。

fc-cache -fv

フォントが綺麗になりました。

結果

これはもう、スクリーンショットでなく写真でお見せします。

f:id:masm11:20211120224208j:plain

いつもの端末と同じ表示です!

問題点

しかし問題もあります。

kmscon からは X は起動しません。/dev/pts/0 はコンソールでないと見なされ、startx がそもそも拒否します。 Wayfire も DRM を確保できなくて起動に失敗しました。

今回の設定では tty1 はいじっていませんので、tty1 からならどちらも起動できます。

環境変数を日本語に設定して window system を起動して、 日本語で出力されるエラーを確認したい場合もありますので、 ここは少し残念です。

tty2 以外にも反映させる

では動作が確認できたので、tty2 以外にも反映させてみます。

sudo ln -s /usr/lib/systemd/system/kmsconvt@.service /etc/systemd/system/autovt@.service

ここで注意です。先程 enable した設定はそのままにしておきます。 これを元に戻すと、autovt 設定が効きませんでした。 また autovt は enable する必要もないようです。 ちょっとよく解らないので、機会があれば仕組みを調べてみたいと思います。

先程設定したものを /etc/kmscon/kmscon.conf に移動させます。

font-name=NasuM
font-size=16
xkb-repeat-rate=20
xkb-options=ctrl:swapcaps
sb-size=100000

フォントはこちらに設定しましたので、/etc/fonts/local.conf は削除しました。 また、systemd 用の追加設定も削除しました。

で reboot します。

なんと、tty1 は今までどおりで、tty2 以降が kmscon になっていました。 これはこれでいいかもしれませんね。window system は tty1 から起動できます。

まとめ

kmscon を導入して生コンソールで日本語を表示できるようにしました。

tty1 は今までどおりで tty2 以降で日本語が表示できるようになりました。 ただ、曖昧幅を全角にする方法は今のところ用意されていないようです。

また、バックスクロールもできるようになりました。 最近の Linux kernel ではバックスクロールが廃止されてて、残念な思いをしていましたが、 これでようやく復活です。

しかし、いいことばかりではなく、kmscon からは X を起動できませんでした。 これについては、tty1 は元のままなので、tty1 から起動すれば良いことにします。

今回、日本語入力については扱っていません。

インゲージではこんな遊べるエンジニアも募集しています。 詳細は以下へ!

https://ingage.co.jp/recruit/

iPhone充電スタンドをMagSafe化してみた、けれど

こんにちは。Tedです。 iPhone 12から採用になったMagSafeが気になっていました。 でも私のiPhoneは11。MagSafeに対応していません。 なのでQi充電器を使う際に、あの「ピタッ」とできるのはいいなあと思っていました。

iPhone 13を購入

今回、iPhone 13を購入しました。 そこでいつも使っているQi充電スタンドをMagSafe化しようと思い立ちました。

f:id:ingage:20211116180101j:plain
日頃使っている私のQi充電スタンド

MagSafe化の作戦

作戦としては、いつも使っている普通のQi充電スタンドにiPhone 13の背面マグネットにくっつく金属プレートを貼り付ければ完成!と思いました。 Amazonにて、その金属プレートはありました。

Magsafe用 リング 磁気増強 メタルリング

f:id:ingage:20211116181831p:plain https://www.amazon.co.jp/gp/product/B08SLCWWP8/

2枚で599円ということで早速購入。 iPhone 13に当てて位置決めを行います。

f:id:ingage:20211116180644j:plain
写真はiPhone 12です

位置決めしてて「あれ、磁力はそんなに強くないな」と思いました。 ケースの上からということもあるのか、ビタッと強力に着くのではなく「ふわっ」と着く感じです。 そのくらいの方が取り外ししやすいかなと思い、作業を進行していきました。

Qiスタンドにプレートを貼り付け

iPhoneを自然に置いた状態で目的の位置にプレートが来るように貼り付けました。 f:id:ingage:20211116181019j:plain

磁力はたしかに強くはないけれど、引き寄せられる感じは感じられたので「まあいいかな」と思いました。

けれど、、、、

貼り付けまではスムーズに行きました。 セットすると充電がちゃんと始まりました。 「これで私のQiスタンドもMagSafe化できた。しかも安価に」と喜んだのもつかの間でした。 寝る前にスタンドにセットして朝見たところ、充電が途中で止まっていたのです。

iPhoneをスタンドにセットし直すと充電は再開されますが、やはり途中で止まります。 給電量が足りないのかと12W対応のACアダプタに変えましたが結果は変わりませんでした。

どうも、金属プレートが非接触充電を邪魔しているような感じです。 考えてみると、それはそうかと思いました。MagSafeを考慮していないスタンドですから、金属プレートが非接触充電をシールドしたりするのでしょう。

というわけで、今回の試みは失敗でした。。。 お金貯めてMagSafe対応の充電スタンドを買おうと思います。

systemd で zfs legacy mount の問題を解決する

f:id:masm11:20211020212947p:plain

こんにちは、masm11 です。

ZFS に関連した mount で苦労したのでご紹介したいと思います。

ZFS の mountpoint 設定とは

ZFS は自前で mountpoint を管理しています。

zfs set mountpoint=/home/service zroot/svc

などとして mountpoint を設定しておくと、OS 起動時に

zfs mount -a

で自動的に mount してくれます。

ZFS の legacy mount とは

ですが、この機能にも限界があります。

私の場合、ZFS 内のディレクトリを bind mount しようとして、できませんでした。

bind mount ってのは、

mount --bind /home/service/storage/music /exports/music

とすることで、/home/service/storage/music が /exports/music でも見える、というやつです (シンボリックリンクと違って、cd .. でちゃんと元の場所に戻れます)。

そこで、ZFS に mountpoint を設定するのをやめました。つまり、以下のようにします。

zfs set mountpoint=legacy zroot/svc

こうすることで、/etc/fstab に記述して mount する方法に切り替えることができます。

うまくいかない

で、やってみました。/etc/fstab には以下のように書きました。

zroot/svc  /home/service  zfs  defaults 0 0

しかし、うまく起動してくれません。

10月 20 20:23:45 mike2 systemd[1]: Failed to mount /home/service.

なにかがうまく行ってないようです。

10月 20 20:23:46 mike2 systemd[1]: Starting Import ZFS pools by cache file...
10月 20 20:23:49 mike2 systemd[1]: Finished Import ZFS pools by cache file.
10月 20 20:23:49 mike2 systemd[1]: Reached target ZFS pool import target.
10月 20 20:23:49 mike2 systemd[1]: Starting Mount ZFS filesystems...
10月 20 20:23:49 mike2 systemd[1]: Starting Wait for ZFS Volume (zvol) links in /dev...
10月 20 20:23:49 mike2 systemd[1]: Finished Wait for ZFS Volume (zvol) links in /dev.
10月 20 20:23:49 mike2 systemd[1]: Reached target ZFS volumes are ready.
10月 20 20:23:49 mike2 systemd[1]: Reached target ZFS startup target.
10月 20 20:23:49 mike2 systemd[1]: Finished Mount ZFS filesystems.

ZFS はちゃんと処理されてるようです。

何がまずいんでしょう?

解決

ふと気づきました。

mount 失敗の方が、ログの上の方にある…??

なるほど、mount しようとした時に、ZFS がまだ準備できてないんですね。

ZFS の準備ができるまで mount を待たせることはできないでしょうか…

闇雲に systemd.mount(5) のマニュアルを探したところ、ありました!

x-systemd.requires=
    Configures a Requires= and an After= dependency between the created
    mount unit and another systemd unit, such as a device or mount
    unit. ...

これを使うと、自動生成される home-service.mount に Requires= が 追加されるようです。そこに zfs.target を追加できそうです。

さっそく /etc/fstab に追加してみました。

zroot/svc  /home/service  zfs  x-systemd.requires=zfs.target  0 0

これで再起動したところ、

10月 20 20:39:03 mike2 systemd[1]: Mounting /home/service...
10月 20 20:39:03 mike2 systemd[1]: Mounted /home/service.

めでたく mount できるようになりました!

まとめ

さすが systemd。fstab にまで拡張を加えるとは、やりすぎです。

そして、journal は読みづらいです。 journal の最後の方を見ても、OS 起動に失敗した理由は載ってません。 上の方を見て赤い文字を探さないといけません。 並列実行の弊害ですね。 (この辺は、プログラムのビルドに使う make コマンドも同様ですが)

自動生成される *.mount に助けられる日が来るとは思ってませんでした…

というか、legacy を使うということは、何か面倒ごとを抱え込んでいるということなので、 その場合は x-systemd.requires は必須なのかもしれませんね。

弊社ではエンジニアを募集しています。詳細は以下のページへどうぞ。

https://ingage.co.jp/recruit/

ではまた!

Ruby の Timeout.timeout の実装を読む

f:id:masm11:20211006192904p:plain

こんにちは、masm11 です。

Ruby の Timeout モジュールは便利で、

gs = TCPServer.open(0)
Timeout.timeout(5) do
  gs.accept
end

このように自由にタイムアウトを設定できます。

今回はこの実装について見ていきたいと思います。

実装を見る

Ruby のバージョンは以下のとおりです。

% ruby --version
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

では実装を見ていきます。

  def timeout(sec, klass = nil, message = nil)   #:yield: +sec+
    return yield(sec) if sec == nil or sec.zero?

sec が nil の場合、または 0 の場合は、そのままブロックを呼び出しています。 この場合はタイムアウトはなしですね。

    message ||= "execution expired".freeze

メッセージが指定されていない場合は、デフォルトのメッセージを設定しています。

    from = "from #{caller_locations(1, 1)[0]}" if $DEBUG

これは後でスレッドの名前に使っています。

    e = Error

e を Error クラスにしています。e は最終的に投げる例外を保持しているようです。

    bl = proc do |exception|

ブロックを呼び出す周辺のコードがここにまとめられています。

      begin
        x = Thread.current

これは自分自身のスレッドです。

        y = Thread.start {
          Thread.current.name = from
          begin
            sleep sec
          rescue => e
            x.raise e
          else
            x.raise exception, message
          end
        }

これは別スレッドを起動して、指定時間が過ぎたら 元のスレッドで例外を発生させています。

        return yield(sec)

指定ブロックを実行しています。 例外が発生しなかった場合はそのまま return しています。

      ensure
        if y
          y.kill
          y.join # make sure y is dead.
        end
      end
    end

別スレッドの終了処理です。

    if klass
      begin
        bl.call(klass)
      rescue klass => e
        bt = e.backtrace
      end
    else
      bt = Error.catch(message, &bl)
    end

例外クラスが指定されている場合は、それを引数として上記 proc を呼び出しています。 そのクラスの例外が発生した場合は、backtrace を bt に入れています。

例外クラスが指定されてない場合は else の方で、Error.catch を呼び出しています。 Error クラスは同じファイル中のすぐ上で定義されています。 catch/throw による大域脱出ができるようですが、 指定ブロックに tag が渡されるわけでもなく、意味はなさそうです。 そして何故返り値が bt として使えるのか全く解りませんでした。

ここから先は、タイムアウトした場合の処理です。

    level = -caller(CALLER_OFFSET).size-2
    while THIS_FILE =~ bt[level]
      bt.delete_at(level)
    end
    raise(e, message, bt)
  end

caller は backtrace を取得するメソッドだそうです。 これを使って bt から余分なものを削除し、 それを backtrace として例外を投げ直しています。

個人的には、例外の backtrace は発生した場所を正確に表していて欲しく、 余計な処理だと思います。

まとめ

Timeout.timeout が何をしているかはわかりました。ただし、

https://docs.ruby-lang.org/ja/latest/method/Timeout/m/timeout.html

timeout による割り込みは Thread によって実現されています。

ここはその通りでしたが、

C 言語レベルで実装され、 Ruby のスレッドが割り込めない処理に対して timeout は無力です。

と書いてあるにもかかわらず、割り込めてるんですよね。不思議です…

さて、弊社ではエンジニアを募集しています。詳細は以下からどうぞ。

https://ingage.co.jp/recruit

ESLint の no-multi-spaces で TypeScript のType 宣言の空白を許容させる

f:id:ishiyu1125:20210923145413p:plain

こんにちは。 @ishiyu です。

ESLint の no-multi-spaces ルールはすごく便利ですよね。

Function の定義で

function   (   aaaa ) {
  return   'test';
}

みたいな不要な空白だらけのコードでエラーを出してくれます。

また、--fix オプションを使えば自動でキレイに取り除いてくれます。

function (aaaa) {
  return 'test';
}

しかし、デフォルトの設定では視認性を上げるために空白を入れた配列もエラーになります。

const mens = [
  ["山田", "y.png",      25],
  ["佐藤", "sato.png",   19],
  ["鈴木", "suzuki.png", 22],
];
// => ESLint: Multiple spaces found before '25'.(no-multi-spaces)

こちらについては、以下のように eslintrc.json に定義しておけば、空白を許容し取り除かれなくなります。

"no-multi-spaces": {
  [
    "error", {
      exceptions: {
        "ArrayExpression": true
      }
    }
  ]
}

その他にも Object や変数宣言など JavaScript に関連する空白の許容は可能になっています。 (詳しくはドキュメントに載ってます)

しかし、TypeScript 特有の Type 宣言や Interface 宣言についてはドキュメントに記載がないため、空白を許容する設定ができず、このように書くしかありません。

type TestType = {
  name: string;
  genderCode: number;
  email: string;
}

ということで、 no-multi-spaces で TypeScript のType 宣言の空白を許容させる方法を調べてみました。

TypeScript のType 宣言の空白を許容させる

で、本題です。

試した環境

ちょっと古いけど。。。

typescript: 4.2.3
eslint: 7.23.0
typescript-eslint/perser: 4.20.0

設定するルール

exceptions"TSTypeAnnotation": true を追加すれば良いです。

これだけで、 Type 宣言時の空白チェックを無視するようになります。

"no-multi-spaces": {
  [
    "error", {
      exceptions: {
        "TSTypeAnnotation": true
      }
    }
  ]
}

なぜこれで空白が許容されるようになるのか

ESLint はコードをパースして AST (Abstract Syntax Tree (抽象構文木)) の集合に変換し、チェックを行なっています。

これは TypeScript でも同様です。 ただし ESLint も TypeScript も変換される AST の Type(NodeType) の種類がそれぞれ定義されており、微妙に異なっています。

その差異を吸収するのが typescript-eslint/perser です。

そのため、TypeScript+ESLint の構成の場合、typescript-eslint/perser のみ意識すれば良いことになります。

ちなみに、AST についてはこちらが分かりやすかったです。

no-multi-spaces の exceptions のキーは、この AST の Type 名になっています。

配列の空白を無視するために設定した ArrayExpression や今回紹介した TSTypeAnnotation も AST の Type の名前です。ですので、ESLint のドキュメントに記載がない設定も空白を許可させることが可能になっています。

この AST については AST Explorer で、確認ができます。

今回紹介したno-multi-spaces ルールに無視する AST Type を確認する場合には、以下のように設定し、typescript-eslint/perser でパースする必要があるので注意してください。

f:id:ishiyu1125:20210923144456p:plain
AST Explorer

おわりに

インゲージでは TypeScript を語りたいフロントエンジニアを絶賛募集中です。

ご応募お待ちしてます。

↓↓↓↓↓↓↓↓↓↓↓↓↓

ingage.co.jp

コマンドラインが変な状態になったら

こんにちは、masm11 です。 今回は小技を一つ紹介したいと思います。

症状

コマンドラインがこんな状態に陥って、端末を閉じるしかなかった経験、ありませんか?

  • ctrl+pctrl+h が効かない
  • Enter が効かない (押しても ^M と表示される)
  • プロンプトが左端に表示されない

等々…

解決策

そんな状態になったら、さくっと以下のコマンドを実行します。

stty sane

これだけです。たいていの症状は解決してくれます。

なお、Enter が効かない場合は、代わりに ctrl+j なら効くことが多いです。

最後に

webpack を使ってると、エラー終了時に

  • ctrl+p が効かない (^P と表示される)

になることが多いです…

さて。弊社ではこんな小ネタもご存知なエンジニアを募集しています。 詳細は以下のページへ。

https://ingage.co.jp/recruit/

N+1問題について勉強会で発表してみた

f:id:ryohei515:20210819203336j:plain

ryohei515です。

Ruby on Railsを使う上で、N+1問題は避けては通れない問題です。

インゲージでは週に1度、社内勉強会を行っているのですが、今週は私が発表担当だったため、このN+1問題を知識整理がてら、社内勉強会で共有しました。

簡単な内容ではありますが、その資料をブログで共有したいと思います。

TL;DR

N+1問題に対しては、これが結論です。

メソッド 結合方法 使用箇所
joins 内部 内部結合で結合先の条件をフィルタしたいとき
left_joins 外部 外部結合で結合先の条件をフィルタしたいとき
preload 外部 結合先のデータを使いたいとき
eager_load 外部 結合先のデータを使いたいかつ、
結合先の抽出条件を指定したいとき
includes 外部 n+1を発生させないようにしたいとき
(できれば preloadeager_load を使う。)

ただ、勉強会では

  • sizecountlength の動きはそれぞれ違うよね。
  • インゲージが提供しているサービス Re:lation だと、 eager_loadpreload 、どっちが早い?
  • パフォーマンスを意識してpluck を使っているけど、結果を hash に加工する gem (pluck_to_hash) を使えば、より使いやすくなりそう!

といった感じで色々な話に派生し、学びの多かった勉強会となりました!

終わりに

こんな技術について語り合う場が欲しいと思っている方はぜひご応募頂けると嬉しいです!

カジュアル面談も可能なので、ぜひ以下からお願いします!

ingage.co.jp