私は今週の自分自身の疑問に答えようとしました。記録のために、この努力は非常に複雑なものに変わったので、Kendall Helmstetter Glenの示唆したように、この質問を読んでいるほとんどの人々はおそらく、単にInstrumentsと突き合わせるべきです。群衆のマゾイストのために、読んでください!
問題を再現することから始めるのが一番簡単でした。私は正当なアイドル時間ではありません システムコール/ mach_msg_trapに費やされた時間の長い期間に警告することにしたい
:ここでは私が思いついたものです。 「合法的な アイドル時間」は、machOSからの次のイベントを待っているmach_msg_trapに費やされた時間として定義されます。
また、重要なことに、私は長い時間がかかるユーザーコードについては気にしませんでした。この問題は、InstrumentsのTime Profilerツールを使用して診断し理解するのが非常に簡単です。私はブロックされた時間について特に知りたがっていました。 Time Profilerを使用してブロックされた時間を診断することも可能ですが、その目的で使用することはかなり難しくなっています。同様に、System Trace Instrumentはこのような調査にも役立ちますが、きわめてきめ細かで複雑です。私はもっと簡単なものを求めていました。
get-goから、ここで選択したツールがDtraceであることが明らかでした。 kCFRunLoopAfterWaiting
とkCFRunLoopBeforeWaiting
を発射したCFRunLoop
オブザーバーを使い始めました。私のkCFRunLoopBeforeWaiting
ハンドラへのコールは "正当なアイドルタイム"の開始を示し、kCFRunLoopAfterWaiting
ハンドラは正当な待機が終了したという私へのシグナルになります。私はDtraceのPIDプロバイダを使用して、正当なアイドルをアイドル状態からブロックする方法としてこれらの関数の呼び出しをトラップします。
このアプローチは私を始めましたが、結局は欠陥があることが判明しました。最大の問題は、多くのAppKit操作が同期であることです。UI内のイベント処理をブロックしますが、実際には呼び出しスタック内のRunLoopを低くします。 RunLoopのこれらのスピンは、その時間中にユーザーがUIとやりとりすることができないため、(私の目的のために)「正当な」アイドルタイムではありません。 RunLoop指向のI/Oを見ているバックグラウンドスレッド上のrunloopを想像してみてください。しかし、メインスレッドでUIがブロックされても、まだブロックされています。例えば、私はIBActionに次のコードを入れて、ボタンからそれをトリガー:
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: @"http://www.google.com/"]
cachePolicy: NSURLRequestReloadIgnoringCacheData
timeoutInterval: 60.0];
NSURLResponse* response = nil;
NSError* err = nil;
[NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err];
コード紡糸から実行ループを防止していないこと - のAppKitはsendSynchronousRequest:...
コールの内側にあなたのためにそれを回転させる - しかし、それそれが返るまでユーザーがUIとやりとりするのを防ぎます。これは私の心には「正当なアイドル」ではないので、どのアイドルがどのようなものであったかを整理する方法が必要でした。 (CFRunLoopObserver
のアプローチでは、最終的な解決策ではないコードの変更が必要になるという欠点もありました)。
私は自分のUI /メインスレッドをステートマシンとしてモデル化することに決めました。 LEGIT_IDLE、RUNNINGまたはBLOCKEDの3つの状態のうちの1つにあり、プログラムが実行されたときにこれらの状態の間を行き来します。私はDtraceのプローブを思いついて、それらの遷移を捕まえることができました。私が実装した最後のステートマシンは、3つの州だけのものよりかなり複雑ですが、それは2万フィートの視点です。
上記のように、どちらのケースもmach_msg_trap()
と__CFRunLoopRun
になるので、正当なアイドル状態から悪いアイドル状態を選別するのは簡単ではありませんでした。コールスタックに単純なアーティファクトが1つ見つかりませんでしたが、その違いを確実に伝えるために使用できました。ある機能の簡単なプローブが私を助けてくれるとは思われません。私は正当なアイドル状態と悪いアイドル状態の様々なインスタンスでスタックの状態を見るためにデバッガを使用しました。
#0 in mach_msg
#1 in __CFRunLoopServiceMachPort
#2 in __CFRunLoopRun
#3 in CFRunLoopRunSpecific
#4 in RunCurrentEventLoopInMode
#5 in ReceiveNextEventCommon
#6 in BlockUntilNextEventMatchingListInMode
#7 in _DPSNextEvent
#8 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
#9 in -[NSApplication run]
#10 in NSApplicationMain
#11 in main
は、だから私は私が到着した時に確立するネストされた/チェーンのpidプローブの束を設定するために努め:私は合法的アイドル時に、私は(一見確実に)このようなコールスタックを参照してくださいねと判断しました、その後、この状態を残した。残念なことに、何らかの理由でDtraceのpidプロバイダは、任意のシンボルすべてに対してエントリとリターンの両方を普遍的に調べることができないようです。具体的には、pid000:*:__CFRunLoopServiceMachPort:return
またはpid000:*:_DPSNextEvent:return
でプローブを取得できませんでした。詳細は重要ではありませんが、他のさまざまな動きを見て特定の状態を把握することで、私が入力されて正規のアイドル状態から抜け出たときに(やはり一見信頼できる)確立することができました。
次に、RUNNINGとBLOCKEDの違いを調べるためのプローブを決定しなければなりませんでした。それは少し楽だった。最後に、私はBSDシステムコール(Dtraceのシステムコールプローブを使用)とmach_msg_trap()
(pidプローブを使用)を呼び出して、legit idleの期間中BLOCKEDになることはないと考えました。 (私はDtraceのmach_trapプローブを見ましたが、私が望んでいなかったので、pidプローブを使用しました。)
最初は、実際に測定するためにDtrace schedプロバイダrealブロックされた時間(スレッドがスケジューラによって中断された時間)がかなり複雑になりました。私は自分自身に考えました。「もし私がカーネル内にいたら、スレッドが実際に眠っているかどうか?それはユーザーにとってはまったく同じです。ブロックされています。したがって、最終的なアプローチはすべての時間を(syscalls || mach_msg_trap()) && !legit_idle
で測定し、そのブロック時間を呼び出します。
この時点では、(例えば、sleep(5)
への呼び出しのような)長い持続時間の単一のカーネル呼び出しをキャッチするのは簡単です。しかし、より多くの場合、UIスレッドのレイテンシは、カーネルへの複数回の呼び出し(read()やselect()の何百回もの呼び出しを考える)に蓄積される小さな待ち時間に起因することが多いため、イベントループの1回のパスにおける全体のシステムコール量または時間が特定のしきい値を超えました。私は、さまざまなタイマーを設定し、各状態で費やされた蓄積された時間を記録し、ステートマシンのさまざまな状態にスコープし、BLOCKED状態から移行したときにアラートをダンプし、しきい値を超えました。この方法は明らかに誤解の対象となるデータを生成するか、赤ちゃん全体(つまり、ランダムな比較的迅速なシステムコールで、警告しきい値を超えてしまう)が発生する可能性があります。
最後に、DtraceスクリプトはステートマシンをD変数に保ち、説明されたプローブを使用してステート間の遷移を追跡し、ステートマシンが(ステートマシンが)特定の条件に基づいて移行状態に移行します。私は、ディスクI/O、ネットワークI/O、スリープ()の呼び出しを行い、合法的な待機に関連するデータから注意をそらすことなく、これら3つのケースすべてを捕まえることができた、 。これはまさに私が探していたものでした。
この解決策は明らかに非常に脆弱であり、徹底的にひどいがほとんどすべての点でそうです。 :)私や他の誰にとっても役立つかもしれませんが、楽しい演習だったので、私はその話をして、Dtraceの結果としてのスクリプトを共有すると思っていました。たぶん誰かがそれが役に立つと思うかもしれません。また、Dtraceスクリプトの作成に関して相対的なということを告白しなければならないので、私は何百万もの間違ったことをしたと確信しています。楽しい!
ラインに投稿するにはあまりにも大きかったので、親切にこっち@Catfish_Manによってホストされています:MainThreadBlocking.d
私はまだこれを実行する試していないが、関係なく、それが動作するかどう、これはおそらく最高です私が見たスタックオーバーフローの答え。私の一言は、あなたがダウンロードしたサイトには何もしないで.exeをダウンロードするようなことはできないということです。 OSXではあまり役に立ちません;) –