package org.activiti.crystalball.simulator.impl; /* * #%L * simulator * %% * Copyright (C) 2012 - 2013 crystalball * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import org.activiti.crystalball.simulator.SimulationEvent; import org.activiti.crystalball.simulator.SimulationEventHandler; import org.activiti.crystalball.simulator.SimulationRunContext; import org.activiti.engine.HistoryService; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.util.ClockUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * event handler schedules processinstances to start according to history which is played back * generates load which was the same as the already started process instances generated * in the past. * */ public class PlaybackScheduleStartProcessEventHandler implements SimulationEventHandler { private static final String PROCESS_INSTANCE_ID = "_playback.processInstanceId"; private static Logger log = LoggerFactory.getLogger(PlaybackScheduleStartProcessEventHandler.class); /** process to start key - this process will be play backed.*/ private String processToPlaybackKey; /** event type on which Handler is listening to start new process */ private String eventType; /** event type to schedule when new process should be started */ private String eventTypeToSchedule; /** history from which process starts will be played */ private HistoryService historyService; /** date from which playback will start */ private Date playBackStart; /** date at which playback will end */ private Date playBackEnd; /** when simulation starts*/ private Date simulationRunStart; /** repeat playback forever flag */ private boolean repeatPlayback = true; /** delta in milisecond - limit bunch of processes to be scheduled in one schedule event*/ private int delta = 1000; @Override public void init() { // initialise simulation start simulationRunStart = ClockUtil.getCurrentTime(); // determine when to start new process List<HistoricProcessInstance> processInstances = getPlaybackProcessInstances( playBackStart); if (!processInstances.isEmpty()) { // schedule new process instance start now for (HistoricProcessInstance processInstance : processInstances ) scheduleNextProcessStart(simulationRunStart.getTime() + getSimulationTimeDelta(processInstance.getStartTime()), processInstance.getId()); } } /** * provide simulation time delta at which process instance has to be started * @param startTime - time when new process instance was started * @return */ private long getSimulationTimeDelta(Date startTime) { long timeDelta = startTime.getTime() - playBackStart.getTime(); long playBackTime = playBackEnd.getTime() - playBackStart.getTime(); long simulationTimeDelta = ClockUtil.getCurrentTime().getTime() - simulationRunStart.getTime(); long playbackRepeated = 0; if ( repeatPlayback ) playbackRepeated = simulationTimeDelta/ playBackTime; return playbackRepeated * playBackTime + timeDelta; } /** * schedule next process start - take history and choose next process from play back interval * @param context * @param simulationTime * @param processInstanceId */ private void scheduleNextProcessStart(long simulationTime, String processInstanceId) { Map<String,Object> properties = new HashMap<String, Object>(); properties.put(PROCESS_INSTANCE_ID, processInstanceId); SimulationEvent completeEvent = new SimulationEvent( simulationTime, eventTypeToSchedule, properties); // add start process event SimulationRunContext.getEventCalendar().addEvent( completeEvent); log.debug("Scheduling new process start simtime [" + simulationTime + "] properties["+properties+"]"); } @Override public void handle(SimulationEvent event) { // // determine next process instance start // // transform simulation time to the playback interval long playBackTime = playBackEnd.getTime() - playBackStart.getTime(); long simulationTimeDelta = ClockUtil.getCurrentTime().getTime() - simulationRunStart.getTime(); long playbackRepeated = simulationTimeDelta/ playBackTime; // only for performance reasons if (!repeatPlayback && playbackRepeated > 0) return; long timeDelta = simulationTimeDelta - playbackRepeated * playBackTime ; Date playbackPositionDate = new Date( playBackStart.getTime() + timeDelta ); // determine new process instances to start from playback List<HistoricProcessInstance> processInstances = getPlaybackProcessInstances(playbackPositionDate); // schedule new process instance start now if (!processInstances.isEmpty()) for (HistoricProcessInstance processInstance : processInstances) scheduleNextProcessStart(simulationRunStart.getTime() + playbackRepeated * playBackTime + (processInstance.getStartTime().getTime() - playBackStart.getTime() ), processInstance.getId() ); } /** * get process instances to schedule their start an schedule new schedule simulation event * * @param playBackPosition * @param context * @return */ protected List<HistoricProcessInstance> getPlaybackProcessInstances( Date playBackPosition) { // set end time for the query Date end = playBackEnd; Calendar c = Calendar.getInstance(); c.setTime(playBackPosition); c.add(Calendar.MILLISECOND, delta); if ( end.after( c.getTime() )) { end = c.getTime(); // add schedule process event after selected process instances SimulationEvent scheduleEvent = new SimulationEvent( ClockUtil.getCurrentTime().getTime() + delta + 1, eventType, null); SimulationRunContext.getEventCalendar().addEvent( scheduleEvent); } else { long playBackTime = playBackEnd.getTime() - playBackStart.getTime(); long simulationTimeDelta = ClockUtil.getCurrentTime().getTime() - simulationRunStart.getTime(); long playbackRepeated = simulationTimeDelta/ playBackTime +1 ; SimulationEvent scheduleEvent = new SimulationEvent( simulationRunStart.getTime()+ playBackTime * playbackRepeated + 1, eventType, null); SimulationRunContext.getEventCalendar().addEvent( scheduleEvent); } // determine process instances to be played back List<HistoricProcessInstance> processInstances = historyService.createHistoricProcessInstanceQuery() .processDefinitionKey(processToPlaybackKey) .startedAfter( playBackPosition ).startedBefore(end ) .orderByProcessInstanceStartTime().asc() .list(); return processInstances; } public String getProcessToPlaybackKey() { return processToPlaybackKey; } public void setProcessToPlaybackKey(String processToStartKey) { this.processToPlaybackKey = processToStartKey; } public String getEventType() { return eventType; } public void setEventType(String event_type) { this.eventType = event_type; } public HistoryService getHistoryService() { return historyService; } public void setHistoryService(HistoryService historyService) { this.historyService = historyService; } public Date getPlayBackStart() { return playBackStart; } public void setPlayBackStart(Date playBackStart) { this.playBackStart = playBackStart; } public Date getPlayBackEnd() { return playBackEnd; } public void setPlayBackEnd(Date playBackEnd) { this.playBackEnd = playBackEnd; } public boolean isRepeatPlayback() { return repeatPlayback; } public void setRepeatPlayback(boolean repeatPlayback) { this.repeatPlayback = repeatPlayback; } public int getDelta() { return delta; } public void setDelta(int delta) { this.delta = delta; } public String getEventTypeToSchedule() { return eventTypeToSchedule; } public void setEventTypeToSchedule(String eventTypeToSchedule) { this.eventTypeToSchedule = eventTypeToSchedule; } }