知らない天井だ

初めて入院しました。

経緯としてはこんな感じです。

技術書典から帰ってきた次の日の火曜日の昼間から、めまいがしだしました。

どんなめまいかというと頭を振ると脳が遅れて回る感じのめまいです。

「まあ一晩寝れば治るだろう」と様子を見てたんですが治らず。

水曜日も治らず、ゆっくりながら外を歩いたら気分の悪さからじっとり冷や汗が出る感じです。

木曜日にさすがに病院に行くかと耳鼻科に行こうと外に出るも、押し車を押すお年寄りよりも遅い速さでしか歩けず、しかも途中で音を吐き引き返しました。

「これはさすがに」ということで妻が救急安心センターおおさかに電話をしてくれ、「脳神経外科に」ということで義父に車を出してもらい病院に行きました。

車の振動がもうつらすぎて何度か嘔吐し、病院に着いた頃には全身の服が冷や汗でぐっしょりでした。

病院では総合内科で診察と検査をしてもらうことになり、いくつか耳鳴り・難聴の確認と運動テスト(?)の後、心電図と血液検査とレントゲン撮影と MRI 検査をしてもらいました。

さいわい、めまいの原因となる重い病気が見つかることはなく、良性発作性頭位めまい症ということになるみたいです。

気分が悪くあまり食べられてなかったこともあり、栄養と抗めまい薬の点滴をしてもらっています。

それが最初の写真でした。

病院で点滴をしてもらいながら一晩寝て、今はトイレに行くくらいなら少ししかめまいを感じないぐらいになっています。

(6月9日追記)6月8日に無事退院しました。

Nix User Repository に自作パッケージを追加する

みなさん Nix は使用していますか?

シェル環境を非破壊的にセットアップできて便利ですよね。

Nix はそれだけでなく、自作ソフトウェアのビルドツールとして利用でき、パッケージの作成と配布の手段としても利用できます。

Nix のパッケージリポジトリーとしてはみなさんご存じ Nixpkgs があります。

search.nixos.org

でも、使われるか分からない自作ソフトウェアを Nixpkgs に公開するのはちょっと気が引けるわけです。

そういうときのために Nix User Repository(NUR)があります。

nur.nix-community.org

どういうパッケージを NUR に登録するとよいかはドキュメントに次のように書いてあります。

  • Packages that are only interesting for a small audience
  • Pre-releases
  • Old versions of packages that are no longer in Nixpkgs, but needed for legacy reason (i.e. old versions of GCC/LLVM)
  • Automatic generated package sets (i.e. generate packages sets from PyPi or CPAN)
  • Software with opinionated patches
  • Experiments

ここまでは、「パッケージを公開したいけど Nixpkgs に公開するほどでもない」という側面から NUR の存在意義を見ました。

逆に次のように思う人もいるはずです。

pkgs.fetchzip や(Flakes なら)inputs で任意のパッケージを取ってこれるんだからわざわざ NUR に登録しなくてもいいのでは?」

確かにそれはそうで、その上で NUR に登録する利点としては NUR のウェブ UI で検索ができるのでユーザーが存在に気付きやすくなるなどです。

Package search for NUR

NUR を使うとき

一旦 NUR を使うときはどう使うのかを見ていきましょう。

次のコードは NUR のパッケージをシェルで使う場合の例で、NUR 公式ドキュメントからの引用です。

(1) の箇所で NUR を取得し、(2) の箇所でオーバーレイを追加し、(3) の箇所でパッケージ pkgs.nur.repos.mic92.hello-nur を参照しています。mic92リポジトリー名(通常はユーザー名と同じ)で hello-nur がパッケージ名です。

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    nur = {                                               # (1)
      url = "github:nix-community/NUR";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, flake-utils, nur }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ nur.overlay ];                     # (2)
        };
      in
      {
        devShells.default = pkgs.mkShell {
          packages = [ pkgs.nur.repos.mic92.hello-nur ];  # (3)
        };
      }
    );
}

パッケージのほかにも Nix ライブラリーや Nix OS モジュール・Nixpkgs オーバーレイが提供され(でき)ます。詳しくは公式ドキュメントを参照してください。

NUR のしくみ

NUR は NUR の Git リポジトリーで完結するわけではありません。NUR Git リポジトリーは個々人のリポジトリーへのリンクを repos.json に持ち定期的に個々人のリポジトリーをマージします。

