2009年1月2日

今年はエロで行きましょう!!

あけましておめでとうございます。今年もよろしくお願いいたします。


MS-IMEには「IMEパッド -手書き-」という機能があります。マウスとかで文字を描くと、対応する漢字と思しきものの候補を出してくれる、という「字が読めない」私にはとても便利な代物です。

さて、このIMEパッドに新年の書初めを。ええ、縁起のいい文字を。とりあえず。これ。

はい。


エロ


でございます。さて、どのような文字に認識されるでしょうか?!


次のようになりました。


思った以上に縁起が良い。





とでました。こりゃ面白い。調子に乗って他のパターンも入れてみます。


エロは「扱う」に限るそうです。エロは吉でもありましたから、これは


今年、景気を良くしたかったらエロを解放せよ


というお告げに違いありません(Microsoft の??)


じゃ、次。「ツンデレ」行ってみましょう。

んー、ちょっと難しすぎたかな。混乱しているようです。もう少し簡単にするために「デ」を「テ」にしてみましょう。



!!栄える!!



ツンデレ、今年も栄えますか…つーかちょっと無理ありすぎです。次行ってみよう!!



あぁっっっ!!!


今年もロリ、受難!!!


只、叩かれるのみのようです。

と言うわけで結論!! 今年のこの突如として吹き荒れる大不況、脱出するには:
  1. エロ解放は絶対条件
  2. ツンデレ属性は念のためにつけておけ
  3. ロリはやめておけ。せいぜい女子校生からにしておけ。若奥様なんか狙い目だ。

こんなんでました~~

2008年12月7日

google のボットは javascript 生成コンテキストを追跡できないのだろうか??

fjの教祖様 の蔵書/既読書@GeoCities というページを持っている。

ここのインデックス部分を、全てのページで static に保持すると物凄くディスク容量を喰う(全体の 3/4 がインデックスだった事が判明している)上に、同じデータを何度も何度もクライアントに転送するのは重たい。さらに言うと、インデックスに何か追加されるたびに全体が再構成、サーバへも再uploadするため耐え難い…というので、ここをどうにかして client side include (CSI)できないかと考え… javascript で生成する事にした。Javascriptならば <script src="xxxx"> と記載すれば別途読み込める。

読み込んだ関数はすごく馬鹿みたいで、document.write() を使って本来そこにあるべきインデックスを現す html 命令をひたすら書き出しているだけだ。すごく簡単だが、結構強力。なによりビルドの相互依存性や再構築範囲の大幅な減少はありがたかった。

ありがたかったのだが、しばらく運用して判った事として、Google のエンジンがこの javascript による URL 展開結果を追跡できないらしい。この変更以降に作られたページが全くでてこないし、それ以前からあるページも古いイメージ分しか検索されない。新しく登録した本とかが出てこないのだ。

うーむ。検索性が下がるのは、それはそれで困るなぁ。だからといって、転送量が増えるのはオリジナル→サーバも、サーバ→クライアントも、困る。

何かいい手を考えなくてはいけないなぁ。

2008年11月11日

shell は奥が深い…

/.J の記事の話。

(便利で)くだらないUnix技は?」の「Re:びっくりマーク」に対するコメントから始まる一連。

いやー失敗。

よく知っているパターンとして
% ^aaa^bbb                             が「最初の aaa を bbb に置き換える」
と言うのがあったので、それと空目していました。
% ^aaa^bbb^
そう。まさか最後にもう一つ「^」がついていたとは。

確かに「最初の2つの aaa を bbb に入れ替えたい」場合は

%^aaa^bbb^:&

がよさそうです。

ちなみに、
以外で呼んだ事が無いのですが、全部確実に置換したければ

%!!:gs/aaa/bbb/

とやるのだそうです。また、: の後ろに置換コマンドを書くことが出来るそうです。例えば、

%!!:gs/aaa/bbb/:gs/ccc/ddd/

とかの感じですね。

知っていればもっと楽になった事が多々あったのにぃ~~~

2008年10月26日

聘珍樓


Amazonのサイトに食い物系が追加されたと聞いてみていたら、なんと「聘珍樓」の商品を扱っていた。
おぉう。聘珍樓と言うとアレだな。新宿三井ビル54階にある中華料理屋。
55階をEMCのマシンルームに取られる、という屈辱を味わっている。つーかEMC、明らかに立地間違えてるだろうが。なんで本来展望ルームかレストランがあるべきところにマシンルームなんだよ。

というわけで。とりあえず美味そうなものを選んでぐるぐるウィジェットにしてみました。
誰かおごってください (^o-)。

東大合格生のノートには汚いものもある

『東大合格生のノートはかならず美しい』
えー??! うっそだー!?
それがタイトルを聴いた瞬間の感想。だって自分のものでこそ無いが、東大生のノートなら知ってる。家族にいるからだが。綺麗でもなければまとまってもいないぞ。

東工大生のノートも知ってるし、早稲田・慶応の理工学部合格者のノートも知っている(これは全部自分)。東大生に輪を掛けて汚い。

と言うわけで。何を勘違いしているんだろう??! と思い立って本を買ってみてみました。一瞬で納得。著者である「太田あや」はノートには3種類もある事を知らないらしい。知らなかったら、そりゃ勘違いするわ。

と言うわけで、せっかくの受験シーズン突入期。ノートの種類と共通する特徴についてちょっと書いて見ましょう。


まず、ノートの種類から。ノートには大雑把に3種類あります。

  1. 資料的ノート

  2. 答案用紙的ノート

  3. メモ


資料的ノート


