アプリケーションのギアを上げよう
― Visual Studio 2010でアプリケーションのパフォーマンス・チューニング ―

第5回 Webサービスのパフォーマンス・チューニング

亀川 和史
2011/12/09
Page1 Page2

 最近の業務用アプリケーションでは、Webサービスを提供することも多い。そこで今回は、Webサービスにおけるパフォーマンスの調査・チューニング方法に関して解説する。

 なお、本稿のコードは、C#を用いて記述する。

Webサービスの負荷テスト

Webサービスのテスト・シナリオの手助けになるWCF Load Test

 Visual Studio 2010 UltimateにはWebサービス用の負荷テスト機能が搭載されている(参考:「MSDN:ロード テストのためのテスト コントローラーおよびテスト エージェントの構成」)。テスト・プロジェクトを作成して負荷テストを作るのもよいが、WCFのトレース情報を基にテスト・コードを作成してくれる「WCF Load Test」というツールがCodePlexで公開されているので、本稿ではこれを利用する。WCF Load Testは、2010年以降更新されていないようだが、Visual Studio 2008および、Visual Studio 2010に対応している。

 テスト・コードの作成方法としては、「C:\Program Files\WcfUnit」(32bit OSの場合。64bit版の場合「C:\Program Files(x86)\WcfUnit」)にインストールされるコンソール・プログラム(=WcfUnit.exeファイル)を使う方法と、Visual Studioの単体テスト・プロジェクトと連携して使用する方法の2つがある。ここではVisual Studioの単体テスト・プロジェクトから呼び出す方法を紹介する。

 CodePlexから最新版(.zipファイル)をダウンロードして展開する。展開されたフォルダ内に「WcfUnitEn.msi」という.msiファイルがあるので、これをダブルクリックして実行すると、WCF Load Testのインストールが行われる。WCF Load Testは、英語版のVisual Studioにしか対応していないので、日本語版Visual Studioではインストール後、以下の作業を行う必要がある。

  1. 「C:\Program Files」の直下(32bit OSの場合。64bit版の場合、「C:\Program Files(x86)」)に「WcfUnit10.zip」という.zipファイルがあるので、「C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\1041」に移動する
  2. [管理者として起動]したVisual Studio 2010コマンド・プロンプトから「devenv /setup」というコマンドを実行する(次の画面を参照)
WCF Load Testを日本語Visual Studioで有効にするために必要なコマンドの実行例

 これでWCF単体テストのプロジェクト・テンプレートがVisual Studioに読み込まれる。この処理にはかなり時間がかかるので、しばらく待ってほしい。終了後、テスト・プロジェクトを作って、[新しいテスト]を追加するときにWCF Testが選択肢として現れるが、手順の都合上、後で解説する。

 テスト対象として、テスト・プロジェクトを作る前に1つ簡単なWCFサービスを作っておこう。「WCF サービス アプリケーション」のプロジェクト・テンプレートから、以下のようなサービス(本稿では「WcfService1」という名前のプロジェクト)を作る。サービス側で指定した行数だけ文字列を詰めて、2秒待ってから返却するという極めて単純なものだ。

using System.Collections.Generic;
using System.ServiceModel;
 
namespace WcfService1
{
  [ServiceContract]
  public interface IService1
  {
    [OperationContract]
    List<string> GetData(int value);
  }
}
WCFサービスの定義(IService1.cs)

using System.Collections.Generic;

namespace WcfService1
{
  public class Service1 : IService1
  {
    public List<string> GetData(int value)
    {
      var result = new List<string>();
      for (int i = 0; i < value; i++)
      {
        result.Add("WCF Service Test");
      }
      System.Threading.Thread.Sleep(2000);
      return result;
    }
  }
}
WCFサービスの実装(Service1.svc.cs)

 次に、同じソリューション内にこのWCFサービスを利用する「コンソール アプリケーション」のクライアント(本稿では「WcfClient」という名前のプロジェクト)を作成しよう。[ソリューション エクスプローラー]のプロジェクト項目の右クリック・メニューから[サービス参照の追加]を実行して、そこで表示されるダイアログで[探索]ボタンをクリックして[アドレス]欄にソリューション内のWCFサービスを指定し、[名前空間]欄に「WcfSampleService」を入力して[OK]ボタンをクリックする。その後、下記のコードを記述する。

using WcfClient.WcfSampleService;
using System;
using System.ServiceModel;
 
namespace WcfClient
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new Service1Client();
      var items = client.GetData(int.Parse(args[0]));
      foreach (var item in items)
      {
        Console.WriteLine(item);
      }
    }
  }
}
WCFサービスを利用するクライアント用コンソール・アプリケーション(Program.cs)

 プログラム起動時の引数に正数を指定すれば「WCF Service Test」という文字列が2秒のスリープの後、返却される。このサービスをWCF Load Testで試してみよう。メニューバーの[ツール]メニューにある、[WCF サービス構成エディター]を起動する。

