ping indicator performance

This commit is contained in:
anhefti 2020-02-26 11:34:31 +01:00
parent 43578d3e1c
commit 36cd75218e
7 changed files with 97 additions and 45 deletions

View file

@ -503,6 +503,9 @@ public final class ClientConnectionTable {
final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i); final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i);
final IndicatorData indicatorData = final IndicatorData indicatorData =
ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType()); ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
if (indicatorData == null) {
continue;
}
if (!this.connectionData.clientConnection.status.establishedStatus) { if (!this.connectionData.clientConnection.status.establishedStatus) {
final String value = (indicatorData.indicator.type.showOnlyInActiveState) final String value = (indicatorData.indicator.type.showOnlyInActiveState)

View file

@ -14,6 +14,9 @@ import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
@ -23,7 +26,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
public class ClientConnectionDataInternal extends ClientConnectionData { public class ClientConnectionDataInternal extends ClientConnectionData {
final Collection<AbstractPingIndicator> pingMappings; private static final Logger log = LoggerFactory.getLogger(ClientConnectionDataInternal.class);
final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping; final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping;
PingIntervalClientIndicator pingIndicator = null; PingIntervalClientIndicator pingIndicator = null;
@ -35,17 +39,13 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
super(clientConnection, clientIndicators); super(clientConnection, clientIndicators);
this.indicatorMapping = new EnumMap<>(EventType.class); this.indicatorMapping = new EnumMap<>(EventType.class);
this.pingMappings = new ArrayList<>();
for (final ClientIndicator clientIndicator : clientIndicators) { for (final ClientIndicator clientIndicator : clientIndicators) {
if (clientIndicator instanceof AbstractPingIndicator) { if (clientIndicator instanceof PingIntervalClientIndicator) {
if (clientIndicator instanceof PingIntervalClientIndicator) { if (this.pingIndicator != null) {
this.pingIndicator = (PingIntervalClientIndicator) clientIndicator; log.error("Currently only one ping indicator is allowed: {}", clientIndicator);
if (!this.pingIndicator.hidden) { continue;
this.pingMappings.add((AbstractPingIndicator) clientIndicator);
}
} else {
this.pingMappings.add((AbstractPingIndicator) clientIndicator);
} }
this.pingIndicator = (PingIntervalClientIndicator) clientIndicator;
} }
for (final EventType eventType : clientIndicator.observedEvents()) { for (final EventType eventType : clientIndicator.observedEvents()) {
this.indicatorMapping this.indicatorMapping
@ -55,6 +55,12 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
} }
} }
public final void notifyPing(final long timestamp, final int pingNumber) {
if (this.pingIndicator != null) {
this.pingIndicator.notifyPing(timestamp, pingNumber);
}
}
Collection<ClientIndicator> getIndicatorMapping(final EventType eventType) { Collection<ClientIndicator> getIndicatorMapping(final EventType eventType) {
if (!this.indicatorMapping.containsKey(eventType)) { if (!this.indicatorMapping.containsKey(eventType)) {
return Collections.emptyList(); return Collections.emptyList();

View file

@ -59,8 +59,7 @@ public class DistributedServerPingHandler implements PingHandlingStrategy {
this.examSessionCacheService.getActiveClientConnection(connectionToken); this.examSessionCacheService.getActiveClientConnection(connectionToken);
if (activeClientConnection != null) { if (activeClientConnection != null) {
activeClientConnection.pingMappings activeClientConnection.notifyPing(timestamp, pingNumber);
.forEach(pingIndicator -> pingIndicator.notifyPing(timestamp, pingNumber));
} }
} }

View file

@ -32,8 +32,7 @@ public class SingleServerPingHandler implements PingHandlingStrategy {
this.examSessionCacheService.getActiveClientConnection(connectionToken); this.examSessionCacheService.getActiveClientConnection(connectionToken);
if (activeClientConnection != null) { if (activeClientConnection != null) {
activeClientConnection.pingMappings activeClientConnection.notifyPing(timestamp, pingNumber);
.forEach(pingIndicator -> pingIndicator.notifyPing(timestamp, pingNumber));
} }
} }

View file

@ -274,43 +274,63 @@ public class ExamAPI_V1_Controller {
.ok() .ok()
.build(); .build();
// @RequestMapping(
// path = API.EXAM_API_PING_ENDPOINT,
// method = RequestMethod.POST,
// consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
// produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// public CompletableFuture<ResponseEntity<String>> ping(
// @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
// @RequestParam(name = API.EXAM_API_PING_TIMESTAMP, required = true) final long timestamp,
// @RequestParam(name = API.EXAM_API_PING_NUMBER, required = false) final int pingNumber) {
//
// return CompletableFuture.supplyAsync(
// () -> {
// final String notifyPing = this.sebClientConnectionService
// .notifyPing(connectionToken, timestamp, pingNumber);
// if (notifyPing == null) {
// return EMPTY_PING_RESPONSE;
// }
//
// return ResponseEntity
// .ok()
// .body(notifyPing);
// },
// this.executor);
// }
@RequestMapping( @RequestMapping(
path = API.EXAM_API_PING_ENDPOINT, path = API.EXAM_API_PING_ENDPOINT,
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public CompletableFuture<ResponseEntity<String>> ping( public ResponseEntity<String> ping(
@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,
@RequestParam(name = API.EXAM_API_PING_TIMESTAMP, required = true) final long timestamp, @RequestParam(name = API.EXAM_API_PING_TIMESTAMP, required = true) final long timestamp,
@RequestParam(name = API.EXAM_API_PING_NUMBER, required = false) final int pingNumber) { @RequestParam(name = API.EXAM_API_PING_NUMBER, required = false) final int pingNumber) {
return CompletableFuture.supplyAsync( final String instruction = this.sebClientConnectionService
() -> { .notifyPing(connectionToken, timestamp, pingNumber);
final String notifyPing = this.sebClientConnectionService
.notifyPing(connectionToken, timestamp, pingNumber);
if (notifyPing == null) {
return EMPTY_PING_RESPONSE;
}
return ResponseEntity if (instruction == null) {
.ok() return EMPTY_PING_RESPONSE;
.body(notifyPing); }
},
this.executor); return ResponseEntity
.ok()
.body(instruction);
} }
@RequestMapping( @RequestMapping(
path = API.EXAM_API_EVENT_ENDPOINT, path = API.EXAM_API_EVENT_ENDPOINT,
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public CompletableFuture<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 ClientEvent event) {
return CompletableFuture.runAsync( this.sebClientConnectionService
() -> this.sebClientConnectionService .notifyClientEvent(connectionToken, event);
.notifyClientEvent(connectionToken, event),
this.executor);
} }
private Long getInstitutionId(final Principal principal) { private Long getInstitutionId(final Principal principal) {

View file

@ -16,9 +16,11 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Pair;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
@ -27,6 +29,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile @WebServiceProfile
@ -35,6 +38,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
public class IndicatorController extends EntityController<Indicator, Indicator> { public class IndicatorController extends EntityController<Indicator, Indicator> {
private final ExamDAO examDao; private final ExamDAO examDao;
private final ExamSessionService examSessionService;
protected IndicatorController( protected IndicatorController(
final AuthorizationService authorization, final AuthorizationService authorization,
@ -43,7 +47,8 @@ public class IndicatorController extends EntityController<Indicator, Indicator>
final ExamDAO examDao, final ExamDAO examDao,
final UserActivityLogDAO userActivityLogDAO, final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService, final PaginationService paginationService,
final BeanValidationService beanValidationService) { final BeanValidationService beanValidationService,
final ExamSessionService examSessionService) {
super(authorization, super(authorization,
bulkActionService, bulkActionService,
@ -53,6 +58,7 @@ public class IndicatorController extends EntityController<Indicator, Indicator>
beanValidationService); beanValidationService);
this.examDao = examDao; this.examDao = examDao;
this.examSessionService = examSessionService;
} }
@Override @Override
@ -95,4 +101,31 @@ public class IndicatorController extends EntityController<Indicator, Indicator>
return EntityType.EXAM; return EntityType.EXAM;
} }
@Override
protected Result<Indicator> notifyCreated(final Indicator entity) {
flushExamSessionCaches(entity);
return super.notifyCreated(entity);
}
@Override
protected Result<Indicator> notifySaved(final Indicator entity) {
flushExamSessionCaches(entity);
return super.notifySaved(entity);
}
@Override
protected Result<Pair<Indicator, EntityProcessingReport>> notifyDeleted(
final Pair<Indicator, EntityProcessingReport> pair) {
flushExamSessionCaches(pair.a);
return super.notifyDeleted(pair);
}
private void flushExamSessionCaches(final Indicator entity) {
if (this.examSessionService.isExamRunning(entity.examId)) {
this.examSessionService.flushCache(this.examSessionService.getRunningExam(entity.examId).getOrThrow());
}
}
} }

View file

@ -259,12 +259,8 @@ public abstract class ExamAPIIntegrationTester {
+ "&" + API.EXAM_API_PING_NUMBER + "=" + num; + "&" + API.EXAM_API_PING_NUMBER + "=" + num;
builder.content(body); builder.content(body);
final MvcResult mvcResult = this.mockMvc final ResultActions result = this.mockMvc
.perform(builder) .perform(builder);
.andExpect(request().asyncStarted())
.andDo(MockMvcResultHandlers.log())
.andReturn();
final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult));
return result.andReturn().getResponse(); return result.andReturn().getResponse();
} }
@ -284,12 +280,8 @@ public abstract class ExamAPIIntegrationTester {
final String body = "{ \"type\": \"%s\", \"timestamp\": %s, \"numericValue\": %s, \"text\": \"%s\" }"; final String body = "{ \"type\": \"%s\", \"timestamp\": %s, \"numericValue\": %s, \"text\": \"%s\" }";
builder.content(String.format(body, type, timestamp, value, text)); builder.content(String.format(body, type, timestamp, value, text));
final MvcResult mvcResult = this.mockMvc final ResultActions result = this.mockMvc
.perform(builder) .perform(builder);
.andExpect(request().asyncStarted())
.andDo(MockMvcResultHandlers.log())
.andReturn();
final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult));
return result.andReturn().getResponse(); return result.andReturn().getResponse();
} }