読者です 読者をやめる 読者になる 読者になる

ローグウェーブソフトウェアのブログ

ローグウェーブソフトウェアは、次世代HPCアプリケーション開発のためのクロスプラットフォーム開発ツールと組込みコンポーネントを提供する世界最大の独立系プロバイダーです。このブログでは最新の製品情報や会社情報、ソフトウェア開発に役立つ情報を掲載していきます。

TotalViewのC++Viewで変数を見やすく表示する方法

CodeDynamics TotalView 製品とサービス

以前並列デバッガTotalViewのType Transformations機能を紹介しました。 blog.roguewave.jp

www.roguewave.jp

今回は類似した機能であるC++Viewを使って変数の表示方法を調整する方法を紹介します。

C++Viewの基本

C++Viewは、TotalViewが提供する表示データ変換用のコールバック関数をユーザーのソースコード内で実装することによって、ユーザがTotalView上でのデータ表示方法をカスタマイズすることができる機能です。

Type TransformationsとC++Viewの違い

両者は類似した機能であり、どちらもTotalView上でのデータの表示方法を任意に変更することができます。

  • Type Transformations: 先述のSTLViewはその一部です。設定ファイルはソースと別に管理されるため、他の多くのTotalViewの機能と同様、元のビルド(-gなどを付けたデバッグビルド)に手を加えることなくカスタマイズできます。その代わり独自の記法があり、やや習得しづらいです。この記法は実はTcl言語に基づくものです。TclはTotalViewのバッチ処理スクリプトや設定ファイル、STLViewといった多くの部分で使われています。
  • C++View: 元のコードを編集して表示変換用のAPI関数を実装する必要があります。ただし常に対象のクラスと一緒に運ばれるため、紛失やバージョン管理の心配が軽減されます。より馴染み深いC++の文法で記述できることもメリットです。C++ViewはType TransformationsのC++ラッパーである、という言い方もできるかもしれません。

C++Viewの実行例

例を示します。以下のように、あるクラスclass1_tとclass2_tがあり、それぞれメンバ変数xとyを持っているとします。TotalViewのTTF関数TV_ttf_display_typeを実装してC++Viewを使うとTotalViewでデバッグする際にこれらの変数が表示される方法を変更することができます。

// TotalView C++View のデモ1

#include  "tv_data_display.h"

// TV_ttf_display_type関数が一般関数である場合.
class class1_t {
public:
  explicit class1_t(int arg) : x_(arg){ /* */ }
  int getX() const { return x_; }
  friend int TV_ttf_display_type (const class1_t *obj);
private:
  int x_;
}; // class class1_t

// TV_ttf_display_type関数がクラスのstaticメンバ関数である場合.
class class2_t {
public:
  explicit class2_t(int arg): y_(arg) { /* */ }
  int getY() const { return y_; }
private:
  int y_;
  static int TV_ttf_display_type(const class2_t *obj);
}; // class class2_t

// TV_ttf_display_type関数がグローバルスコープにある場合
// ただしメンバ変数にアクセスするためにfriend.
int TV_ttf_display_type(const class1_t *obj) {
  // 見やすくするためにC++Viewを使ってフィールドでのx_の表示を大文字Xに変更.
  const int err = TV_ttf_add_row ("X", "int", &obj->x_);
  if (TV_ttf_ec_ok != err) {
    return TV_ttf_format_raw;
  }
  return TV_ttf_format_ok;
}

// メンバ関数版のTV_ttf_display_type。elisionの使い方も参考に.
int class2_t::TV_ttf_display_type (const class2_t *obj) {
  // ここでも変数名y_の表示を大文字Yに変更.
  const int err = TV_ttf_add_row ("Y", "int", &obj->y_);
  if (TV_ttf_ec_ok != err) {
    return TV_ttf_format_raw;
  }
  return TV_ttf_format_ok_elide;
}

int main() {
  class1_t obj1(99);
  class2_t obj2(88);

  // obj1とobj2にdive
  return 0;
}

実行結果は下の画像のようになり、右のData ViewペインのNameフィールドにある変数名が実際のxやyではなくXやYに変わっています。これがC++Viewの基本です。

f:id:RWSJapan:20160817135921p:plain

