あたりの続きです。

ちょっと飽きてきたというか疲れてきたので、とりあえず使えるレベルを目指すという意味でケースに収めることを優先することにしました。

発注した基板そのままでは収めることができなかったので、いろいろ試行錯誤して収めました。本来基板を設計する段階でケースまで決めてしまえればいいんですが、今回そこまで気をまわすことができませんでした。おかげでだいぶ苦労しましたがなんとか入りました。

Bluetooth モジュール用基板

Bluetooth モジュールは別途基板を自分で掘って載せるようにしました。場所がないので2階建です。

切削はそこそこ上手くいったのですが、ほとんど肉眼で確認できないのにGNDと導通があったり、実際実装してみると見えない部分で半田がショートしたりと、なかなか厳しい状態になりました。3回ぐらいモジュールの半田づけをやりなおしたのでパターンが剥れたり、ついでに勘違いからRX/TXが逆配線になっていると思ってリワークまでしたけど元々の配線であってたり、、、みたいないろんな思い出があります。

ケースの加工

CNC でやりましたが、プラスチック(ABS) の加工はものすごく苦手意識があります。アルミと違ってプラスチックは溶けるので早く動かしすぎてもダメだし遅く動かしすぎてもダメだしで「安全方向にふる」という運用が通用しないのです。

今回は以下ぐらいでやるとすくなくともエンドミルに溶けたプラスチックが固着して折れるということはなくなりました…… (2本折りました)

  • φ1mm 2枚刃 エンドミル
  • F500 (割と早い) Z送り50 (かなり遅い)
  • 回転数 50% 約5000rpm
    • S500 指定だが、最大1000を最大の電圧として500だけ電圧をかけるという意味で、500は回転数ではない (最大の50%の回転)

タコメータをつけてないので回転数が正確にわかってません。

インターフェイス

見ての通りですがインターフェイス的には

  • 電源スイッチ
  • LED (Bluetooth の接続状況: 接続待ちは点滅・接続済みは点灯)
  • BNC コネクタ

しかありません。これは最初からこうするつもりで作っていて、Bluetooth 経由で接続したスマートフォンから全ての操作を行うという設計です。

というのも、アナライザを作るなら、まず絶対に Bluetooth などで無線化したいという思いがありました。なぜかといえば、実際にフィールドでアンテナの調整を行う場合、給電点と調整点というのは必ずしも一致しておらず、既存のアンテナアナライザーだと給電点と調整点をいったりきたりする必要があって面倒だからです。例えば7MHz帯の半波長逆Vダイポールをフィールドで一時的に立てようと思うと、給電点と調整点では10mぐらいの距離があってしんどいのです。

今後

これでとりあえず使えるぐらいにはなりました。しかし、かなり問題があります。

  1. Android 側のアプリケーションの完成度がいまいちなのをなんとかする
    • ソフトウェアだけなのでおいおい
  2. インピーダンスのグラフが波うつ
    • どっかで反射の影響が起きていると思うけどどこかわかってない
  3. RFアンプの設計からやりなおす
    • というか面倒なのでMMIC使うとか
  4. 低消費電力化
    • RFアンプに常時電源供給してしまっているのをやめる
    • RFアンプの再設計
    • AD9851 と Bluetooth デバイスの消費電力がデカいのでできることは限られる
  5. ケースに組みこんだら計測にノイズが乗るようになった
    • NiMH 2本を電源にしたいと拘ったため、DC/DC で 5V まで昇圧しているが間違いなくノイズ源になっている
    • Bluetooth モジュールとADC部が近い
  6. もっと小さく作りたい
    • MCU が無駄にでかい

単三電池2本の電源内蔵するのも今回なぜかこだわっていたのですが、どうせDC/DCで昇圧するならモバイルバッテリ使えるようにして、電池は内蔵しないほうがコンパクトかつ総合的には荷物が減って良さそうです。

  1. トップ
  2. tech
  3. アンテナアナライザーをケースに収めた

トロイダル・コア活用百科の応用の章にある10MHz帯用2ポールバンドパスフィルタのうち、挿入損失1dBのもので T50-6 を使ったものを作ってみました。

改訂新版 定本 トロイダル・コア活用百科 —トロイダル・コイルの理論・製作と応用回路 - 山村 英穂

山村 英穂

5.0 / 5.0

回路と実装

4pF が手元にないので3.9pF で代用しました。また、AWG #22 のワイヤーがなかったため、φ0.4mm で代用しています(なのでコイルのQは多少下っているはずです)


特性

書籍内でのこのグラフだと(2)に相当します。

実際にスペアナで同じ範囲を表示すると以下のようになりました。(多少トリマで調整した状態で、完全に追いこめてはいません)

挿入損失が 2dB になってますが、おおむね近い特性が再現できました。

ついでに 50MHz までのグラフです。綺麗に下降せずに盛りあがっています。

ここからさらに広げていっても、ほとんど減衰量は増えていませんでした (スクリーンショットを撮り忘れましたが)

  1. トップ
  2. tech
  3. 10MHz バンドパスフィルタ

[tech] 10MHz バンドパスフィルタ | Sat, Apr 2. 2016 - 氾濫原 の続きです。挿入損失 6dB バージョンの定数を変更してみました。コイルはそのままです。

定数変更

実装の関係で、書籍内の定数とすこしずれています。

評価

書籍内のグラフと範囲をあわせたものです。やってて気付いたのですが、書籍のグラフは中心周波数が10MHzではないんですね。狭帯域だからかアマチュアバンドの中心?にあわせてあるのかな。手元の調整は10MHzを中心としてやっています (かなり繊細な調整が必要でした)。

帯域内は約-9dBの挿入損失になっています。

300MHz までの範囲です。100MHz ぐらいまでは -70dB ぐらいまでの減衰があります。

  1. トップ
  2. tech
  3. 10MHz バンドパスフィルタ2

「伝送線路トランス」という言葉が一般的な用語かどうかよくわからないのですが、トロイダル・コア活用百科ではこのような用語になっていました。一応 transmission-line transformer で検索すると使われており、Amidon のドキュメントでも出てきますが……

それはともかく、伝送線路トランスは一見奇妙な感じがして面白いです。とりあえず LTSpice で等価回路を書いて試してみました。

位相反転回路

GND のとりかたが入力側と出力側で逆なので位相が反転します。

この回路、L1 と L2 が結合しているため、これらで1つのコモンモードチョークとして働いてアイソレーションされるために位相反転ができているのですが、なんとかく不思議な感じがします。

自分の中では「コモンモードチョーク」はノーマルモードに影響を与えないイメージなのですが、「コモンモードチョーク」があるおかげでノーマルモードの位相反転ができているのです。ぱっと見だと結線されていない GND 経由 の電流 (すなわちコモンモード) が阻止されることを強くイメージする必要があります。

ところで、結合係数を減らすとどうなるか見てみます。

これはつまりコモンモードチョークとしての機能が失われている場合です。高い結合が得られていない限り機能しなくなくなることがわかりました。

インピーダンス変換器

1:4 のインピーダンス変換器もシミュレーションしてみました。

純伝送線路トランス

伝送線路トランスを2つ使い、入力を並列、出力を直列にすることで、出力電圧を倍にできる (出力電流は半分) という回路です。単純に、それぞれの入出力がアイソレーションされていると考えると動きそうだなというイメージはできます。

が、やはり一見奇妙に見えます。

伝送線路的トランス

入力信号にトランスの出力を重合する形で出力電圧を倍にする回路です。広域が犠牲になる代わりに純伝送線路トランスよりコアの数を減らせるメリットがあります。実際の回路だとこちらのほうが良く見ます。

ぱっと見だと完全に意味不明ですが「重ねあわせる」ことを意識すると理解できるようなできないような感じがします。

通常のトランスとの違い

通常のトランスは、一旦電気エネルギーを磁気エネルギーに変換して再度電気エネルギーに変換するという動作をします。なので、高い結合係数と低いコア損失を同時に実現できなければいけません。

伝送線路トランスはコモンモードのアイソレーションによって実現されており、磁気エネルギーはメインのエネルギー伝達に使われていないので、結合係数が高ければコア損失が多少あっても問題になりません。

  1. トップ
  2. tech
  3. LTSpice で伝送線路トランス

NHK Eテレの番組はそれぞれ対象年齢が設定されていて、「いないいないばぁ」は0〜2歳、「おかあさんといっしょ」は2〜4歳、「みいつけた」は4〜5歳あたりらしい。

うちの子供はまだ2歳になっていないので「みいつけた」はまだ早いことになるが、見せてみると特に嫌がらずに見ていたりする (オフロスキーは嫌いみたいだけど)。「いしゅ、いしゅ」と言うぐらいには見る。

「いないいないばぁ」と「おかあさんといっしょ」は大人が見ても面白いとはいえない感じだけど (というか無駄に元気がよくて、疲れる)、「みいつけた」は大人が見てもゆるくて面白い。

  • 害になるような人物が出てこない
  • 不愉快ができことが起こらない
  • 現実にはありえない日常を過ごす
  • 説教臭い内容ではない

あたりを考えてみると、日常系アニメと似たようなジャンルに思える。

コッシー

椅子のキャラクターでコッシーというのがいて、スタジオ内で結構自由に動くんだけど、どうやって動いているのかさっぱりわからない。謎の技術。ひっくり返るシーンとかもあって、足の裏が見えたりするけど、車輪がついていたりするようにも見えない。謎

びっくりしたのが声をあててる声優で、高橋茂雄 (サバンナ) とクレジットされている。ブラジルの皆さんじゃないほうの人・ザッカーバーグに似てるほうの人だけど、アメトークとかにしか出てないイメージだったのでびっくりした。エンディングの作詞とかもやってて余計びっくりする。全然違和感がない (喋る椅子に対して違和感もクソもない気もするけど) し聞きやすい声質だし、謎の才能。

歌の作曲が星野源だったり、この番組に限らないけどEテレの子供向け番組は凝ったキャスティングされていることがあって面白い。「おかあさんといっしょ」でもつんく作曲の歌がちょいちょいある。

全部 HTTPS 対応にしてリダイレクトかけるようにしました。

cho45.stfuawsc.com は既に HTTPS にしてありました。こちらは nginx に letsencrypt の証明書を入れた構成だったのですが、これを機に HTTPS のフロントを h2o にして、nginx は HTTP だけを配信するようにしました。これで HTTPS は HTTP2 に対応になりました。

このサイトは割と複雑な URL の rewrite ルールをしいているので、バックエンドのアプリケーションに直接ディスパッチせず、一旦 HTTPS でも nginx を経由するようにして設定し、徐々に h2o で全リクエストを処理するように置き換えていきました。www.lowreal.net も同時に HTTPS 対応しましたがそれぞれのドメインの構成は以下の通りです

  • cho45.stfuawsc.com
    • h2o → static file
    • h2o → backend
  • lowreal.net
    • h2o → static file
    • h2o → backend
  • www.lowreal.net
    • h2o → static file (accept-language を mruby で見てる)

証明書以外にやったこと

一応 mixed content を回避したり、push してみたりしたくていろいろやりました

  • はてなスターのスキーム変更
  • スター画像のホスト元 (フォトライフ) のドメイン変更 (cdn-akナントカに)
  • tumblr のスキーム変更
  • facebook ボタン廃止
  • twitter ボタンのスキーム変更
  • Amazon 画像のドメイン変更 (過去に遡ってエントリ内の画像URLを変更)
  • バックエンドアプリケーション (ブログシステム) へ Link: rel=preload を簡単に吐ける機能を追加

h2o での server push の確認方法

h2o は server push したコンテンツのヘッダに x-http2-push: pushed を含めてくれるので、ちゃんと push されてるか確認する一番簡単な方法はこれを見ることっぽいです。

  1. トップ
  2. tech
  3. lowreal.net のHTTP2/HTTPS 化を実施

このサイトのHTTPS化にあたって nginx で書いていた rewrite のルールを h2o の mruby で処理するように変える必要がありました。

しかしベタで書くのも面倒なので、そこそこ機械的な置換ですむような書きかたができるようなライブラリを書いてしのぎました。