『資料的ノート』というのが、『東大合格生のノートはかならず美しい』という本で集めているノートです。自前の参考書のようなものですね。確かにこの手のノートは長く保存するものですし、丁寧に作るでしょう。もしあなたがこの手のノートを作るタイプならば

そう、本質的に資料を作らない人のほうが多いんです。だってそんなのは参考書でいくらでも代替が利く。適切な参考書をいかに最初に吟味して選ぶか、そこをちゃんとやっていればこの手の資料は作らなくてもいい。

逆にこの手の資料を作るタイプの人は「資料を作る過程で」物事を覚えていく。なのでこのタイプのノートを読み返しているとき、その人は実際には
「そのノートを作っている自分をリフレインする形で記憶を更新している」
のであって、そのノートに書かれていることそのものを覚え直しているのではない、と言うことも念頭に置いた方がよいでしょう。

答案用紙的ノート


『答案用紙的ノート』は問題集を解くためのノート。問題集を解く時に使うノートです。これは単に問題を解くために使うのではありません。自分がどのようにして問題を解いたのかを分析するためにも使います。

答案用紙的ノートを普段のノートと分けていない人は意外と多い。しかし考えて見れば『資料的ノート』の中に、問題集を解いた結果が混ざっているのは読みにくい。『答案用紙的ノート』の中に『資料的ノート』が挟まっているのも(いろいろカンニングが出来て)都合が悪い。なので、ノートとしてこれは独立させる必要があるのです。

メモ


『メモ』は、授業などで使うノートです。授業中と言うのは授業の流れに応じて先生の説明を記録したり、与えられた問題を解いたり、暇になったら落書きしたり…と実に無茶苦茶な使われ方をするものです。なのでメモ用ノートは『資料的ノート』『答案用紙的ノート』のいずれとも分けなくてはいけません。




さて。殆どの人は『メモ』タイプのノートしか持っていないのではないでしょうか?
これに対して、『東大合格生のノートはかならず美しい』で集めているのは資料的ノート。そりゃこの2つを比較したら「東大に合格する奴のノートは違うんだなぁ」と勘違いするでしょう。

でも実際には「別のノートを見せられている」から違うに決まっています。投入する力が違うんですから見てくれからして違うに決まっている。

「なるほど。じゃぁ全く参考にならないのか」

そんな事はありません。資料的ノートとして見るならば十分参考になるでしょう。あと全体でなるべく共有するべき特徴があります。

  1. 余白を十分に取る

  2. 区切りを大事にする

  3. 丁寧に書く

  4. バインダーを使わない(メモは「なるべく」。残り2つは「絶対」)


最初の3つは『東大合格生のノートはかならず美しい』でも述べられていますね。


余白を十分に取る


余白はノートの表面を眼が流れていくときに、自分にとって重要な情報がどこに書かれているのかを知る重要な手がかりです。無駄に文字を密集させたノートは読みづらく、故に読むのに時間がかかります。「資料」「メモ」の2種類についてこの性質が重要なのは言うまでもありません。

「答案用紙」タイプでもこの性質は重要だ、と言うのを知らない人が多いのが問題です。問題を解く場合、特に大学受験の数学は、条件分岐と部分証明の繰り返しが頻発します。極めて煩雑な作業で、途中で気が遠くなる。このときに見通しの悪い回答だと、証明するべき内容を1つ2つすっ飛ばしてしまう、などのミスが発生します。ほんの数分未来の自分がミスをしないようにするために、視認性の高い回答を書く必要があるのです。

あと、もう1つ。「答案用紙」タイプは問題を解くだけでなく、自分がどのように問題を解いたのか、その分析にも使います。このため分析段階で特徴などを見つけ出し情報を記載する空間もなくてはいけません。このためにも余白は十二分に取る必要があるのです。

私の場合、「答案用紙」の場合、キャンパスノートタイプにし、右半分しか使いません。左半分はメモ用。バインダーにしないのは「左右でワンセット」だからです。

区切りを大事にする


東大合格生のノートはかならず美しい』では、見開き単位での区切りについて述べていますが、ノートそのものの区切りと言うものも同じように大切にしているはずです。どのジャンルでもサブジャンルと言うものがありますが、それらの単位でノートはまとまっているでしょう。

ようするに「この内容を知りたければどのノートを見ればいいのか」という段階で混ざらないようにしてあるわけです。

なので、ノートのページ数は少ない目に、冊数は多い目に、というのがノートを買う段階でのコツになります。

答案用紙タイプの場合、答の区切り、と言うものも大事になります。特に数学の証明問題は、
1) 問題の分割
2) 各部分問題の解
3) 全体をまとめ直して問題全体に対する解にする
という3段階に分割できるものです。1,2,3がそれぞれどこからどこまでなのか、2は各部分解がどこからどこまでなのか、判るように記載するべきです。これには「余白」も大事になりますし、「丁寧に書く」事も大事になりますが、同じぐらい「区切り」単位で証明等を離して書く、などの対処にも繋がります。


他に面白い『区切り』として、物理の力学と数学を混ぜるというのもあります。

ご存知の人はご存知の通り、微積分とベクトル・行列はそもそもニュートン力学を表現する上で必須の知識です。逆の言い方をすると、微積分と線形代数を先に、ニュートン力学を後に勉強すると苦労が大幅に減る。この場合、力学と数学を別々に勉強してしまうと逆に苦労が増える。なのでこの2つはわざと「物理・数学のように区切らずに」ノートを作る。

丁寧に書く


メモは比較的走り書きにするでしょう。資料は丁寧に書くのは(後で見直すことを考えれば)当然です。
答案用紙も実は丁寧に書くべきです。これは最終的に他の人に見せ、それによって採点を受ける答案を書くための練習台だからです。字が汚くて☓というのがうれしい人は、そうそういないと思います。

