diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 4f750ff1..50a20ce3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -34,6 +34,7 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("asyncService-"); + executor.setThreadPriority(Thread.NORM_PRIORITY); executor.initialize(); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 9e766723..c33793d3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -371,7 +371,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } public void updateGUI() { - if (this.needsSort) { sortTable(); } @@ -725,10 +724,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.indicatorWeights[i] = -1; } } + + this.monitoringData = monitoringData; + if (this.indicatorValueChanged) { updateIndicatorWeight(); } - this.monitoringData = monitoringData; return this.staticData == null || this.staticData == ClientStaticData.NULL_DATA diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java index c1f79837..7055077c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java @@ -29,4 +29,28 @@ public class WebserviceConfig { return aes256jnCryptor; } +// @Bean +// public WebServerFactoryCustomizer tomcatCustomizer() { +// return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { +// if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { +// System.out.println("*************** tomcatCustomizer"); +// final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector +// .getProtocolHandler(); +// protocolHandler.setKeepAliveTimeout(60000); +// protocolHandler.setMaxKeepAliveRequests(3000); +// protocolHandler.setUseKeepAliveResponseHeader(true); +// protocolHandler.setMinSpareThreads(200); +// protocolHandler.setProcessorCache(-1); +// protocolHandler.setTcpNoDelay(true); +// protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); +// protocolHandler.setMaxConnections(2000); +// if (protocolHandler instanceof Http11NioProtocol) { +// System.out.println("*************** Http11NioProtocol"); +// ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); +// } +// +// } +// }); +// } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 3be4b33c..340cca32 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -101,7 +101,7 @@ public class WebserviceInfo { this.distributedUpdateInterval = environment.getProperty( "sebserver.webservice.distributed.updateInterval", Long.class, - 3000L); + 2000L); this.activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles())); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 27406d9c..ba0abd37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -814,10 +814,13 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { .selectByExample() .where( ClientConnectionRecordDynamicSqlSupport.status, - SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES)) + SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name())) .and( ClientConnectionRecordDynamicSqlSupport.examId, SqlBuilder.isEqualTo(examId)) + .and( + ClientConnectionRecordDynamicSqlSupport.clientVersion, + SqlBuilder.isNotNull()) .and( ClientConnectionRecordDynamicSqlSupport.clientVersionGranted, SqlBuilder.isNull()) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java index 8c8b4cec..b61f1aad 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java @@ -14,10 +14,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A client indicator is a indicator value holder for a specific Indicator * on a running client connection. @@ -64,14 +62,9 @@ public interface ClientIndicator extends IndicatorValue { /** This gets called on a value change e.g.: when a ClientEvent was received. * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. * - * @param event The ClientEvent instance */ - void notifyValueChange(ClientEvent event); - - /** This gets called on a value change e.g.: when a ClientEvent was received. - * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. - * - * @param clientEventRecord The ClientEventRecord instance */ - void notifyValueChange(ClientEventRecord clientEventRecord); + * @param textValue The text based value + * @param numValue The value number */ + void notifyValueChange(String textValue, double numValue); /** This indicates if the indicator indicates an incident. This is the case if the actual indicator value * is above or below the max or min value defined by the indicator threshold settings. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java index a9c9b7df..2ef1ee28 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java @@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A exam session SEB client event handling strategy implements a certain strategy to * store ClientEvent that are coming in within the specified endpoint in height frequency. */ +@Deprecated public interface EventHandlingStrategy extends Consumer { String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index b7f1ab14..1817b413 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.util.Result; public interface SEBClientSessionService { @@ -29,17 +28,16 @@ public interface SEBClientSessionService { /** Notify a ping for a certain client connection. * * @param connectionToken the connection token - * @param timestamp the ping time-stamp * @param pingNumber the ping number * @param instructionConfirm instruction confirm sent by the SEB client or null * @return SEB instruction if available */ - String notifyPing(String connectionToken, long timestamp, int pingNumber, String instructionConfirm); + String notifyPing(String connectionToken, int pingNumber, String instructionConfirm); /** Notify a SEB client event for live indication and storing to database. * * @param connectionToken the connection token * @param event The SEB client event data */ - void notifyClientEvent(String connectionToken, final ClientEvent event); + void notifyClientEvent(String connectionToken, String jsonBody); /** This is used to confirm SEB instructions that must be confirmed by the SEB client. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java index 7639162e..27c35222 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java @@ -77,7 +77,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { private final BlockingDeque eventQueue = new LinkedBlockingDeque<>(); private final BlockingDeque notificationQueue = new LinkedBlockingDeque<>(); private boolean workersRunning = false; - private boolean enabled = false; + private final boolean enabled = false; public AsyncBatchEventSaveStrategy( final SEBClientNotificationService sebClientNotificationService, @@ -95,7 +95,8 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { @Override public void enable() { - this.enabled = true; + log.info("AsyncBatchEventSaveStrategy is deprecated"); + //this.enabled = true; } @EventListener(SEBServerInitEvent.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index 59a64d3c..a4699fef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -39,6 +39,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData { private static final Logger log = LoggerFactory.getLogger(ClientConnectionDataInternal.class); + // TODO why list for type? Is it possible to restrict to one per type? final EnumMap> indicatorMapping; PingIntervalClientIndicator pingIndicator = null; @@ -85,9 +86,9 @@ public class ClientConnectionDataInternal extends ClientConnectionData { } } - public final void notifyPing(final long timestamp, final int pingNumber) { + public final void notifyPing(final long timestamp) { if (this.pingIndicator != null) { - this.pingIndicator.notifyPing(timestamp, pingNumber); + this.pingIndicator.notifyPing(timestamp); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java index 1b5e0fb8..b507d647 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java @@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate @Lazy @Service @WebServiceProfile +@Deprecated public class EventHandlingStrategyFactory { private final EventHandlingStrategy eventHandlingStrategy; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index d8d89ff9..754ce2e7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -378,6 +378,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { @Override public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { + // TODO do we really need to synchronize here? synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { return this.examSessionCacheService.getClientConnection(connectionToken); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java new file mode 100644 index 00000000..087808e2 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.stream.Collectors; + +import javax.annotation.PreDestroy; + +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification.NotificationType; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Pair; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService; + +@Lazy +@Component +@WebServiceProfile +public class SEBClientEventBatchService { + + private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.class); + + private final SEBClientNotificationService sebClientNotificationService; + private final SqlSessionFactory sqlSessionFactory; + private final TransactionTemplate transactionTemplate; + private final ExamSessionCacheService examSessionCacheService; + private final JSONMapper jsonMapper; + + private final SqlSessionTemplate sqlSessionTemplate; + private final ClientEventRecordMapper clientEventMapper; + + public SEBClientEventBatchService( + final SEBClientNotificationService sebClientNotificationService, + final SqlSessionFactory sqlSessionFactory, + final PlatformTransactionManager transactionManager, + final ExamSessionCacheService examSessionCacheService, + final JSONMapper jsonMapper) { + + this.sebClientNotificationService = sebClientNotificationService; + this.sqlSessionFactory = sqlSessionFactory; + this.transactionTemplate = new TransactionTemplate(transactionManager); + this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.examSessionCacheService = examSessionCacheService; + this.jsonMapper = jsonMapper; + + this.sqlSessionTemplate = new SqlSessionTemplate( + this.sqlSessionFactory, + ExecutorType.BATCH); + this.clientEventMapper = this.sqlSessionTemplate.getMapper( + ClientEventRecordMapper.class); + } + + private final BlockingDeque eventDataQueue = new LinkedBlockingDeque<>(); + private final Collection events = new ArrayList<>(); + + public void accept(final String connectionToken, final String jsonBody) { + this.eventDataQueue.add(new EventData( + connectionToken, + Utils.getMillisecondsNow(), + jsonBody)); + } + + public void accept(final EventData eventData) { + this.eventDataQueue.add(eventData); + } + + @Scheduled( + fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.interval:1000}", + initialDelay = 100) + public void processEvents() { + + final int size = this.eventDataQueue.size(); + if (size > 1000) { + log.warn("-----> There are more then 1000 SEB client logs in the waiting queue: {}", size); + } + + try { + + this.events.clear(); + this.eventDataQueue.drainTo(this.events); + + if (this.events.isEmpty()) { + return; + } + + final List events = this.events + .stream() + .map(this::convertData) + .map(this::storeNotifications) + .filter(Objects::nonNull) + .map(this::toEventRecord) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + this.transactionTemplate + .execute(status -> { + events.stream().forEach(this.clientEventMapper::insert); + return null; + }); + + this.sqlSessionTemplate.flushStatements(); + + } catch (final Exception e) { + log.error("Failed to process SEB events from eventDataQueue: ", e); + } + } + + private EventData convertData(final EventData eventData) { + if (eventData == null || eventData.jsonBody == null) { + return eventData; + } + + try { + + final ClientEvent eventModel = this.jsonMapper.readValue( + eventData.jsonBody, + ClientEvent.class); + + eventData.setEvent(eventModel); + return eventData; + + } catch (final Exception e) { + log.error("Failed to convert SEB event JSON data to internal data for: {}", eventData); + return eventData; + } + } + + private EventData storeNotifications(final EventData eventData) { + try { + + if (!eventData.event.eventType.isNotificationEvent) { + return eventData; + } + + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + if (clientConnection == null) { + log.error("Failed to get ClientConnectionDataInternal for: {}", eventData.connectionToken); + return null; + } + + final Pair typeAndPlainText = + ClientNotification.extractTypeAndPlainText(eventData.event.text); + final ClientNotification notification = new ClientNotification( + eventData.event.id, + clientConnection.getConnectionId(), + eventData.event.eventType, + eventData.event.getClientTime(), + eventData.event.getServerTime(), + (eventData.event.numValue != null) ? eventData.event.numValue.doubleValue() : null, + typeAndPlainText.b, + typeAndPlainText.a); + + switch (notification.eventType) { + case NOTIFICATION: { + this.sebClientNotificationService.newNotification(notification); + break; + } + case NOTIFICATION_CONFIRMED: { + this.sebClientNotificationService.confirmPendingNotification(notification); + break; + } + default: + } + + // skip this for further event processing + return null; + + } catch (final Exception e) { + log.error("Failed to verify and process notification for SEB event: {}", eventData); + return eventData; + } + } + + private ClientEventRecord toEventRecord(final EventData eventData) { + try { + + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + if (clientConnection == null) { + log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", + eventData.connectionToken); + return null; + } + + // handle indicator update + clientConnection + .getIndicatorMapping(eventData.event.eventType) + .forEach(indicator -> indicator.notifyValueChange( + eventData.event.text, + (eventData.event.numValue != null) ? eventData.event.numValue : Double.NaN)); + + return ClientEvent.toRecord(eventData.event, clientConnection.clientConnection.id); + + } catch (final Exception e) { + log.error( + "Unexpected error while converting SEB event data to record for: {} Skip this event", eventData, + e); + return null; + } + } + + @PreDestroy + protected void shutdown() { + log.info("Shutdown SEBClientEventBatchStore..."); + if (this.sqlSessionTemplate != null) { + try { + this.sqlSessionTemplate.destroy(); + } catch (final Exception e) { + log.error("Failed to close and destroy the SqlSessionTemplate for this thread: {}", + Thread.currentThread(), + e); + } + } + } + + public final static class EventData { + final String connectionToken; + final Long serverTime; + final String jsonBody; + ClientEvent event; + + public EventData(final String connectionToken, final Long serverTime, final String jsonBody) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = jsonBody; + this.event = null; + } + + public EventData(final String connectionToken, final Long serverTime, final ClientEvent event) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = null; + this.event = event; + } + + void setEvent(final ClientEvent event) { + this.event = event; + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java new file mode 100644 index 00000000..81625a41 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; + +@Lazy +@Component +@WebServiceProfile +public class SEBClientPingBatchService { + + private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class); + + private final ExamSessionCacheService examSessionCacheService; + private final SEBClientInstructionService sebClientInstructionService; + + private final Map pings = new ConcurrentHashMap<>(); + private final Map instructions = new ConcurrentHashMap<>(); + + public SEBClientPingBatchService( + final ExamSessionCacheService examSessionCacheService, + final SEBClientInstructionService sebClientInstructionService) { + + this.examSessionCacheService = examSessionCacheService; + this.sebClientInstructionService = sebClientInstructionService; + } + + @Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}") + public void processPings() { + if (this.pings.isEmpty()) { + return; + } + + final int size = this.pings.size(); + if (size > 1000) { + log.warn("----> There are more then 1000 SEB client logs in the waiting queue: {}", size); + } + + try { + final Set connections = new HashSet<>(this.pings.keySet()); + + connections.stream().forEach(cid -> processPing( + cid, + this.pings.remove(cid), + Utils.getMillisecondsNow())); + + } catch (final Exception e) { + log.error("Failed to process SEB pings from pingDataQueue: ", e); + } + } + + public final String notifyPing( + final String connectionToken, + final String instructionConfirm) { + + if (instructionConfirm != null) { + this.pings.put(connectionToken, instructionConfirm); + } else if (!this.pings.containsKey(connectionToken)) { + this.pings.put(connectionToken, StringUtils.EMPTY); + } + + return this.instructions.remove(connectionToken); + } + + private void processPing( + final String connectionToken, + final String instructionConfirm, + final long timestamp) { + + if (connectionToken == null) { + return; + } + + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService + .getClientConnection(connectionToken); + + if (activeClientConnection != null) { + activeClientConnection.notifyPing(timestamp); + } else { + log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken); + } + + if (instructionConfirm != StringUtils.EMPTY) { + this.sebClientInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); + } + + final String instructionJSON = this.sebClientInstructionService.getInstructionJSON(connectionToken); + if (instructionJSON != null) { + this.instructions.put(connectionToken, instructionJSON); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 7d668cb0..49ba71c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -26,14 +25,13 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrategy; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService.EventData; @Lazy @Service @@ -44,33 +42,34 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private final ClientConnectionDAO clientConnectionDAO; private final ExamSessionService examSessionService; - private final ExamSessionCacheService examSessionCacheService; - private final EventHandlingStrategy eventHandlingStrategy; + private final SEBClientEventBatchService sebClientEventBatchStore; private final SEBClientInstructionService sebInstructionService; private final ClientIndicatorFactory clientIndicatorFactory; private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; private final SecurityKeyService securityKeyService; private final SEBClientVersionService sebClientVersionService; + private final SEBClientPingBatchService sebClientPingService; public SEBClientSessionServiceImpl( final ClientConnectionDAO clientConnectionDAO, final ExamSessionService examSessionService, - final EventHandlingStrategyFactory eventHandlingStrategyFactory, + final SEBClientEventBatchService sebClientEventBatchStore, final SEBClientInstructionService sebInstructionService, final ClientIndicatorFactory clientIndicatorFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final SecurityKeyService securityKeyService, - final SEBClientVersionService sebClientVersionService) { + final SEBClientVersionService sebClientVersionService, + final SEBClientPingBatchService sebClientPingService) { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; - this.examSessionCacheService = examSessionService.getExamSessionCacheService(); - this.eventHandlingStrategy = eventHandlingStrategyFactory.get(); + this.sebClientEventBatchStore = sebClientEventBatchStore; this.sebInstructionService = sebInstructionService; this.clientIndicatorFactory = clientIndicatorFactory; this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.securityKeyService = securityKeyService; this.sebClientVersionService = sebClientVersionService; + this.sebClientPingService = sebClientPingService; } @Override @@ -113,46 +112,15 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { @Override public String notifyPing( final String connectionToken, - final long timestamp, final int pingNumber, final String instructionConfirm) { - processPing(connectionToken, timestamp, pingNumber); - - if (instructionConfirm != null) { - this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); - } - - return this.sebInstructionService.getInstructionJSON(connectionToken); + return this.sebClientPingService.notifyPing(connectionToken, instructionConfirm); } @Override - public void notifyClientEvent( - final String connectionToken, - final ClientEvent event) { - - try { - final ClientConnectionDataInternal activeClientConnection = - this.examSessionService.getConnectionDataInternal(connectionToken); - - if (activeClientConnection != null) { - - // store event - this.eventHandlingStrategy.accept(ClientEvent.toRecord( - event, - activeClientConnection.getConnectionId())); - - // handle indicator update - activeClientConnection - .getIndicatorMapping(event.eventType) - .forEach(indicator -> indicator.notifyValueChange(event)); - - } else { - log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); - } - } catch (final Exception e) { - log.error("Failed to process SEB client event: ", e); - } + public final void notifyClientEvent(final String connectionToken, final String jsonBody) { + this.sebClientEventBatchStore.accept(connectionToken, jsonBody); } @Override @@ -168,39 +136,33 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.internalClientConnectionDataFactory.getGroupIds(clientConnection))); } - private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { - - ClientConnectionDataInternal activeClientConnection = null; - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken); - } - - if (activeClientConnection != null) { - activeClientConnection.notifyPing(timestamp, pingNumber); - } - } - private void missingPingUpdate(final ClientConnectionDataInternal connection) { if (connection.pingIndicator.changeOnIncident()) { final boolean missingPing = connection.getMissingPing(); final long millisecondsNow = Utils.getMillisecondsNow(); - final ClientEventRecord clientEventRecord = new ClientEventRecord( - null, - connection.getConnectionId(), - (missingPing) ? EventType.ERROR_LOG.id : EventType.INFO_LOG.id, - millisecondsNow, - millisecondsNow, - new BigDecimal(connection.pingIndicator.getValue()), - (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"); + final String textValue = (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"; + final double numValue = connection.pingIndicator.getValue(); - // store event and and flush cache - this.eventHandlingStrategy.accept(clientEventRecord); + final EventData eventData = new EventData( + connection.getClientConnection().connectionToken, + millisecondsNow, + new ClientEvent( + null, + connection.getConnectionId(), + (missingPing) ? EventType.ERROR_LOG : EventType.INFO_LOG, + millisecondsNow, + millisecondsNow, + numValue, + textValue)); + + // store missing-ping or ping-back event + this.sebClientEventBatchStore.accept(eventData); // update indicators - if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { + if (EventType.ERROR_LOG == eventData.event.eventType) { connection.getIndicatorMapping(EventType.ERROR_LOG) - .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); + .forEach(indicator -> indicator.notifyValueChange(textValue, numValue)); } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java index 769cacf1..8e009a02 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java @@ -15,12 +15,10 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicator { @@ -38,13 +36,13 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - valueChanged(clientEventRecord.getText()); + public final void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { + if (super.ditributedIndicatorValueRecordId != null) { + this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); + } + this.currentValue = getValue() + 1d; + } } @Override @@ -112,13 +110,4 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato return result; } - private void valueChanged(final String eventText) { - if (this.tags == null || this.tags.length == 0 || hasTag(eventText)) { - if (super.ditributedIndicatorValueRecordId != null) { - this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); - } - this.currentValue = getValue() + 1d; - } - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java index 2ad92b56..b117408e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java @@ -18,7 +18,6 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; @@ -41,31 +40,19 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text, event.getValue()); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - final BigDecimal numericValue = clientEventRecord.getNumericValue(); - if (numericValue != null) { - valueChanged(clientEventRecord.getText(), numericValue.doubleValue()); - } - } - - private void valueChanged(final String text, final double value) { - if (this.tags == null || this.tags.length == 0 || hasTag(text)) { + public void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { if (super.ditributedIndicatorValueRecordId != null) { if (!this.distributedIndicatorValueService.updateIndicatorValueAsync( this.ditributedIndicatorValueRecordId, - Double.valueOf(value).longValue())) { + Double.valueOf(numValue).longValue())) { this.currentValue = computeValueAt(Utils.getMillisecondsNow()); } else { - this.currentValue = value; + this.currentValue = numValue; } } else { - this.currentValue = value; + this.currentValue = numValue; } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java index 14d0ed5b..b93e1aba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java @@ -27,7 +27,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { return this.EMPTY_SET; } - public final void notifyPing(final long timestamp, final int pingNumber) { + public final void notifyPing(final long timestamp) { super.currentValue = timestamp; if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java index 44bf983b..6f257b10 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java @@ -344,10 +344,8 @@ public class DistributedIndicatorValueService implements DisposableBean { } /** Update last ping time on persistent storage asynchronously within a defines thread pool with no - * waiting queue to skip further ping updates if all update threads are busy - * - * TODO: we need a better handling strategy here. - * Try to apply a batch update and store the pings in a concurrent hash map **/ + * waiting queue to skip further ping updates if all update threads are busy **/ + // TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientPingBatchService void updatePingAsync(final Long pingRecord) { try { this.indicatorValueUpdateExecutor @@ -363,6 +361,7 @@ public class DistributedIndicatorValueService implements DisposableBean { /** Update indicator value on persistent storage asynchronously within a defined thread pool with no * waiting queue to skip further indicator value updates if all update threads are busy **/ + // TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientEventBatchStore boolean updateIndicatorValueAsync(final Long pk, final Long value) { try { this.indicatorValueUpdateExecutor diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java index dbd09771..47485823 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java @@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; @Lazy @Component(IndicatorType.Names.LAST_PING) @@ -92,12 +90,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { + public void notifyValueChange(final String textValue, final double numValue) { } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java index 2557e075..436cb042 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java @@ -8,34 +8,28 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; - -@EnableAsync -@Configuration -@WebServiceProfile +//@EnableAsync +//@Configuration +//@WebServiceProfile +@Deprecated public class ControllerConfig implements WebMvcConfigurer { - @Override - public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { - configurer.setTaskExecutor(threadPoolTaskExecutor()); - configurer.setDefaultTimeout(30000); - } - - public AsyncTaskExecutor threadPoolTaskExecutor() { - final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(7); - executor.setMaxPoolSize(42); - executor.setQueueCapacity(11); - executor.setThreadNamePrefix("mvc-"); - executor.initialize(); - return executor; - } +// @Override +// public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { +// configurer.setTaskExecutor(threadPoolTaskExecutor()); +// configurer.setDefaultTimeout(30000); +// } +// +// public AsyncTaskExecutor threadPoolTaskExecutor() { +// final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); +// executor.setCorePoolSize(7); +// executor.setMaxPoolSize(42); +// executor.setQueueCapacity(11); +// executor.setThreadNamePrefix("mvc-"); +// executor.initialize(); +// return executor; +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 4d46c208..db47e307 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -45,7 +45,6 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.RunningExamInfo; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -329,15 +328,11 @@ public class ExamAPI_V1_Controller { public void ping(final HttpServletRequest request, final HttpServletResponse response) { final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); - final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); + //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); final String instruction = this.sebClientSessionService - .notifyPing( - connectionToken, - Utils.getMillisecondsNow(), - pingNumString != null ? Integer.parseInt(pingNumString) : -1, - instructionConfirm); + .notifyPing(connectionToken, 0, instructionConfirm); if (instruction == null) { response.setStatus(HttpStatus.NO_CONTENT.value()); @@ -358,10 +353,10 @@ public class ExamAPI_V1_Controller { @ResponseStatus(value = HttpStatus.NO_CONTENT) public void event( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, - @RequestBody(required = true) final ClientEvent event) { + @RequestBody(required = true) final String jsonBody) { this.sebClientSessionService - .notifyClientEvent(connectionToken, event); + .notifyClientEvent(connectionToken, jsonBody); } private Long getInstitutionId(final Principal principal) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 36aec030..52ef0191 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -322,21 +322,25 @@ public class ExamMonitoringController { createMonitoringFilter(hiddenStates, hiddenClientGroups)) .getOrThrow(); + MonitoringFullPageData monitoringFullPageData; if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { final Collection proctoringData = this.examProcotringRoomService .getProctoringCollectingRooms(examId) .getOrThrow(); - return new MonitoringFullPageData( + monitoringFullPageData = new MonitoringFullPageData( examId, monitoringSEBConnectionData, proctoringData); + } else { - return new MonitoringFullPageData( + monitoringFullPageData = new MonitoringFullPageData( examId, monitoringSEBConnectionData, Collections.emptyList()); } + + return monitoringFullPageData; } @RequestMapping( diff --git a/src/main/resources/config/application-dev.properties b/src/main/resources/config/application-dev.properties index adbcaac7..5d9a3dbe 100644 --- a/src/main/resources/config/application-dev.properties +++ b/src/main/resources/config/application-dev.properties @@ -6,6 +6,7 @@ server.address=localhost server.port=8080 server.servlet.context-path=/ server.tomcat.uri-encoding=UTF-8 +server.http2.enabled=true logging.level.ROOT=INFO logging.level.ch=INFO diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index acafe27a..0cab6d27 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -40,7 +40,7 @@ sebserver.webservice.internalSecret=${sebserver.password} ### webservice networking sebserver.webservice.forceMaster=false sebserver.webservice.distributed=false -sebserver.webservice.distributed.updateInterval=3000 +sebserver.webservice.distributed.updateInterval=2000 sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= sebserver.webservice.http.external.port= diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 2f6bd64a..57b27078 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -15,6 +15,12 @@ server.servlet.context-path=/ # Tomcat server.tomcat.max-threads=2000 server.tomcat.accept-count=300 +server.tomcat.socket.soKeepAlive=true +server.tomcat.socket.performanceConnectionTime=1 +server.tomcat.socket.performanceLatency=2 +server.tomcat.socket.performanceBandwidth=0 +server.tomcat.keepAliveTimeout(20000); +server.tomcat.maxKeepAliveRequests(3000); server.tomcat.uri-encoding=UTF-8 ### encoding diff --git a/src/main/resources/config/ehcache.xml b/src/main/resources/config/ehcache.xml index 74e0a0fa..81f2dddc 100644 --- a/src/main/resources/config/ehcache.xml +++ b/src/main/resources/config/ehcache.xml @@ -34,7 +34,7 @@ 24 - 3000 + 100000 diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java index 5c112723..6d7b05fd 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java @@ -72,7 +72,6 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.monitoring.SimpleIndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; @@ -406,15 +405,8 @@ public class ModelObjectJSONGenerator { } @Override - public void notifyValueChange(final ClientEvent event) { + public void notifyValueChange(final String textValue, final double numValue) { // TODO Auto-generated method stub - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - // TODO Auto-generated method stub - } @Override diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java index 39c6439d..f7baae0f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java @@ -42,6 +42,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService; @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) public class SebConnectionTest extends ExamAPIIntegrationTester { @@ -56,6 +57,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { private ExamDAO examDAO; @Autowired private LmsAPIService lmsAPIService; + @Autowired + private SEBClientEventBatchService sebClientEventBatchStore; @Before public void init() { @@ -558,6 +561,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); + this.sebClientEventBatchStore.processEvents(); + // check event stored on db List events = this.clientEventRecordMapper .selectByExample() @@ -582,6 +587,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { 10000.0, "testEvent2"); + this.sebClientEventBatchStore.processEvents(); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); @@ -616,7 +622,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { "testEvent1"); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); - + this.sebClientEventBatchStore.processEvents(); final List events = this.clientEventRecordMapper .selectByExample() .build() diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java index 4e70e74f..075099b6 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java @@ -12,15 +12,12 @@ import static org.junit.Assert.*; import java.util.Collection; import java.util.Optional; -import java.util.concurrent.Executor; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.jdbc.Sql; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; @@ -35,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; @@ -50,8 +48,12 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { @Autowired private SEBClientSessionService sebClientSessionService; @Autowired - @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) - private Executor executor; + private SEBClientEventBatchService sebClientEventBatchStore; +// @Autowired +// @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) +// private Executor executor; + @Autowired + private JSONMapper jsonMapper; @Test public void testCreateLogEvents() { @@ -110,13 +112,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); @@ -126,6 +130,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } + private String writeValueAsString(final ClientEvent event) { + try { + return this.jsonMapper.writeValueAsString(event); + } catch (final Exception e) { + e.printStackTrace(); + return null; + } + } + @Test public void testInfoLogWithTagCountIndicator() { @@ -156,33 +169,39 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); // test reset indicator value and load it from persistent storage ((AbstractLogLevelCountIndicator) clientIndicator).reset(); @@ -191,13 +210,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } private void waitForExecutor() { - try { - while (((ThreadPoolTaskExecutor) this.executor).getActiveCount() > 0) { - Thread.sleep(20); - } - } catch (final Exception e) { - e.printStackTrace(); - } + this.sebClientEventBatchStore.processEvents(); } @Test @@ -231,23 +244,27 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other"))); waitForExecutor(); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, + " some info other"))); waitForExecutor(); assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, + " some info other"))); waitForExecutor(); assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, + " some info other"))); waitForExecutor(); assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));