Merge remote-tracking branch 'origin/newSEBEventStoreStrategy' into development

This commit is contained in:
anhefti 2023-06-01 16:03:23 +02:00
commit a57e371580
30 changed files with 566 additions and 211 deletions

View file

@ -34,6 +34,7 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer {
executor.setMaxPoolSize(42); executor.setMaxPoolSize(42);
executor.setQueueCapacity(11); executor.setQueueCapacity(11);
executor.setThreadNamePrefix("asyncService-"); executor.setThreadNamePrefix("asyncService-");
executor.setThreadPriority(Thread.NORM_PRIORITY);
executor.initialize(); executor.initialize();
executor.setWaitForTasksToCompleteOnShutdown(true); executor.setWaitForTasksToCompleteOnShutdown(true);
return executor; return executor;

View file

@ -371,7 +371,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
public void updateGUI() { public void updateGUI() {
if (this.needsSort) { if (this.needsSort) {
sortTable(); sortTable();
} }
@ -725,10 +724,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
this.indicatorWeights[i] = -1; this.indicatorWeights[i] = -1;
} }
} }
this.monitoringData = monitoringData;
if (this.indicatorValueChanged) { if (this.indicatorValueChanged) {
updateIndicatorWeight(); updateIndicatorWeight();
} }
this.monitoringData = monitoringData;
return this.staticData == null return this.staticData == null
|| this.staticData == ClientStaticData.NULL_DATA || this.staticData == ClientStaticData.NULL_DATA

View file

@ -29,4 +29,28 @@ public class WebserviceConfig {
return aes256jnCryptor; return aes256jnCryptor;
} }
// @Bean
// public WebServerFactoryCustomizer<TomcatServletWebServerFactory> 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);
// }
//
// }
// });
// }
} }

View file

@ -101,7 +101,7 @@ public class WebserviceInfo {
this.distributedUpdateInterval = environment.getProperty( this.distributedUpdateInterval = environment.getProperty(
"sebserver.webservice.distributed.updateInterval", "sebserver.webservice.distributed.updateInterval",
Long.class, Long.class,
3000L); 2000L);
this.activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles())); this.activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));

View file

@ -814,10 +814,13 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.selectByExample() .selectByExample()
.where( .where(
ClientConnectionRecordDynamicSqlSupport.status, ClientConnectionRecordDynamicSqlSupport.status,
SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES)) SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name()))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.examId, ClientConnectionRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId)) SqlBuilder.isEqualTo(examId))
.and(
ClientConnectionRecordDynamicSqlSupport.clientVersion,
SqlBuilder.isNotNull())
.and( .and(
ClientConnectionRecordDynamicSqlSupport.clientVersionGranted, ClientConnectionRecordDynamicSqlSupport.clientVersionGranted,
SqlBuilder.isNull()) SqlBuilder.isNull())

View file

@ -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;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; 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.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; 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 /** A client indicator is a indicator value holder for a specific Indicator
* on a running client connection. * 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. /** 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. * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received.
* *
* @param event The ClientEvent instance */ * @param textValue The text based value
void notifyValueChange(ClientEvent event); * @param numValue The value number */
void notifyValueChange(String textValue, double numValue);
/** 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);
/** This indicates if the indicator indicates an incident. This is the case if the actual indicator value /** 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. * is above or below the max or min value defined by the indicator threshold settings.

View file

@ -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 /** 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. */ * store ClientEvent that are coming in within the specified endpoint in height frequency. */
@Deprecated
public interface EventHandlingStrategy extends Consumer<ClientEventRecord> { public interface EventHandlingStrategy extends Consumer<ClientEventRecord> {
String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy"; String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy";

View file

@ -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.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; 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; import ch.ethz.seb.sebserver.gbl.util.Result;
public interface SEBClientSessionService { public interface SEBClientSessionService {
@ -29,17 +28,16 @@ public interface SEBClientSessionService {
/** Notify a ping for a certain client connection. /** Notify a ping for a certain client connection.
* *
* @param connectionToken the connection token * @param connectionToken the connection token
* @param timestamp the ping time-stamp
* @param pingNumber the ping number * @param pingNumber the ping number
* @param instructionConfirm instruction confirm sent by the SEB client or null * @param instructionConfirm instruction confirm sent by the SEB client or null
* @return SEB instruction if available */ * @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. /** Notify a SEB client event for live indication and storing to database.
* *
* @param connectionToken the connection token * @param connectionToken the connection token
* @param event The SEB client event data */ * @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. /** This is used to confirm SEB instructions that must be confirmed by the SEB client.
* *

View file

@ -77,7 +77,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
private final BlockingDeque<ClientEventRecord> eventQueue = new LinkedBlockingDeque<>(); private final BlockingDeque<ClientEventRecord> eventQueue = new LinkedBlockingDeque<>();
private final BlockingDeque<ClientNotification> notificationQueue = new LinkedBlockingDeque<>(); private final BlockingDeque<ClientNotification> notificationQueue = new LinkedBlockingDeque<>();
private boolean workersRunning = false; private boolean workersRunning = false;
private boolean enabled = false; private final boolean enabled = false;
public AsyncBatchEventSaveStrategy( public AsyncBatchEventSaveStrategy(
final SEBClientNotificationService sebClientNotificationService, final SEBClientNotificationService sebClientNotificationService,
@ -95,7 +95,8 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
@Override @Override
public void enable() { public void enable() {
this.enabled = true; log.info("AsyncBatchEventSaveStrategy is deprecated");
//this.enabled = true;
} }
@EventListener(SEBServerInitEvent.class) @EventListener(SEBServerInitEvent.class)

View file

@ -39,6 +39,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDataInternal.class); 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<EventType, Collection<ClientIndicator>> indicatorMapping; final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping;
PingIntervalClientIndicator pingIndicator = null; 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) { if (this.pingIndicator != null) {
this.pingIndicator.notifyPing(timestamp, pingNumber); this.pingIndicator.notifyPing(timestamp);
} }
} }

View file

@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
@Lazy @Lazy
@Service @Service
@WebServiceProfile @WebServiceProfile
@Deprecated
public class EventHandlingStrategyFactory { public class EventHandlingStrategyFactory {
private final EventHandlingStrategy eventHandlingStrategy; private final EventHandlingStrategy eventHandlingStrategy;

View file

@ -378,6 +378,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override @Override
public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) {
// TODO do we really need to synchronize here?
synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) {
return this.examSessionCacheService.getClientConnection(connectionToken); return this.examSessionCacheService.getClientConnection(connectionToken);
} }

View file

@ -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<EventData> eventDataQueue = new LinkedBlockingDeque<>();
private final Collection<EventData> 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<ClientEventRecord> 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<NotificationType, String> 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;
}
}
}

View file

@ -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<String, String> pings = new ConcurrentHashMap<>();
private final Map<String, String> 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<String> 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);
}
}
}

View file

@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; 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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; 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.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; 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.SEBClientSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService.EventData;
@Lazy @Lazy
@Service @Service
@ -44,33 +42,34 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
private final ClientConnectionDAO clientConnectionDAO; private final ClientConnectionDAO clientConnectionDAO;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final ExamSessionCacheService examSessionCacheService; private final SEBClientEventBatchService sebClientEventBatchStore;
private final EventHandlingStrategy eventHandlingStrategy;
private final SEBClientInstructionService sebInstructionService; private final SEBClientInstructionService sebInstructionService;
private final ClientIndicatorFactory clientIndicatorFactory; private final ClientIndicatorFactory clientIndicatorFactory;
private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; private final InternalClientConnectionDataFactory internalClientConnectionDataFactory;
private final SecurityKeyService securityKeyService; private final SecurityKeyService securityKeyService;
private final SEBClientVersionService sebClientVersionService; private final SEBClientVersionService sebClientVersionService;
private final SEBClientPingBatchService sebClientPingService;
public SEBClientSessionServiceImpl( public SEBClientSessionServiceImpl(
final ClientConnectionDAO clientConnectionDAO, final ClientConnectionDAO clientConnectionDAO,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final EventHandlingStrategyFactory eventHandlingStrategyFactory, final SEBClientEventBatchService sebClientEventBatchStore,
final SEBClientInstructionService sebInstructionService, final SEBClientInstructionService sebInstructionService,
final ClientIndicatorFactory clientIndicatorFactory, final ClientIndicatorFactory clientIndicatorFactory,
final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
final SecurityKeyService securityKeyService, final SecurityKeyService securityKeyService,
final SEBClientVersionService sebClientVersionService) { final SEBClientVersionService sebClientVersionService,
final SEBClientPingBatchService sebClientPingService) {
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.examSessionCacheService = examSessionService.getExamSessionCacheService(); this.sebClientEventBatchStore = sebClientEventBatchStore;
this.eventHandlingStrategy = eventHandlingStrategyFactory.get();
this.sebInstructionService = sebInstructionService; this.sebInstructionService = sebInstructionService;
this.clientIndicatorFactory = clientIndicatorFactory; this.clientIndicatorFactory = clientIndicatorFactory;
this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.internalClientConnectionDataFactory = internalClientConnectionDataFactory;
this.securityKeyService = securityKeyService; this.securityKeyService = securityKeyService;
this.sebClientVersionService = sebClientVersionService; this.sebClientVersionService = sebClientVersionService;
this.sebClientPingService = sebClientPingService;
} }
@Override @Override
@ -113,46 +112,15 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
@Override @Override
public String notifyPing( public String notifyPing(
final String connectionToken, final String connectionToken,
final long timestamp,
final int pingNumber, final int pingNumber,
final String instructionConfirm) { final String instructionConfirm) {
processPing(connectionToken, timestamp, pingNumber); return this.sebClientPingService.notifyPing(connectionToken, instructionConfirm);
if (instructionConfirm != null) {
this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm);
}
return this.sebInstructionService.getInstructionJSON(connectionToken);
} }
@Override @Override
public void notifyClientEvent( public final void notifyClientEvent(final String connectionToken, final String jsonBody) {
final String connectionToken, this.sebClientEventBatchStore.accept(connectionToken, jsonBody);
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);
}
} }
@Override @Override
@ -168,39 +136,33 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
this.internalClientConnectionDataFactory.getGroupIds(clientConnection))); 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) { private void missingPingUpdate(final ClientConnectionDataInternal connection) {
if (connection.pingIndicator.changeOnIncident()) { if (connection.pingIndicator.changeOnIncident()) {
final boolean missingPing = connection.getMissingPing(); final boolean missingPing = connection.getMissingPing();
final long millisecondsNow = Utils.getMillisecondsNow(); final long millisecondsNow = Utils.getMillisecondsNow();
final ClientEventRecord clientEventRecord = new ClientEventRecord( final String textValue = (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal";
null, final double numValue = connection.pingIndicator.getValue();
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");
// store event and and flush cache final EventData eventData = new EventData(
this.eventHandlingStrategy.accept(clientEventRecord); 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 // update indicators
if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { if (EventType.ERROR_LOG == eventData.event.eventType) {
connection.getIndicatorMapping(EventType.ERROR_LOG) connection.getIndicatorMapping(EventType.ERROR_LOG)
.forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); .forEach(indicator -> indicator.notifyValueChange(textValue, numValue));
} }
} }
} }

View file

@ -15,12 +15,10 @@ import org.mybatis.dynamic.sql.SqlCriterion;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; 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 { public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicator {
@ -38,13 +36,13 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
} }
@Override @Override
public void notifyValueChange(final ClientEvent event) { public final void notifyValueChange(final String textValue, final double numValue) {
valueChanged(event.text); if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) {
} if (super.ditributedIndicatorValueRecordId != null) {
this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId);
@Override }
public void notifyValueChange(final ClientEventRecord clientEventRecord) { this.currentValue = getValue() + 1d;
valueChanged(clientEventRecord.getText()); }
} }
@Override @Override
@ -112,13 +110,4 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
return result; 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;
}
}
} }

View file

@ -18,7 +18,6 @@ import org.mybatis.dynamic.sql.SqlCriterion;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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.ClientEventRecordDynamicSqlSupport;
@ -41,31 +40,19 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
} }
@Override @Override
public void notifyValueChange(final ClientEvent event) { public void notifyValueChange(final String textValue, final double numValue) {
valueChanged(event.text, event.getValue()); if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) {
}
@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)) {
if (super.ditributedIndicatorValueRecordId != null) { if (super.ditributedIndicatorValueRecordId != null) {
if (!this.distributedIndicatorValueService.updateIndicatorValueAsync( if (!this.distributedIndicatorValueService.updateIndicatorValueAsync(
this.ditributedIndicatorValueRecordId, this.ditributedIndicatorValueRecordId,
Double.valueOf(value).longValue())) { Double.valueOf(numValue).longValue())) {
this.currentValue = computeValueAt(Utils.getMillisecondsNow()); this.currentValue = computeValueAt(Utils.getMillisecondsNow());
} else { } else {
this.currentValue = value; this.currentValue = numValue;
} }
} else { } else {
this.currentValue = value; this.currentValue = numValue;
} }
} }
} }

View file

@ -27,7 +27,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
return this.EMPTY_SET; return this.EMPTY_SET;
} }
public final void notifyPing(final long timestamp, final int pingNumber) { public final void notifyPing(final long timestamp) {
super.currentValue = timestamp; super.currentValue = timestamp;
if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) { if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) {

View file

@ -344,10 +344,8 @@ public class DistributedIndicatorValueService implements DisposableBean {
} }
/** Update last ping time on persistent storage asynchronously within a defines thread pool with no /** 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 * 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
* TODO: we need a better handling strategy here.
* Try to apply a batch update and store the pings in a concurrent hash map **/
void updatePingAsync(final Long pingRecord) { void updatePingAsync(final Long pingRecord) {
try { try {
this.indicatorValueUpdateExecutor 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 /** 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 **/ * 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) { boolean updateIndicatorValueAsync(final Long pk, final Long value) {
try { try {
this.indicatorValueUpdateExecutor this.indicatorValueUpdateExecutor

View file

@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import ch.ethz.seb.sebserver.gbl.Constants; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; 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 @Lazy
@Component(IndicatorType.Names.LAST_PING) @Component(IndicatorType.Names.LAST_PING)
@ -92,12 +90,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
} }
@Override @Override
public void notifyValueChange(final ClientEvent event) { public void notifyValueChange(final String textValue, final double numValue) {
}
@Override
public void notifyValueChange(final ClientEventRecord clientEventRecord) {
} }

View file

@ -8,34 +8,28 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; 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 org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; //@EnableAsync
//@Configuration
@EnableAsync //@WebServiceProfile
@Configuration @Deprecated
@WebServiceProfile
public class ControllerConfig implements WebMvcConfigurer { public class ControllerConfig implements WebMvcConfigurer {
@Override // @Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { // public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(threadPoolTaskExecutor()); // configurer.setTaskExecutor(threadPoolTaskExecutor());
configurer.setDefaultTimeout(30000); // configurer.setDefaultTimeout(30000);
} // }
//
public AsyncTaskExecutor threadPoolTaskExecutor() { // public AsyncTaskExecutor threadPoolTaskExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7); // executor.setCorePoolSize(7);
executor.setMaxPoolSize(42); // executor.setMaxPoolSize(42);
executor.setQueueCapacity(11); // executor.setQueueCapacity(11);
executor.setThreadNamePrefix("mvc-"); // executor.setThreadNamePrefix("mvc-");
executor.initialize(); // executor.initialize();
return executor; // return executor;
} // }
} }

View file

@ -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.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; 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.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.model.session.RunningExamInfo;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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) { public void ping(final HttpServletRequest request, final HttpServletResponse response) {
final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); 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 instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM);
final String instruction = this.sebClientSessionService final String instruction = this.sebClientSessionService
.notifyPing( .notifyPing(connectionToken, 0, instructionConfirm);
connectionToken,
Utils.getMillisecondsNow(),
pingNumString != null ? Integer.parseInt(pingNumString) : -1,
instructionConfirm);
if (instruction == null) { if (instruction == null) {
response.setStatus(HttpStatus.NO_CONTENT.value()); response.setStatus(HttpStatus.NO_CONTENT.value());
@ -358,10 +353,10 @@ public class ExamAPI_V1_Controller {
@ResponseStatus(value = HttpStatus.NO_CONTENT) @ResponseStatus(value = HttpStatus.NO_CONTENT)
public void event( public void event(
@RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @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 this.sebClientSessionService
.notifyClientEvent(connectionToken, event); .notifyClientEvent(connectionToken, jsonBody);
} }
private Long getInstitutionId(final Principal principal) { private Long getInstitutionId(final Principal principal) {

View file

@ -322,21 +322,25 @@ public class ExamMonitoringController {
createMonitoringFilter(hiddenStates, hiddenClientGroups)) createMonitoringFilter(hiddenStates, hiddenClientGroups))
.getOrThrow(); .getOrThrow();
MonitoringFullPageData monitoringFullPageData;
if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) {
final Collection<RemoteProctoringRoom> proctoringData = this.examProcotringRoomService final Collection<RemoteProctoringRoom> proctoringData = this.examProcotringRoomService
.getProctoringCollectingRooms(examId) .getProctoringCollectingRooms(examId)
.getOrThrow(); .getOrThrow();
return new MonitoringFullPageData( monitoringFullPageData = new MonitoringFullPageData(
examId, examId,
monitoringSEBConnectionData, monitoringSEBConnectionData,
proctoringData); proctoringData);
} else { } else {
return new MonitoringFullPageData( monitoringFullPageData = new MonitoringFullPageData(
examId, examId,
monitoringSEBConnectionData, monitoringSEBConnectionData,
Collections.emptyList()); Collections.emptyList());
} }
return monitoringFullPageData;
} }
@RequestMapping( @RequestMapping(

View file

@ -6,6 +6,7 @@ server.address=localhost
server.port=8080 server.port=8080
server.servlet.context-path=/ server.servlet.context-path=/
server.tomcat.uri-encoding=UTF-8 server.tomcat.uri-encoding=UTF-8
server.http2.enabled=true
logging.level.ROOT=INFO logging.level.ROOT=INFO
logging.level.ch=INFO logging.level.ch=INFO

View file

@ -40,7 +40,7 @@ sebserver.webservice.internalSecret=${sebserver.password}
### webservice networking ### webservice networking
sebserver.webservice.forceMaster=false sebserver.webservice.forceMaster=false
sebserver.webservice.distributed=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.scheme=https
sebserver.webservice.http.external.servername= sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port= sebserver.webservice.http.external.port=

View file

@ -15,6 +15,12 @@ server.servlet.context-path=/
# Tomcat # Tomcat
server.tomcat.max-threads=2000 server.tomcat.max-threads=2000
server.tomcat.accept-count=300 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 server.tomcat.uri-encoding=UTF-8
### encoding ### encoding

View file

@ -34,7 +34,7 @@
<ttl unit="hours">24</ttl> <ttl unit="hours">24</ttl>
</expiry> </expiry>
<resources> <resources>
<heap unit="entries">3000</heap> <heap unit="entries">100000</heap>
</resources> </resources>
</cache> </cache>

View file

@ -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.UserMod;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.monitoring.SimpleIndicatorValue; 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.ClientIndicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal;
@ -406,15 +405,8 @@ public class ModelObjectJSONGenerator {
} }
@Override @Override
public void notifyValueChange(final ClientEvent event) { public void notifyValueChange(final String textValue, final double numValue) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
}
@Override
public void notifyValueChange(final ClientEventRecord clientEventRecord) {
// TODO Auto-generated method stub
} }
@Override @Override

View file

@ -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.lms.LmsAPITemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; 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.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" }) @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" })
public class SebConnectionTest extends ExamAPIIntegrationTester { public class SebConnectionTest extends ExamAPIIntegrationTester {
@ -56,6 +57,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
private ExamDAO examDAO; private ExamDAO examDAO;
@Autowired @Autowired
private LmsAPIService lmsAPIService; private LmsAPIService lmsAPIService;
@Autowired
private SEBClientEventBatchService sebClientEventBatchStore;
@Before @Before
public void init() { public void init() {
@ -558,6 +561,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
// check correct response // check correct response
assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus());
this.sebClientEventBatchStore.processEvents();
// check event stored on db // check event stored on db
List<ClientEventRecord> events = this.clientEventRecordMapper List<ClientEventRecord> events = this.clientEventRecordMapper
.selectByExample() .selectByExample()
@ -582,6 +587,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
10000.0, 10000.0,
"testEvent2"); "testEvent2");
this.sebClientEventBatchStore.processEvents();
// check correct response // check correct response
assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus());
@ -616,7 +622,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
"testEvent1"); "testEvent1");
// check correct response // check correct response
assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus());
this.sebClientEventBatchStore.processEvents();
final List<ClientEventRecord> events = this.clientEventRecordMapper final List<ClientEventRecord> events = this.clientEventRecordMapper
.selectByExample() .selectByExample()
.build() .build()

View file

@ -12,15 +12,12 @@ import static org.junit.Assert.*;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Executor;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; 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 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.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; 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.ClientIndicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; 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.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.AbstractLogIndicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator;
@ -50,8 +48,12 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
@Autowired @Autowired
private SEBClientSessionService sebClientSessionService; private SEBClientSessionService sebClientSessionService;
@Autowired @Autowired
@Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) private SEBClientEventBatchService sebClientEventBatchStore;
private Executor executor; // @Autowired
// @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME)
// private Executor executor;
@Autowired
private JSONMapper jsonMapper;
@Test @Test
public void testCreateLogEvents() { public void testCreateLogEvents() {
@ -110,13 +112,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token1", "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(); waitForExecutor();
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token1", "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(); waitForExecutor();
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); 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 @Test
public void testInfoLogWithTagCountIndicator() { public void testInfoLogWithTagCountIndicator() {
@ -156,33 +169,39 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "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(); waitForExecutor();
assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<top> some error")); writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<top> some error")));
waitForExecutor(); waitForExecutor();
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "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(); waitForExecutor();
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error")); writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error")));
waitForExecutor(); waitForExecutor();
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "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(); waitForExecutor();
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token2", "token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error")); writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error")));
waitForExecutor(); waitForExecutor();
// test reset indicator value and load it from persistent storage // test reset indicator value and load it from persistent storage
((AbstractLogLevelCountIndicator) clientIndicator).reset(); ((AbstractLogLevelCountIndicator) clientIndicator).reset();
@ -191,13 +210,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
} }
private void waitForExecutor() { private void waitForExecutor() {
try { this.sebClientEventBatchStore.processEvents();
while (((ThreadPoolTaskExecutor) this.executor).getActiveCount() > 0) {
Thread.sleep(20);
}
} catch (final Exception e) {
e.printStackTrace();
}
} }
@Test @Test
@ -231,23 +244,27 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token3", "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(); waitForExecutor();
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token3", "token3",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some info other")); writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0,
"<vip> some info other")));
waitForExecutor(); waitForExecutor();
assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token3", "token3",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, "<battery> some info other")); writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0,
"<battery> some info other")));
waitForExecutor(); waitForExecutor();
assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));
this.sebClientSessionService.notifyClientEvent( this.sebClientSessionService.notifyClientEvent(
"token3", "token3",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, "<battery> some info other")); writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0,
"<battery> some info other")));
waitForExecutor(); waitForExecutor();
assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));