さて次は Himeos でリソース監視を…と思ってダイアログを開くとこんな表示がありました。
なにやら文字が化けています。
よく見るとリポジトリの登録状況がこんな・・・
原因はノードの作成時に情報を SNMP 経由で取得しているのですが、その際にデバイス名が ASCII 文字以外の文字が使われてたからです。(今回の場合は「仮想イーサネットアダプター」)
- どうして?の前に、先に結論(汗
Windows に対して SNMP で取得した文字列は文字化けします。具体的には、ノード登録時の SNMP による探索、SNMP 監視、プロセス監視が該当です。
- 対策
ノードの登録情報は化けている部分を手入力で修正することで対応します。
SNMP 監視とプロセス監視は対策はありません。非 ASCII 文字が監視対象にならないように注意します。どうしても何とかしたい場合にはカスタム監視を検討します。 - 原因
なぜ文字化けが起こるかというと hinemos では SNMP でデータを取得する際のエンコードは UTF-8 固定でしか取得しないからです。対して Windows の SNMP は Shift-JIS で応答します。この結果文字化けが発生します。
従って SNMP でデータを取得する SNMP 監視はもちろんのこと、SNMP 経由で情報を取得するリポジトリへのノードの作成/変更やプロセス監視も影響を受けます。SNMP Trap 監視だけは文字コードが指定できるので大丈夫です(多分) - 確認
本当にそうであるのかどうかを確認してみます。ソースコードは https://github.com/hinemos/hinemos/find/master で公開されています。以下の引用は 2017/1/15~20 頃にしていますので多分 version 6.0.* あたりのコードです。
まずプロセス監視のコードを確認してみます。なぜかというとプロセス監視では以下の理由で Linux 系と Windows で処理が異なると予想されるからです。
プロセス監視は SNMP 経由で情報を取得します。例えば Path を取得するために OID = .1.3.6.1.2.1.25.4.2.1.4 をアクセスします。このとき Linux 系はファイル名を含めて戻しますが (例: HOST-RESOURCES-MIB::hrSWRunPath.1 = STRING: "/usr/lib/systemd/systemd")、Windows は ディレクトリ名まででファイル名を戻しません。(例: HOST-RESOURCES-MIB::hrSWRunPath.388 = STRING: "C:\\WINDOWS\\system32\\") 従って Linux の OID = .1.3.6.1.2.1.25.4.2.1.4 の内容(ディレクトリ名+ファイル名)の内容に合わせたいと考えたなら Windows では OID = .1.3.6.1.2.1.25.4.2.1.4(ディレクトリ名)に加えて OID = .1.3.6.1.2.1.25.4.2.1.5 (ファイル名)を取得して連結する必要があります。
これを hinemos では RunMonitorProcess.java で実施していると思われます。該当部分は以下です。
// Windowsの場合を考慮して、valueCommandとvalueNameを連結する if (command.length() == 0) { // パスが取得できない場合はコマンド名 // パスがnull OR 空文字 command = name; } else if (!command.startsWith("/") && command.endsWith("\\") && !command.equals(name)) { // 条件・・・・ // パスが'/'以外の文字で始まり // パスが'\'で終わっていて // パスとコマンド名が違う command = command + name; }
このコードで分かることは Linux 系か Windows かの判定は、ノードの情報(プラットフォームや OS 名)や監視設定で設定するスコープなどを見ているのではなく SNMP で取得した値を元に判断しているということです。つまり文字コードは意識していない可能性が高いということです。なぜならば Windows の時だけ文字コードの変換を行っているところがあるならそこで command = command + name; を行えばよく「/ で始まらず \ で終わる」のような条件で分岐する必要がないからです。(*1)
また、この command や name に入る文字列を作っているのは Snmp4jPollerImpl.java であろうかと思います。以下に該当部分のコードを引用します。
case SMIConstants.SYNTAX_OCTET_STRING: OctetString octStr = (OctetString) variable; StringBuilder value = new StringBuilder(); byte[] bytes = octStr.getValue(); ・・・・・ } else { // WindowsのNIC名には0x00が含まれることがあるので除外する int length = bytes.length; for (int i = 0; i < bytes.length; i++) { if (bytes[i] == 0x00) { length = i; break; } } value.append(new String(bytes, 0, length)); }
byte[] bytes = octStr.getValue(); で SNMP を経由してバイトストリームを取得しています。
バイトストリームを取得してから後は Windows を考慮してバイトストリーム中に 0x00 が含まれていた場合そこで打ち切る処理がありますが、それ以外にバイトストリームを操作する処理はありません。最終的に new String(bytes, 0, length) でバイトストリームを文字列に変換しています。このコンストラクタを使う場合、文字コードはプラットフォームデフォルトです。(*2)というような状況であるため、Windows から SNMP 経由で ASCII 文字以外を取得するのはおそらく絶望的です。(*3)
- この問題は実際に発生するか?
恐らく SNMP 経由で ASCII 以外の文字列を取得するケースはほぼないと思います。
リポジトリの登録情報は手修正で回避できますし、SNMP 監視で監視するデータで ASCII 以外が必要になることはほぼないかと思います。
もっとも顕在化の可能性がありそうなのはプロセス監視ですが、プロセス名や引数にしたところで ASCII 以外の文字を使うことはほぼないと思われる) ので、この問題が実際に顕在化することはあまりないのではないかと思います。
(*1)
このつくりは賛否両論ありそうです。
利点は他の設定値等を使用せずに取得した値のみで解決できることです。従って Windows なのか Linux なのかを指定しなくてもうまく対処することができます。
欠点は想定外のケースに対応できないことです。今回の場合は後者が顕在化してしまい上手くいかないケースだと思います。つまり SNMP が返す文字列の違いだけではなく文字コードも違うという点は想定に入ってない (仕様に含めていない) ということです。
かといって文字コードを違いを想定に追加するとした場合、現在のコードでは値をみるときには既にバイトストリームはデコードされた後ですから再デコードできませんし、仮にあらためてバイトストリームから再度デコードするとしても「Windows だったら絶対に Shift-JIS でしか戻らない」をハードコードしてしまうことになります。そうすると結局は柔軟性を失わせる結果になってしまいます。
ここは SNMP TRAP 監視がそうしているように、SNMP 監視やプロセス監視に対しても文字コードの指定が行えるようにしたほうがよりよかった気はします。
もちろん文字コードの指定だけだと内部的に command + name しているのはいいのか?という問題は残ったままになりますから、文字コードの指定ができれば全て解決というわけでもありません。その点ではプロセス監視については文字コードの指定ではなく OS の指定ができるようにしたほうがより良い気がします。
なぜならば OS に依存しないコードにしようとした結果、データから OS を類推しそれによって OS に依存した処理を書いており、結局は OS 依存のコードを埋め込んでしまっているからです。それよりはプロセス監視設定に OS の項目を入れてそれにより分岐したほうがスマートである気がします。
(*2)
https://docs.oracle.com/javase/jp/6/api/java/lang/String.html#String(byte[], int, int)
(*3)
システムの文字コードを ja_JP.UTF-8 から ja_jp.shift_jis に代えます?それはそれで非現実的な気がします。
仮にそうして Windows が OK になったとても UTF-8 な Linux で通らなくなりそうですし。