このように、C++Viewを使ってTotalViewによるデータの表示方法を変更することができました。変更対象のクラスはユーザ定義型に限らず、STLやBoost、またQtやOpenFOAMなどといったよく使われるサードパーティのライブラリ等任意のクラスに対して適用できます。ただしTTF関数から何らかの形でメンバ変数の値を取得できる必要があります。

変数obj2はわずか1行で表示されていますが、これはclass2_tに関連付けられたTTF関数の返り値がTV_ttf_format_ok_elideとなっており、class1_tの変数obj1の場合より簡略化された表現(Elision)が適用されています。ウィンドウ下部にはCLIコマンドdprintによる表示結果があり、こちらもData Viewと同様の表現になっていることがわかります。

ちなみに、C++Viewを使わない場合(TTF関数をコメントアウト)、表示は以下のようになります。

f:id:RWSJapan:20160818094642p:plain

やや詳しい説明

C++Viewの詳細な説明はリファレンスガイドの PART I CLI Commands : Chapter 6 Creating Type Transformations : C++Viewをご覧ください。またC++View Example Filesにあるように、TotalViewのインストールフォルダ内には使い方を学べるサンプルコードもあります。上で説明した例はcppview_example_1を編集したものです。C++Viewを使ったコードをコンパイル&リンクする際にはインストールディレクトリのsrcとincludeディレクトリ内にあるtv_data_display.cとtv_data_display.hを含めてください。

f:id:RWSJapan:20160817143339p:plain

ここでは端折って説明します。

TTF関数は一般に

int TV_ttf_display_type ( const T * );

シグニチャを持ちます。const修飾は任意です。先ほどの例にもあった通り、グローバルやファイルスコープ(static)の一般関数でもクラス内のstaticメンバ関数でもどちらでも使えますが、staticでない通常のメンバ関数にすることはできません。

このTTF関数内でTV_ttf_add_row関数を呼び出し、希望する表現方法を指定します。シグニチャは以下の通りで、フィールド表示名、型名、変数のアドレスの3つの引数を取り、返り値で表示の変換が成功したかどうかを示します。実装が先のtv_data_display.cに記述されています。

int TV_ttf_add_row ( const char *field_name,  const char *type_name, const char *address );

この型名は、intや$stringなど、c++言語での正当な表記もしくはTotalViewの表記でなければなりません。慣れないうちは最も失敗しやすいところかもしれません。

template <class T>
int TV_ttf_display_type (const BoundsCheckedArray<T> *a) {
  char type [ 4096 ];
  snprintf ( type, sizeof(type), "value_type[%d]", a->get_size ());
  TV_ttf_add_row("array_values", type, a->get_array ());
  return TV_ttf_format_ok;
}

のようにあらかじめ文字列を作って指定することが多いです。

リファレンスガイドの説明にあるようにTV_ttf_type_ascii_stringとTV_ttf_type_intによる型表記の簡便な指定も可能で、char配列を配列のような縦表示でなく一つながりの文字列のように表示することができます。

TTF関数は表示方法を変更させたい全ての型に対してそれぞれ定義する必要がありますが、テンプレートを使うことも可能です。テンプレートの型Tをvalue_typeにtypedefしてください。これはSTLでのやりかたに則ったものです。T=charのときだけ特殊化し、TV_ttf_type_ascii_stringを使うといったカスタマイズも可能です。cppview_example_3の例を参考にしてください。cppview_example_3にはGCCでのabi::__cxa_demangleを使った型文字列取得方法も記載されています。

TV_ttf_add_row の返り値

TV_ttf_add_row はTV_ttf_ec_ok などのエラーチェック値を返します。TV_ttf_add_row 関数がTTF以外から呼び出された時や、引数に不適切な文字列が渡された時、TTF用のバッファが不足した時にその旨を告げます。

TV_ttf_display_type

返り値はenumのTV_ttf_format_result 型で、cppview_example_4にいろいろな使い方が紹介されています。通常はokで、elideは先述のように簡略表記が可能。TV_ttf_format_ rawはTV_ttf_add_row関数が失敗した時に、元の編集されていない型をそのまま表示します。

Memory Managementに記述されているように、TV_ttf_add_row の3番めのパラメータであるアドレスは、TV_ttf_display_typeの終了後も存在していなければなりません。

ReplayEngine

