2016年 06月 21日

Google の新しい2段階認証「Google からのメッセージ」

認証アプリなどと違って数字を覚えることなしに、ロック解除とダイアログの「はい」を押すだけでよくなるっぽい。

ロックがかかっている端末でも、前もってロックを解除した状態でログインしようとすると、改めてロックコードの確認を求められる。なので、一見「はい」を押すだけでかと思いきや、必ずロック解除を挟む。

確認コードの数字は時間制限があるので、微妙にストレスがかかって嫌な感じでしたが、これはよさそうです。

2016年 06月 16日

「通知の表示」の許可を求めてくるサイトが増えてうざい件

出初めたぐらいに「これ、どうでもいい個人サイトとかもやりだしたらうざいだろうな」と思っていたけど、最近まさにそういう状態になっている。当然サービスワーカーもインストールされてる。ほとんどの場合サイトにアクセスしたときに勝手に出る。

たまたまうっかり見たサイトが「通知の表示」を求めてくるのは「お前おれのこと好きなんだろ」みたいな態度で心底気持ちが悪く、通知については明示的に許可がなければできないけど、サービスワーカーは自動的にインストールされるので、(別にそういう機能はないが) ストーカーっぽくてマジで気持ちが悪い。

特に前段階なしに通知の許可を求めるみたいなの、いったいどういうつもりなのか…… お前は「はじめまして〜」とか言った直後に「僕に興味があるんですよね? つまり好きってことですよね? 付き合いましょうよ〜」とか言う人間なのか。せめて「フィードを登録」みたいなノリで「通知をうけとる」ボタンにでもして、そこでようやく許可を得るのがマトモなコミュニケーション能力ではないのか。


ブラウザでデフォルトブロックに設定変えときゃいいだろ、みたいな話ではなくて、そういう気持ち悪いことしている自覚あるんですか、という話です。

2016年 06月 15日

さくらのVPS、大阪・東京・石狩でどれを選ぶべきか

さくらのVPSはリージョンが3地点から選べるようになっている。これはDR (ディザスタリカバリ=災害復旧) 的な観点からのもので、1台だけ借りるなら、基本的にはどれでもいい。

ただし、施設のナウさでいくと

  1. 石狩
  2. 東京
  3. 大阪

となるようなので、リージョンごとに新機能の導入時期が変わったりする。ホストサーバのCPUの世代が違ったりもするみたいだけど、ガチャ要素なのでここではその話はしない。

VPSを契約しようとするとデフォルトで石狩が選択される通り、さくらインターネット的には石狩オシのように見える。

ただ、国内の通信で考えると石狩はちょっと遠いので、気になるのは遅延になる。

物理的に超えられない壁

情報伝送は光速度を超えることができない。

典型的な光ファイバーである石英ガラスの場合、屈折率は1.5程度。物質中の光速度 は、真空中の光速度 と屈折率 )から、 で求められる。

例えば、新宿・石狩間の直線距離約840kmで考えると、片道(840 km) / (c / 1.5) = 4.2029076 ミリ秒は物理的に超えられない遅延となる。RTT で考えれば 8.4ms を超えることは決してできないことになる。実際はケーブルが直線でひかれているわけではないので、これにさらに増える。

光ケーブルではなく電線の場合、導体中の速度は周囲の空間をすすむ電磁波のスピードと同じ (導体内ではなく) なので一概に言えないが真空中の光速度の6〜9割程度の速度になる。なお同軸ケーブルの場合波形短縮率という形で速度が示される。

いずれにせよ、情報伝送速度は真空中の光速度よりも「結構遅い」し、割と ms 単位はかかるという認識がいる。

実際のRTT

ちなみに、東京・石狩間のRTTはさくらインターネットの公式では平均「18msec」と書いてあるので光速度とかから計算しなくてもよろしい。

で、結局どこがいい

個人のVPSだと石狩のメリットは一切ない (個人の見解) ので、僕は新しい契約から東京リージョンにすることにしました。(今は大阪)

2016年 06月 13日

textarea.value は代入すると値が変わる

以下のような挙動をする。

var textarea = document.createElement('textarea');
textarea.value = "\r\n";
encodeURIComponent(textarea.value);
//=> "%0A"

