開発再開

仕事が立て込んでたこともあって、開発の時間が全く取れなかったけど、やっと余裕が出始めたので開発を再開。
アプリの設計をほとんど忘れてしまっていたので、新機能追加よりも、バグ修正をしつつ、思い出す作業を重視。
主な修正点は下記のとおり。

  • 設定ファイルの文字コードUTF-8で統一し、文字化けが発生するバグを修正
  • ファイルキャッシュのインデックスファイルをCSV形式から追記型ログへと変更し、強制終了時にインデックスファイルが空になるバグを修正
  • zipファイル読み込み時にディレクトリを無視するようにし、NullPointerExceptionが発生するバグを修正
  • WEB巡回モードのとき、重複してHTMLをパースしないようにし、無駄に巡回していたバグを修正
  • その他細かい調整

リリースまでの道のり

開発の時間が取れなかったこともあり、細かい機能追加はあるけど、大きな進捗はない。
隙間時間を使って、機能要件の整理をしてたりする。現状の目標は以下のとおり。目標を達成し次第、ベータ版としてリリースしたい。

  • 画像管理機能
    • タグ管理
    • 類似画像検索
  • 画像収集機能

追加機能一覧

特に目立った機能追加はないけど。。。

  • 設定項目の保存
  • 画像の拡大縮小表示の適用条件の設定
  • 画像の背景色の設定
  • 画像サイズでのフィルタリング

どちらかと言うと、機能追加よりも内部構造の変更が大きい。

  • ログをcommons-loggingを利用するようにした
  • ログメッセージを外部定義化した
  • 画像情報(データではない)をキャッシュするようになった

そろそろ、タグ管理とか類似画像検索とかの機能にも手を出していこうかいくまいか。。。
ともかく、画像キャッシュが1万を超えたので、そろそろ管理機能を実装し始めてもよいかなあと思う今日この頃。

jvisualvmを使ったCPUプロファイリング

ここ最近は機能追加の進捗しか書いていなかったので、今回は、技術的なことも少し書いてみたい。ということで、jvisualvmを利用したCPUプロファイリングの方法を紹介しよう。

jvisualvmとは、JDKに標準で付属するプロファイラだ*1。JavaVMのメモリ使用量や、スレッドの動作状況など、基本的な情報をリアルタイムで取得、表示してくれる。さっそく、起動してみる。

jvisualvmの起動

一番初めに、測定対象のJavaプログラムを起動しておこう。起動を確認したら、コマンドラインより、「jvisualvm」と打ち込む。すると、jvisualvmが起動して、次の画面が表示される。



左側には、起動中のJVMの一覧が表示される。ここでは、jvisualvm自身のvm、開発に利用しているIDEであるNetBeans6.1、そして開発中の画像ビューアjgbがあることが分かる。さっそく、開発中の画像ビューアを選択してみる。

JVMの基本情報表示

JVMを選択すると、JVMの基本情報が表示される。



さて、ここの表示からは色々な情報が読み取れる。例えば、

  • Java Homeより、Java6_u5を利用している*2
  • ヒープサイズを最大512MBとデスクトップアプリの割りに潤沢
  • GCログを出力している
  • クライアントVMを利用中

と言った具合だ。

CPUプロファイリング開始

さっそく、CPUプロファイリングをしてみよう。今回は、インターネット上のURLからHTMLをダウンロードし、その中にある画像の一覧を表示するという操作をプロファイルする。
プロファイルするためには、「Profiler」タブを選択する。すると、下記の画面が現れる。



もし、次のような警告が現れた場合、測定対象となるJavaプログラムの起動オプションに、「-Xshare:off」を付加しよう。



jvisualvmは、プロファイリングのために動作中のjavaプログラム(バイトコード)にダイナミックに修正を施す。javaのクライアントVMは、VM起動を速めるために、クラスファイル(バイトコード)の共有を行っており、jvisualvmで一つのプログラムに修正を施すと、動作中の他のプログラムにも影響が出る可能性がある。そのため、このような警告が発せられる。起動オプションに「-Xshare:off」を付加すると、クラスファイルの共有を強制的に行わないようにするため、他のプログラムに影響を与えることがなくなる。なお、サーバVMの場合、クラスファイルを共有しないため、このようなことを心配する必要はない。

