ぼくの本の読み方
どの本を読むか
まずは、読みたい本を決めることから始めるしかない。本の紹介リストとかAmazonのおすすめとかKindleの0円リストとかTwitterに流れてるとかを興味あったらとりあえずブクログの読みたい本リストに入れている。
本の紹介リストはSFだったら http://love6.2ch.net/test/read.cgi/sf/1251044312/ とか。まあいろんなところに様々なジャンルのおすすめがあるし、そこを全て読んで好きな読者など探索すればよい。
あとは図書館とか部室とかの本棚のてきとーな棚の左上から読んでいくという方法もある。一通り読めるので大変べんりである。
本を手にいれる
本を手元に置くには2種類方法がある。買うか借りるか。4000冊も買っていては破産してしまうので借りるしかない。図書館の蔵書を調べるにはカーリルが大変べんりだ。本のリストが少ないうちは、ここに読みたい本をがんがん登録していくといいと思う。
ぼくはブクログの読みたい本リストに4000冊、Amazonのカート(優先的に読みたい本リストみたいな)に400冊の本が溜っているのでとてもカーリルを使ってられない。
ぼくはまずAmazonのカートの古いものから、そしてブクログの読みたい本リストの古いものから読んでいきたい。 そこで、この順番にリストを見に行って、カーリルのAPIを使って蔵書情報を調べるプログラムを書いて使っている。読みたい本が図書館にあれば、図書館の予約ページが開いてくれてべんりだ。予約して、本が来たら受け取る。これだけ。
予約する本の冊数は、(図書館で借りることができる最大冊数)-(いま借りているうち読み終わっていない冊数)にしている。ただし、コミックはカウントしない。予約可能数の半分をローダンシリーズの予約に使う。10冊借りることができる図書館で、10冊借りていて、2冊読み終わったら、1冊ローダンを予約し、1冊別のなにかを予約する。さらに、もしも手塚治虫のコミックを借りていない or 借りていても読み終わっている場合には手塚治虫のコミックを予約する。
Amazonカートには優先的に読みたい本リストをいれている、といったが基本的に興味があっていますぐ読みたいんだー!という理由でここに本を入れることはない。読んでるシリーズの新刊や、図書館で読み終わった本の次の巻をAmazonカートにつっこむことにしている。そうしないと、ブクログの4000冊の最後に入っちゃってやばいからね。
また、Amazonカートは買いたい本のリストでもある。出先で本が足りなくなってきたら、このリストを見て本を買う。この用途のために、前述の蔵書チェックプログラムはどの図書館にも読みたい本がない場合にカートに本をつっこんでくれるようになっている。 ただ、生活場所が変わり図書館の蔵書が多幅に変わったせいでAmazonのカートの中に、いまでは蔵書の中にある本がたくさん貯ってしまってブクログの読みたい本リストを消化できずにいる。一度カートをクリアすべきかもしれない。
コミック系はTSUTAYA DISCASで30冊単位で借りている。現在30以上のシリーズを読んでいる(Kindleの無料配付の続きを読んでるせいだ、ぷんぷん)ので30種類1冊ずつを借りてる。びみょーにアレだけど図書館にないようなコミック類を安く読めるのでまあよい。
本をスケジューリングする
ここまでで、手元に本がある状態になる。手元の本には様々な返却期限がついている、または自分で買った本などついていないものもある。これらをちゃんと読んでいかなければいけないという問題がある。これまでに様々な方法を試していてきている。
1. 期限が近いのから読む
期限が近いのから読むというシンプルな方法。
2. ラウンドロビン形式
しかし、期限が近いのから読む方法ではひっきりなしに本を図書館から借りているので、これでは期限が付いていない自分の本を読めないという問題がある。
本をラウンドロビンで読む。日本語の本は10ページ、英語の本は5ページ後に栞を入れて、そこまで読むと本を切り替える。これによって自分の本を読み進めることができるようになった。
3. リミット制
しかし、ラウンドロビン方法では20冊手元にある状態で、最初の本を20ページ読むには210ページ読まなければいけない。こうなると期限に間に合わない本が出てきてしまうという問題がある。
そこでリミット制を導入した。1冊にかかる時間を1日と設定し、借りている本の期日の数日前から、借りている本だけを読む期間を作ることにした。たとえば、10月5日返却期限の本が3冊あれば、10月2日からはその3冊だけを読む。1冊読み終われば、リミットは解除され、ふたたび全ての本を読むことが可能となる。10月3日からはまたリミット期間が始まることになる。
これらの期間は重複することがある。たとえば10月10日期限が5冊、10月9日期限が2冊、10月4日期限が2冊ある場合を考えよう。
- 10月04日: 2冊
- 10月09日: 2冊
- 10月10日: 5冊
単純にはリミット期間は以下のように計算される。
- 10月04日: 2冊: 10月2日から
- 10月09日: 2冊: 10月7日から
- 10月10日: 5冊: 10月5日から
しかし、10月5日から10月10日の中には新しく2冊の期限の本が含まれているから、2日リミット期間を延長する。
- 10月04日: 2冊: 10月2日から
- 10月09日: 2冊: 10月7日から
- 10月10日: 5冊: 10月3日から
再度、10月3日から10月10日の中に新しく2冊の本が含まれたので再度延長する。最終的にリミット期間は以下のようになる
- 10月04日: 2冊: 10月2日から
- 10月09日: 2冊: 10月7日から
- 10月10日: 5冊: 10月1日から
こうなると、10月10日からの本が「10月1日から」と一番早いリミット期間を持つことになった。この時、期限が遅いものがリミットに入ると自動的に期限が早いものもリミットに入る。すなわち
- 10月1日から: 4日、9日、10日の本にリミット
- 10月2日から: 4日の本にリミット
- 10月7日から: 9日の本にリミット
4. ソフトリミット制
現実的にはリミット制は十分ではなかった。一日一冊で読むことは難しい。
そこで、早めに「ソフトリミット」を導入することにした。ソフトリミットはリミットと同様だが、1冊2日で計算される。ソフトリミットされた本は「優先」して読まれることになる。ソフトリミットされても、それらの本だけを読むことにはならない。
では、どのように「優先」するのか。ソフトリミットのレイヤによって本にはレベルが付けられることになった。1番優先される本をレベル1、次をレベル2、レベル3としていく。
レベル1の本はいつでも読むことができる。レベルn(nは2以上)の本は、最後に読んだ 2^(n-1) 冊の本が レベル(n-1)以下の本だけで構成される時にだけ読むことができる。
こうして、優先付けが導入された。これを使って期限なしの本にも優先度を付けることができるようになった。いまでは、期限ありの本の下に、期限はないが借りている本や論文など早めに読みたい本、技術書・教科書、図書館から借りている本、その他の本という階層で優先度を付けている。
5. ラウンドロビン単位の変更
長らく4までのシステムで安定していたが、10ページというラウンドロビン切替え単位をページ数ではなく5分という時間単位に変えた。
6. ピックアップ
今年になって導入した仕組みで試行錯誤中。ここまでのシステムでは、自分の蔵書を読む速度がやはり遅くなってしまった。そこで自分の本に期限を付けるピックアップシステムを導入した。
この時、期限をいつにするかが問題となる。現状以下のように決定している。
- ピックアップしている本のうち、最も期限が遠いものの期限が1月以上先の場合: ピックアップしない
- ピックアップしている本がない場合: 今日+1日(明日)を期限とする
- ピックアップしている本が1冊だけの場合: その本の期限+1日を期限とする
- ピックアップしている本が複数の場合: ピックアップしている本の期限のうち最も遠い2つをkとlとおき、l+(l-k)+1を期限とする。たとえば、10月1日と10月3日が期限の本があれば、新しい期限は10月6日となる。
外出する
外出する時には本を3冊以上持っていく。コミックは0.5冊で計算する。この時、歩きながらで読めるぐらいのサイズの本を2冊含める。
前述の優先度に従って読む本をつんでいき、3冊以上かつ小さいの2冊になるまでひいていく。場合によっては小さいのが出てくるまで何十冊もつむこともある。
その後、持っていけるサイズになるまで減らしていく。この時、優先度処理を逆に減らす本を決定していく。すなわち、レベル3までの本がつまれている場合、レベル2の本を外せるのは、最後に外した本がレベル3の本である場合に限る
外出中は本とNexusを5分で切替える。本 -> Nexus -> 本 -> Nexus となる。 Nexusはさらに、2ch・Jコミ・Chrome・論文・Kindleを読んでいるので、本 -> Nexus(2ch) -> 本 -> Nexus(Jコミ) -> 本 -> Nexus(Chrome) -> 本といった読み方になる
自分向け: ファイルシステムが壊れた時に読むエントリ
やあ (´・ω・`)
ようこそ、このエントリへ。
このテキーラはサービスだから、まず飲んで落ち着いて欲しい。
うん、「また」なんだ。済まない。
仏の顔もって言うしね、謝って許してもらおうとも思っていない。
でも、このエントリを見たとき、君は、きっと言葉では言い表せない
「ときめき」みたいなものを感じてくれたと思う。
殺伐とした世の中で、そういう気持ちを忘れないで欲しい
そう思って、このエントリを書いたんだ。
じゃあ、データの復旧を始めようか。
まずは最新データをとりだす
ファイルシステムが読みこみ専用でmountできるなら、とりあえずデータを抜き出しておこう。あとでいろいろ捗るぞ。
できれば、baculaに最新データを読ませたいけど、この方法はまだ確立していない。うまく作業領域をtmpfsでmountすればできるような気もする。
mountすらできない状態からデータを取り出してくれる btrfs restoreも使えるかもしれない。
データを復旧する
とりあえず systemrescuecdでbootするんだ。話はそれからだ。
/dev/vg2/storageを/mnt/gentooにmountしてごらん。そこに recover.shがあるはずだ。 念のために gistにも貼っておこう。 https://gist.github.com/naota/929bf442efbecee5df7b
このスクリプトを動かせば、FSを新しく作り直して、しかるべくmountして、chrootしてくれる。chrootした先には、データ復旧用の環境がある。 ようするに、stage3をunpackしてbaculaをemergeして、/etc/baculaをコピーしただけのところだ。もしこのchroot環境を新しく作る時には、backup jobの設定を削除していた方がいい。データ復旧用環境のデータで、本物の環境が上書きされてしまわないように。
chrootした環境では元環境と同じ設定のbaculaが動いているので、bconsoleをたたけばあっというまにrestoreできる。
とはいえ、時々restoreは失敗するようだ。その時は最後に"yes/no/modify"のところでnoといって、bextract -b /var/lib/bacula/なんたら.bsr FileStorage /mnt/btrfs するといい。エラーを無視して、extractしてくれる。実のところ、こっちの方がどこまでextractしてるかの進捗が見えてべんりだ。
もしも、もしもうっかり、変なデータが復旧された時、たとえばデータ復旧環境のデータでバックアップが上書きされてしまった時には、restoreで日付を指定するといい。
復旧後の作業
データ復旧したからと言って気をぬいてはいけない。FSが新しくなったのでいろいろな作業が必要だ。
まず /mnt/btrfsにchrootしてくれ。
次に mount /boot/efi してから grub2-install だ。新しい/bootの位置をgrubに書きこむんだ。
そして grub2-mkconfig -o /boot/grub/grub.cfg でGRUBの設定ファイルを再生成だ。FSのUUIDが変わっているから、そのままではinitrdが困ってしまうぞ。
準備はいいか? じゃあ、一番上までchrootを抜けてrebootだ
マシンが立ち上がったら
!!!! マシンが立ち上がったからといってうかれてそのままCivを始めたりしてはいけない !!!!!
まずは、必ず絶対に最初に間違いなく確実にbaculaのバックアップが動くかどうかを確認するんだ! 絶対!確実に!!必ず!!!なにをおいても!!!!! あとでデータがまき戻って泣くことになるぞ!!!
あと、snapperとかdockerあたりもいろいろめんどくなってるからぼちぼち直しとくといいよ。このへんはしょせんどーでもいいけどね。
その他: BIOSがリセットされたら
困ったことに、時々、BIOSがリセットされてしまうことがある。そうすると、EFI bootエントリが消えてしまう。
あわてずに、まずはboot時にF1を押してBIOSを設定してほしい。Intel VTをONにして、EFI bootをデフォルトにするんだ。
そして、Gentoo PenguinのUSBメモリをさして起動してほしい。 今度はboot時にDELを押せばEFI設定に入れる。この時、HDMIのモニタはEFI設定画面になるまでつかない。右側のモニタの電源をいれるんだ。
systemrescueがGentoo Penguinから立ち上がれば、後はもうわかるだろう? chrootして、grub2-install するだけだ。Good luck!
大卒しました
なんかTL見てたら思い出したので大卒エントリを書くことにした。
3月に大卒しました。6年目で大学クリアです。やったね。大学生活、だいたいつらいあんど楽しいでした。だいたい人生的なものでなにも変わらない。
1年目(2008年)はめっちゃこう遊んでOSS開発とかして体育と基礎教養と英語とドイツ語を落としてたら、コース配属が第3希望数理になって悲しくなりました。数理だとたとえばOSとかの授業とっても単位にならないしルベーグ積分とか確率論やるしかないというところですね。
2年目(2009年)はなんとか数理の専門科目をとってたんじゃないかなーって思います。 それ以外はだらだらと人生を送っていたのでしょう。この時も体育とってない気がする。数理になってショックの多い日々だった。この年の10月にTwitterをはじめた模様。それからいろいろあったもんだ。
3年目(2010年)この年最初のGoogle Summer of Codeをして、GentooをDragonFlyに移植してイキイキしてました。それまでもemacs-w3mやnavi2chをやってたりしたけれど、OSS開発をしてGoogleから金がもらえておいしさがありましたね。いま思うとここから親受けがよくなっていろいろと楽になったのでしょう。いろんな勉強会に行ったりセプキャンLinux組に参加したりして関係性が大きく拡大しました。いまの自分の基礎となるような年ですね。 授業?なんのことです? ゼミはマンツーマンで数学の本を読んでつらかった。そのつらさで残り全てがきえさったのだな?
4年目(2011年) 進級できるだけの単位があるはずがなかった。ちょっと真剣に授業などしていましたが、それでもOSSをやってたんだと思う。Gentooコミッタに多分なった年。 去年にひきつづきLinuxカーネルHFS+ portingでGSoCに出たけど、中間でだめだったよ。いま思うといろいろあますぎたんだ。
5年目(2012年) 卒業しようと心にきめるぐらいに単位がたまってきてがんばった。4年の時の研究室の先生と相談して、がんばるぞーみたいな感じになったのも大きい。ひたすら授業を受けて単位を回収した年。ルベーグを回収した。確率論を回収した。ぼくでも卒業できるんだ! この年から雑誌の連載記事を書きはじめた。生活が安定して自活できる感じになっていった。自分の金で生きていくとよい。
6年目(2013年) 卒業にむけていろいろした年。このままいまの研究室に進むのはむりだーだったので、他の院試をうけた。結局一個所しか受けなかった。面接はやばかったらしい(ひやひやだ。 はためから見たら数学やってた人がきゅうにOSに来たので、なのでしかたがない。ゼミの輪講ではほそぼそと一人でPRMLを読んでいた。PRML読みたいといって読ませてくれたので感謝がある。なんだかんだいい大学だったのかもしれないとの思いをえて終わりよければ全てよしにできた。引続き連載が続けられてよかった。この年にOSS奨励者賞をもらった。活動していく気持ちが高まった賞状だった。
卒業式の日は新居にいて直前まで行こうかな?どうするかな?とか言っていたのだけれど、結局その日がいんたーねっつ開通の日だったので行かなかった。学位記は後で届いて、うちのデスクトップの上においてある。
結局学部でなにをえたのかというと6年もいて、楽しかったのやら苦しかったのやらわからないままに終わってしまった。もったいないことだ。学部生を見ていると楽しそうに論文読んでたりするし自分ももっと論文を読みたかったと思う。論文もっと知って読んでればもっといろいろ時間があっていろいろできただろうにもったいないことをした。
逆に高校の時に何を大学に見ていたかというと、師匠的な人を見つけられるといいなと思っていた。だけど、うまく大学でマッチングできずに終わってしまったという気がする。もし大学一年の時に圧倒的に興味をひかれるような人に会っていたらどうなっていたんだろう。それともひねてたからそんな人に会ってもつーんと終わっていただろうか。
OSSでの活動という面では6年間でかなりいろいろな活動・活躍ができたと言える。ただ、大学で獲得すべきものを獲得したのか?と聞かれるととても困る。最初の方は授業は出てなかった単位は全然とれてなかったしもうめちゃくちゃだよ。でも、なんだかんだうまくいったしやったなーよかったなあという気持ちがいまは強い。でも、なんというか、成しえるまでは長い道がある。そこで死にそうになるし、その先の保証も特にされてない。自分だってもしかしたら全然だめだったかもしれない。でも楽しかったしいいのかな。いいということにしようか。良い日々だった。
まあ結局人と会うと意外と楽しいのでもっと会おうというような6年間だったのかもしれない。
Emacsを高速起動せよ #kansaiemacs
この前の関西Emacsで発表?した内容を完結させた感じのエントリです。この動画の説明です。
みんなEmacsを高速起動したいですよね。ぼくもしたいです。 今回はカーネルの視点からEmacsを高速起動しちゃうよ。
まず実験用に ~/nisehome をHOMEにしてmelpaのパッケージをほぼ全てインストールして、さらにほとんどのパッケージ内のelispをrequireします。すると、めっちゃ起動が遅くなってくれます。具体的に見てみると
$ echo 3 | sudo tee /proc/sys/vm/drop_caches $ HOME=$HOME/nisehome emacs
で(ファイルキャッシュを全て落としてから)起動してやって (emacs-init-time) を見てやると "348.6 seconds" となってます。だいぶおそいですね。やばい感じです。
このままでは今回の高速起動の対象にはできないので、まず新しいnamespaceに閉じ込めて、VNCの中でEmacsを起動します。
criu のwikiページ VNC - CRIU から newns.c とシェルスクリプトをとってきて起動します。
# ./newns ./newvnc env HOME=/home/naota/nisehome DBUS_SYSTEM_BUS_ADDRESS=unix:/tmp/foo /usr/bin/emacs
ここで DBUS_SYSTEM_BUS_ADDRESS を存在しないダミーのファイルに設定することで、システムのdbus、すなわちnamespaceの外のプロセスとのunix socket接続を防止します。
"vncviewer localhost:5925" するとEmacsが立ち上がっていくのがわかります。
pstreeでnewvncのプロセスIDを確認し、このプロセスツリーをダンプします。 lsすると大量にファイルができているのがわかります。ダンプされたデータです。 さらに pstree|grep vnc してもなにも出てきません。さっきあった VNCやEmacsのプロセスは全てダンプされて、停止されています。
# pstree -p|grep Xvnc |-newvnc(15079)-+-Xvnc(15080)-+-{Xvnc}(15083) | | |-{Xvnc}(15084) | | |-{Xvnc}(15085) | | `-{Xvnc}(15086) # mkdir emacs-dump; cd emacs-dump # criu dump -t 15079 --tcp-established # ls core-1.img core-7.img fdinfo-12.img filelocks-49.img ids-2.img itimers-2.img packetsk.img pages-7.img rlimit-103.img sigacts-49.img signal-p-5.img tunfile.img core-103.img core-9.img fdinfo-2.img filelocks-9.img ids-20.img itimers-20.img pagemap-1.img pages-8.img rlimit-106.img sigacts-9.img signal-p-6.img unixsk.img core-104.img creds-1.img fdinfo-3.img fs-1.img ids-23.img itimers-23.img pagemap-103.img pages-9.img rlimit-13.img signal-p-1.img signal-p-7.img utsns-1.img core-106.img creds-103.img fdinfo-4.img fs-103.img ids-49.img itimers-49.img pagemap-106.img pipes-data.img rlimit-14.img signal-p-103.img signal-p-9.img vmas-1.img core-107.img creds-106.img fdinfo-5.img fs-106.img ids-9.img itimers-9.img pagemap-13.img pipes.img rlimit-16.img signal-p-104.img signal-s-1.img vmas-103.img core-109.img creds-13.img fdinfo-6.img fs-13.img ifaddr-1.img mm-1.img pagemap-14.img posix-timers-1.img rlimit-2.img signal-p-106.img signal-s-103.img vmas-106.img core-13.img creds-14.img fdinfo-7.img fs-14.img inetsk.img mm-103.img pagemap-16.img posix-timers-103.img rlimit-20.img signal-p-107.img signal-s-106.img vmas-13.img core-14.img creds-16.img fdinfo-8.img fs-16.img inotify-wd.img mm-106.img pagemap-2.img posix-timers-106.img rlimit-23.img signal-p-109.img signal-s-13.img vmas-14.img core-16.img creds-2.img fdinfo-9.img fs-2.img inotify.img mm-13.img pagemap-20.img posix-timers-13.img rlimit-49.img signal-p-13.img signal-s-14.img vmas-16.img core-17.img creds-20.img fifo-data.img fs-20.img inventory.img mm-14.img pagemap-23.img posix-timers-14.img rlimit-9.img signal-p-14.img signal-s-16.img vmas-2.img core-19.img creds-23.img fifo.img fs-23.img ipcns-msg-1.img mm-16.img pagemap-49.img posix-timers-16.img route-1.img signal-p-16.img signal-s-2.img vmas-20.img core-2.img creds-49.img filelocks-1.img fs-49.img ipcns-sem-1.img mm-2.img pagemap-9.img posix-timers-2.img sigacts-1.img signal-p-17.img signal-s-20.img vmas-23.img core-20.img creds-9.img filelocks-103.img fs-9.img ipcns-shm-1.img mm-20.img pages-1.img posix-timers-20.img sigacts-103.img signal-p-19.img signal-s-23.img vmas-49.img core-21.img eventfd.img filelocks-106.img ghost-file-1.img ipcns-var-1.img mm-23.img pages-10.img posix-timers-23.img sigacts-106.img signal-p-2.img signal-s-49.img vmas-9.img core-23.img eventpoll-tfd.img filelocks-13.img ids-1.img itimers-1.img mm-49.img pages-11.img posix-timers-49.img sigacts-13.img signal-p-20.img signal-s-9.img core-24.img eventpoll.img filelocks-14.img ids-103.img itimers-103.img mm-9.img pages-2.img posix-timers-9.img sigacts-14.img signal-p-21.img signalfd.img core-4.img fanotify-mark.img filelocks-16.img ids-106.img itimers-106.img mountpoints-1.img pages-3.img pstree.img sigacts-16.img signal-p-23.img sk-queues.img core-49.img fanotify.img filelocks-2.img ids-13.img itimers-13.img netdev-1.img pages-4.img reg-files.img sigacts-2.img signal-p-24.img stats-dump core-5.img fdinfo-10.img filelocks-20.img ids-14.img itimers-14.img netlinksk.img pages-5.img remap-fpath.img sigacts-20.img signal-p-4.img tty-info.img core-6.img fdinfo-11.img filelocks-23.img ids-16.img itimers-16.img ns-files.img pages-6.img rlimit-1.img sigacts-23.img signal-p-49.img tty.img # pstree -p|grep vnc
では、復活させてみましょう。 キャッシュを落としてから復活させても 1秒で復活しています。 これが終わればふたたび "vncviewer localhost:5925" でつなげば Emacsの画面が出てきます。 elispを全て読み終わった状態でdumpしているので、elispの解析やら実行やらが全てスキップされて高速化しているわけですね。
# echo 3 > /proc/sys/vm/drop_caches # time criu restore --tcp-established -d (00.071714) 107: Error (image.c:213): Unable to open vmas-107.img: No such file or directory real 0m1.240s user 0m0.000s sys 0m0.010s
ここで、つないだ VNC で C-x C-c します。当然 Emacsと VNCが終了します。 ではもう一度 restore して vncviewer でつないでみましょう。
# time criu restore --tcp-established -d (00.065680) 107: Error (image.c:213): Unable to open vmas-107.img: No such file or directory real 0m0.422s user 0m0.000s sys 0m0.010s
ふたたび Emacsが VNCで動いているのが見てとれるかと思います。サイコーですね。 Emacsは死なぬ! 何度でもよみがえるさ! ってやつです。
最後に背景について見ていきましょう。 今回使ったのは CRIU (checkpoint/restore in userspace)というものです。 これはプロセスの状態をディスクにダンプし、そのダンプデータからプロセスを再開するものです。
通常はXへの描画などをしていると Xと Emacs間の通信が切れてしまうのでX上で動く Emacsをダンプすることはできません。しかし、EmacsをVNC上で起動し、さらに1つの独立した namespace上でVNC(とEmacs)を起動します。 これでVNC下のプロセスをまとめてダンプできるようになります。
criuコマンドを使うにはカーネル3.11以上が必要です。さらに、いくつかカーネルの設定が必要です。 以下のものを有効にしておいてください。
- CHECKPOINT_RESTORE
- FHANDLE
- EVENTFD
- EPOLL
- INOTIFY_USER
- IA32_EMULATION
- UNIX_DIAG
- INET_DIAG
- INET_UDP_DIAG
- PACKET_DIAG
- NETLINK_DIAG
criuコマンドは気のきいたディストリビューションならパッケージがあるかと思います。 Gentooにはありますし、emerge時に以上のカーネル設定もチェックしてくれるので間違いがありません。 お使いのディストリビューションの気がきいていなければ、 Gentoo をいれてみるのもいいでしょう。これから寒くもなりますし。
criuコマンドが入ったら"criu check"もしておきましょう。基本的なチェックが走ります。あとはおまけですが iptablesとか tunとかもいれとくといいです。 TCP接続を保ったままダンプできたりしてクールです。
Gentooビルドクラスタを作りたいということ 多分次のGenTwooに向けて
https://github.com/naota/portage-daemon
最近こんなのを作ってた。
master: address: localhost builder: command: sudo emerge -1 %s 2>&1 || true target: linux.amd64
fbsd.yamlを
master: address: localhost builder: command: ssh root@<IP> emerge -1 %s 2>&1 || true target: fbsd.x86
manager.yamlを
master: address: localhost manager: builder: Builder command: Command state-file: manager-state.dump
とかして、RabbitMQを起動してから "./builder linux.yaml", "./builder fbsd.yaml", "./manager manager.yaml"を起動する。
そして "./control manager.yaml build linux.amd64 newt"とかするとローカルで"emerge -1 newt"が走るし、"./control manager.yaml build fbsd.x86 freebsd-lib"とすると、どこかのIPのホストでsshこしに"emerge -1 freebsd-lib"が走る。今回はbuilderを"linux.amd64"と"fbsd.x86"を1つずつしか立ちあげてないけれど、別のyaml書いて、たとえば"linux.amd64"をうけつけてsshでどっかのGentoo Linux上でemerge -1するように書くとそっちにも適当に(たしかラウンドロビンに?)分散される。
emergeの結果はこのように確認できる。
% ./control manager.yaml jobs 86d3c69a-d35d-400e-b5fe-43131eb4bd94 New linux.amd64 newt e00c060f-7baf-4846-8100-fb7ab605d4e6 Done linux.amd64 newt
左端がジョブのUUIDになっているので、これを使って結果を取得する。
% ./control manager.yaml result e00c060f-7baf-4846-8100-fb7ab605d4e6 Calculating dependencies .. ... .. .. ... done! >>> Verifying ebuild manifests >>> Emerging (1 of 1) dev-libs/newt-0.52.15 * newt-0.52.15.tar.gz SHA256 SHA512 WHIRLPOOL size ;-) ... [ ok ] ... ecompressdir: bzip2 -9 /usr/share/man ecompressdir: bzip2 -9 /usr/share/doc >>> Installing (1 of 1) dev-libs/newt-0.52.15 >>> Auto-cleaning packages... >>> No outdated packages were found on your system. * GNU info directory index is up-to-date.
と、こんな感じでemergeの出力が全部とれる。
まあ、わかりにくいので図にしておくとこんな感じ
で、なんでこんなのを作った(作っている)のかというと、1つはGentooのbugzillaにバグレポがいっぱい来て自分の手元で再現実験したり、patch書いてそれが直ってるか確認したりするんだけど、いくつも平行でやってるとどれがどういう状況だったのか忘れてしまったり、emergeが多くなりすぎて重くなったりして困ることである。これを使えば1つのマシンで走るemergeは高々1つになるし、出力もちゃんと(一応)永続的に確認できる。
もう1つは「次の世代のGenTwoo」を実現するため。GenTwooはもともともっとバグレポなどのコラボレーションの敷居というのを下げたいという目的で作っている。最終的にはとりあえず起動しているだけでなにかしらの貢献ができる、という形にしたいのだ。たとえば、新しく入ったebuildが本当にビルドできるのかどうかチェックしたり、ビルドしてエラーが出たらその出力を開発者のところに送りかえしたり、うまくビルドできたという情報・エラーが出ているという情報を集めて統計をとってどのぐらいebuildが安定しているのかの指標にしたり、その指標に従って自動アップデートがかかるかどうかの参考にしたり、バイナリパッケージを提供したりする。そのようなつながりを持ったdistroを実現したいのだ。
その第一歩として、とりあえずビルド命令を受け付けるプロセスは全てローカルで動く(だからsshでemergeの走るマシンにつなぐ)・標準出力がそのまんま結果としてとれる・実行の指定はシェルコマンドそのまんまという大変に原始的な形だが一応クラスタにできそーなものを作ってみた。
これから
- emerge出力の解析によるエラー判定
- それを使ったUSEフラグの調整など
- リモートでビルド命令を受け付けできる
- ebuildにこのpatchをあてた状態でemergeとかUSEをこうしてなど、編集を加えたemergeの実現
- バイナリパッケージをキャッシュしクラスタ上のマシンに提供するプログラム
- 隔離された環境(kvmやlxrなど)でのemergeを実現する
- SQLiteとかDBを使う
- ほんとにHaskellで書いてていいのか(
などとやりたいことは多いし、実際にインターネット越しに展開しようとするとセキュリティ面で考慮することはもっと多いだろう。
まあ、でもこんなことをやりたいしやれたらおもしれーんじゃないかな、と思っている。
おれがsudoできないのはどう考えてもmetalogが悪い話
最近、"sudo emerge" などしようとするとその時点で動きが止まってしまうことが多々あってまーたbtrfsか?と思っていました。でも、psを見てもSTATEは"D"になってないし、btrfsぽくはない。そんなある日metalogを強制終了してやると、止まっていたsudoが動きだすことに気がついたのです。さて、いったいmetalogがなにがどーしてsudoを止めているのかな?
こういう状況の時
- metalogの下にconsolelog.shがゾンビになってたくさんぶらさがっている
- metalogのmasterプロセスをstraceするとwriteの途中で止まっている
- gdbでmetalogのmasterプロセスをbacktraceするとmetalog.cのsignal_doLog_queue()で止まっている
ことがわかります
signal_doLog_queue() はこうなっていて、ここのwriteで停止しています。
static void signal_doLog_queue(const char *fmt, const unsigned int pid, const int status) { ssize_t ret; unsigned int fmt_len = (unsigned int)strlen(fmt); char buf[sizeof(pid) + sizeof(status) + sizeof(fmt_len)]; memcpy(buf, &pid, sizeof(pid)); memcpy(buf+sizeof(pid), &status, sizeof(status)); memcpy(buf+sizeof(pid)+sizeof(status), &fmt_len, sizeof(fmt_len)); ret = write(dolog_queue[1], buf, sizeof(buf)); assert(ret == sizeof(buf)); ret = write(dolog_queue[1], fmt, fmt_len); assert(ret == fmt_len); }
ここでwriteされているdolog_queueは、このようにsocketかpipeになっています。
/* setup the signal handler pipe */ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, dolog_queue) < 0) { if (pipe(dolog_queue) < 0) { warnp("Unable to create a pipe"); return -4; } }
すると読みだしは誰なんだ?というのが気になるところで、読みだしているのは、このmetalogのメイン関数であるprocess()になっています
static int process(const int sockets[]) { ... siglog = syslog = klog = NULL; nfds = 0; siglog = &fds[nfds]; fds[nfds].fd = dolog_queue[0]; fds[nfds].events = POLLIN | POLLERR | POLLHUP | POLLNVAL; fds[nfds].revents = 0; ... for (;;) { while (poll(fds, nfds, -1) < 0 && errno == EINTR) ; /* Signal queue */ if (siglog && siglog->revents) { event = siglog->revents; siglog->revents = 0; if (event != POLLIN) { warn("Signal queue socket error - aborting"); close(siglog->fd); return -1; } signal_doLog_dequeue(); }
こんな感じでpollで読めるfdを見つけて読むという単純なやつですね。 問題になるのが、socketなりpipeなりを自分で読んで自分で書いてます。だから、socketなりのバッファがいっぱいになると相手が読み出してくれるまで待つのだけれど、その読む相手というのがwriteで待っている自分なわけで完全にmetalogが停止してしまう、というわけですよ。(具体的にsocketのバッファサイズを調べたりしたかったがうまくいかず)
さて、じゃあなんでそんなにいっぱいになるほどwriteされてるのか?という問題に入ります。
signal_doLog_queue()はもともと、 SIGCHLDのシグナルハンドラである sigchld(int sig)から、子プロセスが0以外の終了ステータスとなった時に呼ばれている関数です。つまり、最初に書いていた consolelog.shが0以外の返り値で死んだ場合にログが書かれています。
consolelog.shはたったこれだけでエラーになりそうなのは evalの書きこみのところだけですね。
#!/bin/sh # consolelog.sh # For metalog -- log to a console set -f . /etc/conf.d/metalog if [ -z "${CONSOLE}" ] ; then CONSOLE="/dev/console" fi if [ -z "${FORMAT}" ] ; then FORMAT='$1 [$2] $3' fi for d in ${CONSOLE} ; do eval echo ${FORMAT} > ${d} done exit 0
"while : ;do echo foo > /dev/tty10;done"などと書いたスクリプトを大量に実行してみるとIOエラーがすぐに出てエラーで死んでくることがわかるかと思います。
結局のところまとめると
- btrfsが大量にWARNを発生
- metalogがそれをconsolelog.shを使って書こうとする
- 同時に書こうとするのでコンソールがIOエラー
- 多くのconsolelog.shが0以外のステータスで終了
- シグナルハンドラでsocketに書こうとするがバッファがいっぱいで読みだされるまで止まる
- writeで止まっているから読みだし側の処理が行なわれない
こんなことがmetalogで起こっている。 大量のログ書きこみ自体は手元でloggerでやろうとしてもうまくいかないので、なかなか再現できない。
sudoできなくなるのはとてもつらいので、別のloggerに乗り換えようかな。
最近のbtrfsの話(linus head) cleaner の lock とか
最近、うちのbtrfsはよくこういうエラーで死んでいて
% cd /sys/fs/pstore % cat dmesg-efi-3 dmesg-efi-2 dmesg-efi-1 Oops#1 Part3 <3>[16662.858996] [drm:ring_stuck] *ERROR* Kicking stuck wait on render ring <7>[17728.729197] BTRFS debug (device sde1): unlinked 3 orphans <7>[17912.803995] BTRFS debug (device sde1): unlinked 8 orphans <4>[21166.871256] general protection fault: 0000 [#1] SMP <4>[21166.871287] Modules linked in: macvtap macvlan ebtables xt_CHECKSUM iptable_mangle hwmon_vid nfsd auth_rpcgss oid_registry nfs_acl lockd sunrpc i915 snd_hda_codec_hdmi snd_hda_codec_realtek snd_hda_intel i2c_algo_bit intel_agp intel_gtt drm_kms_helper snd_hda_codec drm agpgart i2c_i801 pata_via <4>[21166.871414] CPU: 7 PID: 258 Comm: btrfs-cleaner Tainted: G W 3.11.0-rc1+ #271 <4>[21166.871439] Hardware name: System manufacturer System Product Name/P8H67-M PRO, BIOS 3806 08/20/2012 <4>[21166.871469] task: ffff8805fb0c0000 ti: ffff8805faff0000 task.ti: ffff8805faff0000 <4>[21166.871493] RIP: 0010:[<ffffffff812e70c9>] [<ffffffff812e70c9>] btrfs_clean_one_deleted_snapshot+0x49/0x150 Oops#1 Part2 <4>[21166.871527] RSP: 0018:ffff8805faff1e70 EFLAGS: 00010283 <4>[21166.871546] RAX: dead000000200200 RBX: ffff880323212458 RCX: 0000000000003b79 <4>[21166.871569] RDX: dead000000100100 RSI: 0000000000000246 RDI: ffff8805fb52c798 <4>[21166.871591] RBP: ffff8805faff1e88 R08: 0000000000000000 R09: 0000000000000001 <4>[21166.871612] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8805fb52c000 <4>[21166.871633] R13: ffff880323212000 R14: ffff8805fb0c0000 R15: 0000000000000000 <4>[21166.871655] FS: 0000000000000000(0000) GS:ffff88061f3c0000(0000) knlGS:0000000000000000 <4>[21166.871680] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 <4>[21166.871698] CR2: 000012db7ef1c008 CR3: 000000000ec0c000 CR4: 00000000000427e0 <4>[21166.871719] Stack: <4>[21166.871730] ffff8805fb48a800 ffff8805fb0c0000 ffff8805fb0c0000 ffff8805faff1ec8 <4>[21166.871759] ffffffff812df5d0 0000000000000000 ffff8805fb817a48 ffff8805fb48a800 Oops#1 Part1 <4>[21166.871786] ffffffff812df4b0 0000000000000000 0000000000000000 ffff8805faff1f48 <4>[21166.871815] Call Trace: <4>[21166.871829] [<ffffffff812df5d0>] cleaner_kthread+0x120/0x180 <4>[21166.871847] [<ffffffff812df4b0>] ? check_leaf.isra.41+0x310/0x310 <4>[21166.871869] [<ffffffff810aa4d0>] kthread+0xc0/0xd0 <4>[21166.871886] [<ffffffff810aa410>] ? kthread_create_on_node+0x120/0x120 <4>[21166.871910] [<ffffffff81703f1c>] ret_from_fork+0x7c/0xb0 <4>[21166.871930] [<ffffffff810aa410>] ? kthread_create_on_node+0x120/0x120 <4>[21166.871951] Code: 3e 47 41 00 49 8b 9c 24 d8 07 00 00 49 8d 84 24 d8 07 00 00 48 39 c3 0f 84 c5 00 00 00 48 8b 43 08 4c 8d ab a8 fb ff ff 48 8b 13 <48> 89 42 08 48 89 10 48 b8 00 01 10 00 00 00 ad de 48 89 03 48 <1>[21166.872091] RIP [<ffffffff812e70c9>] btrfs_clean_one_deleted_snapshot+0x49/0x150 <4>[21166.872117] RSP <ffff8805faff1e70> <4>[21166.876524] ---[ end trace fa76c497d23a7946 ]---
まあ、cronでsnapshotをdeleteしているタイミングで(確率的に)死んでるからそのcron jobとめればいいんだけど、それも気持ちわるいので放置してn時間に1回再起動している。
死んでる当該部分は fs/btrfs/transaction.c の
int btrfs_clean_one_deleted_snapshot(struct btrfs_root *root) { int ret; struct btrfs_fs_info *fs_info = root->fs_info; spin_lock(&fs_info->trans_lock); if (list_empty(&fs_info->dead_roots)) { spin_unlock(&fs_info->trans_lock); return 0; } root = list_first_entry(&fs_info->dead_roots, struct btrfs_root, root_list); list_del(&root->root_list); spin_unlock(&fs_info->trans_lock);
この list_del のところ。 で、 list_delで dumpのRAXとRDXに "dead000000200200" と "dead000000100100" って出ているのでわかる人にはわかるかと思うのだけど、 &root->root_list が list に入ってないのに del_list してることになってる(正確にはlistにいれて消したのにまた消そうとしているはず)
このコードの部分は trans_lockで spinlock されてるし 他の部分も
inode.c
spin_lock(&fs_info->trans_lock); list_for_each_entry(dead_root, &fs_info->dead_roots, root_list) { if (dead_root->root_key.objectid == found_key.objectid) { is_dead_root = 1; break; } } spin_unlock(&fs_info->trans_lock);
transaction.c
int btrfs_add_dead_root(struct btrfs_root *root) { spin_lock(&root->fs_info->trans_lock); list_add_tail(&root->root_list, &root->fs_info->dead_roots); spin_unlock(&root->fs_info->trans_lock);
ちゃんと trans_lock で lockされてるし、あやしげな
disk-io.c
static void del_fs_roots(struct btrfs_fs_info *fs_info) { int ret; struct btrfs_root *gang[8]; int i; while (!list_empty(&fs_info->dead_roots)) { gang[0] = list_entry(fs_info->dead_roots.next, struct btrfs_root, root_list); list_del(&gang[0]->root_list); if (gang[0]->in_radix) { btrfs_drop_and_free_fs_root(fs_info, gang[0]); } else { free_extent_buffer(gang[0]->node); free_extent_buffer(gang[0]->commit_root); btrfs_put_fs_root(gang[0]); } }
これも、 open_ctreeとclose_ctreeからだけ呼ばれてこのタイミングでは cleaner スレッドは止まっているはずなので、こことぶつかってることはありえない…(と思う)
そーすると、一体どこがどうなって btrfs_clean_one_deleted_snapshot があんな死に方をしてしまうのかまったくもって謎めいている。なにがどうなってるのやら