ReplayEngineを使用すると、記録でも再生モードでもTV_ttf_display_typeを呼ぶ前にプロセスをvolatileモードに移行し、呼び出しが終了すると、volatileから抜け、呼び出し前のメモリ状態に復帰します。したがって、TV_ttf_display_type内でグローバル変数や(実質同じことですが)static変数に変更を加えても、ReplayEngine使用時には状態を変更しません。

以下の動画はその様子を表しています。TV_ttf_display_typeの呼び出し回数を数えるために関数内でグローバル変数counterをインクリメントしています。通常実行時には問題なくインクリメントされますが、記録時や再生時にはcounterは常にゼロです。サンプルコードもあり、これはcppview_example_5と同一です。

youtu.be

動画による説明

TotalViewの開発チームがType Transformations同様、以下の記事でC++Viewを英語で紹介しています。例としてローレンスリバモア研究所のSAMRAI (Structured Adaptive Mesh Refinement Application Infrastructure、構造解適合格子法のアプリケーションインフラストラクチャ)プロジェクトで使われた複雑なデータ構造を見やすく表示しています。やや古い動画であるため、関数名などが現在のものと異なっています。 blog.roguewave.jp

まとめ

TotalViewのデータ可視化機能 の1つ、C++Viewを紹介しました。C++Viewを使ってコードをにコールバック関数を追加すると、複雑なデータ構造を任意の形で表示し、デバッグ効率を高めることができます。この他にもTotalViewには複雑な開発を助けるための様々な機能がありますのでお気軽にお問い合わせください。

お問い合わせ | ローグウェーブ

ローグウェーブ セールスエンジニア 柄澤(からさわ)

ActiveMQの Pluggable storage lockers - CodeBuzzから

OpenLogic プログラミング 記事紹介

ActiveMQJava用の非同期型メッセージキューイングサービスです。ローグウェーブのOSSアーキテクト Justin Reockが書いたActiveMQ Pluggable storage lockersについての解説と設定のサンプルをご紹介します。前回の記事と合わせてご覧ください。

blog.roguewave.jp

ActiveMQのPluggable storage lockers

blog.klocwork.com

ActiveMQは、ブローカーのインスタンスが常にオンラインで、メッセージのトラフィックを処理できるようにするためにいくつかの高可用性(HA、high availability)の仕組みをサポートしています。そのうちよく使用される2つのモデルでは、アクティブおよびパッシブのブローカーインスタンスに対してLevelDBとKahaDBのようなデータストアを提供するためにネットワーク上でファイルシステムを共有しています。

こうしたフェイルオーバーの仕組みが成り立つには、LevelDBやKahaDBのディレクトリ上のファイルに対してOSレベルの「ロック」が取得可能かつ維持可能である必要があります。

f:id:RWSJapan:20160816142057p:plain

ロックを最初に取得したブローカーインスタンスはアクティブインスタンス、またはマスターと呼ばれます。別のインスタンス(パッシブもしくはスレーブ)は定期的にそのファイルをチェックしてロックを取得可能かどうか調べます。ロック可能になったらマスターがロックを解放したと判断し、自身をマスターモードに切り替えます。

従来のモデルの問題点

この仕組みはエレガントですが、マスター不在という状況が起こり得るという問題点があります。つまりスレーブが自分がロック可能であることに気づかなかったり、更に悪いことにmaster-master configurationによってインデクスやジャーナルが壊れ、メッセージが完全に失われてしまうことが起こるのです。

こういった問題の多くはActiveMQのコントロールの及ぶ範囲の外で生じます。たとえばあまり最適化されていないNFSファイルストレージでは、高負荷時にロックしているデータがstaleになり、フェイルオーバー時にマスター不在のダウンタイムが発生してしまいます。CIFS/SMBネットワーク・ソリューションの共有違反も同様の問題を起こします。OSの仮想ファイルシステム(VFS)に不正確なロック情報を伝えるSANソリューションもmaster-masterのシナリオを引き起こします。実に多くのファイルシステム共有ソリューションが利用可能ですが、全ての状況に対応できるようなロックのソリューションを実現するのはActiveMQコミュニティにとって非常に困難です。

Pluggable storage lockersの導入