丁寧に書いていれば綺麗かどうかはともかく「読み易い字」を早く書くことができるようになりますしね。その意味では「丸文字」等の変形文字は使わないことも重要です。

バインダーは使わない


「余白」の部分ですでに説明しましたし、『東大合格生のノートはかならず美しい』にもありますが、資料・答案用紙型のノートは「見開き単位で」埋めていくものです。

しかるにバインダー型ノートは「紙単位」で管理するもの。見開きの左右がばらばらになってしまう危険性があるので、資料・答案用紙についてはバインダーを使わないほうがよいのです。

一方でメモは片面単位で使うことも出来ますし、1枚単位で追加削除できる方が利便性が高まります。なのでこちらはバインダーでも構いません。ただし、ノートはバルクで買うとどんどん安くなるので…気分転換以外の理由でバインダー型を併用するメリットはあまりありません。




というわけで。以上の条件をちゃんと満たしていればOK。インデックスなんて無くても構いません。文頭が揃ってなくてもOK。全く問題はありません。

ちなみに。
この本は「コクヨ」の CAMPASノートのニューバージョンの広告を兼ねています。が、私に言わせれば、ドットを打つよりもプロジェクトペーパー(7mmぐらいの方眼用紙)でノートを作ってもらった方が、よほど使いやすいと思いますね。これを2x2を1枡として使っていく。そのほうが快適なノートが作れます。

2008年10月11日

た…正しい、正しすぎる…

そもそも「止まってはいけない」「RCA」と叫ぶばかりで運用と言う概念が無い会社が多すぎるのが悪い

/.J のメインフレーム技術者不足、問題にという議論より。

そもそも、労働者の供給市場としてメインフレーム技術者が需要過剰になる、と言うことは2通りしかありえない。需要が急激に増えて供給が追いつかないか、世界的・汎業界的なトレンドに反している特殊需要者がいるか、のどちらか。そしてこの場合は後者に決まっている。

そもそも日本の客はオープンソースにメインフレーム並みのサービスや機能を要求するなど、非常識極まりない。「あるものを使う」のではなく「欲しいものを述べてふんぞり返る」事しかしないのだ。その結果、金払いはいいが、コストが掛かりすぎるので利益が圧倒的に少ない、という状態に陥る。受注する側にすれば迷惑な存在でしかないのだ。そのことが判らない、売上げばかりに目が行って純利益を考えない経営者が多すぎる、と言うのも問題だが。

良い機会だ。放置してしまえばよい。誰も対応しないのが一番だ。売上げはたくさん立つだろうがどうせ儲からないのだから。

2008年9月28日

日本の湿気った空気には、水冷が良い

日本の夏は蒸し暑い。これは日本人ならば誰でも知っていると思う。

日中最高気温は35度以上になり、40度だって当たり前。
なのに湿度が60%を下回る事はない。恒温動物にとって地獄のような環境である。

さて、一方その頃(え?)。

飽和水蒸気量という単語をご存知だろうか? Wikipediaへのリンクをくっつけてあるが、ようするに 1m3の空間中に存在できる水蒸気の質量をgで現したもの、だそうだ。当然、これは100%のときの質量であって、たとえば湿度が60%の場合、存在している湿度の量はその60%になる。

で、大事なのはその曲線の形状だ。Wikipediaの画像をちょいと無断拝借:

飽和蒸気圧曲線

数字はどうでもいい。大事なのは形状だ。
わかるだろうか?気温10度から20度へ10度上昇した場合と、20度から30度へ10度上昇した場合とでは、後者の方が増大量が大きい。

問題はこうだ。
データセンターのようなマシンルームでは、室温20~25度、湿度0~30%になるように押さえつけなくてはいけない。内部に格納されている全てのマシンが空冷だからだ。外気温が35度、湿度50%の状態からこの状態へ空気を冷やすにはどれだけの熱量をくみ出せばいいのだろうか?当たり前だが単純に20度に冷やすだけでは湿度は100%になってしまうので、20度のときに湿度30%になるぐらいまで水を減らす必要が出る。

水が同じ温度の水蒸気になるためには気化熱が必要だ。Wikipedia の蒸発熱のページによると、40.8 kJ/mol のエネルギーが必要だ。これは0度から100度まで温度を上げる際に必要な熱量の5倍に当たると言う。逆に言うと水蒸気を水に戻すときにはこれだけのエネルギーを「気温降下とは別に」くみ出さなくてはいけない。

一方で、マシンの冷却に湿度は関係ない。湿度はマシンが錆びたり、黴たりするのを防ぐために下げるのであって『気温は下がらないけれども、湿度を下げれば…』とはならないのだ。マシンは汗をかかないから。




そこでこういう発想が出てくる。

仮に、気温を20度まで下げる代わりに30度までしか下げない事を考えよう。で、チップなど主な冷却対象に液冷装置をつける。液体の温度は30度にすることで結露を防ぐ。代わりに、液体を強制循環させてどんどん熱をくみ出す。CPUなどの表面温度は40度以上なのは空冷であっても変わらないので、30度の液体を強制循環させた場合でも十分な冷却効果は得られるはずだ。

30度までしか気温を下げないので、除湿しなくてはいけない量が圧倒的に少なくて済む。これによってデータセンターの冷却設備の能力を、マシンを冷却する事に集中させる事ができる。