{
    "repos": {
        ⋮
        "kakkun61": {
            "github-contact": "kakkun61",
            "url": "https://github.com/kakkun61/nur-packages"
        },
        ⋮
}

個々人のリポジトリーでは default.nixlib modules overlays それからパッケージのデリベーションを提供します。

{ pkgs ? import <nixpkgs> { } }:
{
  lib = import ./lib { inherit pkgs; }; # functions
  modules = import ./modules; # NixOS modules
  overlays = import ./overlays; # nixpkgs overlays

  wd = pkgs.callPackage ./pkgs/wd { };
}

自作パッケージの追加のしかた

まずは前述の「個々人のリポジトリー」を自分用に作成します。リポジトリーのテンプレートが用意されているのでそれを利用します。

github.com

そして自作ソフトウェアのパッケージを default.nix で提供できるようにします。そのとき、default.nixpkgs を引数に取ること・builtins.fetchTarball ではなく pkgs.fetchzip を使うこと・デリベーションは broken = true; に設定すること、などルールがあるのでドキュメントやテンプレートのコメントを読みましょう。 自分の場合は次のようなファイルを pkgs/wd/default.nix に用意しました。

{ pkgs, fetchzip }:

let
  version = "80e0814140c33201adf0e5213c426fb7bd5f47cf";
  project =
    fetchzip {
      url = "https://github.com/kakkun61/wd/tarball/${version}";
      sha256 = "sha256-usZTBF9pk0IsGdZb6IqEHBEaBX7mnXutykpm9TJHAWk=";
      extension = "tar.gz";
    };
in
(import "${project}/linux" { inherit pkgs; }).default.overrideAttrs {
  broken = true;
}

そしてルートの default.nix でこのファイルを読んでいます。

そしてこれらがちゃんと評価実行ができるか確認します。

ここまでできたら NUR に自分のリポジトリーを登録する PR を出します。PR タイトルのフォーマットが指定されているだけで特に概要に追記することなどはありませんでした。自分の PR はこれです。

github.com

PR がマージされれば NUR が取り込んでくれるのを待ちます。すぐに更新してほしい場合は HTTP エンドポイントがあるので通知します。テンプレートの GitHub Actions に実装されているのでしたい人はセットアップしましょう。

自分のページが NUR のウェブサイトに表示されましたか?おめでとうございます!

Haskell で Open Telemetry を利用してオブザーバビリティーを向上させよう

Open Telemetry って何?

この記事では Open Telemetry のトレースの機能を使います。トレースを使うと、サーバーを越境してコールグラフとその実行時間などを取得することができます。下の画像は Jaeger のスクリーンショットです。Jaeger は Open Telemetry の規格にのっとったコレクター実装のひとつです。

この例では HTTP サーバーと HTTP クライアントでトレースを取得しています。まずサーバーが /1 のパスでリクエストを受けつけたことが分かります。このリクエストに対してレスポンスを返すまでに 845μs かかっていますね。このトレースにおけるひとつの区間をスパンといいます。

次にサーバーはこのリクエストに対して処理をする途中で localhost:7777/2 に HTTP リクエストを投げたことが分かります。リクエストを投げてレスポンスが返ってくるまでに 729μs かかっています。

最後に /2 へのリクエストに対してサーバーが応答したスパンが記録されています。

この例では便宜上、サーバーは自分に対して再度リクエストをしていますが、これは物理的なサーバーが別であっても同様にトレースが取得できます。

Haskell のプログラムに対してトレースを記録したい

Open Telemetry はプログラミング言語や OS などに依存しない仕様ですから、Haskell でもトレースを記録したいです。そうすれば Istio や Node などのスパンとつながったトレースを見ることができます。Haskell では hs-opentelemetry ライブラリーを使用します。

github.com

自分もいっぱいコントリビュートしています。HERP 社からの委託を受け開発しています。

インターフェースは今後破壊的変更が入る可能性が多分にありますが、HERP 社で本番運用している程度に完成しています。

hs-opentelemetry の使い方

hs-opentelemetry はいくつかのパッケージに分かれています。まず基本となるものは hs-opentelemetry-api と hs-opentelemetry-sdk です。apisdk に分かれているのは Open Telemetry の仕様が分けるよう指示しているためであまり意味はありません。トレースを取得するためのトレーサーおよびトレーサーを作成するためのトレーサープロバイダーを作成するために使用します。また「ここからここまでスパンを取得する」というように手動で指定する場合に使用します。手動で指定するには下記の型をもつ inSpan 関数を使用します。

module OpenTelemetry.Trace.Core

…

inSpan ::
  (MonadUnliftIO m, HasCallStack) =>
  Tracer ->
  -- | The name of the span. This may be updated later via 'updateName'
  Text ->
  -- | Additional options for creating the span, such as 'SpanKind',
  -- span links, starting attributes, etc.
  SpanArguments ->
  -- | The action to perform. 'inSpan' will record the time spent on the
  -- action without forcing strict evaluation of the result. Any uncaught
  -- exceptions will be recorded and rethrown.
  m a ->
  m a

inSpan の第4引数の所要時間をスパンとして記録します。

これでスパンは記録できますが、全部を inSpan で書いていくのはいささか邪魔くさいです。そこでインスツルメンテーションが用意されています。初めのトレースの例では wai 用のインスツルメンテーションと http-client インスツルメンテーションを使用しています。インスツルメンテーションを使用すると初めのトレースの例の実装は下のようになります。

{-# LANGUAGE OverloadedStrings #-}

import qualified Network.HTTP.Client as H
import qualified Network.HTTP.Types.Status as H
import qualified Network.Wai as W
import qualified Network.Wai.Handler.Warp as W
-- Network.HTTP.Client の代わりに he-opentelemetry のインスツルメンテーションを使用する
import OpenTelemetry.Instrumentation.HttpClient (
  Manager (),
  defaultManagerSettings,
  httpLbs,
  newManager,
 )
-- he-opentelemetry のインスツルメンテーションで提供される WAI ミドルウェアを使用する
import OpenTelemetry.Instrumentation.Wai (newOpenTelemetryWaiMiddleware)
import OpenTelemetry.Trace (
  initializeTracerProvider,
  setGlobalTracerProvider,
 )


main :: IO ()
main = do
  -- デフォルト設定でトレーサープロバイダーを作成する
  tracerProvider <- initializeTracerProvider
  -- グローバルな IORef に作成したトレーサープロバイダーを参照させる
  setGlobalTracerProvider tracerProvider
  -- トレースが取れるようラップされた http-client を作成する
  httpClient <- newManager defaultManagerSettings
  -- トレースを取得する WAI ミドルウェアを作成する
  tracerMiddleware <- newOpenTelemetryWaiMiddleware
  W.run 7777 $ tracerMiddleware $ app httpClient


app :: Manager -> W.Application
app httpManager req res =
  case W.pathInfo req of
    ["1"] -> do
      newReq <- H.parseRequest "http://localhost:7777/2"
      newRes <- httpLbs newReq httpManager
      res $ W.responseLBS H.ok200 [] $ "1 (" <> H.responseBody newRes <> ")"
    ["2"] -> res $ W.responseLBS H.ok200 [] "2"
    _ -> res $ W.responseLBS H.ok200 [] "other"

app 関数はこれまで通りの書きごこちですが、HTTP リクエストを受けてレスポンスを返すまで、HTTP リクエストを投げてレスポンスを受けるまでのスパンが取得できるようになっています。簡単ですね。

インスツルメンテーションには他にも mysql-simple 版や grpc-haskell 版などが用意されています(というか作成しました)。また Datadog 仕様のトレースと接続するためにプロパゲーターなども用意されています(これも作成しました)。

実際に手元で動かしてみたい場合はリポジトリーの examples ディレクトリーを参照してください。

Open Telemetry を活用してオブザーバビリティーを上げていきましょう。

それではメリークリスマス!


これは Haskell アドベントカレンダー 2023 25日目の記事です。

qiita.com

カトーのボギー貨車用連結器を作った

カトーのタキ1000 1000号入りセットを買ったので、アーノルトカプラーをグリーンマックスのナックルカプラーに換えた

すると、機関車との高低差が大きく登坂後に解結してしまった

そんなわけでカプラー(連結器)を作った

これを3度ぐらい現物合わせで修正していい感じになったらディテールを追加する

パラメーターの問題なのかうちの造形機の問題なのか分からないけどディテールうまく出なかった

制作過程らしき動画

STL は CC BY 4.0 で公開するので許可された範囲で好きに使ってほしい

dot ファイル 2022年版

現状の dot ファイルのたぐいをメモするついでに人に見せる形でまとめておこうと思う。

自分の思想として「フレームワークよりライブラリー」というのがあるので、プロシージャーの形で定型処理をまとめておいて実際の dot ファイルはプロシージャーを呼び出すようにしている。

github.com

Bash

Linux ではデフォルトのことが多い Bash を使っている。

リポジトリーをクローンしてきたところに各種 example を置いてあるので、それをコピーしてきて随時そのマシンに合わせて書き換える。

cd

mv .profile .profile.back
mv .bashrc .bashrc.back
mv .bash_logout .bash_logout.back

dot_files='path/to/this/repo'
cp "$dot_files/bash/.bash_profile.example" .bash_profile
cp "$dot_files/bash/.bashrc.example" .bashrc
cp "$dot_files/bash/.bash_logout.example" .bash_logout

# edit .bash_profile .bashrc .bash_logout

.bash_profile

.bash_profile はこんな感じ。

# dotfiles リポジトリーのディレクトリーを指定する
dot_files=.

# ここでプロシージャーを定義してあるライブラリーをロードする
# shellcheck source=/dev/null
source "$dot_files/sh/lib.sh"
# shellcheck source=/dev/null
source "$dot_files/bash/lib.bash"

# ~/.bin とかを PATH に追加
add_local_bin

# shellcheck source=/dev/null
source "$HOME/.bashrc"

# X 環境なら
setup_sands

# SSH エージェントを起動
# WSL2 の場合は Windows で起動してあるエージェントにつなぎに行くようにしてある(が、ちょっとバグってる
# くわしくは https://kakkun61.hatenablog.com/entry/2022/06/28/WSL2_%E3%81%AE_SSH_Agent_%E7%9B%86%E6%A0%BD
start_ssh_agent
# start_ssh_agent_wsl

# もろもろ設定のための環境変数の定義
setup_gpg

setup_git_env

setup_saml2aws

.bash_logoutSSH エージェントの後始末だけ。

.bashrc

.bashrc はこんな感じ。

# この辺は .bash_profile と一緒
dot_files=.

# shellcheck source=/dev/null
source "$dot_files/sh/lib.sh"
# shellcheck source=/dev/null
source "$dot_files/bash/lib.bash"

# もろもろ設定
# 環境変数の定義したり eval したり source したり
setup_nix

setup_less

setup_prompt "$dot_files"

setup_dircolors

setup_bashmarks "$dot_files"

setup_fuck

setup_bash_config

setup_ls

setup_shellcheck

setup_direnv

setup_git_completion "$dot_files"

使ってるツール

あんまり多くは使ってないが使ってるのは次のような感じ。

  • fuck
    • 「それはまちがいだからこう実行しなおしてね」というエラーのときに fuck と打てばよくなる
  • direnv
    • nix と連携してる
  • nix
    • バージョン違いの C ライブラリーとかインストールして管理できる
  • shellcheck

PowerShell

しごとでないときは WindowsPowerShell を使っている。

PowerShell には標準でパッケージマネージャーが付いてくるので外部ライブラリーを入れるのが簡単。

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'For the fuck')]

