はてブロ@ama_ch

https://twitter.com/ama_ch

IEでoffsetHeightがとれないときの対処方法

ご無沙汰しています。
最近はもっぱら JavaScript ばかり書いているため、偽物臭がかつてないほど強くなっているあまちゃんです。実に1年半ぶりのエントリーです!


最近 IE8 で要素の offsetHeight が取得できなくて困ったので、その対処方法を書いておきます。


まず背景や問題の詳細。
やりたかったことは、

  • height に auto が指定されている div が沢山あり、現在の offsetHeight が最も大きいものにすべて揃える

ということです。


FF や Chrome では DOM 操作をするのと同じタイミングで描画もされ、描画後の任意のタイミングで element.offsetHeight の値を参照することができました。しかし IE では DOM を操作するコードの実行時に画面が描画されないようで、(コード上では)描画済みの要素の offsetHeight を参照しても、前者のブラウザとは違う値になってしまいました。
computedStyle や runtimeStyle でも取得できず。ムムム...


そこで id:teppeis 先生に教えてもらったのが setTimeout を使う方法。

var height = 0;
window.setTimeout(function() {
    height = element.offsetHeight;
}, 0);

なんでこれで取れるかっていうのはamachangのエントリーが分かりやすいです。
JavaScript を学ぶ際に一番重要なのに、誤解されがちな setTimeout 系の概念 - IT戦記


このままだと引数が渡せないのでこんな感じに。

var height = 0;
window.setTimeout(function(self) {
    height = element.offsetHeight;
}, 0, this);


しかし IE では setTimeout の3番目以降の引数はサポートされていなかった...

Internet Explorer では、最初の構文で関数に渡す追加のパラメータは動作しないことに注意してください。

window.setTimeout - MDC


IE で引数を渡すために結局こんな感じに。

var height = 0;
(function(self) {
    window.setTimeout(function(self) {
        // ここでselfが使える
        height = element.offsetHeight;
    }, 0);
})(this);


というわけで無事に IE でも offsetHeight がとれました(^o^)/

2010/10/2 追記

IE で offsetHeight がとれないのはこういう訳だったらしい。。。


@yo_waka先生ありがとうございました。

MacのDropboxクライアントがたまにCPUを消費しまくるっていう

バージョンアップのおかげか、メモリ使用量が問題になることはほとんどなくなったDropboxちゃん。いいぞいいぞ、でもたまにCPUをどか食いするのは何故なの><
ぐぐってみたら、結構同じような症状はあるみたい。mac dropbox cpu - Google 検索

CPU使用率が高いとファンがうるさいし動作全般がもっさりしてしまうので、Dropboxのメモリ消費量があまりにも鬼畜だったので監視して適宜殺すことにしたで書いたシェルスクリプトをちょこっと書き換えてCPU使用率も監視することに。

#!/opt/local/bin/zsh

set -A array $(ps aux -m | grep Dropbox.app | grep -v grep | grep ama-ch)
if [ $(echo $array |wc -w) -eq 0 ]
then    # Dropboxが起動してなかったら通知・起動
    /usr/local/bin/growlnotify -t 'Warning' -m 'DropBox is dead!!' -I /Applications/Dropbox.app
    osascript ~/applescripts/runDropbox.scpt
else    # メモリ消費量が512MBを超えてる or CPU使用率が80%以上ならkill
    if [ $array[6] -gt 512000 -o $(echo "$array[3]>80" | bc) -eq 1 ]
    then
        kill $array[2] && /usr/local/bin/growlnotify -t 'Report' -m 'DropBox was killed.' -I /Applications/Dropbox.app
        osascript ~/applescripts/runDropbox.scpt
    fi
fi

3行目最後のgrep ama-chは、ログインユーザー名に適宜変更します。
7行目のrunDropbox.scptの中身は前回参照。
psコマンドで出力されるCPU使用率は82.6のような小数点以下1桁の小数だから、素直に条件式で

test 82.6 -gt 80 && echo ok