さて。これでどれぐらい無駄なエネルギーを削減できるか。
うーん。面倒なので誰か代わりに計算してください(おぃ

大雑把に言っても、夏場、エアコンはもうジャブジャブと水を「生産」しています。水1molは18gしかありません。これは18ccと同じです。一合は180ccなので計量カップ一杯は10molです。
1molの水がジャブッと出るたびに40.8kJが必要なので、180ccの水が出てくるのには408kJの熱をくみ出す必要があります。

水を1mol、0度から100度にするには7.53 kJ必要です。別の言い方をすると10度下げるには0.753kJくみ出すだけでいい。乾燥空気の場合は(http://q.hatena.ne.jp/1180249482によると)0.288[kcal/m3 ℃]=1.2kJちょいですから12kJくみ出せば10度下がります。このことから考えると、ワンカップの水が冷却器から出てくるたびに、34m3の空気を冷やすのと同じ能力が消費された、と見ることが出来ます。液冷にする事で、この能力の大半を、本来のマシンを冷やす事に振り向け直す事ができるのです。




夏場、冷房装置からどれだけの水が出ているのか見てください。
家庭用のエアコンは真夏の日中に空気を20度に冷やしたりしません。なのにそれだけでるのです。

マシンルームのデザインに液冷対応を考慮するのは十分価値のある事だと思いませんか?

2008年9月6日

Process Monitorの出力をいじる

WindowsにはProcess Monitorというツールがある。多分アンカーの指している場所がダウンロードサイトの中で最も簡単に手に入れられるページだと思う(日本語で説明が書いてあるから)。

対応OSは 2000, XP, 2003, そして Vista だそうだ。

Process Monitor (procmon.exe)は、NT kernelへのシステムコール…というかリクエストキューに積まれたリクエストを記録し続ける。記録されるのは全てのプロセスのリクエストだ。unixのstraceなどと違って「このプロセスの」とか「あのプロセスだけ」という制約は無い。そのおかげでシステム全体の状態が判るが、これはセキュリティ上は結構危険な状態でもある。

procmon.exeは記録したシステムコールを表示するときにも使える。いくつかのフィルター条件を設定して望みのものだけを選び出すことが出来る。ある特定のプロセスが発行したもの、特定のスレッドが発行したもの、あるファイル(あるいはレジストリパス)に対するIO、あるシステムコールだけを選ぶ…などができる。実はシステムコールを発行した(キューに積んだ)時刻だけではなく、これが完了するまでにかかった時間も記録している。
「システムコールが終わるまでに0.1sec以上かかった」
とか
「システムコールに対する応答が無かった」
などという条件も設定できるのだ(NTのシステムコールは「キューにリクエストを積んで」「応答を待つ」形式なので、本質的に全て非同期コールだ)。

障害解析やパフォーマンス解析においてこれほど強力なツールはまず、ない。

が、世の中どんなに頑張っても
「それは俺の設定したい条件じゃない」
と言うことはあるわけで。そうなるとPerlのようなスクリプト言語の出番になる。




Perlに読めるようにするためには、procmon.exeが記録した内容を何らかのテキストの形で出力してもらわなくてはいけない。それ自体は簡単だ。

% procmon.exe /OpenLog 'filename.PML' /SaveAs 'filename.CSV'


PMLは procmonが出力するバイナリフォーマットのログ、filename.csvは「csvフォーマットでファイルを出力しろ」と言う意味だ。ちなみにこの場合、csvファイルはutf-8で出してくれる。なんとUCS2やUTF-16じゃないのだ。ありがたい。

しかし、この出力には2つ問題がある。

1つ目はファイルの先頭にBOM … Byte Order Mark …がついている点だ。確かにこれがあれば確実にUTF-8だと判るのだが、私の知る限り perl が標準入力から読み込むファイルの操作に関して、BOMを適切に処理できる、と言う話は聞かない。だから、BOM…この場合はファイル先頭の3byteなのだが…を削らなくてはいけない。

2つ目はCSVそのものにある。いや、もちろん、CSVだって悪いわけじゃない。もし1つのValueの中にカンマで分割された文字列が入っているのでなければ

perfmon.exe の記録のいくつかのフィールドが、それ自体カンマで区切った複数フィールドから成り立っているのだ。例えば Detail というフィールドなんかがそうだ。たとえば:
"User Time: 0.0781250, Kernel Time: 0.0312500, Private Bytes: 1,806,336, Working Set: 5,439,488"

とか(これは Process Profiling をやっているときの記録だな)、
"Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, Impersonating: S-x-x-xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxx, OpenResult: Opened"

(これはファイルに対する IRP_MJ_CREATE のDetailだな)、このように1つのエントリ自体が複数のカンマで区切られたフィールドになっている。判ると思うが、各エレメントは " で囲まれている。

すると、単純にカンマをセパレータにして split をするとエライ目に合うわけだ。そこで、CSVをTSV… Tab Separated Values …形式にする。" で囲まれた領域に TAB は含まれていないので split をしかけるときにセパレータを /\t/ と定義すれば、あっさりと正しく split できるようになる。ファイルをこの形式にしておく事で、この後何度も統計処理や絞込みのために行う splitting rule を簡素化できるわけだ。

TSVにするフィルター自体はすごく簡単に作れる。これがコードだ:
tail --bytes=+4 input.CSV |\
sed -r -e 's/\r$//g' \
-e 's/\",\"/\"\t\"/g'\
> output.TSV


最初の tail --bytes=+4 は input.CSVの先頭3バイトを取り外す。これでBOMが無くなる。

GNU sed に与えた -r オプションは「拡張正規表現」を使えるようにする、というものだ。実際の正規表現はその次の2つの -e で始まるものになる。

's/\r$//g' は行末の 0x0d を取り外しているだけだ。ようするに改行コードを unix 形式になおしている。

's/\",\"/\"\t\"/g'は 『","』 となっている場合の , を TAB に入れ替える。ここには、procmon は「"," というフィールドデータを吐かない」という前提を使っている。少なくとも今の所、見たことは無い。多分大丈夫だろう。




ちなみに。TSVに直した後も、各エレメントは " で囲まれたままだ。で、 " で囲まれたエレメントを処理するときには「まだ」面倒な問題が残っている。

" で囲まれた中で " を表現するには『""』のように"を2回連続しなくてはいけない。

で、「そのプログラムはどのように起動したのか」を説明する「Command Line」フィールドに絶対パスが例の『Program Files』を含む場合、このようになってしまうのだ:


"""C:\Program Files\IBM\Director\websrv\dirwbs.exe"""


一番外側の " は「フィールド」を囲む " だが、その内側の "" は実際には1つの " を " で囲まれたフィールド内で表現するためのものだ。

この点だけは注意した方がよい。

2008年8月30日

何かを見つけた、その後

おっとっと、忘れてた。

仮に、問題のポイントと相関が高いカウンターが見つかったとしよう。それで終わってくれるならば特にどうと言うことは無いだろうが、普通は
「で、それは何をどうしているって??」
と言うことを確認しなくてはいけない。もし障害分析を仕事でやっている場合ならばお客様には
「r2が 0.43 なのでこいつが問題点です」
という1行よりはもう少し親切な…そう例えばグラフ化するとか…してあげなくてはいけない。

そこで、自分が「これ」と決めたカウンターに関して、Excelシートを作る事を考えよう。

もちろん、対象となるカウンターがもう決まっているのだから、relog.exe に対象カウンター一覧を食わせて TSV ファイルを作り、それをそのまま Excel に飲ませるのだってOKだ。
ただ、このやり方だと、例えば対象となる Performance Monitor のログが30個あると、30回「新しくシートを作って、そこにTSVファイルを読み込ませて」を繰り返さなくてはいけない。はっきり言おう。私は嫌だ。

と言うわけで。Excelシートを作ってくれるスクリプトだ。

toxls.pl

#!/usr/bin/perl
# Output xls file name is described as ARGV[0];
# ARGV[1] and on should contian
# ( ARGV[odd], ARGV[odd+1] ) = ( TSV filename, sheet name ).
# And at least ARGV[1] and ARGV[2] should be given in order to have something meaningful.
# Or, you'll get empty Excel file.


use Spreadsheet::WriteExcel;
use Spreadsheet::WriteExcel::Utility;

$workbookname = shift @ARGV;
if ( !defined( $workbookname ) ) {
die "Excel WorkBook name not given\n";
}

my $workbook = Spreadsheet::WriteExcel->new($workbookname);


@sheetnames = @ARGV;

while( $sheetfilename = shift @ARGV ) {
$sheetname = shift@ARGV;
if ( !defined( $sheetname )) {
die "Excel sheetname not given for TSV file name: $sheetfilename\n";
}

readsheet( $sheetname, $sheetfilename );
}

$workbook->close();
exit 0;


sub readsheet {
my ( $sheetname, $sheetfilename ) = @_;

my $worksheet = $workbook->add_worksheet( $sheetname );
open SHEETFILE, $sheetfilename or die "unable to open filename: $sheetfilename\n";


my $linenumber = 0;
my $offset = 5;
my $maxcells = 0;

while () {
chomp;

my @cells = split /\t/;

if ( $maxcells < $#cells ) {
$maxcells = $#cells;
}

if ( $linenumber == 0 ) {
$column = $linenumber;
} else {
$column = $linenumber + $offset;
}

for ( $i = 0; $i <= $#cells; $i++ ) {
$cells[$i] =~ /^\"(.+)\"/;
my $tmp = $1;
$worksheet->write( $column, $i, $tmp );
}
$linenumber++;
}

$maxlinenum = $linenumber + $offset;

for ( $row = 0; $row <= $maxcells; $row++ ) {

# Since we need to skip first element, headofdata requires to have +1.
my $headofdata = $offset+1 +1;
my $tailofdata = $maxlinenum - 1;

my $ydatatop = xl_rowcol_to_cell( $headofdata, $row );
my $ydatatail = xl_rowcol_to_cell( $tailofdata, $row );

my $xdatatop = xl_rowcol_to_cell( $headofdata, 1 );
my $xdatatail = xl_rowcol_to_cell( $tailofdata, 1 );


my $formulacol = 1;
my $formularow = $row;
if ( $row == 0 ) {
$worksheet->write_string( $formulacol, $formularow, 'R2' );
} elsif ( $row == 1 ) {
$worksheet->write_formula( $formulacol, $formularow, "=RSQ( $ydatatop:$ydatatail, $xdatatop:$xdatatail)" );
} else {
$worksheet->write_formula( $formulacol, $formularow, "=RSQ( $ydatatop:$ydatatail, $xdatatop:$xdatatail)" );
}


$formulacol = 2;
$formularow = $row;
if ( $row == 0 ) {
$worksheet->write_string( $formulacol, $formularow, 'a' );
} elsif ( $row == 1 ) {
# do nothing
} else {
$worksheet->write_formula( $formulacol, $formularow, "=SLOPE( $ydatatop:$ydatatail, $xdatatop:$xdatatail)" );
}


$formulacol = 3;
$formularow = $row;
if ( $row == 0 ) {
$worksheet->write_string( $formulacol, $formularow, 'b' );
} elsif ( $row == 1 ) {
# do nothing
} else {
$worksheet->write_formula( $formulacol, $formularow, "=INTERCEPT( $ydatatop:$ydatatail, $xdatatop:$xdatatail)" );
}


$formulacol = 4;
$formularow = $row;
my $r2 = xl_rowcol_to_cell( 1, $formularow );
if ( $row == 0 ) {
$worksheet->write_string( $formulacol, $formularow, 'High Relation' );
} elsif ( $row == 1 ) {
# do nothing
} else {
$worksheet->write_formula( $formulacol, $formularow, "=IF( $r2>=0.25,\"*\",\"\")" );
}
}

close SHEETFILE;
}


ヘルプも何もありゃしない :p 使い方としてはこんな感じ:


% toxls.pl AFO.xls A.TSV A-dayo B.TSV B-desu


するってーと AFO.xls が作られる。開くと見えるのはこんな感じだ。



タブを見ると「A-dayo」「B-desu」というのがあるのが判るだろう。それぞれ A.TSV, B.TSVの中身がシートになった状態になっている。

こいつにはもう一つおまけの機能がある。2行目から4行目がそれぞれ「r2」「y=ax+bのa」「y=ax+bのb」になっている。これらは Perlで算出したものではなく、Excelの「式」が書いてあってあなたがこのExcelシートを開いたときに値を再計算してくれた状態なのだ。

5行目には「もしr2の値が0.25より大きかったら * を、そうでなければ何も表示しない(エラーの場合を除く)」という式が埋めてある。単なる目印のようなものだが、たまに便利だったりする。

2008年8月26日

15個に分類するには

sort.pl が出してくる3つのファイルを、マシン2台分用意してAとBの邂逅の図1のように分類するのに必要なツールは、実は簡単なものでいい。

今回はcommon.plとonlyFirst.plという2つのプログラムを作り、それを使うことにしよう。
common.plは2つのファイルを受け取り、その両方に存在するカウンター名を出力する。
onlyFirst.plは同じく2つのファイルを受け取るが、最初のファイルにあって2つ目のファイルには存在しないカウンター名を出力する。

「diffコマンドじゃ駄目なのか?」
という声が聞こえてきそうだが、実は駄目だ。diffは厳密な差分を作るわけではない。あまりにも複雑な差分が出てくると、その近辺をまとめて「えいやっ」と全部違うことにしてしまう。パッチを作るのには便利だが、本当の意味で差分を知りたいときには役に立たないのだ。




おっとっと。sort.pl の出力例を示そう。

"\System\Threads"
# "-0.22995779821056035196" "0.05288058895784879381" "-0.07730117409966560384" "143.68969576542572045539"
# "-0.51157343991699204758" "0.26170738442850427247" "-0.28398973957007304341" "268.04879224402808287292"
# "-0.54019071369820630440" "0.29180600716577749228" "-0.44072471855335868898" "364.09672304650726120687"
# "-0.55452955075971981685" "0.30750302266577667691" "-0.35677693634035859957" "315.68199698065233504727"
# "-0.56238435856552132237" "0.31627616675915285595" "-0.25723384667083141975" "251.10319887283403001070"
"\System\System Up Time"
# "-0.01694272285973713476" "0.00028705585790185927" "-0.00002142521429336236" "319.75744872321046887814"
# "-0.10027028487321032493" "0.01005413002855475131" "-0.00006496869460687431" "814.02214841113016417319"
# "-0.15561543326229096847" "0.02421616306941053433" "-0.00009270518792943096" "1140.66520449120347153620"
# "0.02950515281233018273" "0.00087055404247895569" "0.00002478604557354800" "-190.27570068419384733538"
# "0.64118189188208415717" "0.41111421847748865761" "0.00006133456719050028" "-615.26230486597317302122"
"\System\System Calls/sec"
# "-0.18729246211596788708" "0.03507846636546126618" "-0.00055434531116103602" "100.21166379708057100480"
# "-0.29232961498316379384" "0.08545660379620478167" "-0.00111680437443534113" "93.05619900302319881495"
# "-0.34131479803199158732" "0.11649579135561920833" "-0.00090462856546019161" "90.75926531940870958778"
# "-0.34750948339695522578" "0.12076284105081869973" "-0.00149413414952154013" "87.55393220941927354234"
# "-0.51114894911397353620" "0.26127324818031950750" "-0.00296996323726849211" "104.08031976970305210654"


# で始まっている行はコメントだ。この行はデバッグ用に各カウンターから算出されるr, r2, a, b の4つの値を記録しているだけで、この段階では何の意味も無い。これは処理の前に grep -v で消してしまおう。


% cd $TOP
% cd $TOP/06rmComment
% cat filter.sh
#!/bin/sh

for i in ../05sort/*.{HI,BORDER,LOW}; do
o=`echo $i | sed 's/\.\.\/05sort\///g'`
echo $o
egrep -v '^#' $i | sort | uniq > $o
done
% ./filter.sh

sort はデバッグしやすくするためだが(アルファベット順に並んでいると間違ってるかどうか探しやすい)、uniq は「ついカッとなってやった」の類だ。無くても構わない。




では。まず2つのファイルに共通しているカウンター名を引っ張り出すスクリプト。

common.pl

#! /usr/bin/perl

# common.pl
# read and , line by line.
# if common lines were found regardless of it's order, output it to STDOUT.

$inAfn = $ARGV[0];
$inBfn = $ARGV[1];

open( INA, $inAfn ) or die "can't open file $inAfn as read\n";
open( INB, $inBfn ) or die "can't open file $inBfn as read\n";

while () {
chomp;
push @inAline, ( $_ );
}

while () {
chomp;
push @inBline, ( $_ );
}

close INA;
close INB;


while ( $line = pop @inAline ) {
# find same line in @inBline;
for ( $i = 0; $i <= $#inBline; $i++ ) {
if ( $inBline[$i] eq $line ) {
print "$line\n";
}
}
}

えぇ、自分で言うのもなんだが、すげぇ馬鹿コードである。共通要素を見つけた後、next で次に行かないのもどうだかと思うし、inBline から見つかった要素を消さないのもどうだかと思う。

次は片一方にしかないカウンターを見つけるスクリプト。

onlyFirst.pl

#! /usr/bin/perl

# onlyFirst.pl
# read and , line by line.
# if line from does not exist in line from then print that to STDOUT.

$inAfn = $ARGV[0];
$inBfn = $ARGV[1];

open( INA, $inAfn ) or die "can't open file $inAfn as read\n";
open( INB, $inBfn ) or die "can't open file $inBfn as read\n";

while () {
chomp;
push @inAline, ( $_ );
}

while () {
chomp;
push @inBline, ( $_ );
}

close INA;
close INB;

A_line:

while ( $line = pop @inAline ) {
# find same line in @inBline;
for ( $i = 0; $i <= $#inBline; $i++ ) {
if ( $inBline[$i] eq $line ) {
next A_line;
}
}
print "$line\n";
}

ふ。負けず劣らずこちらも馬鹿コードである orz




% cd $TOP
% cd $TOP/07common
% cat filter.sh
##############################################
# re-classify into matrix
#
# B \ A | | | |
# \ | HIGH | BORDER | LOW |
#------------+------+--------+-----+
# HIGH | HH | HB | HL |
#------------+------+--------+-----+
# BORDER | BH | BB | BL |
#------------+------+--------+-----+
# LOW | LH | LB | LL |
#------------+------+--------+-----+

./common.pl ../06rmComment/A.HI ../06rmComment/B.HI > HH
./common.pl ../06rmComment/A.HI ../06rmComment/B.BORDER > BH
./common.pl ../06rmComment/A.HI ../06rmComment/B.LOW > LH

./common.pl ../06rmComment/A.BORDER ../06rmComment/B.HI > HB
./common.pl ../06rmComment/A.BORDER ../06rmComment/B.BORDER > BB
./common.pl ../06rmComment/A.BORDER ../06rmComment/B.LOW > LB

./common.pl ../06rmComment/A.LOW ../06rmComment/B.HI > HL
./common.pl ../06rmComment/A.LOW ../06rmComment/B.BORDER > BL
./common.pl ../06rmComment/A.LOW ../06rmComment/B.LOW > LL

for i in HI BORDER LOW; do
./onlyFirst.pl ../06rmComment/A.$i ../06rmComment/B.HI > ./tmp1
./onlyFirst.pl ./tmp1 ../06rmComment/B.BORDER > ./tmp2
./onlyFirst.pl ./tmp2 ../06rmComment/B.LOW > ./onlyA.$i
done

for i in HI BORDER LOW; do
./onlyFirst.pl ../06rmComment/B.$i ../06rmComment/A.HI > ./tmp1
./onlyFirst.pl ./tmp1 ../06rmComment/A.BORDER > ./tmp2
./onlyFirst.pl ./tmp2 ../06rmComment/A.LOW > ./onlyB.$i
done

% ./filter.sh


C,Dの処理は省略した。

はっきり言ってこれでもかと言うぐらいの力押しスクリプトだ。よい子はまねをしてはいけない(ぇ…)。



というわけで、これで機械的に出来る分類は終わった。
ココから先は、人間の推理力がモノを言う。

実際、ここにある perl のスクリプトはほぼそのまま私が使っているスクリプトだ。シェルスクリプトの方は時々に応じて変更しているのでこれこのままではないが。で、今まで発見できなかった要因をいろいろ見つけることが出来ている。

性能障害問題で悩んでいるなら、きっと役に立つと思う。少なくとも同じ考え方は使えるはずだ。

2008年8月24日

AとBの邂逅

ここまでは、各マシン毎の分類だった。ここからはマシンごとの特徴を見た上での分類が必要になる。要点としては図1のように分類する事になる。

 A only HIA only BORDERA only LOW
Aマシンのカウンター
B only HIBマシンのカウンター(A,B)=(HI,HI)(A,B)=(BORDER,HI)(A,B)=(LOW,HI)
B only BORDER(A,B)=(HI,BORDER)(A,B)=(BORDER,BORDER)(A,B)=(LOW,BORDER)
B only LOW(A,B)=(HI,LOW)(A,B)=(BORDER,LOW)(A,B)=(LOW,LOW)
図1. AとBの分類


まず最も判りやすいところから説明しよう。中央の紫色の3x3=9枡目は、AとBの両方で存在した(NaNによる除去を免れた)カウンターだ。A, B それぞれにおいて、どう分類されていたのか、に応じて3x3通りが存在する。

薄い黄色で塗られた3つの枡目、「A only xxxx」とあるのは『Aには存在したがBには存在しなかった』カウンターを現す。同様に「B only xxxx」とある水色の3つの升目は『Bには存在したが、Aには存在しなかった』カウンターを現す。

生き残ったカウンターは以上15個の升目のどこかに必ず入る。まずこの分類を行う。

さて、これらの升目の内、重要視するのは図2で示した6枡である。


 A only HIA only BORDERA only LOW
Aマシンのカウンター
B only HIBマシンのカウンター(A,B)=(HI,HI)(A,B)=(BORDER,HI)(A,B)=(LOW,HI)
B only BORDER(A,B)=(HI,BORDER)(A,B)=(BORDER,BORDER)(A,B)=(LOW,BORDER)
B only LOW(A,B)=(HI,LOW)(A,B)=(BORDER,LOW)(A,B)=(LOW,LOW)
図2. 重要視するポイント


症状はAでは発生しているが、Bでは発生していない。そして問題が生じているときにはプロセッサが忙しく働いている。ならば、「AのときはIdleと強い相関があり、Bの時にはIdleとの相関が弱い」所に探すべきネタがある。

Aにしか存在しないカウンターの内、相関がLOWのもの以外の2つは、当然検討対象にしなくてはいけない。これは判ると思う。逆にBにしか存在しないカウンターは無視して構わない。

A,Bともに存在したカウンターの内、Aにおいて相関がLOWだったものは、この場合検討対象から外してよい。

Bにおいて相関がHIだったものも、この場合検討から外してよいはずだ。AもHI,BもHIならそれは「いつでも相関が高いもの」…例えば、プロセッサ消費量の内の User の割合など…であることが考えられる。また、AがHIで、BがHIではないのでは明らかに今回探している対象から外れるのは自明だ。

残った6枡の内、A,Bともに BORDERなものは特別扱いが必要になる。BORDERにあるのは計測すると相関が高かったり高く無かったりしたものだ。つまり「AもBも、相関が高いんだか低いんだかよく判らん」ものだったりする。この場合、各カウンターの内容を調べて、個別に判断する必要が出る。しかし、逆に言うとここは手間がかかるところなので、それ以外の部分で何か見つかった場合は無視して構わない、ともいえる。

と言うわけで、残り5枡を最優先で調べるべし、と言うことになる。

2008年8月16日

sort.pl の出力

先に進む前に sort.pl の出力についてもう少し説明する。

sort.pl が入力として受け付ける(前提としている)のは、calcr2.pl の出力を複数 cat でつないだもの。今回は sort もかけることで同じカウンターが近接するようにしてあるが、本質的には sort の必要は無い。結果として、このような内容が延々続くファイルになる:


"\LogicalDisk(C:)\% Disk Read Time" "-0.04062578806352645403" "0.00165045465578256851" "-0.12770872885354865610" "90.45804378385737658023"
"\LogicalDisk(C:)\% Disk Read Time" "-0.04406606083496136481" "0.00194181771751051590" "-0.17957809895894711362" "90.36202077972433047033"
"\LogicalDisk(C:)\% Disk Read Time" "-0.10162240553099880448" "0.01032711330590677641" "-0.25960115664153218127" "91.91528872471089858070"
"\LogicalDisk(C:)\% Disk Read Time" "-0.10606592840069039460" "0.01124998116750038125" "-0.50117255489344281550" "89.78700245921672272973"
"\LogicalDisk(C:)\% Disk Read Time" "-0.63225077753384162361" "0.39974104569214729193" "-0.98409431769226710436" "99.62442055641675669955"
"\LogicalDisk(C:)\% Disk Time" "-0.04336533750022928025" "0.00188055249650879188" "-0.11305679326692022027" "90.53746144831214471477"
:


r2の値を使ってこれらをソートしていくわけだが、全ての計測結果においてr2が同じ性質を持つわけではない。実はここでは4種類に分類している。出力しているファイルは3種類だが、実際には分類は4種類あるのだ。

そこで、まず個々のr2の値はどのような値をとりえるのか、考えて見よう。

r2のとる値


すでに述べたが、r2は0.0から1.0までの間の値を取る。正確には計算できる場合は、それらの間の値をとる。そう。計算できない場合、と言うのがあるのだ。

指定したカウンターに値が全く記録されていない場合がありえる。この場合、データが全くないので、回帰直線も引けない。つまり y=ax+b という形のaとbも求められない。rの値は a と b がないと計算できないのでrも求められないし、その二乗値も求められない。
この場合、r, r2, a, b は全て Not a Number (NaN) となる。

rやr2が NaN になるケースがもう一つある。a が 0.0 になった場合だ。厳密には一定の誤差の範囲内であれば NaN になってしまう。rの計算式には a による除算が含まれているのだが、ここで 0割り(0を分母とする分数の値を計算しようとした時に生じるエラー)が起こるのだ。この場合、r, r2は NaN になるが、a は 0.0 になるし、bはどんな値になるかはデータ依存だ。

以上の2つのケース以外の場合、rの値は 0.0 から 1.0 の間の値を取る。厳密には、計算誤差のせいで 1.0 を超える数字が出たり、0.0を下回る数字が出たりすることもあるが、あくまでも誤差の範囲、と捉えてもらって大丈夫だ。

r2の値による分類


さて、今回、sort.pl でカウンターを分類するにあたって次のような規則を適用した。


1) 同じカウンターに属する、全てのr2の値が NaN ならば、そのカウンターは記録しない。

データが一切ないのであれば、そのカウンターは考慮に値しない。

データがあるが a が 0.0 になる、と言うことは Processor Idle の値に全く影響を受けてない/影響を与えていない、と言うことだ。今回は影響のあるものを探しているのだから、そのカウンターについて、以降考慮する必要は無い。
Process ID のように変わるはずの無いカウンターが該当する。

2) 同じカウンターの一部だけが NaN の場合、そのNaNは無視する。

取りあえず残りのデータだけで分析しよう、と言うことだ。

3) 全ての r2が境界値以上だったらHI

4) 全ての r2が境界値未満だったらLOW

5) r2が境界値以上、未満の両方存在したら、BORDER



なので、全てNaNの場合のカウンターがこの段階で消えている。カウンターの総数は分類前と分類後では、分類後の方が少ない可能性がある、ということだ。