こうしたHAソリューションの多くの問題がOSレベルの不正確なファイルロックに起因しているため、ActiveMQコミュニティはブローカーのバージョン5.7からpluggable torage lockerのコンセプトを導入しました。これによりユーザーは、OSレベルのファイルシステムのロックではなく行レベルのJDBCデータベースのロックを使って、いくつかの共有ロックの手段を利用できるようになりました。最初に挙げられているDatabase Lockerというソリューションは、ロックを保持するためにブローカーとデータベースとの間に永続的な接続を使うのですが、上記のコミュニティサイトで説明されているように、このやり方はマスターブローカーがクラッシュしたりデータベースへの接続を失った時などに効率的ではないことが判明したため、これ以上ここでは触れません。

代わりにここではLease Database Lockerについて詳しく解説したいと思います。このアプローチは、データベースへの永続的接続を要求するのではなくマスターブローカーが定期的にデータベースの行をリース(借り受け)し、一定の期間ごとに更新します。この期間は編集可能です。もしマスターがそのリースを更新しなかった場合、リースは失効し、スレーブがそのロックを取得して、つまり新しいマスターになることができます。このアプローチは品質の悪いネットワーク上でもうまく機能し、リースを取得できなくなったマスターノードを強制的に落とします。

このソリューションは非常に簡単に実装でき、しかも私たちが調べたところActiveMQの共有ファイルシステムのHAモデルとして非常に安定し信頼のおけるソリューションでした。

最初に使用しているデータベースを編集する必要があります。このソリューションはどんなJDBC Compliant なデータベースとも互換であり、わたしたちはPostgres、 MySQL/MariaDBOracleMicrosoft SQL Serverでテストしました。まず私たちの例では新しいデータベースユーザーとしてactivemqというユーザを作成し、パスワードactivemqを与えました。そして新しくactivemqというデータベースを作成し、このユーザーにロックとread/writeパーミッションを付与し、activemq_lockというテーブルを作成しました。スキーマは以下です。

f:id:RWSJapan:20160815154519p:plain

ここでこのテーブルに以下のように一行挿入します。この行に対してActiveMQがロックを行うため、この過程を飛ばしてはいけません。

INSERT INTO activemq_lock(ID) VALUES (1);

データベースのセットアップが終わったらLease Database Lockerを使用するようにactivemq.xmlを編集して、Spring JDBCコネクションのbeanを作成します。

永続的なアダプタの設定

persistence adapter configuration を以下のように設定します。

<persistenceAdapter>
    <levelDB directory="/tmp/activemq-jdbc-locker-data" lockKeepAlivePeriod="5000">
        <locker>
            <lease-database-locker lockAcquireSleepInterval="10000" dataSource="#postgres-ds">
                <statements>
                    <statements lockTableName="activemq_lock"/>
                </statements>
            </lease-database-locker>
        </locker>
    </levelDB>
 </persistenceAdapter>

この例では典型的なLevelDB の永続的ストア設定をカスタムロッカーのlease-database-lockerを使うように拡張しました。ブローカーに行レベルのリースを5秒ごとに行うよう設定し、スレーブインスタンスが10秒ごとにロックの取得を試みるように設定しています。また、このロッカーがSpringデータソースの「postgres-ds」を使うよう指定しています。また、LevelDBにアクセスするための場所をdirectoryパラメータとして指定しています。

これは、LevelDBを共有するためにネットワークマウントを使用しているからで、この部分はオリジナルのモデルから変わっていません。今回は単にロックのメカニズムをJDBCに置き換えただけですが、依然としてブローカーはどちらも共有NFS経由で永続化ストアにアクセスする必要があります。

f:id:RWSJapan:20160815173202p:plain

JDBCコネクションの設定

次のステップはActiveMQにデータベースへの接続方法を指示します。そのためには標準的なJDBC connection spring beanを作成します。

<bean id="postgres-ds" class="org.postgresql.ds.PGPoolingDataSource" destroy-method="close">
    <property name="serverName" value="localhost"/>
    <property name="databaseName" value="activemq"/>
    <property name="portNumber" value="0"/>
    <property name="user" value="activemq"/>
    <property name="password" value="activemq"/>
    <property name="dataSourceName" value="postgres"/>
    <property name="initialConnections" value="1"/>
    <property name="maxConnections" value="10"/>
  </bean>

ここではPostgresのデータベースに接続し、上記 lease-database-locker の設定で与えた識別子をbeanに指定しています。どちらの設定もマスターとスレーブで共通でなければなりません。また、JDBCのドライバ .jarファイルをActiveMQの/libディレクトリにコピーして設定内で指定したクラスへのアクセスを与えなければなりません。具体的な方法は使用しているデータベースによって異なります。

着火

これで設定は終わり。ブローカーに着火して何が起こるかを見てみましょう。アクティブブローカーのログにいくつかの出力があることに気づかれるでしょう。

INFO | amq-master, becoming master with lease expiry Mon Jun 27 15:27:01 EDT 2016 on dataSource: org.postgresql.ds.PGPoolingDataSource@600b90df

一方スレーブインスタンスは定期的にスタンプを発行しています。

INFO | amq-slave failed to acquire lease. Sleeping for 10000 milli(s) before trying again...<br />
INFO | amq-slave Lease held by amq-master till Mon Jun 27 15:29:23 EDT 2016

ボーナスとして、どのブローカーがマスターモードなのか知ることができ、また、以下のようなactivemq_lockへの簡単なクエリによってフェイルオーバーのシナリオを監視することができます。

SELECT * FROM activemq_lock

f:id:RWSJapan:20160815165211p:plain

lock行のbroker_nameの値は現在のマスターインスタンスのブローカー名に対応しています。

時間について

非常に大事なことですが、マスターとスレーブノードはNTPのような時刻同期ソリューションに基いて同期しています。割り当てられたマスターブローカーは、システムが生成したタイムスタンプをリース用に使用します。もしマスターとスレーブインスタンスの間で時刻のずれが生じたら、私たちの例では5秒のずれで、スレーブインスタンスはその時点でマスターがもうリースを更新していないと判断して、自分がマスターになろうと試みます。これはmaster-masterの悪夢であり、ジャーナルは破壊されメッセージは失われます。

結論

ActiveMQが採用している伝統的なネットワーク共有型ファイルシステムのHAモデルはOSレベルのファイルシステムロックで使われていますが、そこには多くの問題が避けられません。そうした問題はActiveMQ自体のせいではなく、ネットワークファイル共有の実装に問題があるのです。Lease Database Lockerはこのロックを提供する非常に定評があるソリューションで、侵襲性が少なく、簡単に設定変更ができます。私たちは安定した高可用性のメッセージング実装としてこのモデルを使用するようお客様に勧めています。

Justin Reock

編集後記

いかがでしょうか?著者のJustinについて以下の記事で人物紹介や得意分野等を紹介していますのでご一読ください。

blog.roguewave.jp

ローグウェーブ セールスエンジニア 柄澤(からさわ)

並列デバッガ TotalView 2016.06 リリース - CUDA 8.0サポート、ライセンス管理の脆弱性対応など

CodeDynamics TotalView 製品とサービス リリース

ローグウェーブの並列デバッガ TotalViewの新バージョンがリリースされました。いつものようにCodeDynamicsも同時にリリースされました。TotalViewとCodeDynamicsの製品詳細についてはこちらをご覧ください。

roguewave.jp

roguewave.jp

新UI関連の更新

  • 新UIのソースコード検索機能が強化されました
  • 環境変数TVNEWUIにTrueを設定すると新UIでTotalViewが起動するようになりました。TotalViewの新UIを常用したい場合、-newUIフラグを毎回付ける必要がなくなります。大規模並列やグラフなどの可視化機能といったHPC向けの従来ユーザーインターフェースを必要とせず、主にマルチスレッド開発やリバースデバッギング機能でTotalViewを活用している方にはお薦めです。

CUDA 8.0対応

TotalViewは昨年の8.15.10でCUDA 7.5に対応しました。今回リリースしたTotalView 2016.06はCUDA 8.0のRCで問題なく動作することを確認しています。CUDA 8.0の新機能は以下のNVIDIA英語ブログで詳しく機能紹介されています。

devblogs.nvidia.com

記事ではCUDA 8の主な内容は

などが挙げられており、性能や開発効率が引き続き向上していることがわかります。TotalViewのCUDA機能を以下の記事に簡単にまとめていますので、こちらもご覧ください。

blog.roguewave.jp

FlexNetの脆弱性への対応

TotalViewが利用しているライセンス管理ソフトに脆弱性が発見されました。脆弱性の詳細については下記のエントリをご覧ください。

TotalViewでこの脆弱性に対応するには、ライセンスサーバを製品に同梱されたものに更新してください。サーバの設定方法はインストールガイド (英語版)をご覧ください。

プラットフォームの更新

ローグウェーブ セールスエンジニア 柄澤(からさわ)