vmstat/mpstatのようなものを作ってみた

PowerShellの勉強として、vmstat/mpstatのようなものを作ってみました。例えば、pstat.ps1という名前で保存すれば、./pstatで1秒毎のCPU使用率が表示されます。

param($delay = 1)

$allPc = new-object System.Diagnostics.PerformanceCounter(
    "Processor", "% Processor Time", "_Total")

$usrPc = new-object System.Diagnostics.PerformanceCounter(
    "Processor", "% User Time", "_Total")

$sysPc = new-object System.Diagnostics.PerformanceCounter(
    "Processor", "% Privileged Time", "_Total")

$idlPc = new-object System.Diagnostics.PerformanceCounter(
    "Processor", "% Idle Time", "_Total")

Write-Output "all usr sys idl"

# 初回は意味をなさないので値を取得するだけで捨てる
$all = $allPc.NextValue()
$usr = $usrPc.NextValue()
$sys = $sysPc.NextValue()
$idl = $idlPc.NextValue()

while($true){
    Start-Sleep -s $delay
    $all = $allPc.NextValue()
    $usr = $usrPc.NextValue()
    $sys = $sysPc.NextValue()
    $idl = $idlPc.NextValue()
    Write-Output ([string]::Format("{0,3:f0} {1,3:f0} {2,3:f0} {3,3:f0}",
        $all, $usr, $sys, $idl))
}

初心者がはまるPowerShellの罠

PowerShellを使い始めたのですが、Unixのシェルや一般的なスクリプト言語とはいくつか異なる点があってはまりましたので、メモに残しておきます。

  • エスケープシーケンスはバックスラッシュではなく、バッククォート。改行は\nではなく、`n。
  • `やその他明らかに継続するだろうとわかる場合(例:行末が|)だと行継続になる。ただし、行継続モードになるので解除には空行が必要。
  • Set-ExecutionPolicy RemoteSigned を実行しないと、スクリプトは許可されない。

Groovyではstatic importがload / requireの代わりになる

groovyshで作業していると、ちょっとした作業はメソッドを作っておいて後から呼び出したいと言うことがあります。ところが、Groovyにはloadとかrequireが無いので、groovyshを起動するたびにコピー&ペーストしていました。

これじゃあ使いにくいからloadやrequireが欲しいと言ったら、id:nowokayさんからstatic importを教えてもらいました。

さっそく試してみたのでメモに残しておきます。

以下の内容で Hello.groovy というファイルを作ります。ポイントはsayHelloをstaticとして定義しているところ。

def static sayHello(name) {
  println "Hello, ${name}!"
}

これをgroovycでコンパイルします。

groovyc Hello.groovy

groovyshで実行するとこの通り。

groovy:000> import static Hello.*
groovy:000> sayHello("World")
Hello, World!
===> null

Sequelはいつの間にか良くなっていた

以前、SequelからJDBC経由でデータベースに接続する場合は注意すべきだと書きました。

Sequelはまだまだ使い物にならないと思っていましたが、いつの間にか良くなっていました。

かつては、sequel_core/sql.rbに以下のような記述がありました。

    # This method quotes the given name with the SQL standard double quote. 
    # It uppercases the name given to conform with the SQL standard. This
    # should be overridden by subclasses to provide quoting not matching the
    # SQL standard, such as backtick (used by MySQL and SQLite), or where
    # lowercase is the default for unquoted identifiers (PostgreSQL).
    #
    # If you are using a database such as Oracle that defaults to uppercase
    # but you are using lower case identifiers, you should override this
    # method to not upcase the name for those identifiers.
    def quoted_identifier(name)
      "\"#{name.to_s.upcase}\""
    end

おせっかいにも、quoted_identifierメソッドは名前を大文字に変換していたのです。

このため、PostgreSQLのように大文字と小文字を区別し、デフォルトでは小文字を使うデータベースの場合、DB[:user_data].allといったシンボルによる表指定ができませんでした。

この問題の回避策として、JDBC経由でPostgreSQLに接続するときは、以下のような小細工をしていました。

module Sequel
  module JDBC
    class Dataset < Sequel::Dataset
      def quoted_identifier(name)
        "\"#{name.to_s}\""
      end
    end
  end
end

Sequel 2.8.0では、sequel_core/sql.rbは以下のようになっています。

    # This method quotes the given name with the SQL standard double quote. 
    # should be overridden by subclasses to provide quoting not matching the
    # SQL standard, such as backtick (used by MySQL and SQLite).
    def quoted_identifier(name)
      "\"#{name}\""
    end

quoted_identifierは余計なことをしなくなりました。

というわけで、改めてSequelからJDBC経由でPostgreSQLに接続するサンプルを書くと、以下の通りになります。

require 'rubygems'
require 'sequel'

DB = Sequel.connect('jdbc:postgresql://localhost/postgres?' +
  'user=postgres&password=postgres')

DB["SELECT * FROM user_data"].all    # 成功
DB[:user_data].all                   # 成功(従来は失敗)

DB[:user_data].all.each do |user_data|
  puts "#{user_data[:user_id]} #{user_data[:user_account]}"
end; nil

JDBCドライバの登録も自動化され、すっきりしました。

jirbで日本語を扱うには、jirb_swing -Kuとする

jirbに直接日本語を入力しようとすると、文字化けしてしまいます。

C:\>jirb
irb(main):001:0> '???{??'
=> "\302\223\303\272\302\226{\302\214\303\252"
irb(main):002:0> java.lang.String.new('???{??').to_s
=> "\302\223\303\272\302\226{\302\214\303\252"

--noreadline オプションを付けて行編集機能を無効にすると、日本語が入力できるようになりますが、評価結果が文字化けしてしまいます。

C:\>jirb --noreadline
irb(main):001:0> '日本語'
=> "\223\372\226{\214\352"
irb(main):002:0> java.lang.String.new('日本語').to_s
=> "\357\277\275\357\277\275{\357\277\275\357\277\275"

-K オプションでSJISを宣言すると、文字列の評価結果は文字化けしませんが、JavaのStringクラスに渡した結果は文字化けしてしまいます。Javaに値を渡す際に、SJISからUnicodeへのコード変換は行っていないようです。

C:\>jirb -Ks --noreadline
irb(main):001:0> '日本語'
=> "日本語"
irb(main):002:0> java.lang.String.new('日本語').to_s
=> "・\275・\275{・\275・\275"

WindowsコマンドプロンプトではSJISを使っているので、当然ながら -K オプションでUTF-8を宣言すると、文字化けしてしまします。

C:\>jirb -Ku --noreadline
irb(main):001:0> '日本語'
=> "\223?{語"
irb(main):002:0> java.lang.String.new('日本語').to_s
=> "・ス・ス{・ス・ス"

Windowsコマンドプロンプトでchcp 65001を実行すると、UTF-8が使えるらしいのですが、なぜか僕の環境ではjirbを実行できませんでした。

Windowsコマンドプロンプトをあきらめ、jirb_swingを使えば、文字化けが無くなります。

C:\>jirb_swing -Ku
irb(main):001:0> '日本語'
=> "日本語"
irb(main):002:0> java.lang.String.new('日本語').to_s
=> "日本語"

SequelはJDBC以外ならいい

今度はSequelを試してみます。まずは、PostgreSQLにpostgres-prでつないでみます。

require 'rubygems'
require 'sequel'

DB = Sequel.postgres(:database => 'postgres',
                     :user => 'postgres', :password => 'postgres')

DB[:user_data].all.each do |user_data|
  puts "#{user_data[:user_id]} #{user_data[:user_account].kconv(Kconv::SJIS, Kconv::UTF8)}"
end

モデルを作らなくても使えるのが、いいですね。
ActiveRecordの時と同様、kconvによるSJIS変換は無視してください。

JDBCだとこんな感じ。

require 'rubygems'
require 'sequel'

DB = Sequel.jdbc(:uri => 'jdbc:postgresql:postgres',
                 :user => 'postgres', :password => 'postgres')

Sequel::JDBC.load_driver('org.postgresql.Driver')

DB["SELECT * FROM user_data"].all.each do |user_data|
  puts "#{user_data[:user_id]} #{user_data[:user_account].kconv(Kconv::SJIS, Kconv::UTF8)}"
end; nil

JDBCドライバの登録が美しくありません。どなたか他に方法がありましたら教えてください。
また、JDBCの場合、シンボルによる表指定ができません。DB["SELECT * FROM user_data"].allは実行できても、DB[:user_data].allはエラーになります。

DB["SELECT * FROM user_data"].all    # success
DB[:user_data].all                   # fail

なぜかというと、JDBC接続時にDB[:user_data].allによって作られるSQL文が SELECT * FROM \"USER_DATA\" になってしまうからなんですよね。PostgreSQLでは表名の大文字と小文字を区別し、デフォルトでは小文字なので、USER_DATAなんて表はないということになってしまうのです。

sequel_core/sql.rbに以下のような記述がありました。

    # This method quotes the given name with the SQL standard double quote. 
    # It uppercases the name given to conform with the SQL standard. This
    # should be overridden by subclasses to provide quoting not matching the
    # SQL standard, such as backtick (used by MySQL and SQLite), or where
    # lowercase is the default for unquoted identifiers (PostgreSQL).
    #
    # If you are using a database such as Oracle that defaults to uppercase
    # but you are using lower case identifiers, you should override this
    # method to not upcase the name for those identifiers.
    def quoted_identifier(name)
      "\"#{name.to_s.upcase}\""
    end

これが原因らしいぞ。デフォルトで大文字にしちゃうわけね。adapters/postgres.rbではquoted_identifierをオーバーライドしてupcaseを消し去るのでDB[:user_data].allはエラーにならないが、JDBCだと大文字の表を探しちゃうわけか。

というわけで、回避策としては、シンボルを使わないか、Sequel::JDBC::Datasetを書き換えるかです。後者だと、以下のようになります。

require 'rubygems'
require 'sequel'

module Sequel
  module JDBC
    class Dataset < Sequel::Dataset
      def quoted_identifier(name)
        "\"#{name.to_s}\""
      end
    end
  end
end

DB = Sequel.jdbc(:uri => 'jdbc:postgresql:postgres',
                 :user => 'postgres', :password => 'postgres')

Sequel::JDBC.load_driver('org.postgresql.Driver')

DB[:user_data].all.each do |user_data|
  puts "#{user_data[:user_id]} #{user_data[:user_account].kconv(Kconv::SJIS, Kconv::UTF8)}"
end

他にも罠がありそうで、JDBC接続でSequelを使うのは危険な感じがします。

ActiveRecordを試してみた

PostgreSQLにpostgres-prでつないでみる。

require 'rubygems'
require 'activerecord'

ActiveRecord::Base.establish_connection(
  :adapter  => "postgresql",
  :host     => "localhost",
  :database => "postgres",
  :username => "postgres",
  :password => "postgres",
  :encoding => "SJIS"
)

class UserData < ActiveRecord::Base
  set_table_name "user_data"
  set_primary_key "user_id"
end

UserData.find(:all).each do |user_data|
  puts "#{user_data.user_id} #{user_data.user_account}"
end

JDBCだとこんな感じ。

require 'rubygems'
require 'activerecord'
require 'kconv'

ActiveRecord::Base.establish_connection(
  :adapter  => "jdbcpostgresql",
  :host     => "localhost",
  :database => "postgres",
  :username => "postgres",
  :password => "postgres"
)

class UserData < ActiveRecord::Base
  set_table_name "user_data"
  set_primary_key "user_id"
end

UserData.find(:all).each do |user_data|
  puts "#{user_data.user_id} #{user_data.user_account.kconv(Kconv::SJIS, Kconv::UTF8)}"
end

Windowsでコンソールに出力させている関係上、kconvSJISへの変換が入っているが、実システムではUTF-8を使うので不要だね。