[WCF サービス構成エディター]を起動するためのメニュー項目

 [WCF 構成エディター]を起動したら、(メニューバーの[ファイル]メニューから[開く]−[構成ファイル]を実行して)WCFサービスを呼び出すクライアント・プロジェクト(WcfClient)内にあるapp.configファイルを読み込み、WCFの構成ファイルを以下のように修正する。

[WCF 構成エディター]における[診断]の設定
左側の[構成]ツリーの[診断]を選択した状態で、下記項目の設定を行う。
  [メッセージ ログの有効化]リンクをクリックして[メッセージ ログ]を「オン」に変更する。
  [Malformed, Transport]リンクをクリックすると表示されるダイアログで[サービス メッセージ]チェックボックスのみにチェックを入れて[ログ レベル]を「Service」のみに変更する。

[WCF 構成エディター]における[メッセージ ログ]の設定
左側の[構成]ツリーの[診断]−[メッセージ ログ]を選択した状態で、下記項目の設定を行う。
  [LogEntireMessage]プロパティを「True」に変更する。

 これだけ変更すればよい。メニューバーの[ファイル]メニューから[保存]すればapp.configファイルが変更され、次の通信からメッセージ・ログが採取される。

 そのログはWCF Load Testが使用する。(WCFサービスを実行した状態で)コマンドラインから先ほど作成したクライアントを実行すると、そのクライアントのプロジェクト・フォルダの中にログ・ファイルが生成される。そのログ・ファイル名は、[WCF 構成エディター]の[診断]の[メッセージ ログ]の[ログ ファイル]ところに記載されている名前(=今回の場合は「app_messages.svclog」というファイル名)となる(そのファイル名のフル・パスは、app.configファイルでは<sharedListeners>要素のinitializeData属性に書かれている)。

 それでは、WCFサービス側の準備は終わったので、同じソリューション内に単体テスト・プロジェクトを追加しよう。

 メニューバーの[テスト]メニューから[新しいテスト]を選択すると、そこで表示される[新しいテストの追加]ダイアログでは、以下のように「WCF Test」テンプレートが選択できるはずだ。

新しいテストの追加]ダイアログで選択できる「WCF Test」テンプレート

 「WCF Test」テンプレートを選択して、[テスト名]欄は「WCFTest1.cs」のまま、[テスト プロジェクトの追加]欄は「Visual C# テスト プロジェクトの新規作成」のまま、[OK]ボタンを押す。[新しいテスト プロジェクト]ダイアログが表示されるので、プロジェクト名(ここでは「TestProject1」)を入力して[作成]ボタンを押すと、テスト・プロジェクトが作成される(もちろん事前にテスト・プロジェクトを作成しておいてもよい。なお、テスト・プロジェクトはWCFのクライアント・プロジェクトと同じソリューションに追加しておけば参照が簡単になるので、可能な限りクライアントと同じソリューションに追加しておくことをお勧めする)。

 次に[New WCF Scenario Test Generation Wizard](以下、「ウィザード」と表記)が表示される。WCF Load Testでは、シナリオに沿って自動的にWCF Load Testが実行するが、このウィザードでシナリオを作成する。

[Next]ボタンを押す
この画面ではメッセージ・ログをどのように採取して使用するかという方法を選択する。必要に応じて使用すればよい
  WCFクライアントがあり、指定したクライアントを実行する場合。
  クライアントを実行する。
  前述の[WCF サービス構成エディター]でメッセージ・ログを設定して、クライアントを実行し、メッセージ・ログを採取済みの場合に使用する。
  事前にキャプチャしたメッセージ・ログ・ファイルを指定する。
  WCFクライアントでメッセージ・ログを採取した場合。
  WCFサーバサイドでメッセージ・ログを採取した場合。
  Fidder2でメッセージ・ログを採取した場合に選択する。以前のVisual Studioでサポートされていた拡張子「asmx」となっているWebサービスを使用する場合、Fiddler2でキャプチャしたメッセージ・ログを使用する必要がある。
  メッセージ・ログの構文チェックを行う。

今回はキャプチャ済みのログを使用するので、[Use a pre-captured message log file]ラジオボタンを選択して、採取済みのメッセージ・ログ・ファイルのフル・パスを指定する
クライアント側でメッセージ・ログを採取したので、[Message log file type]欄で[Client-side]ラジオボタンを選択して[Parse]ボタンを押せば[Next]ボタンが有効になるのでそれを押す。
WCFサービス・クライアントが発行したSOAP呼び出しが表示される。例えば複雑な操作の中で一部の呼び出しのみをテスト・シナリオとしたい場合、不要な呼び出しの行にある[Select]列のチェックボックスからチェックを外せばよい。チェックされた呼び出しのみがテストとして構成される。
  WCFが呼び出したWebサービスのURI一覧。
  呼び出してテストを行いたい呼び出しのみチェックを付ける。
  チェックをすべて外す。
  チェックを入れておくと、Visual Studioの負荷テストツールでテストされるシナリオごとに一定の時間待ちを入れてくれる。チェックを外すと、負荷テストツールが設定する待ち時間を使用しない。より人間の操作に近いテストを行う場合、チェックを入れておくとよい。
  1つの呼び出しごとに単体テストのメソッドを作成する。通常は複数の呼び出しをまとめてテストすると思われるので、チェックしない。

