結局は psutil の方がより救世主なの (psuti.Process の suspend/resume)

glances は素晴らしいことは素晴らしいのだけれども。

Windows + MSYS という環境で作業する場合、「MSYS ネイティブなアプリケーションとそうでないもの」には非常に大きな格差がある。

すなわち。MSYS ネイティブであれば Unix と同じようにリッチなジョブコントロールの恩恵にあずかれる。つまり(bash からであれば) Ctrl-Z すればジョブをサスペンド出来、jobs でジョブを列挙出来、fg でレジュームしつつフォアグランドタスクに、bg でレジュームしつつバックグラウンドタスクに、などなど。しかしながら「MSYS ネイティブでないもの」はその恩恵には全くあずかることが出来ず、唯一「Unix と同じ」振る舞いをするのは「Ctrl-C で強制終了すること」だけである。

ffmpeg なのよね、これの影響がとても大きいのは。何するにも、ffmpeg で行うことはほとんどがとてつもない時間がかかるタスクなのだが、長大なコンソール出力によって、すぐに自分が打ち込んだコマンドラインは見えなくなるため、ffmpeg 自身のプログレス報告のみでは自分が「何してる最中なのか」がわからなくなりがち、なのだが、もしも「せめて」サスペンド出来たなら、コンソールのスクロールバーを落ち着いて操作出来て、かなり気楽になるであろう、と。

psutil.Process がね、suspend、resume できちゃうわけよ。ので、たとえばこんなのを作っとくとイザというとき便利かもなと:

時々空振り、してそう。
 1 # -*- coding: utf-8 -*-
 2 from __future__ import print_function
 3 from __future__ import unicode_literals
 4 
 5 import os
 6 import sys
 7 import pipes
 8 import re
 9 import psutil
10 
11 
12 if __name__ == '__main__':
13     import argparse
14     ap = argparse.ArgumentParser()
15     ap.add_argument("--search_cmdline_regexp", default=".*")
16     ap.add_argument("--search_cmd_regexp", default=".*")
17     args = ap.parse_args()
18     def _catcmdl(cmdl):
19         return " ".join([pipes.quote(a) for a in cmdl])
20     ma = {
21         "cmd": re.compile(args.search_cmd_regexp),
22         "cmdline": re.compile(args.search_cmdline_regexp),
23         }
24     for p in psutil.process_iter():
25         cmdl = ""
26         try:
27             cmdl = p.cmdline()
28         except Exception as e:
29             print(e, file=sys.stderr)
30         if not cmdl:
31             continue
32         tcmdl = _catcmdl(cmdl)
33         if ma["cmd"].search(cmdl[0]) and ma["cmdline"].search(tcmdl):
34             q = input("{}: suspend or resume? (s/r/n) ".format(tcmdl))
35             if q.lower() == "s":
36                 p.suspend()
37                 break
38             elif q.lower() == "r":
39                 p.resume()
40                 break

明らかにもっと便利な道具に発展できるけれど、まぁ手始めとしてはこんな感じね。(なお、ワタシはこのスクリプトを「管理者として実行」で起動した bash から実行してる、ので権限エラーが起こらないが、そうでない場合はエラーになるかもしれない。未確認。)


翌日追記:
やったばかりのネタなのに盛り込むの失念してた:

ややベター
 1 # -*- coding: utf-8 -*-
 2 from __future__ import print_function
 3 from __future__ import unicode_literals
 4 
 5 import os
 6 import sys
 7 import pipes
 8 import re
 9 import psutil