CPUプロファイリングを行うためには、「CPU」ボタンをクリックする。



その状態で、プロファイルしたい処理を実行する。今回は、URLを指定しての画像一覧表示だ。一覧が表示し終わった段階では、jvisualvmの画面は次のようになっている。



詳しく見てみよう

jvisualvmは、リアルタイムにJVMの状況を取得する。そのため、じっくり解析したいときは、スナップショットを取るほうが良い。でないと、解析中にもどんどんと裏ではプログラムが進行し、いらないデータも表示されてしまう。
スナップショットを取るには、Profiling Results:の下にあるボタン群のうち、左から4番目のボタン、右矢印ボタンをクリックする。
すると、次の画面が表示される。



この画面は、スレッドが消費した時間が表示されている。ここから、「AWT-EventQueue-0」スレッドが約4秒、「BackgroundTaskProcessor」スレッドが同じく約4秒処理をしていることが分かる。

もっと詳しく見てみよう

「AWT-EventQueue-0」では、何の処理に時間が掛かっているか、見てみよう。「AWT-EventQueue-0」のツリーを開くと、メソッドごとの処理時間が表示される。



どうやら、「jgb.view.HistoryWindow$1.valueChanged」メソッドが、全体の87%消費していることが分かる。でも、これだけでは良く分からないので、もっとも掘り下げてみる。



こうしてみると、「org.htmlparser.http.ConnectionManager.openConnection」で約6割の時間が掛かっていることが分かる。Self Timeというのは、他のメソッド呼び出し以外のプログラム実行時間となる*3
ここは利用しているライブラリのメソッドであるため何の処理かは正確には分からないが、メソッド名からインターネットからHTMLを取得している時間だろうと推測する。こればかりはどうにもならないため、最適化は厳しそうだ。

取得対象の変更

さて、自分が開発したプログラムだけプロファイリングするとか、ある特定の箇所に絞り込んでプロファイリングすると言った場面は頻繁に発生する。プロファイリングを細かく取ろうとすると、プロファイリングのオーバヘッドが相対的に大きくなり、何を測定しているか分からなくなってしまうためだ。そのため実際は、大まかに重たいメソッドを絞り込んで、少しずつドリルダウンしながら絞り込んでいくことが多い。
visualvmは、測定対象のJavaプログラムを再起動することなくプロファイリング対象を変更できる*4。当然、visualvmの再起動も必要ない。
そのためには、いったん「Stop」ボタンでCPUプロファイリングを停止し、「Settings」チェックボックスをONにした状態で、「CPU settings」タブの内容を変更する。



「Start profiling from classes:」という欄には、プロファイリング開始ポイントを指定する。ここで指定されたパッケージ、クラス、メソッドが呼ばれたら、プロファイリングを開始すると言うことだ。今回は、「jgb.**」とした。これは、jgbパッケージ以下全てのクラスに所属するメソッドが起動したら、プロファイリングを開始するという意味だ。
「Profile only classes:」と「Do not profile classes:」とあるが、前者を選択した場合、指定されたパッケージ・クラスのみプロファイル対象とし、後者を選択した場合、指定されたパッケージ・クラス以外をプロファイル対象とすることを意味する。ここでは、画像ビューアの処理のみを測定対象とするため、前者を選択し、「jgb.*」とした。
この設定で、画像ビューアの作りこみ箇所だけをプロファイリング対象とすることになる。

再測定

先ほどの手順で再測定を行うと、次の結果が得られた。



「jgb.image.thumb.SamplingThumbnailStrategy.loadThumbnail」メソッドが36回実行され、合計処理時間が4388msということが分かった。このメソッドは、サムネイル画像を読込・作成する処理である。サムネイル作成に、1画像あたり約120msということが速いかどうかは。。。

まとめ

jvisualvmを利用すると、こんな感じでCPUプロファイリングが簡単にできる。Java6限定となってしまうが、これから新規で開発する場合、わざわざJDK5を選択する理由もないのでぜひ使ってみていただきたい。なお、いくら簡単にできるからと言って、運用中のシステムにアタッチすることは避けたほうが良い。以前、それでひどい目にあった人がいたからだ。。。 あくまで、開発の一環として利用していただきたい。

*1:JDK6U7より付属する

*2:余談だが、java6のu3だかu4だかで、vmのバージョン表記が変わったのを思い出した。スレッドダンプでこのバージョンしか表示されなくなって何気に不便。

