Programming」カテゴリーアーカイブ

プログラミング関連の話題。

factorio headlessでSteamに紐づいたアカウントのユーザ名を変更してしまいユーザデータが引き継げなくなった問題の対処

If you change your name your save game will see your new name as a new character. You can use a console command to swap your character back in-game:
/swap-players old-username new-username

https://forums.factorio.com/viewtopic.php?t=16811

ということでコンソール(全角/半角キー)からコマンドを入力するものの、「Admin権限が無いのでコマンドが実行できないよ」と怒られる。headlessでホスト以外にadminを付与する方法を調べた。

factorio-current.log が出力されているディレクトリに server-adminlist.json を新規作成して、admin権限を与えるユーザ名をjson形式で列挙する。その後、factorioを再起動すればOK。(systemctlはconohaサーバのセッティングで利用しています)

# pwd
/opt/factorio/factorio

# ll
drwxr-xr-x 8 factorio factorio 4096 Oct 17 19:03 ./
drwxr-xr-x 5 factorio factorio 4096 Oct 15 15:07 ../
-rw-rw-r-- 1 factorio factorio 1527 Oct 15 15:05 achievements.dat
drwxr-xr-x 3 factorio factorio 4096 Oct 15 15:05 bin/
drwxrwxr-x 2 factorio factorio 4096 Oct 15 15:05 config/
-rw-r--r-- 1 factorio factorio 1001 Jan 7 2021 config-path.cfg
-rw-r--r-- 1 factorio factorio 1479 Oct 17 19:03 console-log
-rw-r--r-- 1 factorio factorio 1479 Oct 17 19:03 console-log.prev
drwxr-xr-x 4 factorio factorio 4096 Oct 17 18:55 data/
-rw-r--r-- 1 factorio factorio 5015 Oct 17 19:03 factorio-current.log
-rw-r--r-- 1 factorio factorio 6207 Oct 17 19:02 factorio-previous.log
-rw-r----- 1 factorio factorio 0 Oct 17 19:02 .lock
drwxrwxr-x 2 factorio factorio 4096 Oct 17 19:02 mods/
-rw-rw-r-- 1 factorio factorio 9079 Oct 17 19:03 player-data.json
drwxr-xr-x 2 factorio factorio 4096 Oct 17 16:39 saves/
drwxrwxr-x 3 factorio factorio 4096 Oct 17 19:03 temp/

# vi server-adminlist.json
# cat server-adminlist.json
[ "username1", "username2" ]
# systemctl stop factorio.service
# systemctl start factorio.service

factorioをconohaで動かしてみた

conoha(www.conoha.jp)というVPSサービスが安くて良さそうなので使ってみた。

これまで、AWSでEC2のt2.microを利用してfactorio headlessを立ち上げていたが、突然ゲーム内のスピードが落ちてカクカクになったのに、topコマンドの結果を見るとCPU使用率は低いまま。原因を調べた結果、tシリーズはCPU creditというものが設定されていて、それを使い切ってしまうと、CPUの能力を十全に活用できずキャップがかかる仕様と判明。そもそも、本来、t2.microはCPUをあまり利用しないサービスが前提となっているものの、起動など一時的にCPUを使用するシーンのみ、お情けとして規定された性能分だけCPU性能をオマケしてもらっている、という仕様らしい。CPUをガンガン常用するようなゲームサーバには適していないことが判明。

じゃあ、適したAWSのインスタンスタイプがどれかなと調べているとき、factorioがプリインストールされているVPSを格安で貸し出してくれる本サービスを発見し利用に至る。注意点は、サーバ停止時も利用料がかかってしまうこと。どうせ停止中でもお金がかかるなら起動しっぱなし(factorio headlessは誰もプレイしていないときはゲーム内時間がSTOPする仕様)とするか、毎回、セーブデータをローカルなどにバックアップしてサーバ自体を削除/新規するかの2択となる。

ちなみに、FAQには詐欺(?)のような書き方で「停止していても料金はかかる」と書いてある。

