ただの時報

https://play-morse.lowreal.net/jiho.html

むかーーし作った時報のコードを発掘した。今なら Voicevox つけたらちゃんと喋る時報にできるぞと思ったのでガっとやった。正午だけ特殊なのでレアです。

短波JJYの再現

https://play-morse.lowreal.net/vhf-jjy.html

VHF JJYの再現スクリプトのほうも時報音声を入れるようにした。こっちは10分ごとにしか喋らないのでレアです。

Wikipedia の LDPC の項だけ読むとそんな難しくなさそうに見えるけど、実際に実装しようと思うと、むちゃくちゃ難しい。

H行列 (パリティ検査行列) が難しい

LDPC符号の設計では、性能に優れたパリティ検査行列(H行列)を用意するのがまず難しい。ここにいろんな要素がある。

  • 正則(regular)か(各ノードの次数が一定)
  • 構造的な性質(たとえば巡回構造をもつ QC-LDPC など)を持つか (復号効率に影響する)
  • 系統的符号化 が可能な構造(systematic) を持つか(これは生成行列側の問題)
  • girth(ループの最小長): Tannerグラフにおいて短いサイクルは復号性能を劣化させるため、girthが大きい方が望ましい。

数学的素養がないと難しすぎる。そして自力で設計するのは考えたくない。

Protographが難しい

そんなH行列、例え1つ設計できたとしても、任意のデータ長・符号化率を持つH行列をいくつも作ろうと思うと何倍も大変になってしまう。

そこで Protograph (原始グラフ) という性質の良い原型となるような小さなグラフをまず設計し、それを何らかの方法で拡大して任意の大きさのH行列を得るという設計方法がある。

パンクチャリングが難しい

パンクチャリングは性質の良いH行列を使うために,多少の計算効率を犠牲にしつつ柔軟な送信ビット長/符号化率を得るための方法といえる

良い性質の Protograph を得られたとしても、それを拡大するプロセスには制約がある。整数倍の行列しか作れないとか。

そうなると求めるデータ長・符号化率ぴったりの行列というのは結局作れない。

これを解決するのがパンクチャリング。符号化率の低い(0.4など)のProtographを拡大し、欲しい符号化率 (0.5など)になるまでパリティビットの一部を送信しないことで、ちょうどいいサイズを作れる。

ぴったりよりも大きい行列を使うので計算コストは増えるが、高性能なH行列設計を流用できるという利点がある。

エンコードもデコードも難しい

エンコードは既知の操作するだけやろ? という気持ちを打ち砕く。どっちも難しい。なんならデコードのほうが簡単かもしれない

  • エンコードは生成行列(G行列)を得る必要がある。任意のH行列に対してG行列を導出するのが難しい。
  • デコードには、確率伝播(Belief Propagation)アルゴリズムに基づく Sum-Product 法や、近似的な Min-Sum 法などがある。どちらもTannerグラフ上での繰り返し計算を必要とし、実装や収束性で難しい
  1. トップ
  2. tech
  3. LDPC難しすぎる

performance.now() が monotonic (単調増加) なことを利用すると、システム時計の変化を比較的高精度に得られるなと思ったので、以下のようなClockMonitorクラスを作ってみた。

// ClockMonitor: システム時計の大幅な変更(NTP補正・手動変更等)を検知し、イベントを発行するクラス
// WebAudioやperformance.now()はmonotonicな経過時間だが、絶対時刻(Date.now())はシステム時計依存でジャンプすることがある
// そのため、performance.timeOrigin+performance.now()で絶対時刻を計算している場合、
// システム時計が変化しても自動で補正されない(ズレたままになる)
// このクラスは、定期的にDate.now()とperformance.timeOrigin+performance.now()の差分を監視し、
// 一定以上の差分が発生した場合に"clockchange"イベントを発行することで、
// 利用側がoffset等を補正できるようにする
class ClockMonitor extends EventTarget {
	constructor({ threshold = 2000, interval = 1000 } = {}) {
		super();
		this.threshold = threshold; // 何ms以上の差分で検知するか
		this.interval = interval;   // 監視間隔(ms)
		this.offset = performance.timeOrigin || 0; // performance.now()の起点(初期化時の絶対時刻)
		this._timer = null;
	}

