ブログ

  • Flutter –dart-define が空文字になる

    Flutter 3.29.3 にて。

    String accessToken = String.fromEnvironment("MAPBOX_ACCESS_TOKEN");

    ではダメで、const が必要。

    String accessToken = const String.fromEnvironment("MAPBOX_ACCESS_TOKEN");

    参考: https://stackoverflow.com/a/76718173

    追記: 公式ドキュメントにも書いてありました。

    https://api.flutter.dev/flutter/dart-core/String/String.fromEnvironment.html

    This constructor is only guaranteed to work when invoked as const. It may work as a non-constant invocation on some platforms which have access to compiler options at run-time, but most ahead-of-time compiled platforms will not have this information.

  • 5/12 進捗

    シェル 4問 5~8

  • 5/11 進捗

    シェル 4問

  • Android Flutter メモ

    adb.exe の場所

    C:\Users\[user]\AppData\Local\Android\sdk\platform-tools\

    WiFi Debug 方法: 「ペア設定コードによるデバイスのペア設定」を押して

    adb pair <host:port> <paring code>

    その後, (このポート番号は「IPアドレスとポート」のところに表示されてるやつ)

    adb connect <host:port>

    ビルドの転送のため、ネットワーク速度は開発体験的に重要そう。

  • Factorio 入退出を Discord に通知する

    Factorio server にプレイヤーが入退出した時に Discord に Webhook で通知するスクリプト。

    Factorio server の標準出力を監視して、Join と Leave に関するメッセージを regex で取得している。

    使い方: 以下のスクリプトを factorio_watcher.sh という名前で保存して ./path/to/factorio --start-server ... | DISCORD_WEBHOOK_URL=https:/... ./factorio_watcher.sh

    #!/bin/bash
    # Factorio Watcher Script
    # Provide DISCORD_WEBHOOK_URL as environment variable
    # Check envvar
    if [ -z "$DISCORD_WEBHOOK_URL" ]; then
        echo "DISCORD_WEBHOOK_URL is not set. Exiting."
        exit 1
    fi
    while read -r line; do
        echo "$line"
        if [[ $line =~ \[JOIN\]\ (.*)\ joined\ the\ game$ ]]; then
            player="${BASH_REMATCH[1]}"
            message="${player} が Factorio サーバーに参加しました"
        elif [[ $line =~ \[LEAVE\]\ (.*)\ left\ the\ game$ ]]; then
            player="${BASH_REMATCH[1]}"
            message="${player} が Factorio サーバーから退出しました"
        else
            continue
        fi
        # Send message to Discord
        curl -X POST -H "Content-Type: application/json" -d "{\"username\": \"FactorioWatch\", \"content\": \"${message}\"}" "$DISCORD_WEBHOOK_URL"
    done

    Gist もあります: https://gist.github.com/tinaxd/e468145ccbf268b2ad1d5343e5684727

  • WSL2 で外部物理ディスクをマウントする

    WSL2 で外部ディスクをマウントする方法。

    WSL2 からは ext4 などのファイルシステムの中身も見ることができるので、デュアルブートしている Linux ディスクを読み書きしたいときに便利。

    Step 1: マウントしたいディスクの Device ID を確認する。PowerShell で

    GET-CimInstance -query "SELECT * from Win32_DiskDrive"

    DeviceID のコラムの値をコピーしておく。例:

    \\.\PHYSICALDRIVE3

    Step 2: wsl コマンドでディスクを接続する。

    PowerShellで

    wsl --mount <DiskPath> --bare

    <DiskPath> に DeviceID の値が入る。

    Step 3: WSL2 内でマウントする。

    WSL2のシェルで、マウントしたいパーティションの名前を調べて、マウントする。例:

    $ lsblk
    $ mkdir /mnt/ext
    $ mount /dev/sdd1 /mnt/ext

    Step 4: ディスクを取り外すとき

    PowerShellで

    wsl --unmount <DiskPath>

    参考: https://learn.microsoft.com/ja-jp/windows/wsl/wsl2-mount-disk

  • activitypub 実装メモ

    Rust crate activitypub_federation を使用

    問題
    Misskey 宛にメンション付きのノートを送信すると UnrecoverableError: skip: failed to resolve user になる
    原因
    ユーザー情報を返すエンドポイントの Content-Type が application/activity+jsonになっていなかった

  • SQLite で UNIXタイムスタンプを ISO 8601 に変換する

    UNIX タイムスタンプ形式でテーブルに保管されたデータを ISO 8601 形式で SELECT する方法メモ

    datetime(columnName, 'unixepoch')

    うまくいかないとき

    • UNIX タイムスタンプが秒単位になっていることを確認する
      (JS の Date.now() はミリ秒単位)

    参考

    https://www.sqlite.org/lang_datefunc.html

  • Lightpub を作っています

    Lightpub という ActivityPub 準拠の分散 SNS を開発しています。

    特徴は以下の通りです。

    • UI がシンプル
    • フロントエンドが軽い
    • Plain text のほか、Markdown や LaTeX 記法で投稿できる
    • サーバーが軽い (たぶん)

    とにかくサーバー、クライアント側の要件を小さくすることを目的に開発しています。機能はシンプルでいいから、とにかくよわよわのサーバーやクライアントで動かしたいという私みたいな人がターゲットです。

    Mastodon や Misskey, Pleroma と連合できることを確認しています。現在は実験的にインスタンスを立てて運用しています。→ https://exp3.lightpub.tinax.work/client/user/@tinaxd

    今年の2月か3月ぐらいに MIT ライセンスでOSSとして公開する予定です。Done is better than perfect ということで、まずは慣れている Node.js でサクサク開発を進めましたが、最小限のリソース消費で動くようにGoかRustで書き直すつもりです。

    フロントエンドは Bootstrap, htmx, alpine.js を使って書いています。HTMX は最近登場したライブラリで、html タグにアトリビュートを追加することで様々な ajax リクエストを飛ばせるようになるライブラリです。js をほとんど書かなくてよくなるので、UI が複雑ではない場合にはかなり便利なライブラリです。おすすめ。

    これから ActivityPub の自作実装をするぞという人向けに、役に立つサイトをいくつかリストしておきます。

    • https://www.w3.org/TR/activitypub/
      まず最初に見ることになるであろう、ActivityPub の仕様書です。
    • https://www.w3.org/TR/activitystreams-core/
      これも仕様書です。連合する際に必要となる語彙について書かれています。
    • https://activitypub.academy
      Mastodon のテスト用インスタンスです。Mastodon を開発用に改変していて、送受信した json-ld の中身を見れる機能などが追加されています。Federation のテストに非常に役に立ちました。
    • https://www.w3.org/wiki/ActivityPub/Primer
      ActivityPub 実装のヒントがいろいろ書かれています。仕様書では定義されていない一般的な慣習 (例えば、投稿範囲を Activity Streams 内でどう表現するかなど) がわかります。

    Fediverse Reactions
  • ISUCON12 予選突破

    ISUCON12 の予選を通過しました!
    チーム名は快適PandAです。ISUCON11には快適PandApexという名前で参加していました。
    当日何をやったことを記録として残しておきます。

    メンバー

    • arakistic
    • das (https://blog.das82.com/post/isucon12q/)
    • tinax

    今年はDiscordで通話しながら作業しました。
    Discord Nitro に登録すれば高画質の画面共有ができるので便利です 🙂

    やったこと

    9:40

    全員で集合してyoutubeのライブ動画を見ていました。
    コードフリーズの時間など、全体のおおまかな計画を立てました。

    10:00

    AWSインスタンスの起動を待っている間、全員でマニュアルを読みました。

    10:20

    サーバーにプロファイリングツールなどをインストールしていました。欲しいツールを全部自動でインストールしてくれるシェルスクリプトを用意していたのでそれを使いました。

    僕はソースコードをgitで管理する準備をしていました。作業中、大量のsqliteデータベースファイルが見えて、mysqlに移行することになるのかなあと思っていました。

    10:30 (2500点)

    初回ベンチマークを回しました。

    10:37 (3930点)

    GoのアプリサーバーとDBサーバーを別マシンに分離しました。
    そのほうが、アプリサーバーとDBのどちらに負荷がかかっているか判断しやすいと考えたからです。

    分離作業は練習の段階で何度も行っていたので、作業自体は一瞬で終わりました。

    10:40 (3904点)

    DB側のcpu負荷が異常に大きかったので、arakistic氏にindexを貼ってもらい解決しました。
    点数がなぜか下がっていますが、たぶん slowlog や pprof などのプロファイラを有効にしたからだと思います(あまり覚えてないです)。

    その間das氏はソースコード上のN+1クエリを見つけてTODOコメントを書く作業をしていました。

    11:46 (3969点)

    Docker上に乗っていると再ビルドが遅く、slowlogなどを見るのも面倒だったので、Dockerを剥がしました。

    また、sqlite側のN+1クエリをarakistic氏に修正してもらいました。

    これからsqlite側の改善が主になると思ったので、私はsqliteのクエリログから重いクエリを順番にソートして表示する、sqlite版slowlogのようなツールを開発していました。

    12:01 (4392点)

    arakistic 氏にさらにN+1クエリを改善してもらい、点数が上がりました。

    12:20 (6898点)

    sqlite版slowlogツールが完成し、解析してみたところ INSERT が遅いことがわかったので、competitionScoreHandlerのfor文の中のINSERTを BULK INSERT に変えたところ、点数が上がりました。

    12:32 (7411点)

    BULK INSERT が使える場所がもう一箇所あったのでそちらもBULK INSERTに変更しました。また、playersAddHandler の中で retrievePlayer というSQLクエリを発行する関数を呼び出していましたが、必要ないことに気づいたので関数呼び出しを排除しました。

    12:39 (4727点)

    retrievePlayer という関数が様々な関数から頻繁に呼び出されており、この関数は呼び出しのたびに sqlite にクエリを発行するようになっていたため、das氏が結果をRedisにキャッシュするようにしました。
    しかし、点数が下がってしまいました。

    理由ははっきりとわかりませんでしたが、pprof で見たところ、redisライブラリの内部関数にかなりの時間が食われていたため、Redisとの通信や構造体のシリアライゼーションのオーバーヘッドが大きすぎたことが原因ではないかと思っています。

    そのため、Redis ではなく Go 内の配列にキャッシュデータを保存する計画が立ち上がりました。

    ~14:55

    • tinax: sqlite->mysql にデータ移行
    • das氏: Go内にretrievePlayerのキャッシュを配置
    • arakistic氏: competitionScoreHandlerのキャッシュ
      をやっていました。

    sqlite->mysqlのデータ移行は、1.dbというsqliteファイルだけ異常にサイズが大きく、データ移行が終わらず困っていました。
    最初から用意されてスクリプトではない、自作のスクリプトを作って挑戦したり、DBのパラメータをチューニングしたりしましたが、結局移行しきれませんでした。

    1.db だけ sqlite のまま使って他は mysql に移行するという、いかにもまずそうな方法も思いつきましたが、面倒だったので結局実行していません。

    ほかのチームメンバーはキャッシュを進めていましたが、ベンチがなかなか通らず、この時間はスコアの向上はありませんでした。

    去年もこの時間帯は沼っていた気がします。

    14:55 (9277点)

    arakistic氏が、終了した大会に関してはcompetitionScoreHandlerの結果が変化しないことを利用してキャッシュを行い、スコアが大幅に上がりました。

    この改善のおかげでチームメンバーの士気もかなり高まった気がします。

    sqlite->mysql の移行はこのあたりで諦めて、sqliteのまま改善を続けていく方向に切り替えました。

    TODO 続きを書く