mruby.handler

    paths:
      "/":
        proxy.reverse.url: http://localhost:5001
        proxy.preserve-host: ON
        mruby.handler: |
          require "/srv/www/rewrite_rules.rb"
          lambda do |env|
            RewriteRules.rewrite(env) do
              rewrite '/favicon.ico', '/images/favicon.ico', :break
              rewrite '/apple-touch-icon.png', '/images/apple-touch-icon.png', :break

              rewrite %r{^/2005/colors-canvas\.xhtml$}, '/2005/colors-canvas.html', :permanent
              rewrite %r{^/2005/colors-canvas$}, '/2005/colors-canvas.html', :permanent

              rewrite %r{^/logs/latest$}, '/', :permanent
              rewrite %r{^/logs/latest.rdf$}, '/feed', :permanent
              rewrite %r{^/logs/latest.atom$}, '/feed', :permanent
              rewrite %r{^/latest\.rdf$}, '/feed', :permanent
              rewrite %r{^/blog/index\.(rdf|atom)$}, '/feed', :permanent
              rewrite %r{^/logs(/.+?)(\.(rdf|atom))$}, '/feed', :permanent

              rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
              rewrite %r{^/blog(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
              rewrite %r{^/photo$}, '/photo/', :permanent

              rewrite %r{^/(\d\d\d\d/\d\d(/\d\d)?)$}, '/\1/', :permanent
              rewrite %r{^/\d\d\d\d/$}, '/', :redirect

              rewrite %r{^/view-img(/.+?)$}, '\1', :permanent
            end
          end

h2o の path はディレクトリの指定しかできないみたいなので (自動的に末尾スラッシュがついたりする)、rewrite で同時にパス決め打ちのディスパッチも行っています。

rewrite_rules.rb

class RewriteRules
	class RewriteRulesException < Exception
		attr_reader :response
		def initialize(response)
			@response = response
		end
	end

	attr_reader :env
	attr_reader :path

	def self.rewrite(env, &block)
		self.new(env).run(&block)
	end

	def initialize(env, &block)
		@env = env
		@path_orig = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
		@path = @path_orig.dup
	end

	def run(&block)
		begin
			instance_eval(&block)
			if @path != @path_orig
				return [ 307, { 'x-reproxy-url' => path }, [ ] ]
			else
				return [ 399, {}, [ ] ]
			end
		rescue RewriteRulesException => e
			return e.response
		end
	end

	def rewrite(regexp, replace, flag=:continue)
		if @path.gsub!(regexp, replace)
			case flag
			when :redirect
				raise RewriteRulesException.new([ 302, { 'Location' => @path }, [ ] ])
			when :permanent
				raise RewriteRulesException.new([ 301, { 'Location' => @path }, [ ] ])
			when :break
				raise RewriteRulesException.new([ 307, { 'x-reproxy-url' => @path }, [ ] ])
			when :continue
				# nothing
			else
				raise "unsupported flag: #{flag}"
			end
		end
	end
end
require 'rspec'

describe RewriteRules do
	it "should treat :permanent as 302 redirect" do
		env = {
			'PATH_INFO' => '/logs/latest'
		}
		expect(RewriteRules.rewrite(env) {
			rewrite %r{^/logs/latest$}, '/', :permanent
			rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
			rewrite %r{^/foobar/}, '/bazbaz/'
		}).to eq( [301, {"Location"=>"/"}, []])
	end

	it "should treat :redirect as 301 redirect" do
		env = {
			'PATH_INFO' => '/logs/piyo.html'
		}
		expect(RewriteRules.rewrite(env) {
			rewrite %r{^/logs/latest$}, '/', :permanent
			rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :redirect
			rewrite %r{^/foobar/}, '/bazbaz/'
		}).to eq([302, {"Location"=>"/piyo"}, []])
	end

	it "should treat as internal proxy by default" do
		env = {
			'PATH_INFO' => '/foobar/baz'
		}
		expect(RewriteRules.rewrite(env) {
			rewrite %r{^/logs/latest$}, '/', :permanent
			rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
			rewrite %r{^/foobar/}, '/bazbaz/'
		}).to eq([307, {"x-reproxy-url"=>"/bazbaz/baz"}, []])
	end

	it "can write logic in dsl" do
		env = {
			'PATH_INFO' => '/foobar/baz',
			'XXX' => true,
		}
		expect(RewriteRules.rewrite(env) {
			if env['XXX']
				rewrite %r{^/foobar/}, '/bazbaz/'
			end
		}).to eq([307, {"x-reproxy-url"=>"/bazbaz/baz"}, []])

		env = {
			'PATH_INFO' => '/foobar/baz',
			'XXX' => false
		}
		expect(RewriteRules.rewrite(env) {
			if env['XXX']
				rewrite %r{^/foobar/}, '/bazbaz/'
			end
		}).to eq([399, {}, []])
	end
end

ところで

rewrite ルールが ruby で書けるということは、すなわち自由にテスト可能であることを意味します。Apache の RewriteRule や nginx の rewrite をテストするのはかなり面倒なので、かなり強力で嬉しい感じがします。

  1. トップ
  2. tech
  3. nginx の rewrite ルールっぽく h2o の mruby でリクエストの rewrite を行う

ログの設定を stdout のままにしていたので rotate するように設定しました。(Ubuntu 12.04.5 LTS)

h2o の設定

sudo mkdir /var/log/h2o
sudo chown www-data:adm /var/log/h2o/

YAML の参照を使うとログフォーマットを使いまわせてよいです。まず global に

access-log: &ACCESSLOG
  path: /dev/null
  format: "..."

と書いて、あとは各ホストで path を上書きする形で設定します。

access-log:
  <<: *ACCESSLOG
  path: /var/log/h2o/host.access.log

なおこのYAML参照は h2o 2.0.0 からのサポートのようです。

logrotate の設定

# /etc/logrotate.d/h2o

/var/log/h2o/*.log {
    daily
    missingok
    rotate 90
    compress
    delaycompress
    notifempty
    create 0640 www-data adm 
    sharedscripts
    postrotate
        svc -h /service/h2o
    endscript
}

h2o は daemontools で管理しているので、postrotate では svc を呼んで SIGHUP を送っています。

一旦状態を確認します

sudo logrotate -dv /etc/logrotate.d/h2o

全ログファイルが log does not need rotating になってるはずです。実際に一度実行します

 sudo logrotate -v /etc/logrotate.d/h2o 

/var/lib/logrotate/status を見ると該当するログファイルが記録されているはずです。日付を一日戻して再度実行するとローテートされることが確認できます。

  1. トップ
  2. tech
  3. h2o のログ設定 (logrotate.d)

以下のようなスクリプトを置いて月イチの cron で更新するようにしました。証明書を更新したあと h2o を restart しています。

#!/bin/sh

# sudo crontab -e
# MAILTO = cho45
# PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# 25 14 6 * * /srv/www/renew-cert.sh

BIN=/home/cho45/project/letsencrypt/letsencrypt-auto

$BIN certonly --webroot -w /srv/www/www.lowreal.net --renew-by-default -d www.lowreal.net
$BIN certonly --webroot -w /srv/www/lowreal.net --renew-by-default -d lowreal.net
$BIN certonly --webroot -w /srv/www/cho45.stfuawsc.com  --renew-by-default -d cho45.stfuawsc.com

svc -h /service/h2o
  1. トップ
  2. tech
  3. Let's encrypt の自動更新

今のこのサイトの h2o.conf.yaml です。HTTPS (443) のみを処理しています。HTTP (80) は nginx で受けていて、HTTPS 対応ホストに関しては nginx からはリダイレクトしています。

  • アクセスログフォーマットを LTSV に
    • ログフォーマットを YAML の参照で全ホストで共有
  • rewrite rule
  • Strict-Transport-Security (HSTS)
    • 一旦 https でアクセスしてきたクライアントに対して以後 http でのアクセスをさせない
    • 本来はセキュリティのためだが、リダイレクトを一回減らせるのでパフォーマンス的にも一応得
  • "/.well-known": をバインド
    • letsencrypt のホスト検証に使われる

設定の際参考になれば幸いです。

user: www-data
access-log: &ACCESSLOG
  path: /var/log/h2o/access.log
  format: "time:%t\thost:%h\treq:%r\tstatus:%s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\tcache:%{X-Cache}o\truntime:%{X-Runtime}o\tvhost:%{Host}i\tconnect-time:%{connect-time}x\trequest-header-time:%{request-header-time}x\trequest-body-time:%{request-body-time}x\tprocess-time:%{process-time}x\tresponse-time:%{response-time}x\tduration:%{duration}x\thttp2.stream-id:%{http2.stream-id}x\thttp2.priority:%{http2.priority.received}x"
error-log: /dev/stdout
http2-reprioritize-blocking-assets: ON
ssl-session-resumption:
  mode: all
hosts:
  "lowreal.net:443":
    access-log:
      <<: *ACCESSLOG
      path: /var/log/h2o/lowreal.net.access.log
    http2-casper: ON
    compress: ON
    listen:
      port: 443
      ssl:
        certificate-file: /etc/letsencrypt/live/lowreal.net/fullchain.pem
        key-file:         /etc/letsencrypt/live/lowreal.net/privkey.pem
    header.add: "Strict-Transport-Security: max-age=31536000"
    header.add: "X-Content-Type-Options: nosniff"
    header.add: "X-UA-Compatible: IE=Edge"
    paths:
      "/":
        reproxy: ON
        mruby.handler: |
          require "/srv/www/rewrite_rules.rb"
          lambda do |env|
            RewriteRules.rewrite(env) do
              rewrite '/favicon.ico', '/images/favicon.ico', :break
              rewrite '/apple-touch-icon.png', '/images/apple-touch-icon.png', :break

              rewrite %r{^/2005/colors-canvas\.xhtml$}, '/2005/colors-canvas.html', :permanent
              rewrite %r{^/2005/colors-canvas$}, '/2005/colors-canvas.html', :permanent

              rewrite %r{^/logs/latest$}, '/', :permanent
              rewrite %r{^/logs/latest.rdf$}, '/feed', :permanent
              rewrite %r{^/logs/latest.atom$}, '/feed', :permanent
              rewrite %r{^/latest\.rdf$}, '/feed', :permanent
              rewrite %r{^/blog/index\.(rdf|atom)$}, '/feed', :permanent
              rewrite %r{^/logs(/.+?)(\.(rdf|atom))$}, '/feed', :permanent

              rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
              rewrite %r{^/blog(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
              rewrite %r{^/photo$}, '/photo/', :permanent

              rewrite %r{^/(\d\d\d\d/\d\d(/\d\d)?)$}, '/\1/', :permanent
              rewrite %r{^/\d\d\d\d/$}, '/', :redirect

              rewrite %r{^/view-img(/.+?)$}, '\1', :permanent

              rewrite %r{^/(\d\d\d\d/([^\d]|\d\d[^/]).*)}, '/files/\1', :break
            end
          end
        proxy.reverse.url: http://localhost:5001
        proxy.preserve-host: ON
      "/files":
        file.dir: /srv/www/lowreal.net/files
      "/images":
        file.dir: /srv/www/lowreal.net/Nogag/static/images
      "/css":
        file.dir: /srv/www/lowreal.net/Nogag/static/css
      "/js":
        file.dir: /srv/www/lowreal.net/Nogag/static/js
      "/lib":
        file.dir: /srv/www/lowreal.net/Nogag/static/lib
      "/.well-known":
        file.dir: /srv/www/lowreal.net/.well-known

  "www.lowreal.net:443":
    access-log:
      <<: *ACCESSLOG
      path: /var/log/h2o/www.lowreal.net.access.log
    http2-casper: ON
    compress: ON
    listen:
      port: 443
      ssl:
        certificate-file: /etc/letsencrypt/live/www.lowreal.net/fullchain.pem
        key-file:         /etc/letsencrypt/live/www.lowreal.net/privkey.pem
    header.add: "Strict-Transport-Security: max-age=31536000"
    header.add: "X-Content-Type-Options: nosniff"
    header.add: "X-UA-Compatible: IE=Edge"
    paths:
      "/":
        reproxy: ON
        mruby.handler: |
          lambda do |env|
            link = [
              '/styles/201002/201002.css',
              '/js/site-script.js',
            ].map{|p| "<#{p}>; rel=preload"}.join("\n")
            case env['PATH_INFO']
            when "/"
              if (env['HTTP_ACCEPT_LANGUAGE'] || '') =~ /ja/
                return [307, {"x-reproxy-url" => "/index.ja.html", "link" => link }, []]
              else
                return [307, {"x-reproxy-url" => "/index.en.html", "link" => link }, []]
              end
            when "/index.ja.html", "/index.en.html"
              return [399, {"link" => link }, []]
            end
            return [399, {}, []]
          end
        file.dir: /srv/www/www.lowreal.net
#        file.index: [ index.en.html ]

  "cho45.stfuawsc.com:443":
    access-log:
      <<: *ACCESSLOG
      path: /var/log/h2o/cho45.stfuawsc.com.access.log
    listen:
      port: 443
      ssl:
        certificate-file: /etc/letsencrypt/live/cho45.stfuawsc.com/fullchain.pem
        key-file:         /etc/letsencrypt/live/cho45.stfuawsc.com/privkey.pem
    header.add: "Strict-Transport-Security: max-age=31536000"
    header.add: "X-Content-Type-Options: nosniff"
    header.add: "X-UA-Compatible: IE=Edge"
    paths:
      "/":
        file.dir: /srv/www/cho45.stfuawsc.com
        redirect:
          status: 301
          url:    "/niro/"
      "/niro/":
        proxy.reverse.url: http://localhost:5001/niro/
        proxy.preserve-host: ON
      "/tmp":
        mruby.handler: |
          require "htpasswd.rb"
          Htpasswd.new("/srv/www/.htpasswd", "Restricted")
        file.dir: /srv/www/cho45.stfuawsc.com/tmp
  1. トップ
  2. tech
  3. 現在の h2o.conf.yaml

HTTP2 化に伴なって、サイト全体の最適化を行ないました

依存の整理

もはや jQuery なしでも簡単に書けそうなスクリプト部分から jQuery 依存を抜きました。また、JSDeferred を Promise で置き換えました。

script 要素の async / defer

script 要素については必要に応じて async や defer をつけるようにし、基本的に外部スクリプトでブロックする可能性を排除しました。

async は script 要素同士で独立している場合無条件につけられます (非シーケンシャル)。defer はページのDOMが構築されたあとに実行されるように遅延されます (シーケンシャル)

defer は DOMContentLoaded 直前にまとめて呼ばれるようです。

外部ライブラリを自分でホスト

外部ライブラリをCDN経由でロードしている部分がありましたが、TLS セッションの無駄遣いな気がするので、自分でホストするように変えました。自分のホストであれば HTTP2 のセッションが生きているので無駄な処理が減りそうです。

動的圧縮から静的圧縮に

h2o で compress: ON とすると accept-encoding に応じて gzip か br で動的に圧縮してクライアントに返却してくれます。

しかし殆ど変更されないライブラリみたいなものは動的にやるのがもったいないので、静的に圧縮するようにしました。特に brotli は圧縮処理が遅い感じなので意味がありそうです。

h2o 的には file.send-compressed: ON にしって、filename.js.gz と filename.js.brを置くようにします。

.gz と .br をつくるスクリプト

以下のようなスクリプトを作っておくと、指定したファイルの .gz と .br を作れて便利です。

静的に作っておけば、特に VPS のスペックが非力だと相対的に効果が高いはずです。

#!/bin/sh

for f in "$@"
do
        echo "$f"
        gzip --best < "$f"  > "$f".gz
        bro --quality 10 --input "$f" --output "$f".br
done

brotli について

h2o は brotli に対応していて、accept-encoding: br なときはオンデマンドも br を返すみたいです。

ただ、brotli は chrome に実装済みといっても flags で有効にしないとまだ使えないみたいです。

Firefox 45 は標準で accept-encoding: br を吐くようです。

ブログシステムでのキャッシュ

今まで表示のたびに DB からひいてきてテンプレート処理を行っていましたが、キャッシュするようにしました。もともとそこまで遅いわけではないのですが 、一応これも効果がありました。

ただ、キャッシュを入れるとキャッシュ無効化の処理のために考えることが大変増えます。今のところあまり筋の良い実装ではないのですが、一応動いています。

MathJax のサーバサイド化

[tech] サーバーサイド MathJax で数式表示を高速化する | Fri, Apr 8. 2016 - 氾濫原 別エントリにしました。

いろいろやった結果

キャッシュなしの状態でロードして、DOMContentLoaded まで 300ms を切るぐらいにできました。ただし onload までは2秒〜3秒かかっています。onload まで遅いのは画像のサイズが大きいというのと、あとは主にアドセンスのせいです。

同じ環境で www.google.co.jp のDOMContentLoaded を測ると 170ms ぐらいでしたので、要素数などを比較するといい線まできている気がします。

さらにできることは?

いわゆる minify をやっていないので、これをやる余地がまだあります。しかし結構面倒です。そしてたとえ削れても数kBなので、意味があるのか疑問を持っています。

  1. トップ
  2. tech
  3. サイトの最適化

このサイトでは数式を本文中に TeX 形式で書いて MathJax で処理させています。↓ こういうやつです。ベクターなので昨今の高解像度事情でもいい感じに綺麗に表示できます。

これはクライアントサイドで本文中の TeX フォーマットを探して全部 SVG とかに置き換えていくのですが、これがなかなか重い処理です。

特にスマートフォンの場合、数式がまともに表示されるまで5秒〜10秒ぐらいかかるので、それまでモヤモヤした感じになります。

さらにいえば、これは非同期で変換をかけているのでロード直後にリフローが頻発することになります。

ということで、これらの問題をなんとかしたいので、標題のようにサーバサイドで MathJax を使ってみるようにしてみました。

このサイトははてな記法で書いたエントリを保存時にHTMLに変換し、変換済みをDBにいれて、表示のときはそれを出しているだけです。なので、保存時に MathJax を通して本文中の数式を埋め込み SVG に変換するようにします。

HTML の断片をうけとって MathJax にかける HTTP サーバ

幸い MathJax-node という node.js 上で動かせる MathJax があったので、これをそのまま使い、http.Server でラップして、ブログシステムとは別に API サーバを立ち上げました。

#!/usr/bin/env node

var mjAPI = require("mathjax-node/lib/mj-page.js");


mjAPI.start();
mjAPI.config({
	tex2jax: {
		inlineMath: [["\\(","\\)"]],
		displayMath: [ ["$$", "$$"] ]
	},
	extensions: ["tex2jax.js"]
});

var http = require('http');
http.createServer(function (req, res) {
	var html = [];
	req.on('readable', function () {
		var chunk = req.read();
		console.log('readable');
		if (chunk) html.push(chunk.toString('utf8'));
	});
	req.on('end', function() {
		console.log('end');
		console.log('html', html);

		mjAPI.typeset({
			html: html.join(""),
			renderer: "SVG",
			inputs: ["TeX"],
			ex: 6,
			width: 40
		}, function (result) {
			console.log('typeset done');
			console.log(result);

			res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
			res.end(result.html);
		});
	});
}).listen(13370, '127.0.0.1');
console.log('Server running at http://127.0.0.1:13370/');

ブログシステムからは保存時にこのAPIを呼んでHTMLをフィルタしてDBに保存するようにしました。

ハマりどころ

動的な MathJax だと、ページのコンテナサイズから適切な幅を計算してくれるのですが、サーバサイドだとそれができませんので、適切な横幅を自分で指定する必要がありました。

スマートフォンでの閲覧を考えると、width: 40 ぐらいにして、念のため CSS で svg { max-width: 100% } とかを入れておくとよさそうです。width が大きいと、数式ごとのフォントサイズが変わりまくって大変うっとおしいです。

MathJax-node の速度

MathJax-node はなぜかものすごく遅いです。ライブラリロード済みでも10秒ぐらいかかります。そもそも起動も遅いです。オンデマンドでやるのは無理そうなレベルです。

MathJax の JS のロードを抑制

これに伴なって、MathJax を使わなくても良さそうな場合、JSのロードすらやめるようにしました。

所感

MathJax の JS は2段階ぐらいでロードされるので RTT が長いほど不利になります。サーバサイドでやってしまえばJSを転送する必要も実行する必要もなくなるのでエコです。おかげでロードが結構早くなりました。

しかもページロード直後から数式が完全な形で表示されるので、ページに数式がある場合、体感的な速度は数倍に感じます。

  1. トップ
  2. tech
  3. サーバーサイド MathJax で数式表示を高速化する

先々週も先週も今週も、週末なぜかひたすら寝ていた。無限に疲れている。

このサイトのトップをロードするとこんな感じで、Google 系 (Adsense や Google Photo) は quic/1+spdy/3、このサイトは h2 となっていて、 http/1.1 となっているほうが少ないことに気付きました (はてなスターのみ)。Firefox では quic/1+spdy/3 の部分が全て h2 になってました。

  1. トップ
  2. tech
  3. 意外と http/1.1 のホストがない

こんな感じでシミュレーションしつつ、感覚をつかもうとしています。

今ところ感じたこと

  • を十分に流すこと (データシートに のグラフがあることが多い)
  • を流そうとすると負荷抵抗は大きくできない (大きな増幅率をとれない)
  • そもそも高周波になると増幅率がとれない (ミラー効果)
    • ぐらいが限界周波数になるらしい? このシミュレーションだと の20分の1ぐらいまでしか十分な増幅ができない

100MHz ぐらいまで増幅したい場合 は 1GHz〜2GHz が必要

負荷がコイルの場合は?

このシミュレーションの負荷は、伝送線路的トランスによる 4:1 (または9:1) のインピーダンス変換器になっている。出力側が50Ωになっているので、増幅器の負荷は200Ω (450Ω) になる。つまり増幅率があがる。

このシミュレーションの場合 450Ω 負荷だと頭うちになっていてあまり意味がないように見える。

なぜエミッタ接地なのか?

電圧増幅+エミッタフォロワみたいな形にしないのは、電圧増幅を伴なう場合エミッタフォロワの意味が薄いから?

を十分流す必要がある以上、負荷抵抗を大きくできない。逆にいうと出力インピーダンスは低くならざるを得ない。出力インピーダンスを直接50Ωとかにマッチングさせるなら、エミッタフォロワを使う意味はない。

ベース接地・エミッタフォロワ

いずれも電流増幅率が1だったり、電圧増幅率が1だったりするので電力増幅に向いてない。

カスコード接続

エミッタ接地の負荷にベース接地をつける形のもの。ミラー効果がなくなるので高い周波数まで増幅できるようになる。ただしトランジスタを2つ縦に繋げるので電源電圧をその分上げる必要がある。

  1. トップ
  2. tech
  3. バイポーラトランジスタによるRFアンプの覚書

殆どアクセスがないサイトは、ファーストアクセスでキャッシュを作るようなサーバサイドキャッシュの戦略が全く意味がないので、バッチで予めキャッシュを作っておくみたいなことが必要そうだなと思い、そういうスクリプトを書いて流せるようにしました。

特にサイトのHTML頻繁に変えたりしているとしょっちゅうキャッシュの意味がなくなります。要はこれ、blosxom における静的なページ生成とか、MT におけるリビルドみたいなもんです。

  1. トップ
  2. tech
  3. しょぼい個人サイトのキャッシュ

JS しか書いてないんだなって人は筋悪いものをありがたがっていたりする印象はある。しかし筋悪いものをありがたがるみたいなのはどこにでもいるので、JSがどうとかは直接は関係がないはずではあると思う。JSしか書いてない人とPHPしか書いてない人は似たようなもんで、単に広範囲の知識に興味がないだけな気がする。

それはともかく「これは筋悪そうだな」っていう感覚がどこからくるのかよくわかってないので、現時点で思いつく限り雑にメモしておく。

割の合わなさ

「これは何の問題を解決してるんだろう」と思ってドキュメント読んだりソース読んだりした結果、大したことを解決してなくて、その割に実装量が多いとか学習コストが高いと、筋悪いなあと思う。

フットプリントや学習コストに対して提供されるモノが「割に合わない」のは筋が悪く感じる。

将来性のなさ

「あ、これはただの流行だな」みたいな、5年後には消滅してるなというものは筋が悪い。

標準にそういう機能入るよ、みたいなのを全然違うインターフェイスで実装してたりするのとかがあてはまる。標準で議論されている機能なら、ポリフィルにするのが最も将来無駄にならない。

HTTP2 に向けてキャッシュフレンドリーなリソース構成にしていこうな、という昨今で、何でもかんでもパックや!みたいなのも、今はぎりぎりいいかもしれないけど、既に筋悪い感じがする。

プログラムの見方を変えないラッパー

たとえば Promise はコールバックのちょっとしたラッパーぐらいの機能しかないが、プログラムの見方を変えるという重要な役割を持っているので、意味がある。

しかし単にシンタックスシュガー的なものしか提供していないとかで、何もプログラムの見方が変わっていないのにラッパーがかぶさっているのは、ライブラリとしての意味がない。

「プログラムの理解を助ける」という役割はとても重要だけど、そういう視点で作られているライブラリかどうか、それが割に合うかは難しい。

しかし最悪なのは書き手にとってしかメリットがないというもの。特に実装を全て読まないと使えない系は要注意で、そういう書き手のオナニーで変なラッパーが挟まってるみたいなのは読み手がとても苦労する。これはメリットがないというよりも明確に将来にわたって害となる。

ただし実装を全て読まないといけないものが全てだめかというとそういうわけでもない。実装は読まないと危険だけど可読性はあがるので割に合うこともある。

フルスタック

フレームワークのレールから外れた瞬間アホみたいなマジカルコードを書くことになる。レールから脱線すると必ずハマる。そしてフレームワークの枠組み内で収まるようなアプリケーションはない 。

もし使う場合コピペで実装できる以外のことをしないことがポイントで、「ここはこう書きたいんだ!」という自我を捨ててコピペする機械として生きなければならない。

実際のフルスタックというのは検索して出てきたstackoverflowのコピペで全部やりますよという意味で、なんでもできるという意味ではない。

  1. トップ
  2. tech
  3. 筋の悪さ

意外と何をプッシュすべきか悩んだのでひとまず現時点での自分の結論をまとめました。

CSS は必ずプッシュ・JSは場合による

サイトの構成によりますが、ページの表示に必要なものは全てプッシュするべきのようです。

  • CSS の全て
  • ページの根幹に関わるJSの全て

サーバプッシュの目的

まずサーバプッシュの目的を改めて確認しておくと、これはクリティカルレンダリングパスを削減するためです。

クリティカルレンダリングパスについては クリティカル レンダリング パスのパフォーマンスを分析する | Web Fundamentals - Google Developers がわかりやすいです。

サーバープッシュなしの場合 HTML+CSS 構成のページはクリティカルレンダリングパスが必ず2以上になります。つまり最低でもRTTの2倍の時間がページ表示に加算されます。

これをサーバプッシュで行う場合、HTML+CSSを一度に送り返すので、クリティカルレンダリングパスは1になります。

イメージとしてはHTML内の外部CSSが全てインライン style 要素にしてある場合に似ています。ただしサーバプッシュの場合、適切なキャッシュを効かせることができるケースがあるので、インライン style 要素よりも効率的です。

JSをプッシュすべきか

これは場合によると思っています。JSがないとページの表示に致命的な不具合がある場合、サーバープッシュしないと意味がありません。

一方、JS がページのインターフェイスの向上のために使わていて、とりあえずの表示に関係がない場合、JS をプッシュした分、ファーストペイントが遅れます。

そういうわけでなので、JS をプッシュすべきかどうかは場合によるので簡単に決められない気がしています。

理想のサーバープッシュ

理想のサーバープッシュを考えるにあたって、ロードされるリソースの分類をしてみます。

クリティカルリソース (ブロッキングアセッツ)

ファーストペイントのために必要なリソース

  • CSS
  • ブロックする JS (async/defer のない script 要素)
  • HTML

DOMContentLoaded リソース

DOMContentLoaded までに必要なリソース

  • defer された script

onload リソース

onload までに必要なリソース

  • 画像
  • async された script

どうプッシュするか

最終的に必ずロードされるリソースなら、プッシュしてしまっていいはずです (初回ロードの場合)。

  1. クリティカルリソース
  2. DOMContentLoaded リソース
  3. onload リソース

の順に全てプッシュするのが理想そうです。ただ、現時点で任意の順番に優先順位を明確に決めて送信することはできないような気がしてます。

  1. トップ
  2. tech
  3. HTTP2 で何をサーバープッシュすべきか

たとえ明かなバグ修正、すなわちマージされる公算が大きくても、些細なことでケチがついたりする。これがさらに機能追加みたいな「マージしてもしなくても本流には関係ないね」みたいなのは、マージされる公算がさらに低くてさらに気が重い。

まずプルリクエストを送るケースってのは、別にプルリクエストを送りたくてやってるわけではなく、そのプルリクエストに含まれるコードが自分に必要だからやってるに過ぎない。つまり最悪自分のレポジトリに置いておけばいいのだが、本流に取り込まれていれば今後のバージョンアップで機能が壊れることが減る (ついでに他に困ってる人がいたら助かるかもしれないね)。そういう保守的なモチベーションで動いていることであって、元気良くプルリクを送っているわけではない。

そういうわけで、大抵の場合プルリクエストを投げた時点で「XX だ! とか言われてDISられそうだ」とか「コードスタイルがあってない!!とか言われてリジェクトされないか」とか「オレのところだとテストが通らん!とか言われないか」とか気が滅入る妄想に支配され、燃えつきており、あとはもう勝手にしてくれ (コミュニケーションはしたくないぞ) という気分になっている。


最近良くあるのが、プルリクエスト送る前にコミュニケーションしろ!みたいなルールを強いているプロジェクトで、こういうのはほんとどうしようもない。死ぬほど困っているとか、仕事でやるとかじゃないならプルリク送る気がしない。コミュニケーションしたくないからコード書いてプルリクしてんのに、コミュニケーションを強要してくるのはどういうことなのか。かつて github に感じた居心地の良さはここにはない。

  1. トップ
  2. tech
  3. プルリクエストを送るときは大抵気が重い。

サボさんいい

「ワンワンパッコロ!キャラともワールド」でワンワンとサボさんが共演する夢の回があって、サボさんがダンソン(バンビーノ)をやっていた。この番組、全体的に子供には難しいネタが多い。改めて見るとお笑いって常識(お約束)を裏切る形のパターンがよくあって、そもそも常識のない子供はいつから笑えるようになるのだろうと思った。

ずっと中途半端なデザインだなと思っていたので、改めて全体を見直しました。

大きい画像は devicePixelRatio に基いて大きい画像をロードするようなスクリプトを書いていたのですが、面倒なので最近になって常に 2048px の画像を読みこむように変えました。

しかし画面の表示は 960px 程度を最大にしており、Retina でも若干の無駄があるのが気になってきました。ということで、まず 1024px を最大幅にできるようにしました。

しかし、意外ともう少し狭い幅で見ることも多い気がするので (特に開発ツールを横に開いていると結構画面が狭い)、その場合幅だけ変えたバージョンをメディアクエリでだしわけしています。

スマートフォン向けにはさらに狭い幅向けのバージョンをメディアクエリでだしわけていますが、これはほぼ今まで通りです。

また、大きい画面の場合、photo タグが設定されているエントリと、それ以外のエントリで画像の幅を変えるようにしました。tech カテゴリでも無駄に横幅が大きい画像になっていて一画面の情報量が少なかったので、これで見易くなった気がします。

しかし、テキストの幅を制限しつつ画像は大きく表示したいと思うと、自分のデザイン能力だと綺麗にいかず難しく感じます。

  1. トップ
  2. tech
  3. デザインの調整

STM32F103 C8 T6 の安いボードが ebay で売っているので買ってみました。水晶が実装済み基板が 300円程度。届いたボードに実装済みの外部水晶は8MHz (内蔵RCと周波数は一緒) のようです。また 32.768k の水晶もついています。他に実装されているのはLDOやUSBコネクタぐらいです。

なお USB デバイスを作りたい場合 RC オシレータだとクロック精度が足りないので外部水晶は必須です。

買ったのはこれです: http://www.ebay.com/itm/201529768817

型番について

STM32F103 C8 T6 という型番の読みかた

  • C: 48pins
  • 8: 64kbytes Flash
  • T: LQFP パッケージ
  • 6: -40〜85°C

をあらわしています。STM32F103x8 (64k) や STM32F103xB (128k) はスペックシート共通になっています。 (ピン数とFlashのサイズの違いのみなので)

特徴

以下個人的に良さそうと思った点です。

  • STM32F103 は USB 1.1 FS (フルスピード=12Mbps) に対応している
  • ADC が2つある (チャンネル数は10)
    • 同時サンプリングができそう

Cortex-M3 なので M0 よりはパフォーマンスが良さそうです。一方 FPU や DSP 関係の命令 (SIMDとか) は M0 同様ありません。

クロック

SYSCLK (システムクロック) は

  • HSI
    • High Speed Internal ?
  • HSE
    • High Speed External ?
  • PLL

の3種類のクロックソースがあります。

HSE はさらに HSE バイパスと HSE クリスタルとに別れています。バイパスは発振器のクロックを直接入れるモードです。

PLL のクロックソースにはHSIとHSEがどちらも使えるようになっています。

このボードの場合

8MHz の外部水晶がついてるので、8MHz の HSE を PLL のクロックソースとして 72MHz にします。USB 用のクロックもここから作ることになっています。

  • PLL 入力クロックソースは HSE、分周なし
  • PLL逓倍を9倍に (8 * 9 = 72MHz)
  • USBプリスケーラは1.5に (72MHz / 1.5 = 48MHz)
  • APB1 は2分周 (72MHz / 2 = 36MHz)

みたいになりそうです。が、とりあえず mbed 環境で動かしてみるためクロック設定は無視します (mbed 側で 72MHz に適当に設定されます)

書きこみ (Lチカ)

例によって環境を整える部分は platformio でやります。

platformio.ini は以下のようにします。nucleo_f103rb は型番違いですが、ほぼほぼ互換性があります。nucleo_f103rb の外部水晶も 8MHz のようで、この場合 mbed の初期化コードは一切変更なしでいけそうです。

本来 boards の追加をもっと簡単にできたらいいのですが、現状の platfromio だとそういうことはできなそうです。

# platformio.ini
# nucleo_f103rb は f103c8t6 とフラッシュサイズとピン数以外は互換
# 外部クロックも 8MHz で同じのためそのまま使える
[env:stm32f103c8t6]
platform = ststm32
framework = mbed
board = nucleo_f103rb

Lチカはこのようにしました

#include "mbed.h"


DigitalOut led(PC_13);
// Serial serial(USBTX, USBRX);


int main() {
	for (;;) {
		led = 1;
		wait(0.5);
		led = 0;
		wait(0.5);
	}
}

PC_13 はボード上のLEDに繋がっています。nucleo_f103rb だと LED1 は PA_5 のようなので、ジェネリック名ではなくピン名で直接指定しています。

書きこみはこれまた ebay で購入した st-link2 を使いました (約400円)。Mac の USB ポートに繋ぐと特に何もしなくても認識するようでした。

ボードと st-link を接続して、USB 接続すると、基板上にも電源供給されます (別途電源供給はいらないようです)

書きこむためまず st-util を起動します。platformio で ststm32 環境をセットアップしておくと st-util もインストール済みのため楽です。

$ ~/.platformio/packages/tool-stlink/st-util
2016-04-14T00:48:41 INFO src/stlink-common.c: Loading device parameters....
2016-04-14T00:48:41 INFO src/stlink-common.c: Device connected is: F1 Medium-density device, id 0x20036410
2016-04-14T00:48:41 INFO src/stlink-common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2016-04-14T00:48:41 INFO gdbserver/gdb-server.c: Chip ID is 00000410, Core ID is  1ba01477.
2016-04-14T00:48:41 INFO gdbserver/gdb-server.c: Target voltage is 3254 mV.
2016-04-14T00:48:41 INFO gdbserver/gdb-server.c: Listening at *:4242...

こんな感じで gdb server として立ちあがります。

別のターミナルから gdb server に接続してバイナリを送りこみます。

$  ~/.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-gdb .pioenvs/stm32f103c8t6/firmware.elf 
> target extended-remote localhost:4242
> load
> cont

これでLチカできました。

ref

  1. トップ
  2. tech
  3. STM32F103 C8 T6 の安いボードでLチカ (platformio + mbed)

あいかわらず断続的に体調不良。一日会社を休んで寝てたら回復した気がしたが、また腹痛になった。吐き気はしないのでまだマシ。

ここ最近、夜中に子供がうんちしていることがあって、いざ寝ようと思うと寝室がめっちゃ臭い。子供もおそらく胃腸炎を完治してない気がする。食欲はかなりあるみたいだけど、本調子ではないのだろう。


基本的にストレスが多いのだけど、ストレスが多くなると些細なことにイライラしてさらにストレスを増やしてしまう。一方で捌け口が自分の場合ほとんどないので、どうしようもない。

直近で気が重かった仕事が一旦出て、それは良かったのだけど、出たは出たで次は何をするのかと考えると気が重いので、無限に気が重い。


「スケジュール」について考えると気がとても重くなるのだけど、実際に全体のスケジュールを決めてる人は不思議と前工程のスケジュールをさっぱり守らないので、自分が気を揉むようなことではないはずだと思う。一方スケジュールのことを無視して言われたベースでやってると「やってないんですか」みたいなことになって鬱陶しいし、なんかとにかく何をどうしても嫌な気持ちになるしかない感じがする。


保育園のナニカみたいなのが一年たってだいたい終わったんだけど、自分の役職だけまだ仕事があって、とてもだるい。なんか数秒喋るだけのために1時間か2時間拘束されるハメになる。これが終われば終わりだから我慢しよう。それにしても保育園関係のナニカは本当に不愉快だった…… これは本来「無償ボランティア」の範疇にあることだが、それは名ばかりで強制力がある。責任()というやつです。直接子供のためになるならまだしも、クソどうでもいいこと (進級時に先生にお礼をしましょう、みたいなの。個人的には心底どうでもいい) が実際殆どであった。「無償ボランティア」なうえ特にモチベーションもないので、必然的に優先順位は最下位なのだが、やたらごちゃごちゃ言われて本当に辛かった。得るものがほぼないのに時間がとられる、死にたくなるほど割に合わない。保育園のナニカと比べれば普段の仕事は大変良くて、周りの人は会話が通じるし、効率的なやりかたをしようという前提が共有されている。

JSなしのソーシャルボタンというのを作ってみました。このサイトの各エントリ下部に実装されているものです。

動機

各サービス、JS を読みこんでボタンを表示するタイプのものをメインに提供していますが、ソーシャルサービスへのシェアという機能で外部リソースの読み込みとJSの実行が発生するのは、提供される機能に対して割に合わないのではないかと思っていました。

実際ウェブサイトのパフォーマンスチューニングをしていると、細かいボタンのJSのダウンロード・パース・実行・表示後のリフローが結構多くて気になります。

実装

サービス

  • Facebook
  • Twitter
  • Google+
  • LINE
  • はてなブックマーク

HTML+CSS+各サービスのアイコン画像(5つ)です。

各サービスのアイコンをSVGにしたかったのですが、各サービスのブランドガイドラインを読んでいると面倒になったのでオフィシャルなものを使っています。オフィシャルにSVG版が提供されていれば悩まないんですが、はてなブックマークしか提供していないようでした。

使っている画像はオフィシャルのものですが、さらに optipng や svgo をかけてあります (一部の画像にしか効きませんでしたが)。

LINE it! はスマフォかつアプリが入ってないと機能しません。JS版のボタンはスマフォの場合だけ出すような判定も入っているようです。テストページでは出しわけをしていませんが、このサイト内では画面サイズが小さいときだけ出るようにしています。

  1. トップ
  2. tech
  3. JavaScript の必要ないソーシャルボタン

国産原料、国内生産のグリシン1kg 【3gぴったり計量スプーン付き】 -

4.0 / 5.0

毎日寝る前に飲んでみてる。そんな期待してなかったけど、寝起きは確かに多少良くなった。元々ものすごく寝起きが悪いので少しマシになった程度だと思う。

ただし、短い睡眠時間を補うようなものではないので、睡眠時間が短いとあまり変わらずただただ眠い。どうしようもない。

https://buffer.com

buffer というのをちょっと使ってみようとしています。予め Facebook / Twtter などを接続して、スケジュールを設定しておくと、buffer 経由で投稿したときにいい感じにマルチポストするというツールです。

スケジュールの設定がおもしろくて「このサービスはこの時間帯に投稿すると効果がたかいぞ!」みたいなことをサジェストしてくれて、それを時刻に設定できます。

buffer への投稿自体は chrome 拡張経由で手動でやってみています。

使い続けるかはよくわかりませんが、現在 Twitter とか Facebook を見ないようにしているので、buffer のような別サービスを挟んでおけば、うっかり見ることも減って良さそうです。

  1. トップ
  2. tech
  3. ソーシャルサービスに一括でして予約投稿してくれる buffer

そういえば なんとなく思いたったので Twitter 使うのをやめてみ… | Mon, Feb 15. 2016 - 氾濫原 というエントリを書いてから2ヶ月ぐらい経ってました。意外と見ないと決めてしまえば見ないものです。

最初の一ヶ月ぐらい twitter.com, www.facebook.com, b.hatena.ne.jp を /etc/hosts を使ってブロックしていましたが、twitter.com と www.facebook.com はそんなことしなくても案外見ないのでブロックするのをやめました。b.hatena.ne.jp はうっかり見たい欲求にかられることがあるので、PCによってはブロックしたままにしてあります。

あたり前ですが Twitter や Facebook 由来のストレス(けまらしい感・バカにされている感)と時間の浪費はなくなったので、その点はいい感じです。代わりにそれらよりは多少生産的なこと (日記とか) に時間を消費している気がします。


精神の安定のため、内在的かつアウトプットを増やそうというのが自然にできるようになるといいなと思っています。

  • モチベーションは内在し、無限に沸いてくる
    • 必要なのは沸いてくるものを守ることだけ
  • 幸福感は内在する
  • 「アウトプットした」という事実が幸福感を生むはずだ
  • 昨日より少しだけでも進歩することを繰替えすことが、のちに振り替えったときに幸福感を生むはずだ

誰かに褒められれば嬉しいのは確かだけれども、それは直接次のモチベーションには繋がらないし、「誰かに褒められる」ことよりも「誰かに貶される」という期待値のほうが圧倒的に高いので、そういう場は精神を不安定にするだけで割に合わないという判断にまとめています。

あんまりスター付かないので気付いてなかったのですが、Chrome 拡張の「はてなのお知らせ」とかに通知がこなくなっていることに気付きました。

おそらく「HTTPS にしたこと」というより、http: から https: に URL が代わったことにより Hatena.Star.Token の更新が必要なんだと思います。が、s.hatena.ne.jp/cho45/blogs にログイン状態でアクセスすると現状タイムアウトしてしまうので、詰んでいます。

HatenaStar.js を読んでて気付いたのですが、

Hatena.Star.Token = null;

がベタに書いてあるため、async と併用するとそもそも Token が初期化されてしまうようでした……

HTTPS とか関係なかったです。HTTP ではてなスターに登録していても、リダイレクトしているなら、リダイレクト先の Token を読んで判断するようです。

しかしHTTPSにしたことによって過去のスターが消えてそうなことに気付いたので、どうしようか考えています。

結局にっちもさっちも行かないことがわかったので、HatenaStar.js のコピーを編集して使うようにしました。

はてなスターに渡すURLは http: に戻しました。HTTPS になってからついたスターが表示されなくなってしまいますが (申し分けないのですが)、HTTP のときについたスターは復活するはずです……

まぁそもそも、そろそろはてなスター止めてもいいかもしれないんですが、もうちょっと頑張ってみようという感じです。

--- HatenaStar.orig.js	2016-04-15 23:11:20.355944766 +0900
+++ HatenaStar.js	2016-04-15 23:11:34.687944255 +0900
@@ -4655,7 +4655,39 @@
 
 
 /* start */
-new Hatena.Star.WindowObserver();
+// new Hatena.Star.WindowObserver();
+
+Hatena.Star.Token = '7743b0e60f0e3b267f9723d3a5cf96981a59e4f3';
+Hatena.Star.EntryLoader.loadEntries = function (node) {
+    console.log('custom EntryLoader');
+    var entries = [];
+    var entryNodes = node.getElementsByTagName('article');
+    for (var i = 0, entryNode; (entryNode = entryNodes[i]); i++) {
+        var uri = entryNode.querySelector('a.bookmark').href || '';
+        var title = entryNode.querySelector('h1').innerText;
+        var container = entryNode.querySelector('.social .hatena-star');
+
+        var sc = Hatena.Star.EntryLoader.createStarContainer();
+        container.appendChild(sc);
+        var cc = Hatena.Star.EntryLoader.createCommentContainer();
+        container.appendChild(cc);
+
+        entries.push({
+            uri: uri.replace(/^https:/, 'http:'),
+            title: title,
+            star_container: sc,
+            comment_container: cc
+        });
+    }
+
+    console.log('custom EntryLoader loaded', entries);
+
+    return entries;
+};
+
+window.addEventListener('load', function () {
+    new Hatena.Star.EntryLoader();
+});
 
 /* Hatena.Star.SiteConfig */
 /* sample configuration for Hatena Diary */

[tech] JavaScript の必要ないソーシャルボタン | Fri, Apr 15. 2016 - 氾濫原 これを作るとき、最初のうちは全てSVGにするぞと意気込んでいて、Ligature Symbols に含まれるものをSVGに変換したらいいのではないかと、いろいろ試していました。

結局その方法はやめたのですが、SVG フォントから、個別の SVG ファイルに変換するスクリプトを雑に書いたので残しておきます。SVG フォント全体だとファイルサイズが大きすぎるので、必要なファイルだけ普通の SVG 画像として抽出するということです。

以下のように perl + XML::LibXML で書きました。グリフ名を引数に与えると、該当するグリフを個別の .svg に書き出します。LigatureSymbols でしか試していませんが、SVG フォントなら他のでもいけるかもしれません。

#!/usr/bin/env perl

use utf8;
use strict;
use warnings;
use v5.10.0;
use lib lib => glob 'modules/*/lib';

use XML::LibXML;

open(my $fh, "<", "LigatureSymbols-2.11.svg") or die "cannot open < input.txt: $!";
my $font = do { local $/; scalar <$fh> };
close $fh;

my $doc = XML::LibXML->load_xml( string => $font, load_ext_dtd => 0 );
my $xpc = XML::LibXML::XPathContext->new($doc);

# get copyright metadata
my $original_metadata = $xpc->findvalue('/svg/metadata');
my $units_per_em = $xpc->findvalue('/svg/defs/font/font-face/@units-per-em');
my $ascent = $xpc->findvalue('/svg/defs/font/font-face/@ascent');
my $bbox = $xpc->findvalue('/svg/defs/font/font-face/@bbox');

for my $glyph_name (@ARGV) {
	my $glyph = $xpc->findnodes(sprintf('/svg/defs/font/glyph[@glyph-name="%s"]', $glyph_name))->[0];
	my $horiz_adv_x = $xpc->findvalue('./@horiz-adv-x', $glyph);

	my $document = XML::LibXML::Document->new('1.0', 'UTF-8');
	my $svg = $document->createElement('svg');
	$svg->setAttribute('width',  $horiz_adv_x);
	$svg->setAttribute('height', $units_per_em);
	# $svg->setAttribute('viewBox', $bbox);
	$svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
	$document->setDocumentElement($svg);

	my $metadata = $document->createElement('metadata');
	$metadata->appendChild($document->createTextNode($original_metadata));
	$svg->appendChild($metadata);

	my $path = $document->createElement('path');
	$path->setAttribute('transform', sprintf("scale(1, -1) translate(0,%s)", -$ascent));
	$path->setAttribute('fill', '#fff');
	$path->setAttribute('d', $xpc->findvalue('./@d', $glyph));
	$svg->appendChild($path);

	warn "write to $glyph_name.svg";
	say $document->toString(1) ;

	open(my $fh, ">", "$glyph_name.svg");
	print $fh $document->toString;
	close $fh;
}
  1. トップ
  2. tech
  3. SVGフォントのグリフを個別のSVG画像に変換する

すこし昔書いたGoogle Keepのメモを発掘してきました。

NASの維持コスト

電気料金 を30円/kWh、NAS の消費電力を 20W で計算すると、446円/月。24時間動かすので思いのほか電気料金がかかっていることになり、初期コストも含めて考えると、オンラインサービスと案外競合する。

NAS の特徴は

  • 良:データが手元にある安心感
  • 良:LANで完結するので速度が早い
  • 悪:災害によってデータ消失が起こる
  • 悪:自分でメンテする必要があり、故障が起こると思いがけずコストがかかる

オンラインサービスの維持コスト

オンラインサービスの特徴

  • 良:災害があってもデータが失われない
  • 良:自分でメンテナンスする必要がない
  • 悪:大量のデータの取り出しが難しいことがある
  • 悪:ネットワーク経由でしか入出力できないことがあるので速度が遅い
  • 悪:サービスが終了することがある

Google Drive

エンドユーザ向けサービスだが値下げによりかなり安価になっている。
1TBプランが月額$9.99、30Tまではほぼ$0.01/GBとなる。Nearline とほぼ同じだが、容量を使いきっていなくてもプラン容量分課金される点で異なる。
取り出しは当然無料だが速度の保証はない

Dropbox

エンドユーザ向けのサービス。有料プランは1TBのみで、月額1200円。1TB 以上のプランがないので、それ以上になる予測がたつ場合選択肢にあがらない。

Amazon Clould Drive

エンドユーザ向けサービス。有料プランの最大が1TB、1TBのとき年額40000円。月額換算で3333円とかなり高い。

ただし、プライム会員(3900円/年)の場合、写真データ(RAWも含む)が無制限となっており写真だけ上げるなら異常に安い。しかしこの手の無制限サービスはすぐ終了する予測しかない。

Amazon Glacier

約1円/GB
取り出しコストが大変複雑。月あたり使用容量の5%までを1時間あたりに分散してゆっくり取り出せば (約0.0067%/時) 無料。つまり取り出しソフトウェアがちゃんとしていれば、20ヶ月で常に無料で全量を取り出し可能

Google Cloud Storage Nearline

約1円/GB ($0.01/GB)
取り出しも$0.01/GBと書いてあるが、実際はこれに加えて転送量課金がある。$0.12/GB

なので、1TB ダウンロードしようとすると16000円ぐらいかかる。純粋に従量課金なので Glacier より料金はわかりやすいが、無料の範囲というのは存在しないので、ダウンロードでは必ずこの金額必要になる。

エンドユーザ的には価格で Google Drive に対するメリットがない。

  1. トップ
  2. tech
  3. 大量のファイルのバックアップにNASを使うのは適切なのか?

Firefox だと上記のようにリクエストが消滅してタイミングも全てない表示になるみたいです。

Chrome の Network タブだとプッシュしてもキャッシュからひいてくる時間を表示しているのか区別できなくてモヤっとします。

ref. [tech] lowreal.net のHTTP2/HTTPS 化を実施 | Tue, Apr 5. 2016 - 氾濫原

  1. トップ
  2. tech
  3. Firefox の開発者ツールのほうが HTTP2 でサーバプッシュされたコンテンツがわかりやすい

h2o は mruby ハンドラで link ヘッダを使って push を指示すると、バックエンドへの問合せと非同期で静的ファイルを push してくれます。

もしバックエンドアプリケーションで link ヘッダを吐いて push する場合、バックエンドアプリケーションの処理が終わったあとから push が始まることになるので、アプリケーションの実行時間分、push できる時間を失うことになります。

server-push の指示をテンプレートに書きたい病

自分はプリロード指示をバックエンド側のテンプレートに書きたい病にかかっており、現状で以下のようなテンプレートコードを書いて、バックエンドから preload ヘッダを吐いています。

r.preload() は link ヘッダを追加するメソッドになっており、これを実際に読みこんでいるHTMLの部分の近くに置くことでリソース管理を簡略化しています。

しかし、これだとバックエンドから h2o へ server-push を指示する形になるので、前述のようにアプリケーションの実行時間分、push できる時間を無駄にします。

バックエンドから server-push するリストをあらかじめ取得する

できれば無駄をなくしたいので、やはり h2o の mruby ハンドラでも link ヘッダを吐くことにします。

ただ、二重に設定を書きたくはないので、バックエンドの吐く link ヘッダを、h2o 起動時に取得しておき、以降のリクエストではそれを元に server-push させるようにします。

まずバックエンドのヘッダをファイルに保存しておきます。ここでは適当に curl を使ってます。

curl -s --head -H 'Cache-Control: no-cache' https://lowreal.net > /srv/www/lowreal.net.link.txt

そして、起動時に read して push する分の link ヘッダを作っておき、ハンドラでそれを送るように設定します。

        mruby.handler: |
          LINK = File.read("/srv/www/lowreal.net.link.txt").
            split(/\r?\n/).select{|l| l.sub!(/^link: /, "") and l.match(/rel=preload/) and !l.match(/nopush/) }.
            join("\n")


          $stderr.puts LINK.inspect


          lambda do |env|
            [ 399, { "link" => LINK }, [] ]
         end

運用上は、適当なタイミング(デプロイタイミング)で curl を打って再度ヘッダを保存して h2o を再起動するようにします。

余談ですが curl を使わずとも mruby ハンドラ内で使える http_request() があるので h2o で完結しそうと思いきや、起動中のコンテキストでは使えないみたいです。

効果

mruby ハンドラで server-push を指示しない場合

mruby ハンドラで server-push を指示する場合

見るべきは、各リソースの responseEnd の時間と、 / に対する requestStart の時間の差です。ただ requestStart はいずれも 1ms 程度なので、注目するのは responseEnd だけで良さそうです。

mruby ハンドラで指示しない場合各リソースの responseEnd は100ms以上になっています。これはバックエンドの処理に約100msほどかかっているからです。一方 mruby ハンドラで指示する場合、responseEnd は 22ms ぐらいになっています。

この例だと、静的ファイルの responseEnd から、バックエンドのレスポンス開始までまだ時間があるので、もっと静的ファイルを server-push する余地がありそうです。

('cache-control: no-cache' をつけているのはバックエンド側のキャッシュを無効にして処理時間を作って差をわかりやすくしているだけです)

  1. トップ
  2. tech
  3. h2o での server-push タイミングの最適化

h2o の casper (cache-aware server-push) を有効にしていると、force reload したときでも push されなくなってしまって、だんだん混乱してきます。YAML を一時的に変えて再起動したりしていたのですが、自分以外にも影響が及ぶのでちょっとなんとかしました。

JS で h2o_casper クッキーを削除してからリロードする

最初に思いつく方法で手軽なやつです。以下のようなブックマークレットでリロードすると cookie がない状態からのリロードになります。

javascript:document.cookie="h2o_casper=; max-age=-1; path=/";location.reload(true);

h2o へパッチをあてて、force reload 時は常に h2o_casper を無視してプッシュさせる

force reload 時にブラウザはリクエストヘッダに Cache-Control: no-cache をつけるので (全てのブラウザかどうかわかりませんが)、その場合にはクッキーを無視して push します (set-cookie は吐かれます)

Cache-Control: no-cache とクライアントが宣言しているなら、casper も無効になっているのは正当ではないか?と思い実装しましたが、ほんとにそうか自信がないので、ひとまず自分のところでテストしています (このサーバには適用済み)。

diff --git a/lib/http2/connection.c b/lib/http2/connection.c
index 4395728..bc83829 100644
--- a/lib/http2/connection.c
+++ b/lib/http2/connection.c
@@ -1185,6 +1185,19 @@ static void push_path(h2o_req_t *src_req, const char *abspath, size_t abspath_le
                 src_stream->pull.casper_state = H2O_HTTP2_STREAM_CASPER_DISABLED;
                 return;
             }
+
+            /* disable casper (always push) when super-reloaded (cache-control is exactly matched to no-cache) */
+            if ( (header_index = h2o_find_header(&src_stream->req.headers, H2O_TOKEN_CACHE_CONTROL, -1)) != -1) {
+                h2o_header_t *header = src_stream->req.headers.entries + header_index;
+                if (h2o_lcstris(header->value.base, header->value.len, H2O_STRLIT("no-cache"))) {
+                    /* casper enabled for this request but ignore cookie */
+                    if (conn->casper == NULL)
+                        h2o_http2_conn_init_casper(conn, src_stream->req.hostconf->http2.casper.capacity_bits);
+                    src_stream->pull.casper_state = H2O_HTTP2_STREAM_CASPER_READY;
+                    break;
+                }
+            }
+
             /* casper enabled for this request */
             if (conn->casper == NULL)
                 h2o_http2_conn_init_casper(conn, src_stream->req.hostconf->http2.casper.capacity_bits);

mruby.handler でなんとかできないかと思いましたが、mruby 側の env に渡ってくる HTTP_COOKIE とかを書きかえても h2o 内部の処理には影響しないみたいなので無理そうでした。

  1. トップ
  2. tech
  3. h2o の casper を一時的に無効にする

ほとんどのブラウザで、通常リロードは Cache-Control: max-age=0、スーパーリロードで Cache-Control: no-cache がリクエストヘッダとして送られてくるみたいなのですが、実際の挙動はともかく意味の違いがよくわかりませんでした。

RFC7234 によると max-age は

The "max-age" request directive indicates that the client is unwilling to accept a response whose age is greater than the specified number of seconds. Unless the max-stale request directive is also present, the client is not willing to accept a stale response.

https://tools.ietf.org/html/rfc7234

no-cache は

The "no-cache" request directive indicates that a cache MUST NOT use a stored response to satisfy the request without successful validation on the origin server.

https://tools.ietf.org/html/rfc7234

となっています。読んでも違いがよくわかりません。検索するとWhat's the difference between Cache-Control: max-age=0 and no-cache? あたりがヒットして、古いほうのRFC HTTP/1.1: Caching in HTTPが示されています。

max-age=0 は validate した結果最新とわかったならキャッシュを返すことを許しているが、no-cache はそもそも cache を使うことを許していない、という違いがあるようです。

もっというと、max-age=0 のときは validate して最新なら 304 を返してくれてもいいが、no-cache のときは validate すらせず全て 200 で転送しなおせという意味のようです。

余談:レスポンス時の Cache-Control: no-cache

レスポンス時の Cache-Control: no-cache が語感に反してキャッシュの利用を許可しているというのは良く知られている(?)と思いますが (キャッシュの利用を許可しないのは no-store)、これも max-age=0 との違いがよくわかってませんでした。

これについても上記の stackoverflow でわかりやすい解説があって、Cache-Control: no-cache は実質 Cache-Control: max-age=0, must-revalidate 相当だろうとのことでした。単純な max-age=0 はクライアント側が validate できなければ期限切れのキャッシュ利用を許しているという違いがあるようです。

  1. トップ
  2. tech
  3. リクエスト時の Cache-Control、max-age=0 と no-cache の違い

Hardware IO Tools for Xcode をダウンロードすると Network Link Conditioner という環境設定が含まれていて、インストールするとそこそこ細かくネットワーク環境を再現できます。Xcode → Open Developer Tools → More Developer Tools… とたどるといろんなツールを検索できるページにいくので、そこからダウンロードしてこれます。

プリセットだと LTE っぽいプロファイルがないので、適当にカスタムプロファイルを作ってみました。Downlink/Uplink で別々に設定できることから、Delay はおそらく片道っぽいので、RTT / 2 を設定します。Bandwidth は各社の実効速度データのうち、下限付近を参考に設定しました。

当然ながらテスト環境の回線品質が良くないとテストにならないので、平均とか25%値でテストしようと思うなら無線LANでテストしてたらダメそうです。

また、システム全体に効いてしまうので、他のアプリケーションで通信が発生しまくっているとかなり影響してきます。

throttling しつつ nghttp

なお Chrome の場合は開発者ツールの Network タブで速度と Latency を設定して throttling できます。

ref.

  1. トップ
  2. tech
  3. OS X でのネットワークの throttling を簡単に設定する

そろそろやることなくなったので minify などをやることにしました。

ただ、ブログシステムの出力の最後ほうでページごとに全体を minify すると、全体としてどうしても処理に時間がかかってしまいます。要求として、キャッシュなしの状態からでも1エントリあたり0.1秒ぐらいでは処理したいので、これだと厳しい感じでした。(約7900エントリぐらいあるので、0.1s で処理しても全体のキャッシュ再構築に13分かかる計算です)

いろいろ考えたのですが (そもそも minify しないとかも)、以下のようにしました

  • エントリ本文はエントリ保存時に minify しておく (本文はDB に格納)
  • テンプレートをテンプレートの段階で minify してキャッシュしておく

minify には html-minifier を使っています。html-minifier はテンプレートを対象にした minify も一応サポートしていて、ある程度妥協すればテンプレート対象でも十分に minify できます。

テンプレートとエントリが前もってをminifyされていれば、あとは繋げて出すだけです。

テンプレートの minify

このブログシステムのテンプレートは Text::Xslate::Syntax::TTerse です。

まず、Xslate のビュー読みこみをフックして、テンプレートを動的に minify するようにしました。

{
	no warnings 'redefine';
	*Text::Xslate::slurp_template = sub {
		my ($self, $input_layer, $fullpath) = @_;
		my $source = sub {
			if (ref $fullpath eq 'SCALAR') {
				return $$fullpath;
			} else {
				open my($source), '<' . $input_layer, $fullpath
					or $self->_error("LoadError: Cannot open $fullpath for reading: $!");
				local $/;
				return scalar <$source>;
			}
		}->();
		if ($fullpath =~ /\.html$/) {
			$source = Nogag::Utils->minify($source);
			return $source;
		} else {
			return $source;
		}
	};
	$XSLATE->load_file($_) for qw{
		index.html
		_article.html
		_adsense.html
		_images.html
	};
};

Xslate には pre_process_handler というのがあって、 読みこまれたテンプレートにフィルタをかけることができます。が、この機能だとファイル名がわからないので使っておらず、slurp_template を上書きしています。

(明示的に load_file しているのは、エラーが起きるなら起動時にしたいというのと、fork 前にロードすることでメモリの節約になるからで、本題とは関係ありません)

TTerse なテンプレートに対して html-minifier を使う場合、配慮する点がいくつかあります。

全体的にHTMLとしてパースできること

TTerse の場合以下のようにも書けますが、ダブルクオートの入れ子が HTML として見ると syntax error なので html-minifier で Parse Error になります。

<meta content="[% "foo" _ "bar" %]">

属性を出しわけるとき個別に条件をつける

テンプレートの構文をタグに混ぜるには html-minifier 側にオプションを設定します (customAttrSurround)。

ただ、複数属性を囲うとうまく属性を分解できなくて死ぬっぽいので、個別に囲う必要がありました。

<!-- ダメ -->
<article
   [% IF xxx %]
   itemscope
   itemprop="blogPosts"
   [% END %]
   >
<!-- よろしい -->
<article
   [% IF xxx %]itemscope[% END %]
   [% IF xxx %]itemprop="blogPosts"[% END %]
   >

sortClassName は false にする

クラスをテンプレートで出しわけしていると死にます

実際の html-minifier へのオプション

TTerse 対応のためオプションは以下のようになりました。customAttrSurround が適切に設定されていれば、sortAttributes は true にしても問題ないようです。

function processMinify (html) {
	return Promise.resolve(minify(html, {
		html5: true,
		customAttrSurround: [
			[/\[%\s*(?:IF|UNLESS)\s+.+?\s*%\]/, /\[%\s*END\s*%\]/]
		],
		decodeEntities: true,
		collapseBooleanAttributes: true,
		collapseInlineTagWhitespace: true,
		collapseWhitespace: true,
		conservativeCollapse: true,
		preserveLineBreaks: false,
		minifyCSS: true,
		minifyJS: true,
		removeAttributeQuotes: true,
		removeOptionalTags: true,
		removeRedundantAttributes: true,
		removeScriptTypeAttributes: true,
		removeStyleLinkTypeAttributes: true,
		processConditionalComments: true,
		removeComments: true,
		sortAttributes: true,
		sortClassName: false,
		useShortDoctype: true
	}));
}

エントリ本文のポストプロセス

エントリ保存時には、minify もそうですが、ほかにも修正を加えたいことが多々あります。例えば画像を強制的に https 化して mixed content を防ぎたいとか、コードハイライトを行いたいとかです。

コードハイライトは今まで highlight.js を使い、クライアントサイドでやっていました。これも本来ダイナミックにやる必要はなくポストプロセスでできることなので、そのように変えることにしました。highlight.js をサーバサイドで行うと、付与されるマークアップ分 HTML の転送量が増えますが、highlight.js 自体が結構大きいので、かなり長いコードをハイライトしない限り、サーバサイドでやったほうが得そうです。

以前サーバサイドでMathJaxを処理するようにしましたが、この node.js の内部向けサーバをさらに拡張して、JS でいろいろなポストプロセスの処理を書けるようにしました。

これにより、クライアントサイドでやってたことをそのままサーバサイドできるようになりました。すなわち、hightlight.js をそのままサーバサイドでも動かしていますし、jsdom を使って細かいHTMLの書きかえを querySelector など標準の DOM 操作でできるようにしています。

ポストプロセス用のデーモン

前述のように node.js で動くデーモンで、ブログシステム(Perl)とは別のプロセスで動き、APIサーバになっています。

全体的には以下のようなコードです。なお書き換え部分は変な挙動をするとやっかいなので、テストを書けるようにしてあります

#!/usr/bin/env node


const jsdom = require("jsdom").jsdom;
const mjAPI = require("mathjax-node/lib/mj-page.js");
const hljs = require('highlight.js');


const minify = require('html-minifier').minify;
const http = require('http');
const https = require('https');
const url = require('url');
const vm = require('vm');


const HTTPS = {
	GET : function (url) {
		var body = '';
		return new Promise( (resolve, reject) => {
			https.get(
				url,
				(res) => {
					res.on('data', function (chunk) {
						body += chunk;
					});
					res.on('end', function() {
						res.body = body;
						resolve(res);
					})
				}
			).on('error', reject);
		});
	}
};


mjAPI.start();
mjAPI.config({
	tex2jax: {
		inlineMath: [["\\(","\\)"]],
		displayMath: [ ["$$", "$$"] ]
	},
	extensions: ["tex2jax.js"]
});


function processWithString (html) {
	console.log('processWithString');
	return Promise.resolve(html).
		then(processMathJax).
		then(processMinify);
}


function processWithDOM (html) {
	console.log('processWithDOM');
	var document = jsdom(undefined, {
		features: {
			FetchExternalResources: false,
			ProcessExternalResources: false,
			SkipExternalResources: /./
		}
	});
	document.body.innerHTML = html;
	var dom = document.body;
	return Promise.resolve(dom).
		then(processHighlight).
		then(processImages).
		then(processWidgets).
		then( (dom) => dom.innerHTML );
}




function processHighlight (node) {
	console.log('processHighlight');
	var codes = node.querySelectorAll('pre.code');
	for (var i = 0, it; (it = codes[i]); i++) {
		if (/lang-(\S+)/.test(it.className)) {
			console.log('highlightBlock', it);
			hljs.highlightBlock(it);
		}
	}
	return Promise.resolve(node);
}


function processImages (node) {
	console.log('processImages');
	{
		var imgs = node.querySelectorAll('img[src*="googleusercontent"], img[src*="ggpht"]');
		for (var i = 0, img; (img = imgs[i]); i++) {
			img.src = img.src.
				replace(/^http:/, 'https:').
				replace(/\/s\d+\//g, '/s2048/');
		}
	}
	{
		var imgs = node.querySelectorAll('img[src*="cdn-ak.f.st-hatena.com"]');
		for (var i = 0, img; (img = imgs[i]); i++) {
			img.src = img.src.
				replace(/^http:/, 'https:');
		}
	}
	{
		var imgs = node.querySelectorAll('img[src*="ecx.images-amazon.com"]');
		for (var i = 0, img; (img = imgs[i]); i++) {
			img.src = img.src.
				replace(/^http:\/\/ecx\.images-amazon\.com/, 'https://images-na.ssl-images-amazon.com');
		}
	}
	return Promise.resolve(node);
}


function processWidgets (node) {
	var promises = [];


	console.log('processWidgets');
	var iframes = node.querySelectorAll('iframe[src*="www.youtube.com"]');
	for (var i = 0, iframe; (iframe = iframes[i]); i++) {
		iframe.src = iframe.src.replace(/^http:/, 'https:');
	}


	var scripts = node.getElementsByTagName('script');
	for (var i = 0, it; (it = scripts[i]); i++) (function (it) {
		if (!it.src) return;
		if (it.src.match(new RegExp('https://gist.github.com/[^.]+?.js'))) {
			var promise = HTTPS.GET(it.src).
				then( (res) => {
					var written = '';
					vm.runInNewContext(res.body, {
						document : {
							write : function (str) {
								written += str;
							}
						}
					});
					var div = node.ownerDocument.createElement('div');
					div.innerHTML = written;
					div.className = 'gist-github-com-js';
					it.parentNode.replaceChild(div, it);
				}).
				catch( (e) => {
					console.log(e);
				});


			promises.push(promise);
		}
	})(it);


	return Promise.all(promises).then( () => {
		return node;
	});
}


function processMathJax (html) {
	console.log('processMathJax');
	if (!html.match(/\\\(|\$\$/)) {
		return Promise.resolve(html);
	}
	return new Promise( (resolve, reject) => {
		mjAPI.typeset({
			html: html,
			renderer: "SVG",
			inputs: ["TeX"],
			ex: 6,
			width: 40
		}, function (result) {
			console.log('typeset done');
			console.log(result);


			resolve(result.html);
		});
	});
}


function processMinify (html) {
	return Promise.resolve(minify(html, {
		html5: true,
		customAttrSurround: [
			[/\[%\s*(?:IF|UNLESS)\s+.+?\s*%\]/, /\[%\s*END\s*%\]/]
		],
		decodeEntities: true,
		collapseBooleanAttributes: true,
		collapseInlineTagWhitespace: true,
		collapseWhitespace: true,
		conservativeCollapse: true,
		preserveLineBreaks: false,
		minifyCSS: true,
		minifyJS: true,
		removeAttributeQuotes: true,
		removeOptionalTags: true,
		removeRedundantAttributes: true,
		removeScriptTypeAttributes: true,
		removeStyleLinkTypeAttributes: true,
		processConditionalComments: true,
		removeComments: true,
		sortAttributes: true,
		sortClassName: false,
		useShortDoctype: true
	}));
}
const port = process.env['PORT'] || 13370


http.createServer(function (req, res) {
	var html = '';
	var location = url.parse(req.url, true);
	req.on('readable', function () {
		var chunk = req.read();
		console.log('readable');
		if (chunk) html += chunk.toString('utf8');
	});
	req.on('end', function() {
		console.log('end');


		if (location.query.minifyOnly) {
			Promise.resolve(html).
				then(processMinify).
				then( (html) => {
					console.log('done');
					res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
					res.end(html);
				}).
				catch( (e) => {
					console.log(e);
					console.log(e.stack);
					res.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8'});
					res.end(html);
				});
		} else {
			Promise.resolve(html).
				then(processWithDOM).
				then(processWithString).
				then( (html) => {
					console.log('done');
					res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
					res.end(html);
				}).
				catch( (e) => {
					console.log(e);
					console.log(e.stack);
					res.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8'});
					res.end(html);
				});
		}
	});
}).listen(port, '127.0.0.1');
console.log('Server running at http://127.0.0.1:' + port);

デーモン利用側

利用側は単純に http リクエストを送っているだけです。ときどきパースエラーで失敗したりするので、そういう場合は元々の HTML をそのまま使うようにしています。

運用

  1. エントリ保存時にはてな記法→HTMLと上記のような処理をしてDBに保存する(キャッシュ)
  2. レンダリング結果をページキャッシュ

という2段階のキャッシュがあります。テンプレートを変更しただけの場合、レンダリング結果のキャッシュを作りなおします。これは冒頭の通り約13分程度かかります。このページ単位のキャッシュはエントリを保存すると関連するキャッシュが自動的に無効になるようになっています。この場合、次回アクセス時に再生成になります。

記法フォーマッターのコード変更や、ポストプロセス処理に変更を入れた場合には、エントリのリフォーマットが必要です。これは全てやると約10分ぐらいかかります。ただ一部エントリだけにリフォーマットをかけるようなこともできるようにしています。

  1. トップ
  2. tech
  3. ブログシステムの HTML 生成を効率化

Default と Dark テーマの違い。お分かりいただけるだろうか……?

よく見ると Default のほうは、リソースの Timeline に DOMContentLoaded の線(青い線)がない。

今まで、なぜ表示されないんだろう? と疑問だったけど、Dark にしないと表示されないとは…… (バグじゃないか)

  1. トップ
  2. tech
  3. Chrome の Developer Tools でテーマが Dark のときだけ分かる情報

めっちゃ面白かった。

艦コレみたいなノリのアニメなのだと思って見ていなかったけど、とりあえず1話見てみるかと思った結果そういったものノリのものではないことがわかって、12話まで一気に見てしまった。その後OVA版を見てからもう一周見てしまった。

全体的には弱小高校が部活の全国大会で勝ち上がる王道スポコンみたいなストーリーだった。メインの部分を戦車道という架空の競技に置き換えた感じ。

演出がかっこいいとかいろいろあるけど、結局のところ主人公の徳が高いってのが良かった。

プライムビデオだと、テレビシリーズ12話とOVA1話(これが本当のアンツィオ戦です!)が見れる。OVAはテレビシシーズで飛ばされた試合。劇場版はまだ発売とかされない模様。

やたら人気?だから2期とかあるのかと思ったけど、これが全部みたい。

ガールズ&パンツァー -

5.0 / 5.0

ガールズ&パンツァー これが本当のアンツィオ戦です! -

5.0 / 5.0

ガールズ&パンツァー 劇場版 [Blu-ray] -

5.0 / 5.0

ES2015 の iterable protocol / iterator protocol だとそこそこ自然に無限リストを作れるわけなので、ちょっと試しにやってみました。node v5.2.0 で動かしました。

"use strict";


function* countup(n) {
	for (;;) yield n++;
}


function* map(iterable, func) {
	for (let value of iterable) {
		yield func(value);
	}
}


function* cycle(iterable) {
	for (;;) {
		for (let value of iterable) {
			yield value;
		}
	}
}


function take(iterable, n) {
	const ret = [];
	for (let value of iterable) {
		ret.push(value);
		if (!(ret.length < n)) break;
	}
	return ret;
}


function* zip(iterable) {
	for (;;) {
		const nexts = [];
		for (let it of iterable) {
			nexts.push(it.next());
		}
		yield nexts.map( (i) => i.value);
		if (nexts.some( (i) => i.done) ) break;
	}
}




console.log(
	take(
		map(
			zip([
				countup(1),
				cycle(["", "", "Fizz"]),
				cycle(["", "", "", "", "Buzz"])
			]),
			(_) => _[1] + _[2] || _[0]
		),
		30
	)
);

FizzBuzz の map のところは destructive assignment ができるともうちょい綺麗に書けますが、現時点だとまだオプションが必要なのでキモい書きかたになりました。

気になった点

protocol と言っている通り、next() を適切なインターフェイスで実装しているものは全て iterator なため、イテレータ全般に対して基底クラスみたいなものがありません。これはこれでいいんですが、不便な点があります。イテレータに対してメソッドの追加というのができません。

自分の中のオブジェクト指向の気持ちは以下のように書きたいのです。

[
	countup(1),
	cycle(["", "", "Fizz"]),
	cycle(["", "", "", "", "Buzz"])
].
	zip().
	map( (_) => _[1] + _[2] || _[0] ).
	take(30)

しかし、イテレータの prototype というのは存在しないので、毎回必ず何らかの関数形式のラッパーが必要になってしまいます。

  1. トップ
  2. tech
  3. ES2015 の iterable/iterator/generator による無限 FizzBuzz

お葬式で酒・塩・鰹節

身内(配偶者の祖母)が亡くなったため、かなり久しぶりにお葬式という行事にあった。最中に気になったこととして、出棺前・納骨後に酒・塩・鰹節を使ったお清めがあった。具体的には、手を流水で清めたあと酒と塩と鰹節を口に含む。自分の祖父母は既に亡くなっているが、そういうことをした覚えがなかった。

特にいえば酒・塩はともかく、鰹節を使うイメージが全くなかった。鰹節というと、どちらかといえばおめでたいイメージがあるので、違和感を覚えたのだった。検索するとこの地域独自の文化らしいが、そもそも酒や塩も神饌と考えれば、さらに他にものがあってもおかしくはない気はしてきた。

殆ど子供の様子を書いてないが、育っている。最近の様子を書いておく

日常生活での意思疎通がかなり可能になった

例えば

  • 「パパ、トイレ行ってもいい?」と聞くと「うん」と言ったあとトイレまでついてきてドアを開けてくれる
  • 「鼻でてるよ」と言うとティッシュをとって鼻をゴシゴシやって、ゴミ箱に捨てる
  • うんちで力んでいるとき「ウンチでた?」と聞くと「うん」や「ううん」と言う (あんまりしつこく見てるとバイバイと言う)
  • ウンチ出たあと「じゃあオムツ変えよう」というと袋やオムツで持ってついてくる

理解できてるはずのことを言っても無視することは多々ある。目があってても意図的に無視していることがある。

叱ったとき

叱った直後に即座に「あーんあーん」と泣かないことがある。無表情か、泣くのを我慢しているような表情 (ないしは悔しそうな表情) をする。叱られてることはわかってるようだが、説明がわからないのかもしれない。

最後に「わかった?」「うん」「はいでしょ?」「はい」みたいなのを毎回やってる。これたぶんわかってないけど、これ以上確認する術がないし、やらないと話が終わらないので、どうすればいいかよくわからない。

あと叱るときはかなり真剣に叱らないとダメで、中途半端に叱るとニヤニヤしていて冗談だと思っている節がある。両手握って眼を見て叱るとすくなくとも真剣なのは伝わる模様。

泣くのを我慢しているような表情はあきらかに面白いんだけど、ここで笑うと気持ちが伝わらない(気がする)ので難しい。

言葉

言葉はそれほど話せない。2語出ることもあるが基本的には1語

  • 泣くとすぐに「ママ・だっこ」という (正確には「ママ、ぁっこ〜」みたいな発音)
  • 「ない!」「あった!」「もっかい」が得意
  • 「バイバイ」も良くいう。電車に対して自発的に言うこともある。
  • 一緒に風呂に入ったりすると「チンチン!」とかいう(ほんとに全く教えてないんだけど……)
  • テレビのキャラクターは割とわかってて、見たいものを主張してくる
    • 「ワンワン」「ウータン」「モーモー(メーコブ ※ただしメーコブは羊)」「ニャーニャー (ミーニャ)」「チューチュー (ムテキチ)」「イシュ (コッシーのこと)」「ッチ (ピタゴラスイッチ)」「ダンダンダン (ピタゴラ拳法)」
  • 「らりるれろ」の発音ができない (母音だけになる)

イヤイヤ

もうすぐ2歳なので結構言い出している。「イヤ」ではなくて「あイヤ・あイヤ」という。謎。

  • 「手繋ごう」→「あいや」といって手を後ろに隠す
  • 「爪切ろう」→「あいや」
  • 本を読んであげようとすると「あいや」といって別の本を渡されるが、それを読もうとすると「あいや」と言ってまた別の本になる。謎

食事

あいかわらず食事のマナーは良くない。口に詰め込みまくって吐きだすことがある。詰め込むまえに「口の中にまだ入ってるよ」というと一応気付くみたいで、飲みこんでから口をあけて「ア!」と言ってもう中にないことを主張する。

薬飲むとき毎回ヨーグルトを使っているが、なぜかヨーグルトだけは毎日食べても飽きない(習慣と認識している?)みたいで素直に食べてくれる。

その他

テレビがついていないときに、PS4 のコントローラを探し出して電源をつけていることがある。本人はそれ以上ちゃんと操作ができないので大人にコントローラを押し付けてくる。

大人が大人用の番組 (アニメを含む) を見ているとたいそう不満なようで、ひたすらワンワンワンワンと主張する。

「ないない (片付け)」も本人の気がのってればやってくれる。おもちゃの一部がないときは「これがないよ」というと、多少は探してくれる。

ES2015 の iterable/iterator/generator による無限 FizzBuzz | tech - 氾濫原 に続いて、オブジェクト指向っぽく書けるようにしてみました。

ポイントはジェネレータ的なものをラップして常に It というクラスのインスタンスにするところです。

"use strict";

function It(iterable) { if (typeof this === 'undefined') return new It(iterable); this.iterable = iterable; }
It.countup = function* (n) {
	for (;;) yield n++;
};
It.prototype = {
	zip : function* () {
		const iterators = [];
		for (let it of this) {
			iterators.push(it[Symbol.iterator]());
		}
		for (;;) {
			const nexts = [];
			for (let it of iterators) {
				nexts.push(it.next());
			}
			yield nexts.map( (i) => i.value);
			if (nexts.some( (i) => i.done) ) break;
		}
	},
	map : function* (func) {
		for (let value of this) {
			yield func(value);
		}
	},
	cycle : function* () {
		for (;;) {
			for (let value of this) {
				yield value;
			}
		}
	},
	take : function (n) {
		const ret = [];
		for (let value of this) {
			ret.push(value);
			if (!(ret.length < n)) break;
		}
		return ret;
	}
};
It.prototype[Symbol.iterator] = function () { return this.iterable[Symbol.iterator]() };
{
	let wrapGenerator = function (generator) {
		return function () {
			const self = this;
			const args = arguments;
			const iterable = {};
			iterable[Symbol.iterator] = function () {
				return generator.apply(self, args);
			}
			return new It(iterable);
		}
	};
	let generatorConstructor = Object.getPrototypeOf(function*(){}).constructor;
	for (let key of Object.keys(It.prototype)) {
		if (It.prototype[key] instanceof generatorConstructor) {
			It.prototype[key] = wrapGenerator(It.prototype[key]);
		}
	}
}


console.log(Array.from(
	It([
		It.countup(1),
		It(["", "", "Fizz"]).cycle(),
		It(["", "", "", "", "Buzz"]).cycle()
	]).
		zip().
		map( (_) => _[1] + _[2] || _[0] ).
		take(30)
));
  1. トップ
  2. tech
  3. ES2015 の iterable/iterator/generator による無限 FizzBuzz (オブジェクト指向編)

表題の通りですが、Generator にはいずれの protocol も実装されています。気になるのは iterable の挙動ですが、どうやらレシーバーの Generator 自身を返すようです。

function* count (n) {
	for (;;) yield n++;
}

var c = count(1);
console.log(c.next, c[Symbol.iterator]);
//=> [Function: next] [Function: [Symbol.iterator]]

// iterator protocol
console.log(c.next()); //=> { value: 1, done: false }
console.log(c.next()); //=> { value: 2, done: false }
console.log(c.next()); //=> { value: 3, done: false }

// iterable protocol
console.log(c[Symbol.iterator]().next()); //=> { value: 4, done: false }

console.log(c[Symbol.iterator]() === c); //=> true

iterator protocol (next) で状態をすすめたあとに iterable protocol (Symbol.iterator) で iterator を取得すると、状態は継続されています。

  1. トップ
  2. tech
  3. Generator は iterator であり、iterable でもある

最近になって「関連コンテンツ」と「ページ単位の広告」というのが beta になって登場した。サイト最適化と同時にこれらも有効にしてみたりしていた。

関連コンテンツ

広告ユニットではあるが、サイト内の関連コンテンツを表示してくれるので普通に便利。先頭に2つぐらい広告が入る感じ。便利になるだけで収益にならないユニットなのかと思ったけど、ちゃんと収益にもなる模様。

パフォーマンスレポートを見てみても、他のユニットと比較してかなり収益性が高そう (このサイトの場合そもそも微々たるものだけど)

ページ単位の広告

「ページ単位の広告」という名前の中にさらに2種類ある。

「アンカー広告」はよくあるスマフォ向けの広告で、画面下を常時占領するタイプ。ただ、position: fixed で固定しているようで、JS の scroll イベントを奪っている系のウザさはないのが救い。

「モバイル全面広告」はときどきあるだいぶうざい全面広告に似ているが、ページを開いたときではなくて、次ページに遷移しようとするときに次のページをロードしながら表示される広告、ということになっているらしい。

ページのパフォーマンスと広告

明かなことだけど、Adsense を貼るだけで、おそろしくページのロードは遅くなる。非同期コードを使おうがページ全体の onload までの時間が伸びるのはどうしようもない。

たいして儲かるわけでもない Adsense を貼って遅くするのもどうかとは思うけど、Adsense なくしたらロード早いのは当たり前すぎるので、かえって意固地になって広告を貼りまくっている。遅くなっても儲かってくれるならいいんだけど

  1. トップ
  2. tech
  3. Adsense の新しい広告ユニット

前提知識:レスポンシブ広告といっても、既定の広告サイズからコンテナサイズによって選ばれて表示されるだけで、広告そのもののサイズは固定です。

そのまま使うと、広告がロードされた時にページの高さの再計算が入って、コンテンツがガタガタと動いて大層鬱陶しいです。とりあえず便利そうだから貼ってみると、この挙動でギョギョっとします。

しかし、レスポンシブ広告はCSSで設定する width/height によって正確に対応する広告サイズを入れることができ、この場合は高さが固定になるのでガタガタしません。CSSなのでメディアクエリでサイズを変えられ、レスポンシブサイトと大変相性が良いです。

公式のドキュメントに詳細な設定方法が書いてあります。といっても width/height をコンテナに設定しているだけです。

既にサイトがメディアクエリによってレスポンシブになっているなら、レスポンシブ広告にした場合、広告サイズもCSSで指定するだけでよくなってます。

余談:Adsense の広告コードの修正・改変

「広告コードには一切の変更が認められない」みたいなことが過去に書いてあったような記憶があるんですが (記憶違いかも)、現状では上記の通り、改変が認められるケースがあり、悪意をもってやるようなことじゃなければだいたい大丈夫そうな雰囲気があります。

  1. トップ
  2. tech
  3. Adsense のレスポンシブ広告の正しい使いかた

ティファール 電気ケトル 「ジャスティン プラス」 シンプルモデル サーブル 1.2L KO340177 -

3.0 / 5.0

これの古いモデルをずっと(7年ぐらい)つかっていたが、底のステンレス部分が緑色に錆びのようなものができていて、さすがにどうかと思ったので買い替えた。

緑色の何かの正体はよくわからず。こすっても落ちないし使用頻度が高いのでカビではないと思うが、クエン酸を入れて沸かしてもとれなかった。

ティファール 電気ケトル 「アプレシア プラス」 コンパクトモデル メタリックノワール 0.8L BI805D70 -

3.0 / 5.0

これにした。沸かせる量が減ったが、1.2L 沸かすことがまずなかったように思えるので小さいモデルに。外装がプラスチックのものは嫌だったのでステンレスのものにした。燃費を上げるためか全体を金属にしたものはないみたいだった。

電気ケトルの電気料金

メーカーのページを見てみると、同じメーカーのほぼ同じモデルでも微妙に電気料金が違く書かれている。

例えば、アプレシアプラス(プラスチックモデル)では約0.44円/カップ1杯と書いてあるが、アプレシアプラスメタリックでは約0.50円/カップ1杯となっている。といっても、この差だと365回沸かしてようやく約22円程度の差なので誤差ではありそう。

なお、このメーカーの場合だとスペック上で最も燃費が悪いもので約0.56円/カップ1杯、最も良いもので約0.44円/カップ1杯と、0.12円の差がある。