psutil

GC

FreeBSDのportsを点検してたんだ。そしたらこんな宣伝が掲げてあった。

Rust is an open-source systems programming language that runs blazingly
fast, prevents almost all crashes, and eliminates data races.
Some of its features:

  - Algebraic data types, type inference
  - Pattern matching and closures
  - Concurrency without data races
  - Guaranteed memory safety
  - Optional garbage collection
  - Zero-cost abstractions
  - Minimal runtime
  - Efficient C bindings

WWW: https://www.rust-lang.org/

GCとは無縁と思っていたんだけど、どうやらそうではないみたい。現に、 gc 0.41 なんてのが見付かった。更に、 象牙の塔では、 Rust as a Language for High Performance Garbage Collector Implementation なんて亊が行われていた。まあ、好きにやってください。

それより気になったのは、そろそろ新しいrustがふって来る予感がするな。

去年のあれ

何と言うタイトルだ。。。って、近頃のTVの真似をしただけ。天下のNHKも民放宜しく、平気でやってるからね。

そう、去年見付けておいた@ITに出てた rustとpythonの力比べの記事を、FreeBSDでやろうとしてたんだ。

   Compiling serde_urlencoded v0.7.0
   Compiling psutil v3.2.1
error[E0432]: unresolved import `crate::cpu::cpu_times_percpu`
 --> /home/sakae/.cargo/registry/src/github.com-1285ae84e5963aae/psutil-3.2.1/src/cpu/cpu_times_percent.rs:6:29
  |
6 | use crate::cpu::{cpu_times, cpu_times_percpu, CpuTimes};
  |                             ^^^^^^^^^^^^^^^^
  |                             |
  |                             no `cpu_times_percpu` in `cpu`
  |                             help: a similar name exists in the module: `cpu_times_percent`

この通り、psutilのcpu,network,disk,process等で、ことごとくエラーですよ。 これのREADME.mdを見ると

## Platform Support

Currently, only Linux and macOS are supported, but support is planned for 
all major platforms.

rust-psutil / rust-psutil を見るまでもなく、BSD族は蚊帳の外である。

psutil

取り敢えず、どんな使いかたになってるか身辺調査。ええ、あわよくばOpenBSDにも引きいれられないかと思いましてね。

まずは、公開情報って亊で、友人関係を当る亊にしました。 Cargo.lock

name = "psutil"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e780a52bf9358cb8257cac630b130dc603901f7488f8eef13e2d512cead10739"
dependencies = [
 "cfg-if 0.1.10",
 "darwin-libproc",
 "derive_more",
 "glob",
 "mach",
 "nix",
 "num_cpus",
 "once_cell",
 "platforms",
 "thiserror",
 "unescape",
]

色々な協力者がいるんですなあ。そんじゃ、専用の道具を使って、少し追跡。

sakae@deb:/tmp/web_engineer_in_rust/chapter2/rust$ cargo tree
 :
├── psutil v3.2.1
│   ├── cfg-if v0.1.10
│   ├── derive_more v0.99.16 (proc-macro)
│   │   ├── proc-macro2 v1.0.30 (*)
│   │   ├── quote v1.0.10 (*)
│   │   └── syn v1.0.80 (*)
│   ├── glob v0.3.0
│   ├── nix v0.17.0
│   │   ├── bitflags v1.3.2
│   │   ├── cfg-if v0.1.10
│   │   ├── libc v0.2.104
│   │   └── void v1.0.2
│   ├── num_cpus v1.13.0 (*)
│   ├── once_cell v1.8.0
│   ├── platforms v0.2.1
│   ├── thiserror v1.0.30 (*)
│   └── unescape v0.1.0

これと、rust-psutilを比べてみると面白いな。 単一故障点と思われる、gitの在処は、 rust-psutil / rust-psutil だ。

どんな使われ方をしてるか、元記事を当ってみた。