10 
11 
12 def _str(s):
13     return s.encode(sys.getfilesystemencoding(), errors="xmlcharrefreplace").decode(
14         sys.getfilesystemencoding())
15 
16 
17 if __name__ == '__main__':
18     import argparse
19     ap = argparse.ArgumentParser()
20     ap.add_argument("--search_cmdline_regexp", default=".*")
21     ap.add_argument("--search_cmd_regexp", default=".*")
22     args = ap.parse_args()
23     def _catcmdl(cmdl):
24         return " ".join([pipes.quote(a) for a in cmdl])
25     ma = {
26         "cmd": re.compile(args.search_cmd_regexp),
27         "cmdline": re.compile(args.search_cmdline_regexp),
28         }
29     for p in psutil.process_iter():
30         cmdl = ""
31         try:
32             cmdl = p.cmdline()
33         except Exception as e:
34             print(e, file=sys.stderr)
35         if not cmdl:
36             continue
37         tcmdl = _catcmdl(cmdl)
38         if ma["cmd"].search(cmdl[0]) and ma["cmdline"].search(tcmdl):
39             q = input("{}: suspend or resume? (s/r/n) ".format(_str(tcmdl)))
40             if q.lower() == "s":
41                 p.suspend()
42                 break
43             elif q.lower() == "r":
44                 p.resume()
45                 break

これに書いたように、この「措置」は本当の理想とは違っていて、いわば妥協案である。妥協案でない術についてはリンク先参照。

というか、本当は「妥協案」であるとともに、「Python のポリシーとの喧嘩」であることにも一応注意。「表現出来ない文字コード値を使うなんて万死に値する」と決断することはこれは「迷惑を撒き散らさない」ために必要な戦略であった。一昔前は「誰かが勝手に豆腐(□)に置き換えて復元不可能になった多バイト文字列」が大量発生していた。そう、今やってみせたように xmlcharrefreplace ならともかく、15年ほど前までは「表現できないものは豆腐にリプレース」が半ば当然のように行われていたので、表示上の文字化け以上に問題となるケースが多かったわけだ。データベースに記録することを考えてみるといい。ユーザが意図した文字列はそうやって問答無用で置き換えられた上でデータベースに記録され、そしてそれを二度と復元出来ない、ということである。だから Python が「かたくな」なのにはちゃんと意味があるわけだ。

今のケースの場合、「これのことなんだけどどーよ?」と確認のために表示する際だけの問題なので、そこで表示できない文字を xmlcharrefreplace したところで「後日問題になる」ことがない。ので、まぁいい、ってこと。まぁ今の場合恨むべきは「Chcp の使い勝手が非常に悪い」ことかな、って気はするね。


2021-03-29追記:
Windows における「プロセスの操作・管理」に関する「救世主」について、一応知らない人のためにもう一つ紹介しとく。

ワタシのサイトではちょいちょい名前を出してるんだけれど、「SysinternalsSuite」は、名前だけでも憶えておいて。既にかなりの老兵だけれど、今でも役に立つ。いまや Micorosoft 公式となったこれの、Microsoft による紹介文:

The Sysinternals web site was created in 1996 by Mark Russinovich to host his advanced system utilities and technical information. Whether you’re an IT Pro or a developer, you’ll find Sysinternals utilities to help you manage, troubleshoot and diagnose your Windows systems and applications.

ワタシが Microsoft にこうして公式に取り込まれる前の Mark Russinovich にお世話になっていたのは 2000~2005 年のあたりではなかったかと思う。確か MSDN マガジンに彼の記事の翻訳が載ってたんじゃなかったっけかなぁ? 「internal」の名前の通り、Windows 内部の仕組みやシステムコールなどを解説する記事だった。今どこかで読めないんだろうか、これ?

「サスペンド/レジューム」に関しては Sysinternals utilities には「PsSuspend.exe」が含まれてる。ほかにも色々使えるものが入ってるので、知らなかったなら是非入手してみて欲しい。Windows 10 ではタスクマネージャが相当拡張されて使い勝手も良くなったけれど、そうでなかった頃は Sysinternals utilities の「プロセスエクスプローラ」などははっきりいって「強烈」だった、凄すぎて。

ほかの場所でワタシが SysInternalsSuite について触れているのは、プロセスエクスプローラ以外のネタだと、junction について。MSYS の ln の話をした時だね。紹介したことのないもので、知っといたらいいと思うのが PendMoves/MoveFile かな。このツールそのものよりも、「MoveFileEx API」(とそれが依存するレジストリ)についての情報が役に立つ。日々経験するでしょ、「誰かが使ってるんで消せねーんでやんの」状態。そのためのものね。