この挙動、現在のブラウザではバグではなくて、仕様である。どういうことかというと、HTML Standard で明言されている

For historical reasons, the element's value is normalised in three different ways for three different purposes. The raw value is the value as it was originally set. It is not normalized. The API value is the value used in the value IDL attribute. It is normalised so that line breaks use U+000A LINE FEED (LF) characters. Finally, there is the value, as used in form submission and other processing models in this specification. It is normalised so that line breaks use U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pairs, and in addition, if necessary given the element's wrap attribute, additional line breaks are inserted to wrap the text at the given width.

https://html.spec.whatwg.org/multipage/forms.html#concept-textarea-api-value

value プロパティ代入動作 (Getter) は raw value の代入として働き、取得動作 (Setter) は API value の取得して働くという非対称的な挙動を示す。そして raw value を取得する方法はない。

SQLite の WITHOUT ROWID の効果測定

SQLite で「PRIMARY KEY」を《真のプライマリキー》とするには | tech - 氾濫原 の続きです。

以下のような簡単なベンチマークスクリプトを使って差を測ってみました。

Linux の VPS と OS X とでやってみましたが、だいたいこの例では5%ぐらいの差がつくようでした。

#!/usr/bin/env perl


use v5.14;
use utf8;


use DBI;
use DBD::SQLite;
use Benchmark qw(:all) ;
use String::Random qw(random_regex random_string);


sub dbh {
	my ($db) = @_;
	my $dbh = DBI->connect('dbi:SQLite:dbname=', "", "", {
		sqlite_allow_multiple_statements => 1,
		RaiseError => 1,
		sqlite_see_if_its_a_number => 1,
		sqlite_unicode => 1,
	});
}


say "DBI::VERSION: $DBI::VERSION";
say "DBD::SQLite::VERSION: $DBD::SQLite::VERSION";
say "SQLite version: ". dbh()->{sqlite_version};


my $dbh_rowid = dbh();
$dbh_rowid->do(q{
	CREATE TABLE tfidf (
		`id` INTEGER PRIMARY KEY,
		`term` TEXT NOT NULL,
		`entry_id` INTEGER NOT NULL,
		`term_count` INTEGER NOT NULL DEFAULT 0,
		`tfidf` FLOAT NOT NULL DEFAULT 0,
		`tfidf_n` FLOAT NOT NULL DEFAULT 0
	);
	CREATE UNIQUE INDEX index_tf_term ON tfidf (`term`, `entry_id`);
	CREATE INDEX index_tf_entry_id_tfidf_n ON tfidf (`entry_id`, `tfidf_n`);
});


my $dbh_without_rowid = dbh();
$dbh_without_rowid->do(q{
	CREATE TABLE tfidf (
		`term` TEXT NOT NULL,
		`entry_id` INTEGER NOT NULL,
		`term_count` INTEGER NOT NULL DEFAULT 0,
		`tfidf` FLOAT NOT NULL DEFAULT 0,
		`tfidf_n` FLOAT NOT NULL DEFAULT 0,
		PRIMARY KEY (`term`, `entry_id`)
	) WITHOUT ROWID;
	CREATE INDEX index_tf_entry_id_tfidf_n ON tfidf (`entry_id`, `tfidf_n`);
});




say "insert";
{
	my $i = 0;
	cmpthese(-1, {
		'with rowid' => sub {
			$dbh_rowid->prepare_cached(q{
				INSERT INTO tfidf (`term`, `entry_id`, `term_count`) VALUES (?, ?, ?);
			})->execute(random_regex('[a-z]{2,10}'), $i++, 1);
		},
		'without rowid' => sub {
			$dbh_without_rowid->prepare_cached(q{
				INSERT INTO tfidf (`term`, `entry_id`, `term_count`) VALUES (?, ?, ?);
			})->execute(random_regex('[a-z]{2,10}'), $i++, 1);
		},
	});
};


say "select";
{
	my $i = 0;
	cmpthese(-1, {
		'with rowid' => sub {
			$dbh_rowid->selectall_arrayref(q{
				SELECT * FROM tfidf WHERE `term` = ? AND `entry_id` = ?
			}, { Slice => {} }, random_regex('[a-z]{2,10}'), $i++);
		},
		'without rowid' => sub {
			$dbh_without_rowid->selectall_arrayref(q{
				SELECT * FROM tfidf WHERE `term` = ? AND `entry_id` = ?
			}, { Slice => {} }, random_regex('[a-z]{2,10}'), $i++);
		},
	});
}
2016年 06月 09日