	start() {
		if (this._timer) return;
		this._timer = setInterval(() => {
			const perfNow = performance.now();
			const now = Date.now();
			// 現在の絶対時刻の期待値(初期offset+経過時間)
			const expected = this.offset + perfNow;
			const diff = now - expected;
			// threshold以上の差分が出たらシステム時計変更とみなす
			if (Math.abs(diff) > this.threshold) {
				// offsetを補正し、イベント発行
				this.offset += diff;
				this.dispatchEvent(new CustomEvent("clockchange", {
					detail: { offset: this.offset, diff }
				}));
			}
		}, this.interval);
	}

	stop() {
		if (this._timer) {
			clearInterval(this._timer);
			this._timer = null;
		}
	}
}

他の方法

Date.now() を単に保持しておいて比較することでも、過去への遡りは検出できる。が、未来へ進むのは検出できない (ただのタイマーの遅れと区別できない)

問題点

問題点: performance.now() は monotonic ではあるがスリープで時間の連続性が失われることがある
仕様上は連続することになっているが、一部の環境のブラウザだけ。

  1. トップ
  2. tech
  3. JSでシステム時計の変化(時刻変更、NTP同期)を検知する

micro-template.js という2012年に作った embed JS 的なテンプレート処理ライブラリがある。コピペできるぐらい小さくて、早いことがコンセプト。

完全に放置してたけど、ちょっと手を入れはじめたらいろいろやりたくなってしまったので、だいぶ改修をいれてしまった。

  1. トップ
  2. tech
  3. micro-template.js を13年ぶりにいろいろいじった

いきなりトランスパイルの環境作って常時ビルドツールを動かして開発するのがいまいち性にあわず、いまだにそういうことをしないようにしてる。小さいプロジェクトだと管理が面倒くさい。

Vue3 はなんかいろいろさらに面倒になっており、公式のクイックスタートが全然クイックじゃねーよと思っていた。さらにクイックな方法を試行錯誤してたけど、現状の最小限サンプルを作っておくことにした。追記: 公式にも最小限サンプルあるわ……

最近のブラウザは ESM の import に対応しており、これ前提なら変なこと(ビルド)しなくても、このままに動く。ただブラウザで開くだけで良い。開発開始には十分だし、余計なことを考える必要はない。

一応ファイルを分けてるけど、app.js の内容は index.html に埋めこんでも良い。

「バニラJSだと面倒くさいUI状態があるが、いっぱいビルドとかはしたくない」みたいなことは多い。

以下は template 要素を使っている。template 要素を div 要素にして createApp に template を指定しなくても、mount("#app") でもいける。けど、template じゃない場合、table の td tr などで要素が消滅してハマることがある。

// app.js
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
createApp({
	data() {
		return {
			counter: 0,
		}
	},
	mounted() {
		console.log('App mounted');
	},
	template: document.querySelector('#app').innerHTML
}).mount(document.body);
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Hello</title>
	<script type="module" src="./app.js" defer></script>
</head>
<body>
	<template id="app">
		<h1>Hello</h1>
		<button @click="counter++">Click me</button>
		<p>Counter: {{ counter }}</p>
	</template>
</body>
</html>

petite-vue はメンテされてるのだろうか。どこかのタイミングでVue3の機能やらビルドをしたくなったときのことを考えると Vue3 そのまま使ってもいいと思う。

  1. トップ
  2. tech
  3. 一瞬で開発開始するための Vue3 無トランスパイル環境

音価を維持して和音をペーストする MuseScore のプラグイン を書いてみた。(Claude Codeが書いた)

ギターの譜面を作ろうと思うと同じリズムでコードだけ違うのをいっぱい入力する必要がでてくる。MuseScore 標準だとコピペがかなり不自由で嫌気がさしていた。普通に考えて、コピーしているものをペースト先の音価を維持して置き換える機能があっていいと思うんだけど (もしかしてある?)

使いかた

プラグインを起動するとコピーとペーストのボタンが出るので、コピー元の和音を1つ選択してコピーボタンを押し、ペーストしたい先の範囲を指定してペーストボタンを押す。すると音価は維持されたまま、指定した範囲の和音が置き換わる。

あーだこーだ

  • プラグインからはクリップボードを触れないので、こういうやりかたにするしかない
  • MuseScore のプラグインシステムはかなりひどい (ずっと改修中になっているが)
    • console.log がサンプルに書かれているが、どこにも書かれない
    • C:\Users\cho45\AppData\Local\MuseScore\MuseScore4\logs に書かれそうだと思うだろ? 書かれないんだなこれが。
    • エラーが出ても無言
    • どのリファレンスが最新なのかわからない
  1. トップ
  2. tech
  3. 音価を維持して和音をペーストする MuseScore のプラグイン

久しぶり VPS の top を眺めていたら systemd-journald が9%食っていることに気付いた。2GB のマシンなのに。

どうやら SystemMaxFileSize を小さくするとメモリ消費量も減るらしい。ので以下のように設定した。

[Journal]
SystemMaxFileSize=32M
SystemMaxFiles=100
systemctl restart systemd-journald

ディスクの消費

/var/log/journal はあり、こちらでディスクを 4GB ほど消費している。

$ ls -altrh /var/log/journal/*/
-rw-r-----+ 1 root systemd-journal 128M Apr 29 13:41 'system@...68e.journal'
...

1ファイルあたり128MBが32個ほどあった。SystemMaxUse のデフォルト値はややこしいので man 確認する必要がある。自分の環境の場合、ディスクが48GBで、デフォルトの最大は10%の4.8GB、ただし 4GB が上限。

ファイルの数 (SystemMaxFileSize) は SystemMaxUse の 1/8 らしいので過去ログは7件しかないはずだけど、なんかいっぱいあって謎。しらべてもよく挙動がわからんかった

ref

  1. トップ
  2. tech
  3. systemd-journald がメモリ食いすぎ

Text-Xatena というはてな記法に近い記法をパースしてフォーマットする Perl のモジュールがある (ややこしい言いかただが……)。この日記も Text-Xatena + 拡張インライン記法でフォーマットされている。

golang にも同様のものが欲しくなったので作ってみることにした。

一応もとの Text-Xatena 同様、HatenaCompatible モードというのをつけてある

GOOS=js GOARCH=wasm によるデモページ

https://cho45.github.io/xatena-go/

golang は wasm に直接出力できるので、これでデモページを作ってみた。.wasm ファイルが 6MB超あるけど……

実装

Text-Xatena のほぼ単純移植として実装する方針で、Copilot (GPT 4.1) と共にやってみたけどまぁまぁ面倒だった。

元実装があり、あまりデザインを変えずに言語を変更するという感じなので、AIでもできるだろと思ったけど、案外うまくいかず、結構いちいち指示やら手を入れることが必要になってしまった。とはいえ自分で書くよりは早くできたと思う。

ある程度できてくれば複数ファイルにまたがる変更もやってくれるけど、微妙に修正漏れがあったりして面倒。

テストの雛形をつくったあと、Perl のテストファイルをコピペして、それぞれ分けてテストファイルつくるように指示したけど、テストの中身を適当に書きかえられてひどかった。

コンテキストを小さくするために、先に全体の要約とか実装方針とかをまとめたほうがよかったのかも…… 自分は元コードを理解してるから、元コードの要約をさせる発想がなかったのが敗因かもしれない。

  1. トップ
  2. tech
  3. xatena-go (はてな記法のような記法フォーマットパーサー) を作った

IT技術者なのに情報処理系の試験はまったく受けずに生きてきてしまった。アマチュア無線技士第二種電気工事士とは違って法的に別にできることが増えるわけではなく特に受けるモチベがなかったからだけど、諸事情によりITパスポート試験は受けてみることにした。

毎週試験が受けれるみたい。過去問を1週間で1450問ほど解いた。

出題範囲が広い割に問題数は100しかないから、問題運で結構ブレそう。結果はテクノロジがストラテジより低くなってしまった。個別の正誤判定はないから何ミスったかはわからない。

なんかよく意図がわからない問題があった。あとは普通に知らない言葉の意味を聞かれる問題があった。ので、そのあたりを順当に間違えてそう……

定義が問題文に書いてある(ほぼ無知識で回答できる)やつと、聞かれてるものはわからないけど消去法で1択のものを落とさないように気をつけた。知ってないと答えようがない問題はあきらめた……

  1. トップ
  2. tech
  3. ITパスポート試験を受けてきた

自宅ラズパイのメトリクスとかセンサー類を VPS 上の prometheus に溜めているけど、1年分で12GBぐらいと、用途の割にかなり大きくて、容量が足りなくなったときに悩んでいた。

VictoriaMetrics だと圧縮効率がよいらしいので試しに移行してみた。クエリ (MetricsQL) は PromQL 互換らしい。事前に調べたけどあんまりデメリットがなさそうに思えた。クラッシュなどによる多少の損失を許容しているところぐらい? 自宅のあれこれでは問題ない要件

✅ 前提条件

  • OS: Ubuntu
  • Prometheus は systemd により `/var/lib/prometheus` にデータ保存中
  • 移行後は VictoriaMetrics(+vmagent)で運用する
  • Prometheus の停止は TSDB データ移行直前まで遅らせる
  • すべてのバイナリは `/usr/lib/victoriametrics/bin` 以下に配置
  • 設定ファイルは `/etc/victoriametrics/` 以下に配置

1. ディレクトリ構成の準備

sudo mkdir -p /usr/lib/victoriametrics/bin
sudo mkdir -p /etc/victoriametrics/vmagent
sudo mkdir -p /var/lib/victoriametrics

2. VictoriaMetrics のインストール

sudo useradd -s /usr/sbin/nologin victoriametrics
sudo chown -R victoriametrics:victoriametrics /var/lib/victoriametrics
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.117.1/victoria-metrics-linux-amd64-v1.117.1.tar.gz
tar -xzf victoria-metrics-linux-amd64-*.tar.gz
sudo cp victoria-metrics-prod /usr/lib/victoriametrics/bin/

3. vmagent, vmctl のインストールと設定

wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.117.1/vmutils-linux-amd64-v1.117.1.tar.gz
tar xzvf vmutils-linux-amd64-*.tar.gz
sudo cp vmagent-prod /usr/lib/victoriametrics/bin/
sudo cp /etc/prometheus/prometheus.yml /etc/victoriametrics/vmagent/scrape.yml
sudo cp vmctl-prod /usr/lib/victoriametrics/bin/

`scrape.yml` は `scrape_configs` 部分と global の scrape_interval だけのこす

5. systemd ユニットファイルの作成

/etc/systemd/system/victoriametrics.service
[Unit]
Description=VictoriaMetrics service
After=network.target

[Service]
Type=simple
User=victoriametrics
Group=victoriametrics
ExecStart=/usr/lib/victoriametrics/bin/victoria-metrics-prod \
  -storageDataPath=/var/lib/victoriametrics \
  -retentionPeriod=365d -selfScrapeInterval=10s
SyslogIdentifier=victoriametrics
Restart=always

PrivateTmp=yes
ProtectHome=yes
NoNewPrivileges=yes

ProtectSystem=full

[Install]
WantedBy=multi-user.target
`/etc/systemd/system/vmagent.service`
[Unit]
Description=vmagent
After=network.target

[Service]
User=victoriametrics
Group=victoriametrics
ExecStart=/usr/lib/victoriametrics/bin/vmagent-prod \
  -promscrape.config=/etc/victoriametrics/vmagent/scrape.yml \
  -remoteWrite.tmpDataPath=/var/tmp/vmagent/tmp/ \
  -remoteWrite.url=http://localhost:8428/api/v1/write
  Restart=on-failure
  User=nobody
  Group=nogroup

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable victoriametrics
sudo systemctl enable vmagent

7. VictoriaMetrics と vmagent の起動

sudo systemctl start victoriametrics
sudo systemctl status victoriametrics

sudo systemctl start vmagent
sudo systemctl status vmagent

6. TSDB データ移行(vmctl)

Prometheus snapshot の作成
curl -XPOST http://127.0.0.1:9090/api/v1/admin/tsdb/snapshot
{"status":"success","data":{"name":"20250516T103320Z-3bf4745e0d76170b"}}%

(snapshot はハードリンクなだけなのでいらなくなったらディレクトリごと消せばよい)

vmctl を使ってデータを移行
sudo /usr/lib/victoriametrics/bin/vmctl-prod prometheus --prom-snapshot=/var/lib/prometheus/snapshots/20250516T103320Z-3bf4745e0d76170b
2025/05/16 19:33:50 ERROR: metrics: disable exposing PSI metrics because of failed init: open /sys/fs/cgroup/system.slice/ssh.service/cpu.pressure: no such file or directory
Prometheus import mode
Prometheus snapshot stats:
  blocks found: 27;
  blocks skipped by time filter: 0;
  min time: 1714608000341 (2024-05-02T09:00:00+09:00);
  max time: 1747391598321 (2025-05-16T19:33:18+09:00);
  samples: 11071261940;
  series: 135060.
Found 27 blocks to import. Continue? [Y/n]

結構時間がかかる。12GBで2時間弱ぐらい。インポート後は2.5GBになった。

8. Grafana の設定変更(任意)

  • Grafana にログイン
  • Data Sources > Add data source > Prometheus
  • URL を `http://localhost:8428` に設定
  • Save & Test

http://localhost:9090/

9. Prometheus の停止

sudo systemctl stop prometheus
sudo systemctl disable prometheus
  1. トップ
  2. tech
  3. Prometheus から VictoriaMetrics への移行(Ubuntu, systemd)

mqtt_topic_exporter という mqtt ブローカーに接続して特定の topic を subscribe して prometheus 形式で出すというのを、7年前に書いたまま放置していた。

放置といっても(珍しく)使ってなかったわけではなく、ビルド済みバイナリをずっと使い続けていた。けどまぁ7年も経過すると何もかもかわっており (当時 go.mod すらなかった)、現行のツールチェインではビルドできなくなっていたので、なおすことにした。

  1. 現行ツールチェイン (go 1.22) でビルド可能に
    • ついでに kingpin を使うには小粒ツールすぎるので標準の flag に置き換えた
  2. Dev Container に対応させた
    • mqtt broker がないとデバッグできないので、Eclipse Mosquitto を docker-compose で同時に立ち上げてあげる感じに
  3. e2e テストを追加
    • Dev Container 内部で、Mosquitto と接続してうまいこといくかのテスト
  4. graceful shutdown
  5. リファクタリング

全体的に Copilot Agent に分からんこと聞いたり、やりたいことを指示するだけでほぼ完了させることができた。特にテストは自力で書こうと思うとダルすぎてたぶん一生やらなかった。「どうしたらいいかねえ」ぐらいのふわっとした段階から壁打ちして方針 (e2eだけテストしようとか) 決めて、雑に初期実装してくれるのはありがたい……

ただ今回、普通に指示するだけだとベタ書きする感じだったので、分けてほしい部分は個別に指示する必要があった。あとやっぱり普通に嘘をつかれる。「version.NewCollector() はなくなりました (正しくは別パッケージに移動)」とか

  1. トップ
  2. tech
  3. 7年放置した mqtt_topic_exporter を Copilot とともに書きかえた