[Next]ボタンを押す
WCFのコントラクトのプロキシを使う場合、ここでプロキシ・クラスを含むアセンブリを追加する。
  アセンブリの追加を行う。

[Add]ボタンを押して、WCFのコントラクトのプロキシを含むアセンブリ(ここでは「WcfClient.exe」ファイル)の参照設定を行う

最後に[Finish]ボタンを押す
[New WCF Scenario Test Generation Wizard]によるシナリオを作成する

 ウィザードの実行が完了すると、テスト・プロジェクトに2つのソース・ファイルが追加される。1つは実際のテストに直接関係するWebサービスの呼び出しをそのままコードにしたテスト・コード(WCFTest1.csファイル)。もう1つはテストの初期化などを行うスタブ・コード(WCFTest1.Custom.csファイル)。

 生成されたコードはVisual Studioの通常の単体テストのそれと変わらないので、特徴的なところだけを解説する。以下は、生成されたテスト・コードの抜粋である。

[TestMethod()]
public void WCFTest1() 
{
  this.GetData();
}

private string[] GetData()
{
  int value = 20; 
  this.CustomiseGetData(ref value);
  _testContext.BeginTimer("WCFTest1_GetData"); 
  try
  {
    return service1Client.GetData(value);
  }
  finally
  {
    _testContext.EndTimer("WCFTest1_GetData"); 
  }
}
テスト・コードの抜粋(WCFTest1.cs)
  WCFTest1がテスト・シナリオの名前になる。テスト・プロジェクトの名前空間にも使用される。
  変数valueはログをキャプチャしたときにWebサービスに渡した値(この例では「20」)がそのまま格納されている。
  BeginTimerメソッド〜EndTimerメソッドで処理時間を計測する。

 次はスタブ側のコードを紹介する。

using System.ServiceModel;

public partial class WCFTest1Tests
{
 
  private static Dictionary<int, WcfClient.WcfSampleService.IService1> service1ProxyTable = new Dictionary<int, WcfClient.WcfSampleService.IService1>();
 
  [TestInitialize()] 
  public void InitializeTest()
  {
    System.Threading.Monitor.Enter(service1ProxyTable);
    try
    {
      service1ProxyTable.TryGetValue(
        System.Threading.Thread.CurrentThread.ManagedThreadId,
        out service1Client);
      if (((service1Client == null)
            || (((System.ServiceModel.ICommunicationObject)(service1Client)).State ==
        System.ServiceModel.CommunicationState.Faulted)))
      {
        // service1Client = new WcfClient.WcfSampleService.Service1Client();
       
        var Endpoint = new EndpointAddress("http://server/WcfService1/Service1.svc");
        var Binding = new BasicHttpBinding();
        var Factory = new ChannelFactory<WcfClient.WcfSampleService.IService1>(Binding);
        service1Client = Factory.CreateChannel(Endpoint);

        ((System.ServiceModel.ICommunicationObject)(service1Client)).Open();
        service1ProxyTable[System.Threading.Thread.CurrentThread.ManagedThreadId] = service1Client;
      }
    }
    finally
    {
      System.Threading.Monitor.Exit(service1ProxyTable);
    }
  }
 
  private void CustomiseGetData(ref int value)
  {
   
  }
}
スタブ側のコード(WCFTest1.Custom.cs)
  マネージ・スレッド単位に1つサービス・プロキシのスレッドを生成するため、負荷テストを実行するときは注意する必要がある(後述)。
  サービス・プロキシのスレッド初期化のタイミングで呼び出される。
  ここでFactoryおよびバインディングをコードで構築する必要がある。簡単に作っておいたので見比べてほしい。
  負荷テスト実行時にWebサービスに意図しない(=テスト対象ではない)値を渡された場合などはこの中で変更できる。

 以上でソリューションをリビルドする。


 INDEX
  アプリケーションのギアを上げよう ― Visual Studio 2010でアプリケーションのパフォーマンス・チューニング
  第5回 Webサービスのパフォーマンス・チューニング
  1.Webサービスのテスト・シナリオの手助けになるWCF Load Test:準備
    2.Webサービスのテスト・シナリオの手助けになるWCF Load Test:実践

インデックス・ページヘ 「アプリケーションのギアを上げよう」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH