Perl::Metrics::Lite - プラガブルなメトリクス測定モジュール - Perl Advent Calendar 2011

Perl Advent Calendar Hacker Trackの21日目です。
http://perl-users.jp/articles/advent-calendar/2011/hacker/21

こんばんは。dann です。みなさん、意識は高まっていますか? 僕は上々です。
今回は拙作の Perl::Metrics::Lite というモジュールを紹介させて頂きます。

開発した動機

Perl::MetricsやPerl::Metrics::Simpleなど、PerlのコードのMetricsを測定するツールがあるのですが、どれも使い勝手の点でいまいちです。

Perl::Metricsは測定項目をプラガブルに追加可能なのですが、APIがプリミティブすぎてMetricsを測定するには使いずらいです。

一方、Perl::Metrics::Simpleは、Perl::MetricsをよりユーザーフレンドリなAPIにして、名前の通りシンプルに使えるように作られているのですが、今度は測定項目が拡張可能になっておらず、特定のメトリックしか測定できませんでした。

Perl::Metrics::Lite - プラガブルなメトリクス測定モジュール

従来のモジュールの問題点を解決するために、Perl::Metrics::SimpleとPerl::Metricsのいいとこどりをして、測定項目をプラガブルに追加できるPerl::Metrics::Simpleのような使い勝手のいいモジュールを作ってみました。
http://github.com/dann/p5-perl-metrics-lite

使い方はPerl::Metrics::Simpleと殆ど同じで、以下のように使います。

use Perl::Metrics::Lite;
my $analzyer   = Perl::Metrics::Lite->new;
my $analysis   = $analzyer->analyze_files(@ARGV);
my $file_stats = $analysis->file_stats;
my $sub_stats = $analysis->sub_stats;

egディレクトリに実行可能な簡単なサンプルをいれてあるので、
是非実行して確認してみてください。
https://github.com/dann/p5-perl-metrics-lite/blob/master/eg/measureperl

測定項目の拡張方法

以下に幾つかFileとSubroutineのMetricsの測定のPluginのサンプルをいれています。
initとmeasureメソッドを実装する必要があります。measureメソッドのfileまたはsubroutineのPPI::Documentを操作して、必要なMetricsの計算を行います。

まとめ

Perl::Metrics::Liteにより、簡単にFileとSubroutineのMetricsを拡張して、測定項目を追加していくことが可能になりました。是非色々なMetricsの測定をしてみてください。

Enjoy measuring!

Jenkinsで継続的メトリクス測定のすすめ - Perl Advent Calendar 2011

Perl Advent Calendar Test Trackの11日目です

はじめに

こんばんは、家で凍死しそうなので、そろそろセラムヒートでも買おうと思っているdannです

Test Track 11日目です! ikasam_a さんから「Jenkinsの話を書いて!」と言われたので、ACDD(Advent Calendar Driven Development) という手法で作った、メトリクス測定ツールとJenkinsへのインテグレーションについての話をします。

メトリクスを測定すると捗るぞ

大きなチームで開発する時に、数百行の謎メソッドができたりとかあったりしますよね(涙)

僕は、Working Effectively With Legacy CodeやClean Codeを読んでいること前提にチームの人がコードを書いていると思っていたら、あれ?という場面があったりします。こういうときは、品質の問題を測定できるようにすると、数値レベルで具体的にした共通認識ができ、レビューが捗ります。

今回測定したメトリクス

メトリクスを測定するなんていうと、用語の定義が間違ってる喝!とSoftware Engineeringの専門家に怒られちゃうわけですが、そこは気にせずメトリクスを測定って言うことにします。

今回測定したのは、以下の二つのメトリクスです。

  • メソッドの行数
  • Cyclomatic Complexity

メソッドの行数は、そのまんまですね。Cyclomatic Complexityは10を超えていると複雑すぎなので、分割を検討した方が良いというのが一つ一般的な指標になってます。

これらのメトリクスは、Perl::Metrics::Simpleというモジュールで簡単に取得できます。ではこれをJenkinsに統合しましょう。

Jenkinsでのメトリクス監視の方法

これらのメトリクスの測定結果をどのようにJenkinsに統合するかです。今まで、自分の日記のJenkinsのエントリを読んでもらっている人は、勘のいい方は気づいてるんじゃないかと思うんですが、Javaのツールにマッピングすればいいんですね。

この分野は、静的言語でもあることから、Javaは周辺ツールがとても充実しており、今回はそのうちの一つであるCheckStyleXMLに測定結果をマッピングしてみました。これにより、チェック結果のViewをそのまま流用できるわけです。

作ってみたマッピングするツール pm-checkstyle は以下の通りです。
https://github.com/dann/p5-app-checkstyle

使い方

上記のスクリプトのconfigurationの設定を最初にします。
CCが10、sub lengthが50行という設定だと、以下のように書きます。

our $MAX_CYCLOMATIC_COMPLEXITY = 10;
our $MAX_SUB_LENGTH            = 50;

以下のようにスクリプトの引数にlibディレクトリを指定します。そうすると、workspaceにcheckstyle-result.xmlが生成されます。

pm-checkstyle lib

後は、Jenkinsにcheckstyle pluginをいれておけば、ビルド時に自動でcheckstyleのViewにメトリクスでの測定結果が表示されます。

check styleでのチェック結果

ビルド結果には次のようにcheck styleでチェックに引っかかった項目がひっかかります

エラー数はファイル毎に次のようにみることができます

ファイルをクリックするとエラー詳細は次のように表示されます。

おまけ

以下のようにPerl::Metrics::Simple::Analysis::Fileを変更することでメソッドの行数を取得することが可能になります。上記のpm-checkstyleでは、line numberは1という固定値になってますが、以下の変更を加えてcheckstyle.plのテンプレートを変更すれば、行数のマッピングも出来ます。そうすることで、check_styleのDetailのレポートから直接ソースコードの該当メソッドにジャンプすることができるようになります。

--- Perl/Metrics/Simple/Analysis/File.pm.orig	2011-12-10 13:49:45.000000000 +0900
+++ Perl/Metrics/Simple/Analysis/File.pm	2011-12-10 13:34:52.000000000 +0900
@@ -327,6 +327,7 @@
             name              => $sub->name,
             lines             => $sub_length,
             mccabe_complexity => $self->measure_complexity($sub),
+            line_number       => $sub->line_number,
             };
     }
     return \@subs;

メトリクス測定の効果

メトリクスの測定をCIに組み込むことの効果には以下のようなものがあります。

  • 継続的にメトリクスをチェックすることができ、問題を早期に発見できる
  • Jenkinsおじさんが自動でチェックしてくれるので、レビューの工数が削減できる
  • 指摘をJenkinsおじさんがしてくれるのでレビュー時に人間関係が悪化しない
  • Jenkinsおじさんに指摘されないように気をつけるため、全員のコードが綺麗になる

まとめ

メトリクスの測定で、レビュー対象を早期に摘出するとコード品質をあげることが可能になります。早期に問題を見つけるのは、テストやビルドだけでなく、こういった静的解析による機械的なレビューによってもあげることが可能なわけです。

これらのメトリクスによる定量評価もCIに組み込むことでその価値を発揮します。性能だけでなく、品質もある一定線は定量評価が可能であり、人感じるのではなく、測定によって定量評価をすすめることで、改善効果を実感することができます。

CheckStyleのチェック項目は数多く存在しますが、Perl::Metrics::SimpleをベースにしてJava版と同じようにモジュール化をすることで、より高度なチェックも可能になります。興味のある方は是非トライしてみてください。

2003年頃からCruise Control -> Continuum -> Hudson (Jenkins) とCIシステムを使ってきたわけですが、当時はJava界隈ではよく使われてましたが、あまりPerlのプロジェクトでは使われていませんでした。今では、Jenkinsの素晴らしいQualityから、Perlのプロジェクトでも大分使われるようになってきていますね。遅筆ではありますが、今後もJava界隈で今までに培ったCIの使い方のノウハウを、Perlのプロジェクトでも活かす方法を書いていきたいところです。

では、Enjoy Jenkins!

次回は、tsucchi さんです!お楽しみに!

# 追記
現在は、拙作のPerl::Metrics::Liteのツールを使うことで、以下のようにJenkinsに統合することが可能になっています。

measureperl-checkstyle --max_sub_lines 100 --max_sub_mccabe_complexity 10 --directory lib > checkstyle-result.xml

Javaの標準Collectionフレームワーク代替としてのfastutil/HPPCの使用のすすめ

Goな人が最適化しているエントリ(http://blog.golang.org/2011/06/profiling-go-programs.html
)を読んで、ちょっと面白いなと思ったので、元の論文を読んでみました。読んでみたところ、Java 64bit版は標準のC++の5.8倍遅いとなっていたので本当かな?と思い、元のプログラムを見てみました。
http://research.google.com/pubs/pub37122.html

プログラムをざっと見て、オリジナルのJavaの標準のコレクションフレームワークの使用時の罠にはまっていることに気づいたので、以下のチューニングを施してみました。

  • -XX:+UseCompressedOopsオプションを使っても、Integerなどのオブジェクトはプリミティブ型の数倍のメモリ消費量を使ってしまうことから、整数オブジェクトの配列ではなく、プリミティブ型専用のコレクションを使うこと
  • 内部のストレージにダイレクトにアクセスが可能なオーバーヘッドの少ないコレクションを使うこと
  • Iteratorでのアクセスが遅いことから、拡張for文を使うのではなくリストにダイレクトにアクセスすること

たった数十行の修正で、C++のバージョンよりも約2割ほど早くすることができました。また、このコードは、オリジナルのmulti-language-benchmarkプロジェクトのJavaでの最適化バージョン(java-pro)のものよりも1割程度高速です。測定環境(CPU, Memory, Javaのversionなどなど)により性能値は変わるので、測定可能なコードを以下においておきました。
https://github.com/dann/java-multi-language-benchmark

ここで使ったのはfastutilなんですが、fastutil/HPPCは、Javaのコレクションフレームワークに近い(一部互換の)APIを持つ、上記のことを実現するのに適したコレクションフレームワークで、比較的簡単に標準コレクションフレームワークを使ったコードの置き換えが可能です。上記のような理由で、Javaのコレクションフレームワーク使用時に性能が出せない場合にはとてもお勧めできます。

また、Javaで性能を出すためのプログラムをどのように書けばよいのかという点で、とても学ぶことの多いソースであるので、アプリケーションとしてのJavaのコードを読み飽きた方にも非常におすすめです。何故Javaのコレクションフレームワークが遅いのかをJDKのコード、メモリの使用量などを調べながら読み比べてみると面白いです。

# YourKitでプロファイルした感じだと、もう少しチューニングできそうなので、暇な時にもう少しやってみようかと思ってます。

PerlプロジェクトでテストカバレッジのCIをする方法

前回から大分時間が経ってしまいましたが、今回はPerlのテストカバレッジ結果のサマリをJenkinsにIntegrationする方法について説明します。


概要

以前、proveのJUnit用のFormatterを使ってテスト結果をJUnit形式のXMLに変換する方法を紹介しましたが(http://dann.g.hatena.ne.jp/dann/20100404/p1)、カバレッジについても、Java用のプラグインの形式にフォーマットを変換して使うことで、Jenkinsに簡単に統合することができます。

Java界隈ではCloverというテストカバレッジ用ツールがあり、Jenkins用にプラグインが用意されています。一方、PerlではDevel::Coverという有名なテストカバレッジ取得用ツールをがあります。要するに、Devel::Coverの出力結果を、Clover用のXMLに変換できればいいわけです。

Devel::CoverにはReport機能が用意されており、それによって出力方法を変更することが可能になっています。これは、proveのformatterの機能と同等のものになります。テスト結果を、proveのJUnit形式のformatterでJenkinsに統合したように、Devel::CoverもReport機能で出力形式をClover形式にあわせることで統合できます。

Jenkinsへの統合方法

既にCPANには、Devel::Cover::Report::CloverというClover用のレポートモジュールが用意されているので、これを使えばよいだけです。

build stepを追加して、以下のようなコマンドを追加します。coverコマンドの後のreportオポションがポイントです。

perl Makefile.PL
cover -delete
HARNESS_PERL_SWITCHES=-MDevel::Cover=+ignore,inc prove -lv t
cover -report clover

これにより、プロジェクトのトップページにテストカバレッジのサマリが表示されるようになります。そして、各ビルドには各ファイル毎のカバレッジ詳細が見えます。

これに加えて、Devel::CoverのHTMLも出力して詳細を確認できるようにしておくとよいでしょう。カバレッジが極端に低い場合には、サマリだけでなく詳細を確認していきたくなるからです。以前、やり方を紹介してますので、ご興味あれば参考にしてみてください。
http://dann.g.hatena.ne.jp/dann/20100331/p1

テストカバレッジ取得のCIの重要性

品質は、人が作り込むもので、毎日の積み重ねが重要です。それを実現するためには、品質が高まっていることが目に見えるということがモチベーションを高める上で大事になります。従って、CIによりテストカバレッジのサマリがプロジェクトのトップページから簡単にみれるというのは重要なわけです。

高いテストカバレッジになっていることで、リファクタリングの安心度があがり、プロダクトの改善を進めやすくなります。これにより、プロダクト規模が大きくなってきた場合にスピードを維持しながら機能改善・リファクタリングを行っていくことが可能になります。結果として、高いカバレッジが効果として実感できるようになり、よりカバレッジが高まっていくというよいサイクルを生み、結果として品質を高めていきます。

このようにテストカバレッジをCIに組み込むことで、高いカバレッジを維持しながら楽しいリファクタリングライフを送ることが可能になります。皆さんもJenkinsさんのアメリカンな笑顔を見ながら、テストカバレッジを楽しんでみるのはいかがでしょうか。

Enjoy!

大規模(AP/DB) サーバーでのLarge Pageの使用のすすめ

近年はメモリも安くなり、性能を向上させるために多量のメモリを使うことで性能を維持するのは、ある程度の大規模環境であれば一般的なことではないかと思います。特に、JVMOracleなどで多量のメモリを使うアプリケーションでは、ヒープサイズが数Gb-数十Gbだったり、buffer cacheのサイズが数十Gbだったりといったことも多くなっているのではないかと思います。

このような場合には、Large Pageの使用により、以下の3点の理由から性能が向上するケースがあります。

1ページのサイズが512倍と大きくなるため、TLBでカバーされる範囲が大きくなるため、キャッシュヒット率が向上します。ミスヒット時のペナルティは大きいので、これは一つ大きな効果があります。

また、1pageあたり4kで管理されていたメモリが、HugePageにより1pageあたり2mbになるわけで、メモリ容量をアロケーションする回数は1/512になるので、メモリアロケーションが高速になります。

さらに、TLBミスヒットした際に、2Mbの場合にページ階層が4->3になるため、アドレス変換も高速になるというわけです。

Large Pageの使用は、I/O性能の問題は解決できていて、問題がCPU/メモリに移っているケースでないとあまり効果は見られません。非常に早いディスクを使っているケースやほぼ全ての処理がメモリ上で行われるケースでは大きな効果が出ます。例えば、JVMではGC回数/時間の削減という形で非常に効果が見えるので、x64環境でGCに困っている方は試してみられることをお勧めします。

Large Pageを使用するための設定方法

仮に6Gb程度のヒープサイズを確保したとしましょう。
この場合、/etc/sysctl.confを変更します。

次のように設定します。

8Gb shared memory segment sizeを確保すると、1024 * 1024 * 1024 * 8 = 8589934592 のbyte数が必要なので、

kernel.shmmax = 8589934592

と設定します

また、hugetlbが使えるように、groupのIDを設定します。
java, mysql, oracleなどのユーザーのgroup idを次のように設定します。

vm.hugetlb_shm_group = <group id>

次にlarge pageのページ数を設定します。確保するメモリは6Gbなので、次のように計算します。

6Gb/ 2Mb = 3072 pages

設定は次のようにして設定します

vm.nr_hugepages = 3072

最後に物理メモリにpinします

javauser       soft  memlock     6291456
javauser       hard  memlock     6291456

最後にsysctl -p で設定を反映して、cat /proc/meminfo でHugePage数が設定できているかを確認して、3072が設定できていれば完了です。このページ数はサンプルですので、ページ数は実際の規模にあわせて設定してください。

Javaで使う場合には、上記に加えて、 XX:+UseLargePagesをJVMオプションに加えます。
Oracleなどで使う場合には、初期化パラメータにuse_large_pages=onlyを設定して使います。

また、この設定により、HugePageがRAMにPINされるため、ページアウトが起きなくなります。非常に大きなSGAサイズを確保しており、OSでのpage out回数の問題で性能が劣化するような大規模なトランザクションを扱っているケースなどでも効果があります。

大規模DBサーバーでのInfiniBandの利用のすすめ

大規模なトランザクションが求められるシステムでは、ストレージには一定以上の投資をしたほうが全体の運用コストは下がります。アーキテクチャをシンプルに出来、かつ台数を少なくできることでオペレーションコストが劇的に下がるからです。

従来はHDDを中心にしたストレージサブシステムが利用されていたため、ネットワークの帯域やレイテンシがDBのレイヤで問題になるほどの性能が求められるハードを調達できるケースは少なかったのですが、FusionI/OをはじめとするEnterpriseレベルのSSDの登場でネットワークのレイテンシ/帯域を考慮する意味のある場面が出だしています。これは、ストレージレイヤでの問題が解消されることで、ネットワークおよびCPUにボトルネックが移ってきているためです。

このような場合、Infinibandは10Gbps Ethernetとも比較してレイテンシが低いため、非常に高いトランザクション量が求められるDatabase Serverには適しています。

この低いレイテンシの低さは、次の2点の機構で実現されています。

1点目は、InfiniBandのRDSをUDPソケットとして使用する機能を用いていることです。これにより、例えば、Oracleでは以下のようにTCP/IPのレイヤをバイパスしています。

 Oracle/RDS (User Mode) -> RDS(Kernel Mode)                          -> HCA
 Oracle/UDS(User Mode) -> (TCP -> IP -> IPoIB (Kernel Mode)) -> HCA

2点目は、RDS のゼロコピー実装である ZDP(Zero-loss Zero-copy Datagram Protocol)を使うことで、ブロックの不要なコピーを削減していることです。

OSのシステムバッファーへのデー タコピーを抑え、カーネルの仕事量を減らすことでCPU使用率を下げ、ネットワークを効率的に利用できるようにしているわけです。

このようなプロトコルのオーバーヘッドの削減とメモリコピー回数の削減により、非常に低いネットワークのレイテンシを実現しています。このように、Infinibandの低レイテンシを活かすことにより、ネットワーク のレイテンシおよび帯域幅が問題となるアプリケーションでは大幅に性能を向上することが可能になります。

特に非常に大規模なトランザクションが求められるシステムでは、アプリケーションだけの改善では十分な効果を得られないことがあり、ハードウェア、ミドルウェア、アプリケーションまでを含めてトータルな改善が必要になります。

Infinibandとそれに対応したミドルウェアの利用はそのような場合の一つの解になります。特にストレージサブシステムの進化が目覚ましい昨今では、利用により効果がみられるシーンも増えてきており、大規模トランザクションに困っているケースには十分検討の価値があります。

OracleでSQLのParse回数を軽減するための方法

OracleではSQLの実行手順は、次の3フェーズから構成されます。

  • Parse
  • Execute
  • Fetch

その中でParse処理は、次のような手順で行われます。

  • セッションにキャッシュされたカーソルが存在しない場合
    • カーソルをオープン
      • 共有プール上に共有カーソルがある場合
        • soft parse
      • 共有プール上に共有カーソルが無い場合
        • Hard Parse
  • セッションにキャッシュされたカーソルが存在する場合
    • そのカーソルを利用

特にCPU負荷の高いHard Parseの処理では

  • SQLの文法的チェック
  • SQLの意味的チェック
  • 実行計画生成
  • 解析済みSQLの共有プールへのキャッシュ

という処理をします。処理を見ればわかるように、この処理が毎回走ってしまってはとてもCPUコストの高い処理になってしまいます。

そのため、共有プールに既に解析済みSQL情報があれば、Hard ParseはパスしてSoft Parseをし、共有プールから解析済みSQL情報を取得します。セッションにキャッシュされたカーソルがあれば、その部分についてはSoft Parseもパスできます。

従って、Parse回数の削減のためには、共有プールサイズに解析結果を乗せてHard Parseが発生しないようにし、Soft Parseになるようにするようにすればいいわけです。それに加えて、Session Cursor CacheでSoft ParseもさせないSofter Soft Parseをするようにすることで共有プールのチェックもしないようにすれば、よりCPUには優しくなります。

Parseについては、OracleとAPの話と両方あるのでまとめると、SQLのパース処理は次のように削減することができます。

  • Oracle
    • ハードパースの削減
      • 共有プールにキャッシュ
    • ソフトパースの削減
      • Session Cursor Cacheの利用
  • AP
    • Prepared Statemenc CacheでRDBMS側のPrepare作業をさせないようにすることでParse回数を削減する

このような方法で、SQLのParse回数を削減することで、CPU負荷は確実に削減できます。ここでのパース回数の削減結果は、StatspackのInstance Efficiency Sectionで確認できます。I/O性能に余裕があり高負荷の場合には、CPU側がネックになるということもあり、少しでもOracleのCPU Timeを削減したいということがあります。このチューニングは容易に出来るものでもあるので、あらかじめやっておくのがいいでしょう。

ここからは余談ですが、この話を書いていた時に、DeNAのHandler Socketでは、このSQLのパース処理がコスト高ということで、パース処理をショートカットすることでQPSを高めるということをやっているという話を思い出しました。Prepated Statementなどでキャッシュできる場合を考慮すると、Handler Socketでパース処理コストを削減と書いているのは、正確には上記で書いたパース処理の範囲より広範囲なものをさしていると推測されますが、SQL関連処理をばっさり切ってしまっていることでCPU Timeの削減を図るというのは面白いなと思いました。KVS的に使う場合には、SQLをほぼ捨てることで、このように過激に性能改善をする方法もあるのは面白いと思ったのと、ここらあたりの仕組みを外からプラガブルに組み込めるmysqlは非常に素晴らしいなと思ったのでした。

# 追記: Handler Socketの存在意義について
なんで、そんな何回もHard Parseする必要があるのだろうと思ってたんですが、sh2さんのコメントをみてわかりました。「MySQLには共有プールなんてないしPrepared Statementをキャッシュしても実行計画は都度生成する、など考えるとHandlerSocketの存在意義がよく分かる」と。

Persistent Connection + Prepared Statementを使いにくいという事情があるのはConsumer系ではよくある話なので、それで難しい部分はあるのかなとは思ってました。ただ、共有プールもないとすると、そもそもMySQLでは毎回Hard Parseするしかないということなんだなあと。これはちょっといけてないですね。MySQLも、ここらはまだこれからって感じなのかな。