/* * Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved * http://www.griddynamics.com * * This library is free software; you can redistribute it and/or modify it under the terms of * the Apache License; either * version 2.0 of the License, or any later version. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.griddynamics.jagger.engine.e1.process; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.griddynamics.jagger.agent.model.GetGeneralNodeInfo; import com.griddynamics.jagger.coordinator.CommandExecutor; import com.griddynamics.jagger.coordinator.ConfigurableWorker; import com.griddynamics.jagger.coordinator.NodeContext; import com.griddynamics.jagger.coordinator.Qualifier; import com.griddynamics.jagger.kernel.WorkloadWorkerCommandExecutor; import com.griddynamics.jagger.storage.fs.logging.LogWriter; import com.griddynamics.jagger.util.ExceptionLogger; import com.griddynamics.jagger.util.GeneralInfoCollector; import com.griddynamics.jagger.util.GeneralNodeInfo; import com.griddynamics.jagger.util.TimeoutsConfiguration; import com.griddynamics.jagger.util.UrlClassLoaderHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import java.util.Collection; import java.util.Map; import java.util.UUID; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Adapts {@link PerThreadWorkloadProcess} and {@link PeriodWorkloadProcess} to coordination API. */ public class WorkloadWorker extends ConfigurableWorker { private static final Logger log = LoggerFactory.getLogger(WorkloadWorker.class); private static final int CORE_POOL_SIZE = 5; private TimeoutsConfiguration timeoutsConfiguration; private GeneralInfoCollector generalInfoCollector = new GeneralInfoCollector(); private Map<String, WorkloadProcess> processes = Maps.newConcurrentMap(); private Map<String, Integer> pools = Maps.newConcurrentMap(); private LogWriter logWriter; private UrlClassLoaderHolder classLoaderHolder; @Override public Collection<CommandExecutor<?, ?>> getExecutors() { return super.getExecutors(); } public void setClassLoaderHolder(UrlClassLoaderHolder classLoaderHolder) { this.classLoaderHolder = classLoaderHolder; } @Required public void setTimeoutsConfiguration(TimeoutsConfiguration timeoutsConfiguration) { this.timeoutsConfiguration = timeoutsConfiguration; } @Override public void configure() { onCommandReceived(StartWorkloadProcess.class).execute( new WorkloadWorkerCommandExecutor<StartWorkloadProcess, String>() { @Override public Qualifier<StartWorkloadProcess> getQualifier() { return Qualifier.of(StartWorkloadProcess.class); } @Override public String doExecute(StartWorkloadProcess command, NodeContext nodeContext) { log.debug("Processing command {}", command); int poolSize = command.getPoolSize(); if (poolSize < command.getThreads()) { throw new IllegalStateException("Error! Pool size is less then thread count"); } WorkloadProcess process; if (command.getScenarioContext().getWorkloadConfiguration().getPeriod() > 0) { log.info("start periodic load process"); // start periodic process process = new PeriodWorkloadProcess(command.getSessionId(), command, nodeContext, getFixedThreadPoolExecutor(poolSize), timeoutsConfiguration); } else { log.info("start per thread load process"); process = new PerThreadWorkloadProcess(command.getSessionId(), command, nodeContext, getFixedThreadPoolExecutor(poolSize), timeoutsConfiguration); } String processId = generateId(); processes.put(processId, process); pools.put(processId, poolSize); process.start(); return processId; } } ); onCommandReceived(ChangeWorkloadConfiguration.class).execute( new WorkloadWorkerCommandExecutor<ChangeWorkloadConfiguration, Boolean>() { @Override public Qualifier<ChangeWorkloadConfiguration> getQualifier() { return Qualifier.of(ChangeWorkloadConfiguration.class); } @Override public Boolean doExecute(ChangeWorkloadConfiguration command, NodeContext nodeContext) { Preconditions.checkArgument(command.getProcessId() != null, "Process id cannot be null"); Integer poolSize = pools.get(command.getProcessId()); if (poolSize < command.getConfiguration().getThreads()) { throw new IllegalStateException("Error! Pool size is less then thread count"); } WorkloadProcess process = getProcess(command.getProcessId()); process.changeConfiguration(command.getConfiguration()); return true; } } ); onCommandReceived(PollWorkloadProcessStatus.class).execute( new WorkloadWorkerCommandExecutor<PollWorkloadProcessStatus, WorkloadStatus>() { @Override public Qualifier getQualifier() { return Qualifier.of(PollWorkloadProcessStatus.class); } @Override public WorkloadStatus doExecute(PollWorkloadProcessStatus command, NodeContext nodeContext) { Preconditions.checkArgument(command.getProcessId() != null, "Process id cannot be null"); WorkloadProcess process = getProcess(command.getProcessId()); return process.getStatus(); } } ); onCommandReceived(StopWorkloadProcess.class).execute( new WorkloadWorkerCommandExecutor<StopWorkloadProcess, WorkloadStatus>() { @Override public Qualifier<StopWorkloadProcess> getQualifier() { return Qualifier.of(StopWorkloadProcess.class); } @Override public WorkloadStatus doExecute(StopWorkloadProcess command, NodeContext nodeContext) { log.debug("Going to stop process {} on kernel {}", command.getProcessId(), nodeContext.getId().getIdentifier()); Preconditions.checkArgument(command.getProcessId() != null, "Process id cannot be null"); WorkloadProcess process = getProcess(command.getProcessId()); process.stop(); processes.remove(command.getProcessId()); logWriter.flush(); return process.getStatus(); } }); onCommandReceived(GetGeneralNodeInfo.class).execute( new WorkloadWorkerCommandExecutor<GetGeneralNodeInfo, GeneralNodeInfo>() { @Override public Qualifier<GetGeneralNodeInfo> getQualifier() { return Qualifier.of(GetGeneralNodeInfo.class); } @Override public GeneralNodeInfo doExecute(GetGeneralNodeInfo command, NodeContext nodeContext) { long startTime = System.currentTimeMillis(); log.debug("start GetGeneralNodeInfo on kernel {}", nodeContext.getId()); GeneralNodeInfo generalNodeInfo = generalInfoCollector.getGeneralNodeInfo(); log.debug("finish GetGeneralNodeInfo on kernel {} time {} ms", nodeContext.getId(), System.currentTimeMillis() - startTime); return generalNodeInfo; } }); onCommandReceived(AddUrlClassLoader.class).execute(new WorkloadWorkerCommandExecutor<AddUrlClassLoader, Boolean>() { @Override public Qualifier<AddUrlClassLoader> getQualifier() { return Qualifier.of(AddUrlClassLoader.class); } @Override public Boolean doExecute(AddUrlClassLoader command, NodeContext nodeContext) { if (classLoaderHolder != null) { return classLoaderHolder.createFor(command.getClassesUrl()); } return null; } }); onCommandReceived(RemoveUrlClassLoader.class).execute(new WorkloadWorkerCommandExecutor<RemoveUrlClassLoader, Boolean>() { @Override public Qualifier<RemoveUrlClassLoader> getQualifier() { return Qualifier.of(RemoveUrlClassLoader.class); } @Override public Boolean doExecute(RemoveUrlClassLoader command, NodeContext nodeContext) { if (classLoaderHolder != null) { return classLoaderHolder.clear(); } return null; } }); } /** * @param poolSize size of fixed thread pool to create * @return {@link ThreadPoolExecutor} instance with given pool size */ private ThreadPoolExecutor getFixedThreadPoolExecutor(int poolSize) { int corePoolSize = poolSize < CORE_POOL_SIZE ? poolSize : CORE_POOL_SIZE; return new ThreadPoolExecutor(corePoolSize, poolSize, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryBuilder() .setNameFormat("workload-thread %d") .setUncaughtExceptionHandler(ExceptionLogger.INSTANCE) .build()); } private String generateId() { return "WorkloadProcess-" + UUID.randomUUID(); } public void setLogWriter(LogWriter logWriter) { this.logWriter = logWriter; } public LogWriter getLogWriter() { return logWriter; } public WorkloadProcess getProcess(String processId) { WorkloadProcess result = processes.get(processId); for (int i = 0; (result == null) && (i < 1000); ++i) { try { Thread.sleep(60); } catch (InterruptedException e) { throw new RuntimeException(e); } result = processes.get(processId); } if (result == null) { throw new RuntimeException("Problem with process registration"); } return result; } }