param ()

# dotfiles リポジトリーのディレクトリーを指定する
$dotFiles = '.'

# 自前ライブラリーをインポート
. $dotFiles\pwsh\lib.ps1

# 外部ライブラリーをインポート
Import-Module Posh-Git
Import-Module posh-sshell
Import-Module PSBookmark
Import-Module psake
Import-Module ghcman
Import-Module path-switcher
Import-Module code-page

Import-DotenvModule

# SSH エージェントの起動
Start-SshAgent -Quiet

# 環境変数の設定とかローカルの ps1 ファイルのインポートとか
Initialize-Chocolatey
Initialize-Python

# the fuck
Invoke-Expression "$(thefuck --alias)"

# prompt
Set-Item -Path Function:\prompt -Value (Get-Prompt) -Options ReadOnly

# arduino
. $dotFiles\lib\arduino-cli\completion.ps1

使ってるツール

  • Posh-Git
    • プロンプトに Git の情報を表示したり
  • posh-sshell
    • SSH エージェントの管理
  • PSBookmark
  • psake
  • ghcman
    • GHC の管理(自作)
  • path-switcher
    • PATH の管理用ユーティリティー(自作)
  • code-page

NixOS

この年末年始に NixOS 機が1つできたのでそれは当然 /etc/nixos/configuration.nix で管理しているが、home-manager までは手が出ていない。

github.com