println!(
    "プロセスの使用メモリ量(参考):{:?}MB",
    psutil::process::Process::new(std::process::id())
        .unwrap()
        .memory_info()
        .unwrap()
        .rss() as f64
        / 1e6

ふーん、PIDを得てそいつのメモリー使用状況のうちRSSを取り出しているってパイプが組まれているんだな。ああ、ruby風に言うとメソッド・チェーンだ。途中に挟まっているunwrapは魔除けじゃなくて、エラーかも知れないって型に包まているのの梱包を剥す役割になってる。

example

このpsutilにはexampleってのが付属してる。そして、それを簡単に実行する機構がcargoに用意されている。rustの解説WEBを見てても、いきなり借用がどうのこうのと言語よりの話に行っちゃって、周辺の話は余り出てこない。んでもって、オイラーが実験しとく。

sakae@deb:/tmp/vendor/psutil$ ls examples/
all.rs  collectors.rs  kill.rs  ps.rs

bashのプロンプトが /tmp/vendorで始まってるけど、これは元記事の所で cargo vendor して、出来たものを移動しといたんだ。この方が、見やすいからね。

最初はグーじゃなくて、ps してみる。

sakae@deb:/tmp/vendor/psutil$ cargo run --example ps
   PID %CPU %MEM COMMAND
     1 0.0 0.2 /sbin/init
     2 0.0 0.0 [kthreadd]
     :
  7797 12.2 0.1 target/debug/examples/ps

動いているな。次は殺してやる。

sakae@deb:/tmp/vendor/psutil$ cargo run --example kill
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', examples/kill.rs:7:32

反抗されたので、ソースしろ。

sakae@deb:/tmp/vendor/psutil$ cat examples/kill.rs
//! Kill a process, reading it's PID as a cli argument.

use psutil::process::Process;

fn main() {
        let args: Vec<String> = std::env::args().collect();
        let process = Process::new(args[1].parse().unwrap()).unwrap();

        if let Err(error) = process.kill() {
                println!("Failed to kill process: {}.", error);
        };
}

そりゃそうだ。殺しのターゲットを指定しなきゃ、いかにゴルゴ13でも、実行出来ませんです。ターゲット番号は、コマンドラインから与えるんだな。

sakae@deb:/tmp/vendor/psutil$ sleep 1000 &
[1] 7829
sakae@deb:/tmp/vendor/psutil$ cargo run --example kill -- 7829
[1]+  Killed                  sleep 1000

今度はちゃんと殺せた。

sakae@deb:/tmp/vendor/psutil$ cargo run --example kill -- 7829
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: NoSuchProcess { pid: 7829 }', examples/kill.rs:7:58

そして、一度死亡したpidを指定すると、パニクってしまうのは、お約束? って、ちゃんと、その対処方法が記述されてるからね。

small app

use psutil::host::info;

fn main() {
    println!("{:?}", info());
}

実行例

sakae@deb:/tmp/vendor/psutil/examples$ cargo r --example info
Info { operating_system: Unknown, release: "5.10.0-10-686-pae", version: "#1 SMP Debian 5.10.84-1 (2021-12-08)", hostname: "deb", architecture: Unknown }

32ビット輝は、もう無視の段階だなあ。

Info { operating_system: Unknown, release: "5.10.0-10-amd64", version: "#1 SMP Debian 5.10.84-1 (2021-12-08)", hostname: "pen", architecture: X86_64 }
Ok(6375.72s)

ついでに、アップタイムも出してみた。結果がリザルト型になってる。

use std::thread;
use std::time::Duration;

use psutil::*;

fn main() {
    let hoge = host::info();
    dbg!(hoge);
}

こういう方法も有り

[examples/mini.rs:8] hoge = Info {
    operating_system: Unknown,
    release: "5.10.0-10-686-pae",
    version: "#1 SMP Debian 5.10.84-1 (2021-12-08)",
    hostname: "deb",
    architecture: Unknown,
}

で、こちらの方が、かっこいい。

地獄の再来

ある日psutilを極めようとして、 Cargo.tomlに

[dependencies]
psutil = "3.2.1"

とだけ書いてbuildしよとしたんだ。

sakae@deb:/tmp/hoge$ cargo b
    Updating crates.io index
error: failed to select a version for the requirement `platforms = "^0.2.1"`
candidate versions found which didn't match: 2.0.0, 1.1.0, 1.0.3, ...
location searched: crates.io index
required by package `psutil v3.2.1`
    ... which satisfies dependency `psutil = "^3.2.1"` of package `hoge v0.1.0 (/tmp/hoge)`

DLを拒否されたよ。psutilに記述してある、platformsが0.2.1ってなってて、それがもうこの世の中には存在しないので、駄目って亊だ。

こういう依存地獄は、haskellで頻繁に怒っていて、その解決に、パトロール隊が組織され、矛盾がないように目を光らせていたな。rustでもこの依存地獄が発生。これ、原理的には避けられない。みんな非同期に更新してるからね。

件のplatforms version によると、古い版は整理して削除されちゃったみたいだ。だから、依存はなるべく少ないライブラリィーを使うべきってな亊が、haskellの指南書に書かれていたな。

これの解決には、psutilの作者さんに通報して、新しい依存に変更したものをリリースしてもらうより方法は無い。作者さんが在命でかつ意欲もりもりを祈るしかない。

そんな受け身は、いかんぜよ。気が付いた人が通報すればよい。gitのアカウントを持ってるなら、リクエストするのが筋だろう。けど、オイラーは持っていない。そこで、git log して出て来る記録から、目星い人を選べばよい。あるいは、Cargo.tomlにも作者名が有るんで、それを使ってもよい。

ob$ git log | grep Author: | sort | uniq -c | sort -r

この結果を見ると、途中からオーナーが交代したみたいだ。一番最近の投稿(2021/04/11)をした人に、依頼メールを出しておいたよ。

Cargo.toml を壊れたままにしない こういう記事を発見した。オイラーの行為は一応正しかったみたいだ。まだ、修整された風ではないみたいだけど。

縮退運用

折角なんで、コンパイルエラーになる所を避けて、コンパイルを完了させてみる。 Cargo.toml を勘で修整

[features]
default = [ "host", "memory",  "sensors"]
#default = ["cpu", "disk", "host", "memory", "network", "process", "sensors"]

面白い所を全部削除してみた。って、それじゃ話にならんじゃん。まあ、コンパイルが成功すれば、docも作成されるから、依存してるクレートの閲覧が出来るから佳としておこう。

ob$ cargo r --example info
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/examples/info`
[examples/info.rs:8] hoge = Info {
    operating_system: Unknown,
    release: "7.0",
    version: "GENERIC.MP#3",
    hostname: "ob.localhost.jp",
    architecture: Unknown,
}

OpenBSD(amd64)なマシンなんだけど、OS名は不明、石の名前も不明って、どゆ亊? データの出所は、

ob$ uname -a
OpenBSD ob.localhost.jp 7.0 GENERIC.MP#3 amd64

だと思うんだけど、それならOS名とかを取得出来そうだと思うんだけど。

ob$ rust-gdb -q target/debug/examples/info
Reading symbols from target/debug/examples/info...done.
(gdb) b 7
Breakpoint 1 at 0x18b5b: file examples/info.rs, line 7.
(gdb) r
Starting program: /tmp/rust-psutil/target/debug/examples/info

Breakpoint 1, info::main::h4c381eab8bf58502 () at examples/info.rs:7
7     let hoge = host::info();

BPは、行番号だけでOKみたい。

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000006b61f34ab5b in info::main::h4c381eab8bf58
502 at examples/info.rs:7
  breakpoint already hit 1 time

アプリ名::mainってのがgdbが認識してるやつなのか。

(gdb) s
psutil::host::sys::unix::info::info::hb01864db9d31455a ()
    at src/host/sys/unix/info.rs:11
11  let utsname = sys::utsname::uname();
(gdb) n
13  let operating_system = OS::from_str(utsname.sysname()).unwrap_or(OS::Unknown
);
(gdb)
14  let release = utsname.release().to_string();

もう少し分かり易く、俯瞰してみる。

(gdb) l
9
10 pub fn info() -> Info {
11  let utsname = sys::utsname::uname();
12
13  let operating_system = OS::from_str(utsname.sysname()).unwrap_or(OS::Unknown);
14  let release = utsname.release().to_string();
15  let version = utsname.version().to_string();
16  let hostname = utsname.nodename().to_string();
17  let architecture = Arch::from_str(utsname.machine()).unwrap_or(Arch::Unknown);
18
(gdb) bt 2
#0  psutil::host::sys::unix::info::info::hb01864db9d31455a ()
    at src/host/sys/unix/info.rs:14
#1  0x000006b61f34ab68 in info::main::h4c381eab8bf58502 ()
    at examples/info.rs:7
(More stack frames follow...)

OS名を聞いてもちゃんと答えてくれない亊を想定して、そんなの知らんって予防してたのね。

(gdb) n
15  let version = utsname.version().to_string();
(gdb) p utsname
$1 = nix::sys::utsname::UtsName (libc::unix::bsd::utsname {sysname: [79, 112,
      101, 110, 66, 83, 68, 0 <repeats 249 times>], nodename: [111, 98, 46,
      108, 111, 99, 97, 108, 104, 111, 115, 116, 46, 106, 112,
      0 <repeats 241 times>], release: [55, 46, 48,
      0 <repeats 253 times>], version: [71, 69, 78, 69, 82, 73, 67, 46, 77,
      80, 35, 51, 0 <repeats 244 times>], machine: [97, 109, 100, 54, 52,
      0 <repeats 251 times>]})

あれ??

rust-gdbgui

FreeBSDに待望の2021式のrust族がやってきた。そこで面白いものを発見。

[sakae@fb ~]$ rust-gdbgui

rust-gdbgui
===========
gdbgui - https://gdbgui.com - is a graphical front-end to GDB
that runs in a browser. This script invokes gdbgui with the Rust
pretty printers loaded.

Simple usage  : rust-gdbgui target/debug/myprog
With arguments: rust-gdbgui 'target/debug/myprog arg1 arg2...'
  (note the quotes)
   :

喜びいさんで起動してみると、肝心のgdbguiってのがなかった。糠喜びさせないでよね。プンプン。

[sakae@fb ~]$ PATH=/home/sakae/.local/bin:$PATH
[sakae@fb ~]$ pipx install gdbgui
  installed package gdbgui 0.15.0.1, installed using Python 3.8.12
  These apps are now globally available
    - gdbgui
done! ✨ 🌟 ✨

起動するとブラウザーが動くのはいいんだけど、どうもgdbと接続出来無いみたいだ。リナでやってみるかな。コネクトに失敗したってなったら、再ロードすればいいみたい。

下記は、無理を承知で、あらましの確認モード。

[sakae@fb /tmp/vendor/psutil]$ rust-gdbgui target/debug/examples/info
usage: gdbgui [-h] [-g GDB_CMD] [-p PORT] [--host HOST] [-r]
              [--auth-file AUTH_FILE] [--user USER] [--password PASSWORD]
              [--key KEY] [--cert CERT] [--remap-sources REMAP_SOURCES]
              [--project PROJECT] [-v] [-n] [-b BROWSER] [--debug]
              [--args ...]
              [debug_program]
gdbgui: error: unrecognized arguments: --gdb-args target/debug/examples/info

[sakae@fb /tmp/vendor/psutil]$ gdbgui target/debug/examples/info
Opening gdbgui with default browser at http://127.0.0.1:5000
w3m: Can't load http://127.0.0.1:5000.
View gdbgui dashboard at http://127.0.0.1:5000/dashboard
exit gdbgui by pressing CTRL+C

どうも、コマンドの指定が間違っているみたいだなぁ。

at debian

どんな風にプロセスが動いているか。

3381 pts/0    Sl+    0:01 /home/sakae/.local/pipx/venvs/gdbgui/bin/python /home/sakae/.local/bin/gdbgui --args target/debug/myprog
3412 ?        Ssl    0:09 firefox-esr http://127.0.0.1:5000
3573 ?        Sl     0:00 /usr/lib/firefox-esr/firefox-esr -contentproc -childID 4 -isForBrowser -prefsLen 5773 -prefMapSize 240761
3617 pts/6    Ssl+   0:00 gdb -iex=new-ui mi2 /dev/pts/5 -iex=set inferior-tty /dev/pts/4 -iex=set pagination off

バックエンドでpython製のサーバーが走り、gdbが起動するんだな。それがpid=3617になる。3381の方はブラウザーと接続するWebサーバーも兼ている。どうやら実体はjavascriptに任せているそうだ。今風の作り。

昔は、stdin/stdout/stderrだったけど、それがブラウザーに置き換わってしまってるんだな。 でも、色々な窓を一辺に見せられると、オイラーはおろおろしちゃうぞ。

一度に数十の監視窓を確認出来無きゃ墜落しちゃうパイロットなんてのは絶対に無理。精々鼠捕りに引っ掛らないようにスピードメーターを確認しながら走る車ぐらいが関の山ですよ。

ああ無理してrustのデバックをこれでやるなら、 Debugging Rust Programs は、ちゃんと見ておくべき。今の所、大事な注意だからね。

sakae@deb:/tmp/myprog$ rust-gdb -q target/debug/myprog
Reading symbols from target/debug/myprog...
(gdb) b myprog::main
Breakpoint 1 at 0x4e95: file src/main.rs, line 2.
(gdb) r
Starting program: /tmp/myprog/target/debug/myprog
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

Breakpoint 1, myprog::main () at src/main.rs:2
2           println!("Hello, world!");

こちらの方法は、なかなか捨てられませんです。 emacsからなら、emacs -f gdb しておいて、

Run gdb (like this): rust-gdb -i=mi target/debug/myprog

これで用が足りちゃう。画面も、gdbのコントロールとソースの表示だけだしね。


This year's Index

Home