フライパンでにんじんタルトを作ろう

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

オーブンじゃなくてもフライパンでおいしいにんじんタルトが作れる。 写真を撮り忘れるという失態で製作途中の風景がまるでない。

はじめに

おいしいにんじんタルト食べたくない?食べたいよね! 簡単に作れたほうがよくない?いいよね! 電子レンジを使うよりもフライパンで作れたほうが簡単ぽくない?簡単ぽいよね!

というわけで、フライパンでにんじんタルトを作りました。

材料

以下、フライパン1個分(直径24cmくらい)です。

タルト生地

  • 薄力粉 120g
  • 砂糖 40g
  • 卵黄 1個分
  • バター 50g

タルトフィリング

  • にんじん 1本
  • 砂糖 40g
  • 薄力粉 50g
  • 卵 1個
  • 生クリーム 30ccくらい
  • お好みでレモン汁を大さじ1杯(自分のときは入れなかった)

必要な道具

  • フライパン
  • フライパンのふた(ないとつらい)
  • はかり(小麦粉や砂糖を測るのに使う。100円ショップで買える)
  • 金属ざる(薄力粉をふるう用)
  • ピーラー(にんじんの皮むき用。100円ショップで買える)
  • 大根おろし器(にんじん用。100円ショップで買える)
  • ボウル(タルトフィリングを作る用。IKEAのゴムボウルが便利だった)
  • 大きめの透明ビニール袋(ジップロックなどの丈夫なもの。タルト生地を作るのに使う。100円ショップで買える)

作り方

  1. 薄力粉 120gをざるでふるってボウルに入れる
  2. 砂糖 40g、卵黄 1個、バター 50gをボウルに入れる
  3. 軽くまぜた後、大きめの透明ビニール袋に全部入れて、ひたすらこねる。ここの作り方(2)にあるように、 手に付かない程度になるまでがんばってこねる
  4. こね終わったら、ビニール袋の口を縛って乾燥しないようにする(タルト生地はここでほぼ完成)
  5. (ここからタルトフィリング)にんじんの皮をピーラーでむいて、大根おろし器ですりおろす。
  6. すりおろしたにんじんに、砂糖 40g、ふるった薄力粉 50g、卵 1個、生クリーム 30ccを加えてまぜる(タルトフィリングはここでほぼ完成)。
  7. タルト生地をフライパンに広げて、フォークで穴を適度に開ける(出来上がりはここの作り方(3)を参照)
  8. 広げたタルト生地の上に、タルトフィリングを流し込んで、むらがないように広げる
  9. フライパンにふたをして、ごく弱火で焼く
  10. 15~20分くらいでこんがりいいにおいが漂ってくるので、タルトの出来上がり具合を見ながら火を消す。目安は、タルトのふちがきつね色になるくらい。
  11. 火を止めて粗熱を取る。
  12. おいしいにんじんタルトの完成!

注意点

  • 薄力粉は必ずふるってから使おう(粉が固まっていて非常に混ざりにくい)
  • 粗熱取る前に食べてもおいしいけど、タルト生地は冷えるとさくさく感がでるので、待ったほうがいいと思う
  • 生地をこねるときに、ビニール袋に入れずに素手でやってももちろんよいけど、手がよごれないビニール袋のほうが楽でいい
  • タルト生地をフライパンに広げるのが結構難しい。ここさえクリアできれば、手をほとんどよごさずにタルトができる
  • ビニール袋は丈夫なものじゃないと耐えられないので、丈夫なものにしよう
  • ゴムボウルは形に融通がきくので、ビニール袋に入れやすくて便利
  • テフロン加工とかフッ素加工してあるフライパンにはくっつかないので、油は引かなくても大丈夫(ひくと大惨事になると思う)
  • 必ず粉の重さは測ろう。測らなくていいのはにんじんくらい。
  • バターは高いのでケーキ用マーガリンなどで代用してもよい(実際代用した)
  • オーブンだともっとタルトがカリッと焼けるかもしれない。焼いたことはない・・・
  • 冷蔵庫に入れてればけっこう日持ちする。4日目に食べても特に問題はなかった。
  • いちどに食べるのには多すぎる量が焼ける

おわりに

フライパンでも簡単ににんじんタルトが焼ける。 何より自分で作るお菓子はおいしいね!

参考にしたサイト

簡単♪❤米粉de人参タルト❤ by ☆♪シフォン♪☆ [クックパッド] 簡単おいしいみんなのレシピが224万品

モッチリ☆ソフト食感☆人参タルト by MIKIKYO [クックパッド] 簡単おいしいみんなのレシピが224万品

フライパンで焼く人参ケーキ ミックス不要 by niconicoえま [クックパッド] 簡単おいしいみんなのレシピが224万品

フライパンで☆チーズケーキ(タルト) by shokolat [クックパッド] 簡単おいしいみんなのレシピが224万品

今年やったボドゲのメモ

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

はじめに

今年やったボドゲとメモを載せます。

やったボドゲたち

禁断の砂漠(原題:Forbidden Desert)

ゲーム紹介やストーリーなど

遭難した探検隊のメンバーであるあなたは、 砂漠に埋もれている伝説の飛行船を掘り出して脱出しなければなりません。 大変難しいゲームだけど、脳みそが沸騰するくらい楽しい。

www.tgiw.info

感想など

  • 最初のころのぼく「協力ゲーなんてどうせつまんねえよ」
  • 1プレイ後のぼくたち「砂漠から脱出できていないのに晩ごはんに行くとかねえだろ!!!おい!集中力を切らさないために寿司とかピザ取るぞ!!!」
  • 終電のときのぼくたち「砂漠から脱出できていないのに家に帰れるわけねえだろ!集中!!集中!!!」
  • 20時に脱出開始して、28時に脱出成功した。脱出成功時の達成感というか脳内麻薬が出る感じがたまらなかった。
  • 日を置いて再チャレンジしたときも同じパターンにはまって笑うしかなかった。メンバーの脳みそが湧いていたとしか思えない。

Amazonへのリンク

www.amazon.co.jp

Small World

ゲーム紹介やストーリーなど

ファンタジー系種族(エルフとかドワーフとか)を駆使して 土地を支配し、お金を稼ぐゲーム。陣取りゲームの一種です。 シンプルなルールで結構楽しめる。3人~がおすすめ。2人だといまいちかもしれない。

hobbyjapan.co.jp lfz.blog.so-net.ne.jp

感想など

  • 戦線を広げすぎたりすると土地を乗っ取られてしまうし、かといって固まってると稼げないしで、結構いいところを付くのが難しい。
  • 種族の特殊能力に加えて、追加特殊能力という要素がある。追加特殊能力と種族の組み合わせは毎回ランダムに決まるので、似たり寄ったりなゲームにならず何度も楽しめる。
  • 最初に1つ種族を選ぶんだけど、最初に選んだ種族にずっと肩入れしていると勝てないので、うまく次に乗り換える(このゲームでは"衰退"と呼んでいる)必要がある。この衰退のおかげでゲームが少し複雑になっていて面白い。

Amazonへのリンク

www.amazon.co.jp

ワンナイト人狼

ゲーム紹介

村に人狼が紛れ込んでいる。村人たちは人狼を探し出せば勝利。 人狼は逃げ切れれば勝利。3人以上いないと遊べない。

本家人狼は何日かやるのか時間がかかるし人数も必要だけど、 1ゲーム10分程度で終わるくらいに簡略化されている。

1nite-jinro.strikingly.com

感想など

  • シンプルだしちょこっと遊ぶ分には大変よい。もちろん何度やってもよいが、少し物足りなくなってくるかもしれない。
  • お互い全力で騙し合いをするので、友達が信じられなくなる

Amazonへのリンク

www.amazon.co.jp

ダイヤモンスターズ

ゲーム紹介

手持ちの1~5の5枚のカードをお互い出しあい、 ダイヤを5個以上集めるか、同じ種類のモンスターを3体集めれば勝利。 シンプルなルールで、2人でも充分楽しめる。

www.g-rounding.com

感想など

  • ルールがシンプルな分、相手の思考を考えるのに集中できるゲーム。思考を読みきって裏をかいたときの楽しさはたまらない。
  • 2人でずっとやっていると、相手の思考パターンがわかってくるせいか、ある種の"あいこ"が連発するようになる。
  • 相手の思考を考えだすと沼にはまるが、しかしそれが楽しい。

Amazonへのリンク

生産中止なのかな・・・あのとき買っておけばよかった・・・

www.amazon.co.jp

Mow

ゲーム紹介

ハエのついた牛を押し付けあうゲーム。 ルールはいたってシンプルだけど、カードの出しどころをきちんと考えないと とたんに負けてしまうのがポイント。

yurukoi.impress.co.jp

感想など

  • いつ誰とやっても外れないゲームで、やるたびに中毒者が続出する危険なゲーム。
  • 盛り上がりすぎて、みんなの言動がおかしくなってくるのはよくあること。
  • かなり運要素がからむので、安定して勝てなかったりするのがよい。

Amazonへのリンク

ポケット版で充分といううわさもある。ちなみに自分が買ったのはポケット版。

www.amazon.co.jp

まだ全然全部紹介できていないので、最後に列挙くらいはする

だいたいお友だちに呼んでもらってやらせてもらったものが多い(みなさんありがとうございます)。 - カタン - カルカソンヌ - 電力会社 - ラブレター - ペンギンパーティ - グースカパースカ - ドミニオン - 心臓発作にならないための10の方法 - 枯山水

おわりに

最近はボドゲで徹夜をすることがなくなってしまった。 もっとやりたい。

Pebble Timeを買って最初にやったこと

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

www.adventar.org

はじめに

Pebble Timeを買ったので、最初にやった設定などについてまとめます。

Pebble Timeとは

最近はやりのスマートウォッチというやつです。 Apple Watchのようにスペックは高くないけれど、 電池は長持ちするし、カラー表示もできるし、 何よりお財布にやさしいという特徴を持っています。

レビューはいろんな人が書いているので今更書きません。

smhn.info

【Pebble Time 】買ってよかった!と感じたシーン3つ
tobalog.com

どうして買ったか?

値段そこそこで電池長持ち、かつかわいらしいデザインだったからです。 あとはシンプルなところかな。 普段心置きなく使えるようなものが欲しかったんです。

どこで買えるか?

日本のAmazonでももちろん買えるけど、公式サイトだと割引していたので 公式サイトで買いました。12/20現在でも$150で買えるようでした。 日本への送料は無料でしたよ、確か。

www.pebble.com

買って何をやったか

PebbleのアップデートとWatchfaceの入れ替え

何はなくとも、まずは手持ちのAndroidとつなげてアップデートします。 Play StoreにPebble Time用アプリがあるので、それを入れましょう。 最初に接続したときにアップデートは勝手に始まります、確か。 その他、Pebble Timeアプリの指示に従って必要なものを入れればOKです。 僕のときは何でかわからないけどもAndroid Wearアプリを入れろと指示がありました。

Pebble Time - Google Play の Android アプリ

アップデートが終わったらまずは時計としての体裁を整えるため、 Watchfaceを入れ替えます。壁紙を設定するようなもんですね。

ぐぐったらこんなのが出てきたので、載せておきます。 shikarunochi.matrix.jp

日本語化

AndroidはPebbleに通知画面(例えばメールが来たとかLINEが来たとか)を飛ばして くれるわけですが、Pebbleはデフォルトで日本語表示できないので、 有志のみなさまが作ってくださっている日本語化パックを入れます。

いろいろあるみたいですが、僕はこれを入れました。 Pebble language pack: 日本語言語パック

以上でやったことはおしまいです。

アプリを入れる

最近のアップデートで睡眠時間と歩数をデフォルトでトラックしてくれるようになったので、 他にアプリをいれなくても充分かな、という感じです。

おわりに

まだ使って4日くらいですが、結構楽しく使っています。 通知が飛んで来るだけで結構充分な感。

秒速でRAM Diskを作成する

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

LIOでループバックのRAM Diskを作成する。

はじめに

書くネタがだいぶ苦しくなってきたので、特に意味もなくRAM Diskの作り方について書きます。

そもそもRAM Diskとは何か

DRAM(要はメモリ)を一部確保して、HDDやSSDのごとく使えるようにしたものです。 SSDがいくら速いと言っても、メモリにはもちろん勝てないです。 最近のマシンはメモリを山ほど積んでるので、一部切り取って、 何かのキャッシュ置き場として使うといいのでは感があります。

Windowsだとなんかわけわからんドライバやら入れないといけないのでは、というところですが、 Linuxだとまあ文字通りあっという間にできます。

想定環境

必要なもの

LIOがあれば万事OK。 ビルドは結構大変なので以下のコマンドで入れます。

$ sudo apt install targetd --install-recommends

作り方

targetdをインストールすると、targetcliというある種のシェル的なものが使えるようになります。 targetcliを使うとRAM Diskを作ることができます。

targetcliを起動してみます。何かまあプロンプトが出ますね。

$ sudo targetcli
targetcli GIT_VERSION (rtslib GIT_VERSION)
Copyright (c) 2011-2013 by Datera, Inc.
All rights reserved.
/> 

lsをたたいてみましょう。いろいろ出てきましたね。 LIOはフロントエンド(ユーザがI/OするためのI/F。/dev/sdXとかそういうの)と、 backstores以下のバックエンド(ユーザのI/Oを実際に受けるところ)に分かれています。 今回は、フロントエンドにloopback(自分のサーバ内でのみ有効な/dev/sd何とかに見せる)、 バックエンドにbackstores/rd_mcp(要はRAM Disk)を選びます。

/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [0 Storage Object]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback .................................................. [0 Targets]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

まずはバックエンド部分を作ります。ってもコマンド1発です。 今回は、ramdiskという名前の1GBのRAM Diskを作りました。

/> /backstores/rd_mcp create name=ramdisk size=1G
Generating a wwn serial.
Created rd_mcp ramdisk ramdisk with size 1G.
/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [1 Storage Object]
  |   o- ramdisk ..................................... [ramdisk deactivated]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback .................................................. [0 Targets]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

バックエンドを作っただけではアクセスできないので、ramdiskのステータスは dactivatedになっています。では、フロントエンドを作りましょう。 こちらも同じくコマンド1発・・・とはいかなくて、2発叩き込む必要があります。 タブキーで補完が効くので、あまり面倒ではないです。作業自体はこれで完了です。

/> /loopback create
Created target naa.60014053e8e7b899.
/> /loopback/naa.60014053e8e7b899/luns create /backstores/rd_mcp/ramdisk 
Selected LUN 0.
Successfully created LUN 0.
/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [1 Storage Object]
  |   o- ramdisk ....................................... [ramdisk activated]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback ................................................... [1 Target]
  | o- naa.60014053e8e7b899 ......................... [naa.6001405ec6fd7aef]
  |   o- luns ...................................................... [1 LUN]
  |     o- lun0 ................................. [rd_mcp/ramdisk (ramdisk)]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

さて、lsscsiでチェックしてみましょう。僕の環境では/dev/sdcに見えていますね。 lsscsiがない人はaptで入れましょう。

$ lsscsi
()
[10:0:1:0]   disk    LIO-ORG  RAMDISK-MCP      4.0   /dev/sdc 

見えない場合は、以下コマンドでディスクをリスキャンしてみましょう。

#!/bin/bash

for i in /sys/class/scsi_host/*
do
  echo "- - -" > $i/scan
done

さて、試しにinquiryを投げつけてみましょう。 inquiryとは、SCSIというDiskとやりとりするためのプロトコルで定義されているコマンドで、 Diskのベンダやら種類やらを知るためのものです。

何かいろいろ出てきましたが、 Product identificationがRAMDISK-MCPになっていて、確かにRAM Diskぽいということがわかりますね。 これでSSDより圧倒的に速いDiskを手に入れることができました。

$ sudo sg_inq /dev/sdc   
standard INQUIRY:
  PQual=0  Device_type=0  RMB=0  version=0x05  [SPC-3]
  [AERC=0]  [TrmTsk=0]  NormACA=0  HiSUP=0  Resp_data_format=2
  SCCS=1  ACC=0  TPGS=3  3PC=1  Protect=0  [BQue=0]
  EncServ=0  MultiP=0  [MChngr=0]  [ACKREQQ=0]  Addr16=0
  [RelAdr=0]  WBus16=0  Sync=0  Linked=0  [TranDis=0]  CmdQue=1
    length=36 (0x24)   Peripheral device type: disk
 Vendor identification: LIO-ORG 
 Product identification: RAMDISK-MCP     
 Product revision level: 4.0
 Unit serial number: 7539e091-7407-4637-8edc-f8d4a531c92b

使う分にはあまり関係ないですが、このRAM Disk、 SATAではなくてSASディスクとして見えているようですね。

$ sudo sg_inq -i /dev/sdc
VPD INQUIRY: Device Identification page
  Designation descriptor number 1, descriptor length: 20
    designator_type: NAA,  code_set: Binary
    associated with the addressed logical unit
      NAA 6, IEEE Company_id: 0x1405
      Vendor Specific Identifier: 0x7539e0917
      Vendor Specific Identifier Extension: 0x40746378edcf8d4a
      [0x60014057539e091740746378edcf8d4a]
  Designation descriptor number 2, descriptor length: 61
    designator_type: T10 vendor identification,  code_set: ASCII
    associated with the addressed logical unit
      vendor id: LIO-ORG
      vendor specific: RAMDISK-MCP:7539e091-7407-4637-8edc-f8d4a531c92b
  Designation descriptor number 3, descriptor length: 8
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: Relative target port,  code_set: Binary
    associated with the target port
      Relative target port: 0x1
  Designation descriptor number 4, descriptor length: 8
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: Target port group,  code_set: Binary
    associated with the target port
      Target port group: 0x0
  Designation descriptor number 5, descriptor length: 8
    designator_type: Logical unit group,  code_set: Binary
    associated with the addressed logical unit
      Logical unit group: 0x0
  Designation descriptor number 6, descriptor length: 36
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: SCSI name string,  code_set: UTF-8
    associated with the target port
      SCSI name string:

さいごに

LIOを使うとRAM Diskが簡単にできちゃうわけですが、 /dev/shm/以下もRAM Disk扱いになっているので、別に無理に作る必要はない。

Disk I/O性能測定するときに活躍する道具たち

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

Disk(HDD/SSD/仮想ディスク等)のI/O性能を測定するのに 活躍するツールについて、簡単に使い方を説明する。

はじめに

Diskに限定せずとも、性能測定と言えば、 おおまかには、スループット(Diskに関して言えば、1秒あたりどれくらいのI/Oを処理できるか?)と レイテンシ(Diskに関して言えば、1個のI/Oを処理するのにどれくらい時間がかかったか?) の2つについて見ることになる。 特にLinuxに関しては、このあたりをよく使うような気がする(主観)。

  • fio:Diskに対してI/Oを発行してくれて、スループットやレイテンシを算出してくれるもの。
  • iostat:Diskごとにスループットなどの情報をリアルタイムで見せてくれるもの
  • blktrace:LinuxがDiskに対してどんなI/Oを出していたかの履歴(=トレース)を見せてくれるもの。

fioはスループットもレイテンシも算出してくれるので、基本的にはfioだけで事足りるのだけど、 本当にfioに指定したとおりにLinuxがI/Oを出しているのか、に関してはちゃんと確認する意義があるので、 iostatとblktraceを追加した。

以後、以下の環境を前提とするので、必要があれば適宜読み替えてほしい。

fio

fioのインストール

fioはメジャーなツールなので、こんな感じでaptでインストールできる。

$ sudo aptitude install fio

ソースからビルドしたい原理主義者は、作者様のgithubから落としてくると幸せになれる。 (ビルド前にlibaioを入れておくことを忘れなければ、さらに幸せになれる) github.com

手順としてはこんな感じ。

$ sudo aptitude install libaio1 libaio-dev
$ git clone git@github.com:axboe/fio.git
$ ./configure
$ make
$ sudo make install

使い方

コマンドとしてはこんな感じ。起動時に、どんなI/Oをかけるのかを記述した 設定ファイルを指定する。

$ fio -f script.fio --output output_filename # 場合によってはsudoが必要

fioはコマンド引数でもろもろ指定ができるんだけど、 後々の再利用を考えると、設定ファイルを逐一書いたほうがよいと思う。

設定ファイルの書き方

最小限ではないが、これだけ書けばI/Oをかけることができる。 細かいところはman fioを見るのがいちばん。 英語で書いてあるけど、割と簡単に読めると思う。

fioの設定ファイルは、全体で共通の[global]以下の設定と、 個別の[job]に分けられる。[global]予約語だけど、 [job]に関しては好きな名前を指定できる。もちろん、[job]複数個書くことができる。

[global]                                
ioengine=libaio    # I/Oを発行するのにlibaioを使う。性能測るときは
                   # とりあえずlibaioを指定しておけばよいと思う。
blocksize=4KB      # 1回のI/Oの転送長
blockalign=4KB     # I/Oのアラインメント。
                   # I/O先のアドレスが4KB刻みになる。
kb_base=1024       # K=1000かK=1024かわからなくなるので、
                   # 書いておくと親切。defaultは1024。
filesize=1-1GB     # I/Oをかけるアドレス範囲の指定。
                   # この設定だと、各ファイルそれぞれについて、
                   # 1-1GBの範囲にI/Oをかける(1始まりなので注意)。
direct=1           # Disk I/O性能を測るので、Linuxのキャッシュを
                   # 素通りするようにする。
ioscheduler=noop   # Disk I/O性能を測るので、Linuxに何も考えず
                   # I/Oしてもらうようにする。

[job]
rw=randread        # I/Oの種別。[global]で指定したアドレス範囲から
                   # ランダムに選んでリードする。                                       
filename=/dev/sdc:/dev/sdd:/dev/sde 
                   # どのファイルにI/Oをかけるかを指定。
                   # : で複数指定できる。
                   # 見ての通り、別にファイルでなくともよい。
ramp_time=30       # I/Oかけはじめの、性能測定はしない時間。
                   # I/Oかけはじめは安定しないこともあるので、
                   # 少し時間を置くほうがよいこともある。
runtime=60         # I/Oをかけて、性能測定をする時間。
time_based=1       # filesize分I/Oかけても、runtimeが経過するまで
                   # I/Oをかけ続ける。
numjobs=6          # いくつのプロセスでI/Oするかを指定。
                   # thread=1とすると、forkではなく
                   # pthreadを使ってくれる。
group_reporting=1  # プロセスごとに結果を報告するのではなく、
                   # ジョブごとにまとめて報告する。
iodepth=1          # I/Oの多重度。概して多重で出したほうが性能が上がる。

得られる結果

上記をtest.fioとして保存して、RAM Disk相手にfioを実行してみる。 I/O中はこんな感じで表示される。

$ sudo fio -f test.fio --output test.result             
Jobs: 6 (f=18): [r(6)] [60.0% done] [1137MB/0KB/0KB /s] [291K/0/0 iops] [eta 00m:36s] 

test.resultの中身はこんな感じ。 注意すべきはレイテンシがslat、clat、latの3つあること。 それぞれは以下のとおり。

  • slat:fioがI/O発行して、Linuxがコマンド発行までにかかっている時間。
  • clat:コマンド発行から応答を受け取るまでにかかった時間(単にディスクのレイテンシを見るだけならここだけ見ればよいかも)。
  • lat:全体。
read_test: (g=0): rw=randread, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
(略)
read_test: (groupid=0, jobs=6): err= 0: pid=8858: Sun Dec 13 22:27:14 2015                             
  read : io=67192MB, bw=1119.9MB/s, iops=286680, runt= 60001msec                                       
    slat (usec): min=4, max=11476, avg=16.24, stdev=13.23                                              
    clat (usec): min=0, max=10280, avg= 2.26, stdev= 8.96                                              
     lat (usec): min=11, max=10293, avg=18.94, stdev=14.98                                             
    clat percentiles (usec):                                                                           
     |  1.00th=[    0],  5.00th=[    0], 10.00th=[    0], 20.00th=[    1],                             
     | 30.00th=[    1], 40.00th=[    1], 50.00th=[    1], 60.00th=[    1],                             
     | 70.00th=[    1], 80.00th=[    1], 90.00th=[   12], 95.00th=[   13],                             
     | 99.00th=[   15], 99.50th=[   15], 99.90th=[   18], 99.95th=[   20],                             
     | 99.99th=[   47]                                                                                 
    bw (KB  /s): min=    0, max=228328, per=16.54%, avg=189615.16, stdev=19299.85                      
    lat (usec) : 2=88.03%, 4=0.33%, 10=0.02%, 20=11.56%, 50=0.05%                                      
    lat (usec) : 100=0.01%, 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01%                                
    lat (msec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%                                                  
  cpu          : usr=12.58%, sys=37.56%, ctx=17215603, majf=0, minf=8                                  
  IO depths    : 1=150.9%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%                         
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%                        
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%                        
     issued    : total=r=17201111/w=0/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0                         
     latency   : target=0, window=0, percentile=100.00%, depth=1                                       
                                                                                                       
Run status group 0 (all jobs):                                                                         
   READ: io=67192MB, aggrb=1119.9MB/s, minb=1119.9MB/s, maxb=1119.9MB/s, mint=60001msec, maxt=60001msec
                                                                                                       
Disk stats (read/write):                                                                               
  sdc: ios=8650516/0, merge=0/0, ticks=102172/0, in_queue=103080, util=61.34%                          
  sdd: ios=8650522/0, merge=0/0, ticks=104708/0, in_queue=103800, util=62.18%                          
  sde: ios=8650522/0, merge=0/0, ticks=101136/0, in_queue=101780, util=60.36%                          

iostat

fioはおおよそのスループットを表示してくれるわけだけど、いかんせんざっくりすぎるので、 iostatを使って、もう少し細かく見てみる。

iostatの入手

何も考えずにaptでinstallする。

$ sudo aptitude install sysstat

iostatの使い方

1秒間隔で表示させておけば困ることはないはず。

$ iostat -x 1 sdc sdd sde

得られる結果

こんな感じで毎秒表示してくれる。 よく使う前半部分に関しては、こんな感じ。

項目 意味
rrqm/s 1秒あたりにマージしたリードリクエストの回数
wrqm/s 1秒あたりにマージしたライトリクエストの回数
r/s 1秒あたりのリード回数
w/s 1秒あたりのライト回数
rkB/s 1秒あたりにリードしたデータ量
wkB/s 1秒あたりのライトしたデータ量
%util I/OリクエストにCPU時間をどれくらい使ったか?(100%=CPUが全力でI/Oを発行しまくっている)
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00   64.45    0.00    0.00   23.24

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sdc               0.00     0.00 96948.00    0.00 387796.00     0.00     8.00     1.08    0.01    0.01    0.00   0.01  60.00
sdd               0.00     0.00 96949.00    0.00 387796.00     0.00     8.00     1.15    0.01    0.01    0.00   0.01  64.40
sde               0.00     0.00 96949.00    0.00 387796.00     0.00     8.00     1.08    0.01    0.01    0.00   0.01  57.60

blktrace

測定をしたはよいが、芳しくない結果が得られたときに、一体何が起こっていたのかを 確認するためのトレース採取ツール。困ったときに覗くとよいと思う。

blktraceの入手

考える前にまずはaptを叩く。

$ sudo aptitude install blktrace

blktraceの使い方

I/O中に、デバイスを指定してトレースを取得する。 トレースはコア数分できる。

$ sudo blktrace -d /dev/sdc # Ctrl-Cでトレース採取終了
$ ls
sdc.blktrace.0  sdc.blktrace.2  sdc.blktrace.4  sdc.blktrace.6 
sdc.blktrace.1  sdc.blktrace.3  sdc.blktrace.5  sdc.blktrace.7  

得られる結果

得られたファイルはバイナリなので、blktraceとセットになっているblkparseというコマンドを使って、 読みやすい形に整形する。

$ blkparse -i sdc.blktrace.0 > sdc.blkparse.0

sdc.blkparse.0を覗いてみると、左から順に以下のようになっている(デフォルト設定なのでもちろん変えられる)。

  • デバイスのmajor番号, minor番号
  • CPUコア番号
  • シーケンス番号
  • トレース採取時からの経過時間(秒)
  • I/Oしていたプロセスのpid
  • コマンド状況を表すアルファベット(詳細はmanを参照のこと)
  • 処理を表すアルファベット(詳細はmanを参照のこと)
  • I/Oの先頭アドレス+オフセット
  • プロセス名
  8,32   0        1     0.000000000  9718  G   R 1839944 + 8 [fio]   
  8,32   0        2     0.000000383  9718  P   N [fio]               
  8,32   4        1     0.000000413  9717  G   R 1831472 + 8 [fio]   
  8,32   4        2     0.000000643  9717  P   N [fio]               
  8,32   0        3     0.000001059  9718  I   R 1839944 + 8 [fio]   
  8,32   0        4     0.000001355  9718  U   N [fio] 1             

CPUコア0だけ取り出したのが以下。 多重でI/Oかけていると、トレースが混迷を極めそうだ・・・

8,32   0        1     0.000000000  9718  G   R 1839944 + 8 [fio]
→ fioがI/Oの先頭アドレスを算出した
8,32   0        2     0.000000383  9718  P   N [fio]              
→ fioがカーネルに対してリードリクエストを発行するために
   コマンドをためておくキューに接続
8,32   0        3     0.000001059  9718  I   R 1839944 + 8 [fio]  
→ fioのリードリクエストがコマンドをためておくキューに、
  コマンドを追加
8,32   0        4     0.000001355  9718  U   N [fio] 1            
→ fioのリードリクエストがコマンドをためておくキューとの接続を解除
8,32   0        5     0.000001688  9718  D   R 1839944 + 8 [fio]
→ コマンド発行
8,32   0        6     0.000014064     3  C   R 1839944 + 8 [0]    
→ 応答が返ってきた

さいごに

以上、Disk I/Oの性能測定をするときに活躍する道具たちとその使い方でした。 使い方とか説明が間違っていたらごめんね!

the platinum searcher(pt)をビルドして使ってみよう

※この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

Golang初心者だけどthe platinum searcherをビルドして使ってみた。ついでにpull requestもしてみた。

the platinum searcherとは何か

ひとことで言うと、プログラマ向けの速いgrep。 作者様のblogはこちら

世の中には改良版grepとして、以下のものがあるみたい。

ptとagの違いは、EUC-JPやShift_JISに対応しているか否か。 (agはUTF-8しか対応していなくて、EUC-JPやShift_JISのファイルが スキップされてしまうという罠がある)

the platinum searcherのビルドとインストール

README.mdにしたがっていけばOK。

$ go get -u github.com/monochromegane/the_platinum_searcher
$ go get -u github.com/jessevdk/go-flags        # なぜか入らなかったので別立て。普通はいらないはず。
$ go get -u github.com/monochromegane/conflag   # なぜか入らなかったので別立て。普通はいらないはず。
$ go get -u github.com/monochromegane/terminal  # なぜか入らなかったので別立て。普通はいらないはず。
$ cd $GOPATH/src/github.com/monochromegane/the_platinum_searcher
$ go build -o $GOPATH/bin/pt cmd/pt/main.go

おまけ:pull requestを投げてみた

pull requestした背景

githubを使ってはいるが、恥ずかしながら1度もpull requestというものを投げたことがなかったので、 pull requestを投げてみたかったんです・・・ というわけで、A Tour of Goをひととおりやった程度の僕でもなんとかできそうなissueを選んでpull requestを投げてみた。

以下のissueは、.gitconfigにexcludesfileが指定されていないときに、グローバルなgitignoreファイルを 読んでくれないよ、っていうもの。

github.com

問題箇所の特定

おおよそこんな感じだったと思う。gdbで実行するまでもなかったかもしれないけど、 gdbを使ってみたかったのもあって、gdbでステップ実行しながら特定した。

  1. gdbで追いかけられるように、まずはptをビルドし直す:go build -v -gcflags "-N -l" -o pt_debug cmd/pt/main.go

  2. ptなりgrepなりで"gitignore"をソースのどこで使っているか探す

  3. ソースコードでは、option.goとoption_test.goとignore.goがひっかかるので、中を覗く。ignore.goだけが関係あることがわかる。

  4. gdbでptを起動する。入り口と思しきignore.go内のglobalGitIgnore関数にbreakpointを作成する。"b globalGitIgnore"と打つと、gdbはそんな関数しらねとのたまうので、"b ignore.go:88"とファイルかつ行数指定でbreakpointを作成する。

  5. gdbでステップ実行していくと、globalGitIgnoreName関数内で、gitconfig内にexcludesfilesが定義されていないときに空文字列が返されることがわかるので、その部分を修正すればよいことがわかる。

追記

何か中途半端だったし、元のソースコードとコンフリクトしてたしでプルリクを引っ込めた。

プログラミング言語pyoko

※この記事はピョッコリンアドベントカレンダーのために書かれたものです

はじめに

みんなのアイドルピョッコリンさんのために、 プログラミング言語pyoko(以後、単にpyokoと呼びます)を開発しました!!

pyokoとは

pyokoは、言語のシンプルさとピョコ感を出すことを念頭に置いて設計された言語です。 字面の似ているpythonと同等の表現力を持ちながらも、圧倒的可読性・記述性を達成しました。 また、ピョコ感を演出することうけあいでしょう。

サンプルコード

お決まりのHello, Worldをみてみましょう。 ね、圧倒的可読性・記述性とピョコ感が伝わるでしょう?

ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョリコピョッコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコピョコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコ
ピョコピョコピョコピョコピョ?!ピョ?!ピョ?!ピョコリピョコ-ピョッコ
ピョッコリンピョッコピョコピョコピョッコリンピョコピョコピョコピョコピョコ
ピョコピョコピョッコリンピョッコリンピョコピョコピョコピョッコリンピョッコ
ピョコリピョッコリンピョコリピョコリピョコリピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョ?!ピョコピョコピョコ
ピョコピョコピョコピョコピョコピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョコピョコピョコピョッコリンピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョッコピョコピョッコリン

上記テキストをhello.pyokoとして保存し、インタプリタを実行します。

$ pyoko hello.pyoko
Hello, world!

ね、Hello, worldでしょう?

ソースコード

pyokoのインタプリタpythonで実装されています。

#!/usr/bin/env python
#fileencoding: utf-8

from __future__ import print_function
import sys
import codecs

class pyokolang():
    plus   = "+"
    minus  = "-"
    gt     = ">"
    lt     = "<"
    lb     = "["
    rb     = "]"
    comma  = ","
    period = "."

    def __init__(self,filename):
        with codecs.open(filename, "r", "utf-8") as f:
            self.raw_code = ("".join(f.readlines())).strip()
        self.iptr = 0
        self.dptr = 0
        self.data = [0]*100
        self.loopmap = {} 
        self.insts = []
        self.__parser()

    def __parser(self):
        self.__tokenizer()

    def __get_token(self, pos):
        is_same = lambda raw_code, pos, x: raw_code[pos:pos+len(x)] == x
        quasi_plus   = u"ピョコ"
        quasi_minus  = u"ピョコリ"
        quasi_gt     = u"ピョッコ"
        quasi_lt     = u"ピョ?!"
        quasi_lb     = u"ピョリコ"
        quasi_rb     = u"ピョコ-"
        quasi_comma  = u"ピョコ?!"
        quasi_period = u"ピョッコリン"

        if is_same(self.raw_code, pos, quasi_minus):
            return quasi_minus, pyokolang.minus
        elif is_same(self.raw_code, pos, quasi_period):
            return quasi_period, pyokolang.period
        elif is_same(self.raw_code, pos, quasi_gt):
            return quasi_gt, pyokolang.gt
        elif is_same(self.raw_code, pos, quasi_lt):
            return quasi_lt, pyokolang.lt
        elif is_same(self.raw_code, pos, quasi_lb):
            return quasi_lb, pyokolang.lb
        elif is_same(self.raw_code, pos, quasi_rb):
            return quasi_rb, pyokolang.rb
        elif is_same(self.raw_code, pos, quasi_plus):
            return quasi_plus, pyokolang.plus
        elif self.raw_code[pos:pos+len(quasi_comma)] == quasi_comma:
            return quasi_comma, pyokolang.comma
        else:
            return False, False

    def __tokenizer(self):
        pos = 0
        while pos < len(self.raw_code):
            token, symbol = self.__get_token(pos)
            if token is False:
                pos += 1
                continue
            self.insts.append(symbol)
            pos += len(token)

    def __gen_loopmap(self):
        stack = []
        for i, val in enumerate(self.insts):
            if val == pyokolang.lb:
                stack.append(i)
            elif val == pyokolang.rb:
                if len(stack) == 0:
                    print("parse error: right brackets exists more than left brackets")
                    sys.exit()
                lb_index = stack.pop()
                self.loopmap[i] = lb_index
                self.loopmap[lb_index] = i

        if len(stack) != 0:
            print("parse error: left brackets exists more than right brackets")
            sys.exit()


    def executor(self):
        self.__gen_loopmap()
        while self.iptr < len(self.insts):
            if self.insts[self.iptr] == pyokolang.gt: # >
                self.dptr += 1
                if self.dptr >= len(self.data):
                    for i in range(len(self.data)):
                        self.data.append(0)
            elif self.insts[self.iptr] == pyokolang.lt: # <
                self.dptr -= 1
                if self.dptr < 0:
                    print("data pointer underflow", self.iptr, self.insts, self.dptr, self.data)
            elif self.insts[self.iptr] == pyokolang.plus: # +
                self.data[self.dptr] += 1
            elif self.insts[self.iptr] == pyokolang.minus: # -
                self.data[self.dptr] -= 1
            elif self.insts[self.iptr] == pyokolang.period: # .
                print(chr(self.data[self.dptr]), end="")
            elif self.insts[self.iptr] == pyokolang.comma:  # ,
                self.data[self.dptr] = sys.stdin.read(1)
            elif self.insts[self.iptr] == pyokolang.lb: # [
                if self.data[self.dptr] == 0:
                    self.iptr = self.loopmap[self.iptr]
            elif self.insts[self.iptr] == pyokolang.rb: # ]
                if self.data[self.dptr] != 0:
                    self.iptr = self.loopmap[self.iptr]
            self.iptr += 1

    def run(self):
        self.executor()

def usage():
    print("usage: python pyokolang.py <filename>")
    sys.exit()

def main():
    if len(sys.argv) != 2:
        usage()
    bp = pyokolang(sys.argv[1])
    bp.run()
    print()

if __name__ == "__main__":
    main()

実装に関して

ここまで来ればお気づきの方もいるかもしれませんね。 そう、pyokoは難読で有名なプログラミング言語brainf*ckを ベースに作られています。 実装方針は単純で、brainf*ckを構成する8個の実行命令(+/-/>/</[/]/,/.)を ピョコ感ある単語に置き換えるだけです。

処理の流れも単純で、以下の2ステップです。

  1. ピョコ感ある単語をbrainf*ckの8個の実行命令に置き換え(__tokenizer)

  2. brainf*ckインタプリタで実行(executor)

感想や反省など

  • 感想:意外に簡単にできた。
  • 反省:もっとhello.pyokoを複雑な文章にすべきだった。少々面白みに欠ける。

おまけ

インタプリタソースコードはここにあります。 github.com