Gist に置いた JavaScript のベンチマークをとる

デモ

使いかた

gist に以下の命名規則でファイルを作ります

  • *.js にベンチマーク対象コード
  • *.js の拡張子をhtml に書いたものに実行するページのHTML
    • または common.html に共通の HTML

そして gist の URL をコピーして、このページで実行させます。

挙動

空関数呼び出しも含め、50msごとにそれぞれのコードをできるだけ実行します。これを1フェーズとして、100回実行します。なので、終わるまで対象テストケース×5秒かかります。

コードは対応するHTMLをロードした iframe 内で実行されます。これは文字列から iframe.contentWindow.Function のインスタンスを作ることで実現しています。

Rate limit 対策

Gist から API 経由でデータをとってきていますが、Github の API は Rate limit がキツいです。

一応、localStorage にキャッシュを持っており、gist 側を更新しない限りは 304 を返してもらうようにしています。304 の場合はAPIアクセスしてもカウントされません。

これでも、gist を更新してベンチマークを繰替えすと意味がないので、Personal Access Token による上限拡大も雑に実装してあります。

経緯

jsperf.com が動いていなくて不便ですね。不便すぎるので5年前ぐらいに自分で書いたコードをひっぱりだしてきて、書きなおしたという感じです。

jsperf と比べ、結果を集約したりする機能はありませんが、クライアントサイドのみで動くので気楽なツールです。

2016年 06月 08日

Google Photo が自動的に場所を推定して表示するようになった

こんな感じで、Google Photo 上で場所が推定されて表示されるようになっていることに気付きました。

メニューマークをクリックすると明示的に削除もできます。また「撮影場所(推定)」についてのヘルプにリンクがあって以下のように書かれています。

「撮影場所(推定)」とはどのようなものですか?

Google フォトでは、ご利用の Google アカウントに保存されている Google ロケーション履歴などの情報をもとに、撮影場所を推定することがあります。

「ロケーション履歴など」と書いてありますが、ロケーション履歴以外はなんでしょうね。被写体認識も入ってるんですかね。自分は常時ロケーション履歴を有効にしているので、ロケーション履歴がない場合の推定状況については調べられませんでした。

「これどこで撮ったかな〜」と思いつつロケーション履歴を辿るほどでもないなということは多々あったので結構嬉しい気がします。

2016年 06月 04日

video/audio 要素の timeupdate イベントを高頻度にする

video/audio 要素には再生時間が変わったときに timeupdate イベントが発生します。これは Chrome においては最頻でも 250ms ごとにしか呼ばれません (500ms のときもある)。

もっと頻度をあげてイベントをとりたいと思うことがあったので以下のようにしました。

video.addEventListener('loadedmetadata', function (e) {
	var time = video.currentTime;
	requestAnimationFrame(function me () {
		if (time !== video.currentTime) {
			time = video.currentTime;
			video.dispatchEvent(new CustomEvent("timeupdate"));
		}
		requestAnimationFrame(me);
	});
});

video.addEventListener('timeupdate', function (e) {
	console.log(video.currentTime);
});

requestAnimationFrame で定期的に currentTime を監視して timeupdate を自力で発火させるアプローチです。

元の timeupdate イベントも一応受けとっています。というのも requestAnimationFrame はタブがバックグラウンドにいった場合などに呼ばれる頻度がとても落ちることがあるので、本来のイベントも受けて保険としています。そして「やっぱ高頻度じゃなくてもいいや」となっても該当部分をコメントアウトするだけですみます。

さくらのVPSを Mondo Rescue でバックアップして Virtual Box に再現

現在は Ubuntu 12.04 を使っております。12.04 にするときは 10.04 のサポートが終わってからやるというひどさでしたが、さすがにダメだろうということで最近早めのアップデートを考えてます。

ただ、ディストリビューションアップデートが不安なので Virtual Box で予行練習をすることにしました。

VPS 環境を Mondo Rescue でバックアップ

Mondo Rescue のインストール

