Programming with JMeter-- ThreadGroup


プロジェクトは一段落して、やっと帰ってきて続ける時間があった.
前回はJMeterEngineがJMeter ThreadGroupを駆動してTest Threadsを起動してテストを実行すると書いてありましたが、それ自体もRunnableです.ここではテストドライバ(JUnitや他のmainのような)をメインスレッド(main thread)と見なすと、JMeterEngineは第1層サブスレッド(First Child Thread)として機能するので、プロジェクトコードが多くのモジュール(例えばOSGIベース)に基づいている場合、ClassLoaderの調整が必要な場合は、JMeterEngineのrunTestメソッドを書き換えることができます.例:
                @Override
		public void runTest() throws JMeterEngineException
		{
			try {
				tcClassloader = Thread.currentThread().getContextClassLoader();

				Future<?> f = DefaultExecutorService.getInstance().submit(this);
				f.get();
			}
			catch (Exception err) {
				stopTest();
				throw new JMeterEngineException(err);
			}
			finally {
				
			}
		}

                @Override
		public void run()
		{
			ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
			Thread.currentThread().setContextClassLoader(tcClassloader);
			try {
				super.run();
			}
			finally {
				Thread.currentThread().setContextClassLoader(oldCl);
			}
		}

 
JMeterEngineは、JMeterのデータ構造HashTreeからThreadGroupを取得した後、ThreadGroupのAPI:start/stopThread/waitThreadsStopped/tellThreadsToStop/numberOfActiveThreads/verifyThreadsStoppedによってテストスレッドの開始/終了を行い、対応する段階でいくつかのリスナーに通知し、Test Threadsを本当に作成して起動する場所はThreadGroupである.ThreadGroup#start(int groupCount,Listener Notifier,ListedHashTree threadGroup Tree,StandardJMeterEngine engine)メソッドを見てください.スレッドを開始する方法は2つあります.
1)dealyedStartup、すなわちHashTreeにThreadGroupが配置する.delayedStartはtrueであり、ThreadGroupは内部クラスThreadStartに渡されてテストスレッドを作成/起動します.ThreadStartは個々のスレッドとして、各テストスレッドのdelay time、duration time、end timeを計算し、これらのtime値に基づいてテストスレッドを作成/開始/終了します.
JMeterではスレッドStop Conditionに対していくつかのデフォルトの選択を提供していることを知っています:LoopController,IfController,RunTimeなど.「テストを実行してしばらく停止する」というシーンが必要な場合、org.apache.jmeter.control.RunTimeを選択して各スレッドのループを制御する必要があります.スレッド起動段階でendTimeが古くなった場合、テストスレッドを起動する必要はありません.
 
2)dealyedStartupでない場合は、すべてのテストスレッドを作成して起動します.
      
ThreadGroupは重要な拡張点であり、実際のプロジェクトには多くのリソースを統合する必要があります.例えば、プロジェクトには独自に統一されたスレッドプールがあり、特定のサーバがあるなどです.一方、2つのスレッド起動モードはやや複雑です.特にclass loadに問題が発生した場合、スレッドコンテキストclassloaderを制御する必要があります.したがって、実際のプロジェクトではThreadGroupを書き換えるには、1つのモードを統合すれば十分です.個人的には、weblogic統合ワークマネージャを統合してスレッドスケジューリングを行い、RemoteとLocalのテストスレッドを区別する必要があります.
 
 ThreadGroup#start:
        @Override
	public void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine)
	{
		running = true;
		int numThreads = getNumThreads();
		int rampUp = getRampUp();
		double perThreadDelay = rampUp * 1000.0 / getNumThreads();
		log.info("Starting thread group number " + groupCount + " threads " + numThreads);

		final JMeterContext context = JMeterContextService.getContext();
		long now = System.currentTimeMillis();
		for (int i = 0; running && i < numThreads; i++) {
			final CustomJMeterThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, i, context);
			jmThread.setInitialDelay((int) (i * perThreadDelay));
			scheduleThread(jmThread, now);
                        //ITestResourceHolder: , JMeterEngine , 
			final ITestResourceHolder resourceHolder = (ITestResourceHolder) engine;
			Runnable runnable = null;
			switch ((TestType) resourceHolder.getResourceMap().get(TestType.class.getName())) {
				case LOCAL:
					runnable = new DaemonizableNamedRunnable() {
						@Override
						public String getName()
						{
							return jmThread.getThreadName();
						}
						@Override
						public boolean isDaemon()
						{
							return true;
						}
						@Override
						public void release()
						{}
						@Override
						public void run()
						{
							jmThread.run();
						}
					};
					break;
				case REMOTE:
					final Subject subj = Security.getCurrentSubject();
					runnable = new DaemonizableNamedRunnable() {
						@Override
						public String getName()
						{
							return jmThread.getThreadName();
						}
						@Override
						public boolean isDaemon()
						{
							return true;
						}
						@Override
						public void release()
						{}

						@Override
						public void run()
						{
							Security.runAs(subj, new PrivilegedAction<Void>() {

								@Override
								public Void run()
								{
									jmThread.run();
									return null;
								}
							});
						}
					};
					break;
			}
			Future<?> f = DefaultExecutorService.getInstance().submit(runnable);
			allThreads.put(jmThread, f);
		}
		log.info("Started thread group number " + groupCount);
	}

 
startは書き換えられています.stopなどの関連も書き換えなければなりません.
     
/* (non-Javadoc)
	* @see org.apache.jmeter.threads.ThreadGroup#stop()
	*/
	@Override
	public void stop()
	{
		running = false;
		for (JMeterThread item : allThreads.keySet()) {
			item.stop();
		}
	}

	/* (non-Javadoc)
	 * @see org.apache.jmeter.threads.ThreadGroup#tellThreadsToStop()
	 */
	@Override
	public void tellThreadsToStop()
	{
		running = false;
		for (Entry<JMeterThread, Future<?>> entry : allThreads.entrySet()) {
			JMeterThread item = entry.getKey();
			item.stop(); // set stop flag
			item.interrupt(); // interrupt sampler if possible
			Future<?> f = entry.getValue();
			if (f != null) {
				f.cancel(true);
				allThreads.remove(item);
			}
		}
	}

/* (non-Javadoc)
	 * @see org.apache.jmeter.threads.ThreadGroup#waitThreadsStopped()
	 */
	@Override
	public void waitThreadsStopped()
	{
		for (Future<?> f : allThreads.values()) {
			try {
				if (getDuration() > 0) {
					f.get(getDuration(), TimeUnit.SECONDS);
				}
				else {
					f.get();
				}
			}
			catch (InterruptedException e) {
				return;
			}
			catch (ExecutionException e) {
				log.error("Exception occurred when try to retrive future value from JMeterThread ", e);
			}
			catch (TimeoutException e) {
				tellThreadsToStop();
				return;
			}
		}
		allThreads.clear();
	}