*3:正確には、測定対象のメソッド以外の処理時間

*4:もっとも、まだ十分安定していないので、反応しなくなることもしばしばあるけど…

機能追加一覧

  • 機能追加
    • リンク先解析機能 (rev:18)

リンク先解析機能

URL指定で画像一覧を表示するとき、リンク先のURLがHTMLでも画像一覧条件に合致していた場合、追加で画像一覧に含められる。画像一覧条件は以下の通り。

  • HTML Root URL
  • 画像 Root URL
  • 深さ

HTML Root URLとは、リンク先のURLがHTMLのとき、取得対象とするか否かを判断するときに用いる。リンク先URLがHTML Root URLから始まるとき、画像一覧取得対象とする。指定しない場合、指定したURLのドメインとなる。
画像 Root URLとは、リンク先のURLが画像のとき、取得対象とするか否かを判断するときに用いる。リンク先URLが画像 Root URLから始まるとき、画像一覧取得対象とする。指定しない場合、指定したURLのドメインとなる。
深さとは、指定したURLから何クリックでそのページに辿り付けるかを表し、リンク先URLがHTMLのときでかつ、ここで指定された値よりも小さいクリック数でたどり着けるとき、画像一覧取得対象とする。指定しない場合は、1となる。

追加したい機能残

  • ユーザインターフェイス
    • ブラウザからのURL指定
    • スライドショー
  • 画像リポジトリ
    • ディレクトリから辿れる一連の画像の表示
    • パスワード付きzipアーカイブへの対応(新)
    • URLから辿れる一連のアーカイブのダウンロードと管理(新)
    • 画像一覧ファイル内の全画像表示
  • 画像管理
    • タグ
    • 任意検索条件指定
    • サムネイル保存
    • ダウンロード画像の保存
  • アプリケーション
    • 常駐アプリ化
    • HTTP Proxy化

既知バグ

  • ユーザインターフェイス
    • 画像ダウンロード中のファイルを表示しようとすると、キャッシュ内の画像ファイルに不整合が発生し、該当ファイルが正しく表示されなくなる。
    • ヒストリに登録されたURLをクリックしても、リンク先を手繰って表示しない。
    • ヒストリのタイトルが文字化けする。

機能追加一覧

  • 機能追加
    • ヒストリ機能(rev:15)
    • クリップボード内URLのダイレクト表示(rev:15)
    • キャッシュファイルの一覧表示(rev:16)
    • ウィンドウサイズの保存(rev:17)
  • バグ修正
    • サムネイル読み込み失敗時に無限回読み込もうとするバグの修正(rev:15)

ヒストリ機能

過去アクセスしたURLを保存しておき、それを表示できるようにした。URLをクリックすると、そのURLにアクセスし、サムネイル一覧を表示する。

クリップボード内URLのダイレクト表示

クリップボードにURLが存在する場合、入力ダイアログを直接介することなく、サムネイル一覧の取得と表示をするようにした。

キャッシュファイルの一覧表示

全キャッシュファイルのサムネイル一覧を表示する。

ウィンドウサイズの保存

ウィンドウサイズを保存しておき、次回起動時に、前回終了時のウィンドウ位置およびサイズを復旧するようにした。

追加したい機能残

  • ユーザインターフェイス
    • ブラウザからのURL指定
    • スライドショー
  • 画像リポジトリ
    • ディレクトリから辿れる一連の画像の表示
    • パスワード付きzipアーカイブへの対応(新)
    • URLから辿れる一連の画像の一覧表示(新)
    • URLから辿れる一連のアーカイブのダウンロードと管理(新)
    • 画像一覧ファイル内の全画像表示
  • 画像管理
    • タグ
    • 任意検索条件指定
    • サムネイル保存
    • ダウンロード画像の保存
  • アプリケーション
    • 常駐アプリ化
    • HTTP Proxy化

画像一覧表示時のフィルタ機能

画像名(主にファイル名)について、一覧として表示するか否かを指定できるようにした。指定方法は以下の通り。

  • 画像名が指定された正規表現にマッチした場合に、一覧として表示する
  • 指定された文字列が画像名に含まれた場合に、一覧として表示する
  • 指定された文字列が画像名に含まれていない場合に、一覧として表示する