こんな風に比較するとエラーになっちゃう。*1
そこでbcコマンドの比較演算子を使って、

test $(echo "82.6>80" | bc) -eq 1 && echo ok

このように対処した。bcの比較は成立すると1を、しなければ0を返すから、これなら判定できる。
でももっとスマートな方法ありそうな気がする><


そんな訳で、このスクリプトをcronに突っ込んでおけば自動でCPU使用率・メモリ使用量を監視,必要な時に再起動してくれます!

*1:シェルは整数しか扱えないため

シェルの処理順序

詳解 シェルスクリプト

詳解 シェルスクリプト


今読んでる詳解シェルスクリプトにシェルの処理順序が詳しく書いてあり、とっても参考になったのでメモしておきます。


p.176 7.8 処理の順序とevalコマンド より


標準入力やスクリプト中の各行からシェルへと読み込まれる1行分のデータを、パイプラインと呼ぶ。パイプラインには1つまたはそれ以上のコマンドが記述され、それぞれの間にはパイプ記号(|)やその他の記号(;, &, &&, ||)が記述される。シェルは読み込んだパイプラインをコマンド単位に分割し、それぞれについて以下の順に処理を行う。

  1. スペース、タブ、改行、;、(、)、<、>、|、&を区切り文字として、コマンドをトークンと呼ばれる単位に分割する。
  2. コマンド中の最初のトークンについて、それがクオート*1されておらずかつキーワードであるかどうかを確認する。もしこれが開始のキーワード(if, { ,()だった場合、そのコマンドは複合コマンドであると判断され、次のコマンドを読み込んで処理を継続する。最初のトークンが開始のキーワードでなかった場合(then, else, do, fi, doneなど)は、構文エラーが発生する。
  3. コマンド中で最初に現れた語について、それがエイリアスのリストに含まれているかどうかチェックする。含まれている場合、その語を置き換え、1. に戻って処理を続ける。含まれていなかった場合は4. に進む。なお、語を一部でもクオートしてある場合はエイリアスを使った置換は行われない。
  4. 語が~で始まる場合、$HOMEの値に置き換える(チルダ展開)。
  5. $で始まる部分について、変数名を実際の値に置き換える。
  6. $(string)または`string`と記述されている部分を、コマンド展開する。
  7. $((string))と記述されている部分について、算術展開を行う。
  8. 以上の処理を経た結果の文字列を、もう一度語の単位に分解する。ここでは1. で使われた文字ではなく、変数IFSで指定された文字が区切り文字として使われる。
  9. *, ?, [...]についてワイルドカード展開を行い、ファイル名を生成する。
  10. 最初の語をコマンド名とみなす。特別な組み込みコマンド、関数、通常の組み込みコマンド、変数PATHを探索して最初に見つかったものの順にコマンドを探す。*2
  11. 入出力のリダイレクトなど、必要な設定を行ってコマンドを実行する。


なんだかごちゃごちゃしているので、例で実際に処理の流れを見てみます。
ちなみに詳解シェルスクリプトのp.178には上記の処理手順がフローチャートになっており、そちらを見る方が分かりやすいです。

% mkdir temp; cd temp
% touch file{0..9}    # file0〜file9を生成
% f=file
% str="foo bar"
% echo ~/temp/${f}[12] $str $(echo iam ama-ch.) $((15 + 3 + 4)) years old.    # ここの処理を見るよ!
/Users/ama-ch/temp/file1 /Users/ama-ch/temp/file2 foo bar iam ama-ch. 22 years old.

上のコマンドの5行目(echo hoge)について、処理を追ってみます。


1. シェルで定められた構文に従って、コマンドが以下のトークンに分解される。

1 2 3 4 5 6 7
echo ~/temp/${f}[12] $str $(echo iam ama-ch.) $((15 + 3 + 4)) years old.

2. 最初の語がifやforなどの開始キーワードかどうかチェックする。echoはキーワードではないので、そのまま次へ進む。
3. 最初の語にエイリアスが存在するかどうかチェックする。echoはこれも当てはまらないので、次へ進む。
4. すべての語に対して、チルダ展開が可能かどうかチェックされる。ここでトークン2のチルダが展開され、以下のようになる。

1 2 3 4 5 6 7
echo /Uses/ama-ch/temp/${f}[12] $str $(echo iam ama-ch.) $((15 + 3 + 4)) years old.

5. 変数展開が行われ、トークン2と3が展開される。

1 2 3 4 5 6 7
echo /Uses/ama-ch/temp/file[12] foo bar $(echo iam ama-ch.) $((15 + 3 + 4)) years old.

6. コマンド展開が行われ、トークン4が展開される。*3

1 2 3 4 5 6 7
echo /Uses/ama-ch/temp/file[12] foo bar iam ama-ch. $((15 + 3 + 4)) years old.

7. トークン5の算術展開が行われる。

1 2 3 4 5 6 7
echo /Uses/ama-ch/temp/file[12] foo bar iam ama-ch. 22 years old.

8. 変数IFSの値を区切り文字として、ここまでの処理結果を分解しなおす。

1 2 3 4 5 6 7 8 9
echo /Uses/ama-ch/temp/file[12] foo bar iam ama-ch. 22 years old.

9. そしてワイルドカード展開が行われ、トークン2が2つに展開される。

1 2 3 4 5 6 7 8 9 10
echo /Uses/ama-ch/temp/file1 /Uses/ama-ch/temp/file2 foo bar iam ama-ch. 22 years old.

10. コマンドを実行する準備が整い、echoというコマンドを検索する。
11. コマンドが実行される!echoによって、引数の値が出力される。

% echo ~/temp/${f}[12] $str $(echo iam ama-ch.) $((15 + 3 + 4)) years old.
/Users/ama-ch/temp/file1 /Users/ama-ch/temp/file2 foo bar iam ama-ch. 22 years old.


なるほど、こういう風に動いてたのか〜。

*1:*や?などの特別な意味を持つ文字を、普通の文字として解釈させる方法。バックスラッシュによるエスケープや、引用符の利用がこれに当たる。

*2:だから関数で通常のコマンドを上書きできるのか!

*3:実際にはコマンド展開で1. からの手順がもう1度適用されるが、ここでは省略。

便乗してPythonでやってみた

簡単な問があったのでPythonでやってみた - 牌語備忘録
元の問題は、10分でコーディング|プログラミングに自信があるやつこい!!からです。


とりあえず10分目標で何も考えずに書いたのがこれ。

#!usr/bin/env python
# -*- coding: utf-8 -*-

def cards(num, card):
    length = len(card) / num
    order = ["" for x in range(num)]
    for i, j in enumerate(card):
        if len(order[num-1])  == length:
            break
        order[i % num] += j
    return order

ちゃんと時間計ってないけど、10分は超えた。しかも超汚い。
自分はかなりプログラミングができない。
とつらい事実を認識しました\(^o^)/


あんまり汚いままなのも気持ち悪いので、+10分くらいかけて精一杯綺麗にしたのがこれ。

#!usr/bin/env python
# -*- coding: utf-8 -*-

def deal(num, cards):
    print "args: %d, '%s'" % (num, cards)
    order = num * [""]
    for i, j in enumerate(cards[:len(cards)/num*num]):
        order[i%num] += j
    return order
   

if __name__ == "__main__":
    print deal(4, "123123123")
    print deal(3, "123123123")
    print deal(6, "012345012345012345")
    print deal(4, "111122223333")
    print deal(1, "012345012345012345")
    print deal(6, "01234")
    print deal(2, "")

実行結果

10:37 ama-ch% python cards.py                                                                                                                                           [~/python]
args: 4, '123123123'
['12', '23', '31', '12']
args: 3, '123123123'
['111', '222', '333']
args: 6, '012345012345012345'
['000', '111', '222', '333', '444', '555']
args: 4, '111122223333'
['123', '123', '123', '123']
args: 1, '012345012345012345'
['012345012345012345']
args: 6, '01234'
['', '', '', '', '', '']
args: 2, ''
['', '']

目的のプロセスをコマンド一発で見つける方法

$ alias pfind='ps aux | grep '

Helpful alias to grep for the PID. | Command-line Fu

こういう方法が紹介されていたけど、これだとgrep自身のプロセスが表示されちゃうから、こうした方が良いと思う。

function pfd(){
    PSAUX=$(ps aux)
    echo $PSAUX | head -1
    echo $PSAUX | grep $1 | grep -v grep
}

実行結果の違い

21:54 ama-ch% ps aux | grep Firefox                                                                                                                                            [~]
ama-ch     477  59.3  9.2  1453984 385752   ??  Rs    9:53AM 242:26.88 /Applications/Firefox.app/Contents/MacOS/firefox-bin -foreground
ama-ch    8887   0.0  0.0    87080    320 s001  R+   10:01PM   0:00.00 grep Firefox
22:01 ama-ch% pfd Firefox                                                                                                                                                      [~]
USER       PID %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
ama-ch     477  57.0  9.2  1453488 385256   ??  Rs    9:53AM 242:30.85 /Applications/Firefox.app/Contents/MacOS/firefox-bin -foreground

後者の方が見やすいです多分!


あと参照元ページのコメントに「pidofかpgrep使えボケ*1」って書いてあったので、MacPortsで調べてみた。

21:48 ama-ch% port search pidof
No match for pidof found
21:49 ama-ch% port search pgrep
proctools @0.4pre1 (sysutils)
    pgrep, pkill and pfind for OpenBSD and Darwin (Mac OS X)

pgrepは見つかりました。pgrep,pkill,pfindがセットなのかぁ。
使ってみたけどpidが出るだけでよくわからりませんでした。酔っぱらってるからかも知れません。

*1:意訳ってレベルじゃねーぞ

Dropboxのメモリ消費量があまりにも鬼畜だったので監視して適宜殺すことにした

一応前回のエントリーGrowlで監視プロセスを通知してみよう - テックノート@ama-chからの続き。
Dropboxがいつの間にか落ちていてもGrowlさんが通知してくれるようになりすっかり安心した僕は、卒論も書かず元気に面白い動画を求めてニコニコ動画をさまよっていたのでした。
「・・・ん、なんか再生がもたつくな?」
何となくActivity Monitorを開いて見たら、そこには驚くべき光景が広がっていました。



ちょおおおおい!1.84GBっておま!!!
ぐぐってみたらどんなに食ってる人でも100MBとかだったのに、この数値は一体なんなんだ!どうりでいきなりファンが高速回転を始めたり、何もしてないのにメモリの空きが10MBになったりしてたわけですね\(^o^)/


これはどうにかしないと非常にマズイ!
とりあえずダウンロードページからアプリケーションをダウンロードし直してみる。バージョンはおそらく0.6.384から0.6.402に上がった。でもメモリ使用量に目立った改善なし。起動してすぐに100MB超えました。うおおあー
「なぜ重くなるのか」という根本的な部分がわからないから、対症療法で頑張るしかない><
Dropboxは起動してないと困るけど、メモリ1GBとか食ってたら一旦終了して欲しい。起動してない時はGrowlで通知するようにしたから、今度は起動中にメモリ欲張り出したら殺す処理を考えよう。


メモリの消費量を調べるといってまず思いつくのはtopコマンドだけど、これは変数に入れてどうこうできそうにない><
グーグル先生によると、どうやら"ps aux"でメモリの消費量も出るらしい。どれどれ・・・

18:40 ama-ch% ps aux -m |head -1; ps aux -m |grep Dropbox.app |grep -v grep                                                                                                    [~]
USER       PID %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
ama-ch     167   0.2 45.6  3136996 1914132   ??  S     2:51PM  26:28.98 /Applications/Dropbox.app/Contents/MacOS/Dropbox -psn_0_143395

おぉ。RSSという項目がメモリの消費量(KB)だそうな。%MEMはメモリの消費量をパーセントで示したものだそうです。45.6%てちょwww
1914132KB≒1.8GBだから、なるほど確かに半分弱食ってるなぁ。それじゃあこのコマンド結果のRSSの部分を判定して、ある値を超えたらkillしちゃえばいいんじゃね!


そんで、前回も踏まえて考えたのがこれ。

#!/opt/local/bin/zsh

# コマンドの実行結果を単語ごとに配列へ格納
set -A array $(ps aux -m |grep Dropbox.app |grep -v grep)
if [ $(echo $array |wc -w) -eq 0 ];
then    # 配列が空だったら警告
    /usr/local/bin/growlnotify -t 'Warning' -m 'DropBox is dead!!' -I /Applications/Dropbox.app
else    # 起動中にメモリ消費量が1GB(1048576KB)を超えてたらkill
    test $array[6] -gt 1048576 && kill $array[2] && /usr/local/bin/growlnotify -t 'Report' -m 'DropBox was killed.' -I /Applications/Dropbox.app
fi

crontabにこう書いて使う

*  *  *  *  * /Users/ama-ch/scripts/check_dropbox.zsh


はぁはぁ、zshで配列を扱う方法が絶望的に分からなかった・・・色々調べたおかげでシェルスクリプトの勉強になりました。 zshだと配列の添字は1からなんだ!
簡単に解説すると、

  1. "ps aux -m |grep Dropbox.app |grep -v grep"の実行結果を単語ごとに配列へ格納
  2. 配列が単語を1つも含まない≒空っぽだったら、「Dropbox落ちてるよ!」とGrowl通知
  3. 空っぽじゃなかったら、メモリ消費量を見てそれが1GBを超えていればkill&そのことをGrowlで報告

という処理を毎分おこないます。
ワンライナーにして直接crontabに記述したら、やっぱり動かなかった>< なんかcronがコマンドを実行する環境?って、普段自分が使ってるのと違う気がする。今度ちゃんと調べたい。


こんなもんでとりあえずどうだろう。あんまり頻繁にkillしちゃうようだとかえって面倒なことになりそうだなー、1日に数回以内でおさまってくれれば良いんだけど。今のところ起動して1時間くらいで150MB程度でおさまってる。
あとなんで「open -a Dropbox」だとちゃんと起動しないのかが気になるなー。

追記

公式wikiの機能要求リストに

Some memory usage optimizations (particularly on OS X)

FeatureRequests - Dropbox Wiki

と書かれてた。OS Xでのメモリ消費は最適化が不十分ということなのかな?
更にフォーラムではOS Xユーザーで常時700MB〜800MB食ってるというユーザーが。

Sorry... but I have to laugh... Granted I'm on OS X so it's not directly comparable, but I'm currently at 500MB RAM usage and it tends to go up to 700-800MB

Using ~50Mb of RAM « Dropbox Forums

うーん、Dropboxがメモリ食いまくるのは仕様なのかなー。バージョンアップに期待しとこう。

更に追記(2/9)

AppleScriptを使ったらコマンドラインでDropboxを起動することができました。これでDropboxを全自動監視&再起動できる!!
まず、スクリプトエディタで以下の内容を記述します。

tell application "Dropbox" to run

アホみたいにシンプルですね!適当にrunDropbox.scptとか名前をつけて保存します。
あとは、先述のGrowlで警告を出していたところにこいつを実行する行を追加すればおk。

#!/opt/local/bin/zsh

# コマンドの実行結果を単語ごとに配列へ格納
set -A array $(ps aux -m |grep Dropbox.app |grep -v grep)
if [ $(echo $array |wc -w) -eq 0 ];
then    # 配列が空だったら警告
    /usr/local/bin/growlnotify -t 'Warning' -m 'DropBox is dead!!' -I /Applications/Dropbox.app
    osascript ~/applescripts/runDropbox.scpt    # この行を追加
else    # 起動中にメモリ消費量が500MBを超えてたらkill
    test $array[6] -gt 512000 && kill $array[2] && /usr/local/bin/growlnotify -t 'Report' -m 'DropBox was killed.' -I /Applications/Dropbox.app
fi

これで、Dropboxを全自動で起動できます。せっかくなのでついでにkillするメモリ消費量の基準は500MBと強気に設定してみました!
更に余談で、

% osascript -e applescript

と叩くと、applescriptの部分に記述したAppleScriptが実行できるらしい。1行のスクリプトをわざわざファイルに保存するのは面倒なので、こりゃいいやと思って意気揚々と

% osascript -e "tell application \"Dropbox\" to run" 

と叩いたら、"open -a Dropbox"と打った時と同じようにすぐ落ちてしまいました。なんでじゃー><

Growlで監視プロセスを通知してみよう

最近DropBoxのアプリケーションが頻繁に落ちてて、同期が実行されないことが多いんです><
プロセス監視して、DropBoxが落ちてたらコマンドから起動しようと思ったら

% open -a Dropbox

これがうまく動かない>< あ、openはMac OS X固有のアプリケーションを起動するコマンドです。
なぜか起動したと思ったらすぐ落ちちゃうんだよなー。どうしよー。
コマンドからDropBoxを起動できないなら、落ちた時に通知をするしかなさそう・・・って通知の方法がわからん\(^o^)/


思いついた順に、

  • アラートを鳴らす ← そんなコマンドあるのか知らない
  • AppleScriptでどうにかする ← めんどくさい
  • ログに吐いてtailする ← 自分でログ見ないと気付かない
  • メールを送る ← 分からないしめんどくさそう
  • Growlする ← これだ!!!!


マカーにはおなじみのGrowlですが、実はこれコマンドからGrowl通知できるツールが付属してるんですよね。ダウンロードパッケージのExtras/growlnotifyってやつがそれです。

% cd /Volumes/Growl-1.1.4/Extras/growlnotify
% ./install.sh

install.shを実行するだけで/usr/local/binにインストールしてくれます。

% /usr/local/bin/growlnotify -t 'test' -m 'this is a test.'

こんな風にコマンドを叩くだけでGrowlが通知してくれます!
なぜかgrowlnotifyだけだとうまく動かなかったので、絶対パスで書いてます><*1
23:50追記 PATH設定し直したりターミナル再起動したりしたら直りました。


ではこのgrowlnotifyを使ってDropBoxを監視してみましょう。

  1. 定期的にDropBoxプロセスを監視
  2. DropBoxプロセスが落ちていたら、Growlで通知

こんな手順で行います。

% ps -ax |grep Dropbox.app |grep -v grep |wc -l
       1
%

これでDropbox.appが起動していれば1を、起動していなければ0を返します。
このコマンドをcronで毎分走らせて、0が返ったらGrowl通知してあげればDropBoxが落ちてもすぐ気づけるという寸法!
crontabにこんな風に書きました。

13:38 ama-ch% crontab -l                                                                                                                 [/Volumes/Growl-1.1.4/Extras/growlnotify]
# +------------ 分 (0 - 59)
# | +---------- 時 (0 - 23)
# | | +-------- 日 (1 - 31)
# | | | +------ 月 (1 - 12)
# | | | | +---- 曜日 (0 - 6) (日曜日=0)
# | | | | |
# * * * * * 実行されるコマンド

# DropBoxアプリケーションを監視
*  *  *  *  * if [ $(ps -ax |grep Dropbox.app |grep -v grep |wc -l) -eq 0 ]; then /usr/local/bin/growlnotify -t 'Warning' -m 'DropBox is dead!!' -I /Applications/Dropbox.app; fi

13:38 ama-ch%


Dropbox.appが起動していないと、毎分こんな通知が出るようになります。

うぜーw
けどこれでDropBoxが落ちたら気づけるので一安心!起動まで自動化できなかったのが気になるけど><

*1:/usr/local/binにパスは通ってるんだけどなんでだろー