停止時の料金はありますか?(ConoHa VPS・ConoHa for Windows Server・ConoHa for GAME)
ConoHa VPS・ConoHa for Windows Server・ConoHa for GAMEについて、停止(シャットダウン)いただいた場合でも料金が変動することはございません。サーバーの稼動、停止の状態にかかわらず、ご契約数で料金が発生いたします。

https://support.conoha.jp/common/faq/payment-q/

セキュリティグループはプリセットから下記を適用する。Webコンソールは使いにくいし、セーブデータのアップロードやダウンロードができないので、SSH portを開放してやり、Teratermなど手に馴染んだクライアントソフトから接続するのが吉。

  1. IPv4v6-SSH
  2. IPv4v6-Factorio

セーブデータは下記に格納されている。コマンドからマップのカスタマイズをするのは面倒なので、ローカルのfactorioで作成したセーブデータを同名でアップロードしてやるのが吉。

  • アップロード先:/opt/factorio/savedata/factorio_save.zip
  • アップロード元(Windows Steam版):%appdata%\Factorio\saves

conohaのすごいところは、factorioがプリインストールされているだけではなく、serviceまで作り込まれているところ。enableになっているので、サーバを起動した時点でfactorioが起動している。おなじみのsystemctlからサービスを起動/停止しよう。なお、ポートはデフォルト(34197)となっている。先程設定したセキュリティグループ(IPv4v6-Factorio)も34197が許可されている。

  1. systemctl status factorio.service
  2. systemctl stop factorio.service
  3. systemctl start factorio.service

statusを確認してみた。