各ディストリビューションごとにパッケージが提供されているので、これを使ってみます。トップページに最近のリリースファイルがいっぱい列挙されていますが、これは無視して Downloads ページに行き、ftp サーバを直接見ます。

該当するディトリビューション・バージョンのftpディレクトリを開いて、必要ファイルをダウンロードします。Ubuntu の場合、sources.list が提供されているようなので、これを使います。

wget ftp://ftp.mondorescue.org/ubuntu/12.04/mondorescue.sources.list
sudo mv mondorescue.sources.list /etc/apt/sources.list.d/
sudo apt-get update
sudo apt-get install mindi mindi-busybox mondo

これでインストールできました。なお mindi とはブータブルディスク作成用の Linux mini-distribution とのこと。mondo はミュータントタートルズ由来みたいなことが書いてありますがよくわからず。

バックアップの実行

バックアップしたのち、ローカルに転送して捨てるので、/tmp/backup へバックアップファイルを保存することとします

mkdir /tmp/backup
sudo mondoarchive -O -i -N -d /tmp/backup -s 30g

-O バックアップを作成
-i ISOイメージとする
-N 全てのネットワークファイルシステムを無視する
-d バックアップの保存先を /tmp/backup に
-s 30g バックアップファイルの分割単位を30GBに(実質分割しない)

/tmp 以下はデフォルトで非バックアップ対象なので、-E オプションはつけていません。なお -d の指定ディレクトリは前もって作っておく必要があります。

手元の環境だと約1時間かかりました。(バックアップ対象約8GB メモリ1GB HDD 出力 mondorescue-1.iso 3.3G)

このファイルを scp で手元に転送しておきます。

Virtual Box に環境を復元してみる

先にいくつかハマったポイントを列挙しておきます

  • パーティション番号の最大が4までなので5を使っているとエラる
  • F12 を押すと終了するので注意
  • mkinitrd コマンドが存在しなくて再生成できなかった。が起動した。
    • Ubuntu は mkinitramfs らしい
    • mkinitramfs というか update-initramfs -u が一番簡単っぽい
  • インストール直後にネットワークインターフェイスの設定をやること
    • /etc/network/interfaces を編集
  • /etc/hosts に hostname に関するエントリを追加しておくこと (後述)

手順

Virtual Box 上で「新規仮想マシン」

  • 「Linux」「Ubuntu (64bit)」 で「仮想ハードディスクを作成する」で「作成」
  • 仮想ハードディスクはバックアップしたファイルが納まるなら任意
    • ファイルコピーなのでパーティションサイズをあわせる必要はない
仮想マシン設定

「仮想マシン設定ウィンドウ」→「ストレージ」で、「コントローラ: IDE」にある光学ドライブに mondorescue-1.iso を指定

「ネットワーク」で「ブリッジアダプター」にしておく。ホストコンピュータと同じLANに参加するようになる。

起動

nuke か interactive で起動します。違う環境への復元なので nuke でも結局 interactive になります。

さくらのVPSでは /dev/vda1 (ext4) と/dev/vda5 (swap) ですが、Virtual Box だと /dev/sda なので、mountlist を適当に編集します。/dev/sda1 と /dev/sda2 に変えました。

このあとパーティションのフォーマット・ディスクコピーなどがあります。

コピーが終わったあと

  • 「Initialize boot loader?」→ YES
  • 「You will now be able to re-generate your initrd.」→ YES
    • chroot / された状態でシェルが開くなので cd /boot にいって initramfs
    • 今回はやらなかった
  • 「Did you change the mountlist or cloned the system?」→ YES
  • 「Boot device」を /dev/sda に
  • 「You will now edit fstab, mtab, device.map and menu.lst/grub.cfg in order to fix grub install」と言われて nano が起動するので vda を検索 (C-w) して置換していく
    • fstab が UUID ベースで書かれていたのを /dev/sda1 と /dev/sda2 に

これでインストールが終わり、シェルが開くので、

  • /etc/network/interfaces を編集
  • /etc/hosts に hostname に関するエントリがあることを確認。ないなら増やしとく

ディスクまわりの変更は Virtual Box だから必要なので、さくらのVPS間の復元では必要ないはず…

Ubuntu のネットワークまわり

/etc/init/failsafe.conf を編集しといたほうがいい

Waiting for network configuration…
Waiting up to 60more seconds for network configuration…

で2分も待たされて大変うざい。

sudo するとハングする

sudo すると Password 入力がはじまる前にハングする。が、実際はハングしてないので3分ぐらい待てば起動する。

hostname があってないとハングするらしい。どうしよもないのでリカバリモードにでやるか、/etc/hosts に 127.0.0.1 hostname を追加する。

2016年 06月 03日

codesign した Electron アプリがフリーズするのを修正

リファレンスマニュアル閲覧用の Chemr だけど、ベースの Electron を1.2.1にバージョンアップした。

しかし codesign するとあいかわらずフリーズするので改めて調べてみたら Console.app に以下のようなログがでていた。

 sandboxd[131]: ([19968]) Chemr Helper(19968) deny mach-lookup org.chromium.Chromium.rohitfork.19966
 sandboxd[131]: ([19973]) Chemr Helper(19973) deny mach-lookup org.chromium.Chromium.rohitfork.19966
 sandboxd[131]: ([19974]) Chemr Helper(19974) deny mach-lookup org.chromium.Chromium.rohitfork.19966

ググってみると Electron broken on OS X in Apple Sandboxed apps (App Store) · Issue #3871 · electron/electron · GitHub あたりの問題のようだ。

Sign Your App のentitlements あたりに変更が必要になった模様。

長いので結論だけ書くと以下のようにすればなおった。electron-packager を使っている前提で

Team ID を取得する

Apple Developer Center にいくと、Membership Details に Team ID という項目がある。

あるいはコードサイン用に Key Chain に自分用の証明書を入れていると思うが、その証明書名の括弧の中の文字列がチームIDとなっている。

追加の Info.plist を作る

<plist version="1.0">
<dict>
	<key>ElectronTeamID</key>
	<string>$TEAM_ID$</string>
</dict>
</plist>

こんな感じで Info.plist を作っておく、不完全なファイルに見えるが、残りは electron-packager が埋めるのでこれで良い。

こうした上で electron-packager に --extend-info=dev/Info.plist をつけて実行する。

entitlements ファイルの更新

parent.plist を以下のようにする。com.apple.security.network.client は今回の件とは関係なく、Chemr に必要なのでつけているだけで、必須ではない。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.application-groups</key>
    <string>$TEAM_ID$.net.lowreal.Chemr</string>
  </dict>
</plist>

child.plist は以下のようにする

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.inherit</key>
    <true/>
  </dict>
</plist>

codesign

electron-packager にも codesign のオプションはあるが、使ってない。electron-osx-sign を直接呼びだしている。

./node_modules/.bin/electron-osx-sign --no-pre-auto-entitlements --version=1.2.0 "$APP_PATH" --entitlements=dev/parent.plist --entitlements-inherit=dev/child.plist --identity="$APP_KEY"

こんな感じで、上記 entitlements ファイルを指定して codesign させる。

なお electron-osx-sign の 0.4.0-beta4 以上だとこの entitlements の処理を自動でやってくれるみたいだが、なんとなく自動的にやるのが信用ならないのでこれはつかってない。

備考

使ったツールのバージョン

  • "electron-osx-sign": "^0.4.0-beta4"
  • "electron-prebuilt": "1.2.1"
  • "electron-packager": "^7.0.3"
  • electron-packager に渡す --version の値: 1.2.1 (新しいものじゃないと ElectronTeamID に対応してないので注意)

証明書なしでサンドボックスアプリとして実行したい

デバッグのため、とりあえず証明書なしでサンドボックスを有効にしたい場合、ad-hoc signing でも動かすことができた。これは identity に - を指定することでできる。

すなわち

./node_modules/.bin/electron-osx-sign --no-pre-auto-entitlements --platform=mas --version=1.2.0 "$APP_PATH" --entitlements=dev/parent.plist --entitlements-inherit=dev/child.plist --identity="-"

のようにする。おそらく Team ID は一致さえしていればなんでもいい(と思うが試してはいない)

備考 electron-osx-sign を verbose にする

環境変数 DEBUG を設定すると詳細なログがでるようになる。DEBUG='electron-osx-sign' これは debug という npm パッケージを使っているため。