# systemctl status factorio.service
● factorio.service - Factorio Server
     Loaded: loaded (/etc/systemd/system/factorio.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2023-10-10 09:30:36 JST; 2h 8min ago
   Main PID: 138266 (sh)
      Tasks: 12 (limit: 1065)
     Memory: 651.7M
     CGroup: /system.slice/factorio.service
             tq138266 /bin/sh -c /usr/bin/screen -DmS factorio /opt/factorio/factorio/bin/x64/factorio --start-server /opt/factorio/save>
             tq138267 /usr/bin/SCREEN -DmS factorio /opt/factorio/factorio/bin/x64/factorio --start-server /opt/factorio/savedata/factor>
             mq138276 /opt/factorio/factorio/bin/x64/factorio --start-server /opt/factorio/savedata/factorio_save --console-log /opt/fac>

Oct 10 09:30:36 vm-1c3075e2-0b systemd[1]: Started Factorio Server.

config.iniはデフォルトなので、必要に応じて修正する。デフォルトだと、10分間隔にオートセーブでフリーズがかかってうざいので、autosave-intervalは長めにしておくことを推奨。この方法だと、何故か設定が適用されない不具合が発生。解消方法は後述。

# pwd
/opt/factorio/factorio/config

# diff config.ini.default config.ini
21c21
< ; autosave-interval=5
---
> autosave-interval=20
23c23
< ; autosave-slots=3
---
> autosave-slots=5

上記の方法だと何故か設定が反映されない。別の方法を模索した結果、下記のjsonで設定してやるとうまくいくことが判明。exampleをコピーして設定ファイルを新規作成。仲間内でのゲームなので、public/lanをいずれもfalseにして、autosave_intervalを60分に変更。その後、この設定ファイルを読み込むよう、起動オプションに追加してやる。

  • /opt/factorio/factorio/data/server-settings.example.json
  • /opt/factorio/factorio/data/server-settings.json
# diff server-settings.example.json server-settings.json
13,14c13,14
<     "public": true,
<     "lan": true
---
>     "public": false,
>     "lan": false
48c48
<   "autosave_interval": 10,
---
>   "autosave_interval": 60,

serviceファイルの起動オプションに「–server-settings /opt/factorio/factorio/data/server-settings.json’」を付与する。systemctl daemon-reloadでserviceの再読み込みを忘れずに。

# cat /etc/systemd/system/factorio.service
[Unit]
Description=Factorio Server
After=network.target nss-lookup.target

[Service]
Type=simple
User=factorio
Group=factorio
WorkingDirectory=/opt/factorio
ExecStart=/bin/sh -c '/usr/bin/screen -DmS factorio /opt/factorio/factorio/bin/x64/factorio --start-server /opt/factorio/savedata/factorio_save --server-settings /opt/factorio/factorio/data/server-settings.json'
ExecStop=/usr/bin/screen -p 0 -S factorio -X eval 'stuff "/server-save\015"'
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S factorio -X eval 'stuff ^C'
ExecStop=/bin/sleep 5
Restart=always

[Install]
WantedBy=multi-user.target

# systemctl daemon-reload

WSLインストール時のエラー(0x80370102)

WSLをインストール(wsl –install)後に再起動するとコマンドプロンプトが自動で起動しインストールが始まるものの、下記のエラーが発生した。調べてみると、Intel CPUで言うところのVirtualization TechnologyがOFFになっていたことが原因だった。AMD CPUでは「AMD Virtualization (AMD-V™)」と呼ぶらしく、ASUSマザーボードのUEFI BIOSでの設定項目は「SVM MODE」ということだった。

WslRegisterDistribution failed with error: 0x80370102

Windowsコマンドプロンプト

最近のPCでは、再起動後にASUSのロゴが表示されない(早すぎてFxキーでUEFI BIOS画面に入れない?)ようで、Shiftキーを押しながら、Windowsの左下スタートメニューの再起動を実行することでUEFI BIOS画面に入ることが出来る。

更に、HDMIとDPを両方使用して2画面構成にしている場合、DP側のみにUEFI BIOS画面が表示されるので注意。私の場合、DP側をサブディスプレイに出力させ、普段はディスプレイをOFFにしていたので数分ハマった。

UEFI BIOS画面に入ったら、右下のAdvanced Mode > Advanced > CPU Configuration > SVM Mode > Enable > 右下EzMode > Save & Exitで再起動すればOK。

再起動後は、左下スタートメニュー > 最近追加されたもの > Ubuntuを選択することでインストールを再開できる。新規作成するユーザ名を聞かれパスワードも合わせて設定することで設定完了する。

Java9ではCMS GCが非推奨らしい

はじめに、当方はJava初心者であります。(これを言っておかないと後で大変なことになるので……)

Java9ではCMS GC(Concurrent Mark Sweep Garbage Collector)が非推奨(Deprecate)になるらしいです。そもそもCMS GCは、「停止できないエンタープライズ・アプリケーション」に向いた、MajorGCにおけるSTW(Stop The World:アプリケーションの停止時間)が短いという特性のあるアルゴリズムです。一方で、コンパクション(HDDで言うところのデフラグみたいなもの)されないという側面もあり、結局はFullGCが選択されることもありました。つまるところ、GC手法としてどれを選択するかはトレードオフの話ではありますが、一般的にはCMS GCが選ばれてきたことと思います。

それが、Java9から非推奨となり、それ以降では廃止も視野に入れられているとのことです。Java9以上へのマイグレーションにより、CMS GCからG1GCに乗り換える場合、大規模かつ長期に亘る性能試験が必要になるでしょう……。正直、頭が痛くなるので、あまり考えたくありません。

そもそも廃止される理由は、G1GCがCMS GCの優位点である「STWの短さ」に追いつきつつあるからという正当な理由1と、CMS GCを廃止することでCMS GCに注いでいた開発体力を他GCに振り分けて開発を加速させるという不当な理由2があるようです。

Dropping support for CMS and then removing the CMS code, or at least more thoroughly segregating it, will reduce the maintenance burden of the GC code base and accelerate new development. The G1 garbage collector is intended, in the long term, to be a replacement for most uses of CMS.

Google翻訳:CMSのサポートを中止し、CMSコードを削除するか、少なくともそれをより完全に分離すると、GCコードベースの保守負担が軽減され、新しい開発が加速されます。 G1ガベージコレクタは、長期的には、CMSのほとんどの用途を置き換えるものです。

http://openjdk.java.net/jeps/291

文書の中では、こうも言及されています。

For some applications CMS is a very good fit and might always outperform G1.

Google翻訳:一部のアプリケーションでは、CMSは非常に適しており、常にG1を上回る可能性があります。

ならさ……。

Java8から導入されたMetaspaceで痛い目を見た身としては、二の舞になりそうな予感がめっちゃしています……。因みに、本件に言及した日本語の記事はググっても殆ど出てこないので、怖がっている人は殆ど居ないっぽいです。

waitコマンドとLinux/Unixの仕様メモ

バックグラウンドプロセス2個以上をwaitして実行結果を取得する際のメモです。Linux(RHEL7、CentOS7)で確認済みですが、Unixでも同じことが言えるはずです。

まずは間違っている例です。下記の様に、プロセスIDを取得して、waitする際に2個のプロセスIDを指定してしまうと、誤った結果が得られます。(最後に指定したプロセスIDの結果のみが取得できる。)

$ cat parent.sh
#!/bin/sh

./child.sh $1 &
child1p=$!

./child.sh $2 &
child2p=$!

sleep 3

wait $child1p $child2p
childr=$?

echo "child1p:$child1p, child2p:$child2p, childr:$childr"
$ cat child.sh
#!/bin/sh
sleep 1
exit $1

下記が実行結果です。

$ time ./parent.sh 1 1
child1p:10159, child2p:10160, childr:1

real    0m3.005s
user    0m0.005s
sys     0m0.003s
$ time ./parent.sh 0 0
child1p:10165, child2p:10166, childr:0

real    0m3.005s
user    0m0.004s
sys     0m0.003s
$ time ./parent.sh 0 1
child1p:10171, child2p:10172, childr:1

real    0m3.005s
user    0m0.001s
sys     0m0.006s

うまくいくやん! と、思われ方。要注意! 下記を実行すれば誤りに気付きます。

$ time ./parent.sh 1 0
child1p:10152, child2p:10153, childr:0

real    0m3.006s
user    0m0.004s
sys     0m0.004s

引数pidを指定した場合,waitコマンドは最後の完了を待ったプロセスのコマンドの終了コードで終了します。
http://itdoc.hitachi.co.jp/manuals/3021/3021313320/JPAS0399.HTM

正しい実装は下記。それぞれ個別にwaitする必要がある。

$ cat parent2.sh
#!/bin/sh

./child.sh 0 &
child1p=$!

./child.sh 1 &
child2p=$!

sleep 3

wait $child1p
child1r=$?

wait $child2p
child2r=$?

echo "child1p:$child1p, child2p:$child2p, child1r:$child1r, child2r:$child2r"

ん? と思われた方。そう、子プロセスが[sleep 1]しているにも関わらず、親プロセスで[sleep 3]しているため、waitにたどり着くまでに子プロセスは終了しています。「終了したプロセスの実行結果なんて取得できるの?」と。

waitコマンドについて詳しく言及した記事はありませんでしたが、waitシステムコールについて調べると、ありました。

終了したが、wait されていない子プロセスは「ゾンビ」になる。後で親プロセスが wait を実行して子プロセスについての情報を取得できるように、カーネルはゾンビプロセスについて最小限の情報 (PID、終了ステータス、 リソース使用状況) を保持する。ゾンビプロセスは、 wait によってシステムから削除されない限り、カーネルのプロセステーブルの 1 エントリーを消費する。このプロセステーブルが 一杯になると、新たにプロセスを作ることができなくなる。親プロセスが終了すると、その親プロセスの「ゾンビ」の子プロセスは (もしあれば) init(1) の養子となる。 init(1) は wait を自動的に実行し、ゾンビを削除する。
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/wait.2.html

「養子」という表現、かなり好きです。(そこではない) つまり、子プロセスの実行結果は、親プロセスが終了するまでは間違いなく保持されるようです。そのため、上記コードは問題なしです。

$ time ./parent2.sh 1 0
child1p:10185, child2p:10186, child1r:0, child2r:1

real    0m3.006s
user    0m0.004s
sys     0m0.004s

位置100で不正な入力シーケンス(iconv)

「位置xxxで不正な入力シーケンス」は、いわゆるWindowsの機種依存文字である丸付き数字等をiconvでutf8→shift_jisに変換しようとしたときに発生するエラー。丸付き数字は、utf8では規定されているが、厳格なshift_jisには存在しないので、対応する文字無しということでエラーになる。(Windowsにおけるshift_jisは、正確にはshift_jisではなく、暗黙的に拡張文字(丸付き数字等)を含んだもの。)拡張文字も含んだcp932を変換後の文字コードとして指定してやれば解消する。

下記は、Linux上の日本語名ファイルを、zip等に纏めてftpでWindowsに送ったときに文字化けさせない対応のサンプルスクリプト。(utf8→cp932に変換している。)

mv "$filename" "`echo $filename | iconv -f utf8 -t cp932`"

意地でも正規表現を使わずに拡張子とファイル名を分割する

良く使うのに毎回調べている気がする正規表現をあえて意地でも使用せずに、拡張子(.tar.gz)とファイル名を分割する。ファイルのバックアップをアンダースコア+日付でcpして取得する場合が良くあるが、そういったときのアンダースコア分割にも応用できる。特にアンダースコアはファイル名自体にも使用されることがあるので、revで逆さにしてから変換する。(セパレータが何個存在するか分からないので、逆から処理するとうまくいく)

echo test.test.tar.gz | rev | cut -d '.' -f 3- | rev

逆にして、先頭からセパレータを探索、3個目以降を取得、再度逆にする。言われてみるとシンプルだけれど、なかなか出来なかった発想。

PostgreSQLでSQL発行時のエラー対応

PostgreSQLでselect句に定数が含まれていた場合、下記エラーが出力されることがあります。後に続いて型を決めてやれば問題は解消します。::textの部分です。

failed to find conversion function from unknown to text
select *
from (select a_column, 'const'::text as "b_column" from c_table)
  as d_table

メモでした。

PostgreSQLのpg_dumpで単一のfunctionのみ取得する

pg_dumpでは-tオプションで単一または複数のtable/view/sequenceを指定して定義の抽出が可能ですが、functionの指定はできません。これを解決する、裏技チックな方法が英語ページにしか掲載されていなかったので、日本語ページの一人目になるべく本記事を執筆しました。

pg_dump -Fc -s | pg_restore -P 'funcname(args)' > function_define.dmp

-sオプションでスキーマ(定義)のみを抽出します。(ここでいうスキーマとは、テーブルの完全修飾名の前半に使う名称(schema)ではないので注意。単なる定義情報(雑に言うとcreate table)のことを指します。この注意事項は本家にも記されています。ややこしいですね。)

これと–schemaオプションと混乱しないでください。”schema”という単語を異なる意味で使用しています。pg_dump

結果をパイプでpg_restoreに繋いでいます。pg_restoreはcustom形式しか処理できないため、-Fcオプションを付与しています。(デフォルトはplainです。)

-Pは名称を指定してcustomからfunctionの定義を取り出すことができます。また、pg_restoreは特に接続先データベースが指定されていない場合、標準出力にplain形式で吐き出すため、リダイレクトでファイルに出力しています。

データベース名が指定された場合、pg_restoreはそのデータベースに接続し、アーカイブを直接そのデータベースにリストアします。 データベース名が指定されなかった場合は、データベースを再構築するために必要となるSQLコマンドが含まれたスクリプトが作成されます(ファイルもしくは標準出力に書き出されます)。pg_restore

functionに引数(args)がある場合、正しい引数の数だけ、型を指定してやる必要がありますので注意が必要です。不明の場合は下記コマンドで確認できるでしょう。

pg_dump -Fp -s | less

注意事項

私の環境では2回以上-Pを指定した場合、最後に指定したfunction以外が無視されました。そのため、下記のように工夫する必要があります。

pg_dump -Fc -s | pg_restore -P 'funcname(args)' > function_define.dmp
pg_dump -Fc -s | pg_restore -P 'funcname2(args)' >> function_define.dmp
pg_dump -Fc -s | pg_restore -P 'funcname3(args)' >> function_define.dmp

最終的な出力ファイルを見ると、SETが重複して出力されていますが、特に弊害はありません。

参考