Bug 2059: Moved parts of statistics calculation to sailing server for

reusability in RacingEventService
This commit is contained in:
Steffen Schaefer
2017-06-27 10:46:39 +02:00
parent 3294cafb07
commit 1e41b026b3
6 changed files with 245 additions and 175 deletions
@@ -35,6 +35,7 @@ import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO;
import com.sap.sailing.gwt.home.server.EventActionUtil.RaceCallback;
import com.sap.sailing.gwt.server.HomeServiceUtil;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.util.LeaderboardUtil;
import com.sap.sailing.util.RegattaUtil;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -237,7 +238,7 @@ public class LeaderboardContext {
}
}
regattaDTO.setCompetitorsCount(HomeServiceUtil.calculateCompetitorsCount(leaderboard));
regattaDTO.setRaceCount(HomeServiceUtil.calculateRaceCount(leaderboard));
regattaDTO.setRaceCount(LeaderboardUtil.calculateRaceCount(leaderboard));
regattaDTO.setBoatClass(HomeServiceUtil.getBoatClassName(leaderboard));
if (leaderboard instanceof RegattaLeaderboard) {
regattaDTO.setStartDate(getStartDateWithEventFallback());
@@ -1,33 +1,9 @@
package com.sap.sailing.gwt.home.server;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.gwt.home.communication.event.statistics.EventStatisticsDTO;
import com.sap.sailing.gwt.home.server.EventActionUtil.LeaderboardCallback;
import com.sap.sailing.gwt.server.HomeServiceUtil;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL;
/**
@@ -40,120 +16,31 @@ import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL;
* <li>Information from tracked races - by environment variable <code>DISABLE_STATS</code></li>
* </ul>
*/
public class StatisticsCalculator implements LeaderboardCallback {
private static final Logger logger = Logger.getLogger(StatisticsCalculator.class.getName());
public class StatisticsCalculator extends com.sap.sailing.server.statistics.StatisticsCalculator
implements LeaderboardCallback {
private static final boolean CALCULATE_MAX_SPEED = false;
private static final boolean CALCULATE_SAILED_MILES = true;
private Set<Competitor> competitors = new HashSet<>();
private int races, trackedRaces, regattas;
private long numberOfGPSFixes, numberOfWindFixes;
private Distance totalDistanceTraveled = Distance.NULL;
private Triple<Competitor, Speed, TimePoint> maxSpeed = null;
private final TimePoint now = MillisecondsTimePoint.now();
public StatisticsCalculator() {
super(CALCULATE_MAX_SPEED, CALCULATE_SAILED_MILES);
}
@Override
public void doForLeaderboard(LeaderboardContext context) {
races += HomeServiceUtil.calculateRaceCount(context.getLeaderboard());
trackedRaces += HomeServiceUtil.calculateTrackedRaceCount(context.getLeaderboard());
regattas++;
String disableStats = System.getenv("DISABLE_STATS");
if (disableStats == null || "false".equals(disableStats)) {
try {
for (TrackedRace trackedRace : context.getLeaderboard().getTrackedRaces()) {
doForTrackedRace(trackedRace);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Exception during calculation of event statistics", e);
}
}
addLeaderboard(context.getLeaderboard());
}
private void doForTrackedRace(TrackedRace trackedRace) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
competitors.add(competitor);
doForCompetitor(trackedRace, competitor);
}
for (Mark mark : trackedRace.getMarks()) {
doForMark(trackedRace, mark);
}
for (WindSource windSource : trackedRace.getWindSources()) {
doForWindSource(trackedRace, windSource);
}
}
private void doForCompetitor(TrackedRace trackedRace, Competitor competitor) {
GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = trackedRace.getTrack(competitor);
competitorTrack.lockForRead();
try {
numberOfGPSFixes += Util.size(competitorTrack.getRawFixes());
} finally {
competitorTrack.unlockAfterRead();
}
if (trackedRace.hasStarted(now)) {
final NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
MarkPassing lastMarkPassingBeforeNow = null;
trackedRace.lockForRead(competitorMarkPassings);
try {
MarkPassing next = null;
Iterator<MarkPassing> i = competitorMarkPassings.descendingIterator();
while (i.hasNext() && (next = i.next()).getTimePoint().after(now));
if (next != null) {
lastMarkPassingBeforeNow = next;
}
} finally {
trackedRace.unlockAfterRead(competitorMarkPassings);
}
if (lastMarkPassingBeforeNow != null) {
// competitor has started and has at least one mark passing before now; compute distance traveled up to that
// mark passing, increasing likelihood of cache hits as compared to using "now" as the query time point
TimePoint from = trackedRace.getStartOfRace(), to = lastMarkPassingBeforeNow.getTimePoint();
if (CALCULATE_SAILED_MILES) {
totalDistanceTraveled = totalDistanceTraveled.add(competitorTrack.getDistanceTraveled(from, to));
}
if (CALCULATE_MAX_SPEED) {
Pair<GPSFixMoving, Speed> competitorMaxSpeed = competitorTrack.getMaximumSpeedOverGround(from, to);
if (competitorMaxSpeed != null && (maxSpeed == null || competitorMaxSpeed.getB().compareTo( maxSpeed.getB()) > 0)
// only accept "reasonable" max speeds
&& competitorMaxSpeed.getB().compareTo( GPSFixTrack.DEFAULT_MAX_SPEED_FOR_SMOOTHING) <= 0) {
maxSpeed = new Triple<>(competitor, competitorMaxSpeed.getB(), competitorMaxSpeed.getA().getTimePoint());
}
}
}
}
}
private void doForMark(TrackedRace trackedRace, Mark mark) {
GPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
markTrack.lockForRead();
try {
numberOfGPSFixes += Util.size(markTrack.getRawFixes());
} finally {
markTrack.unlockAfterRead();
}
}
private void doForWindSource(TrackedRace trackedRace, WindSource windSource) {
// don't count the "virtual" wind sources
if (windSource.canBeStored() || windSource.getType() == WindSourceType.RACECOMMITTEE) {
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrack.lockForRead();
try {
numberOfWindFixes += Util.size(windTrack.getRawFixes());
} finally {
windTrack.unlockAfterRead();
}
}
}
/**
* @return the {@link ResultWithTTL} containing the calculated statistics.
*/
public ResultWithTTL<EventStatisticsDTO> getResult() {
Distance totalDistanceTraveled = getTotalDistanceTraveled();
totalDistanceTraveled = totalDistanceTraveled == Distance.NULL ? null : totalDistanceTraveled;
return new ResultWithTTL<EventStatisticsDTO>(Duration.ONE_MINUTE.times(5), new EventStatisticsDTO(regattas,
competitors.size(), races, trackedRaces, numberOfGPSFixes, numberOfWindFixes, maxSpeed, totalDistanceTraveled));
return new ResultWithTTL<EventStatisticsDTO>(Duration.ONE_MINUTE.times(5),
new EventStatisticsDTO(getNumberOfRegattas(), getNumberOfCompetitors(), getNumberOfRaces(),
getNumberOfTrackedRaces(), getNumberOfGPSFixes(), getNumberOfWindFixes(), getMaxSpeed(),
totalDistanceTraveled));
}
}
@@ -20,9 +20,7 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardGroupBase;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
@@ -179,51 +177,6 @@ public final class HomeServiceUtil {
return Util.size(sl.getCompetitors());
}
public static int calculateRaceCount(Leaderboard sl) {
int nonCarryForwardRacesCount = 0;
for (RaceColumn column : sl.getRaceColumns()) {
if (!column.isCarryForward()) {
nonCarryForwardRacesCount += Util.size(column.getFleets());
}
}
return nonCarryForwardRacesCount;
}
public static int calculateRaceColumnCount(Leaderboard sl) {
int nonCarryForwardRacesCount = 0;
for (RaceColumn rc : sl.getRaceColumns()) {
nonCarryForwardRacesCount += rc.isCarryForward() ? 0 : 1;
}
return nonCarryForwardRacesCount;
}
public static int calculateTrackedRaceCount(Leaderboard sl) {
int count=0;
for (RaceColumn column : sl.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
TrackedRace trackedRace = column.getTrackedRace(fleet);
if(trackedRace != null && trackedRace.hasGPSData() && trackedRace.hasWindData()) {
count++;
}
}
}
return count;
}
public static int calculateTrackedRaceColumnCount(Leaderboard sl) {
int count=0;
for (RaceColumn column : sl.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
TrackedRace trackedRace = column.getTrackedRace(fleet);
if(trackedRace != null && trackedRace.hasGPSData() && trackedRace.hasWindData()) {
count++;
break;
}
}
}
return count;
}
public static String getBoatClassName(Leaderboard leaderboard) {
BoatClass boatClass = getBoatClass(leaderboard);
return boatClass == null ? null : boatClass.getName();
@@ -54,4 +54,6 @@ Export-Package: com.sap.sailing.server,
com.sap.sailing.server.operationaltransformation,
com.sap.sailing.server.operationaltransformation.racelog,
com.sap.sailing.server.security,
com.sap.sailing.server.simulation
com.sap.sailing.server.simulation,
com.sap.sailing.server.statistics,
com.sap.sailing.server.util
@@ -0,0 +1,194 @@
package com.sap.sailing.server.statistics;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.server.util.LeaderboardUtil;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* Calculates the statistics for the regatta(s) passed to the {@link #addLeaderboard(Leaderboard)} method. Because this
* calculations can be costly and time consuming, several information can be disabled/enabled:
* <ul>
* <li>Maximum speed (of a competitor) - by constructor parameter {@link #calculateMaxSpeed} within this class</li>
* <li>Total of sailed miles - by constructor parameter {@link #calculateDistanceTravelled} within this class</li>
* </ul>
*/
public class StatisticsCalculator {
private static final Logger logger = Logger.getLogger(StatisticsCalculator.class.getName());
private final boolean calculateMaxSpeed;
private final boolean calculateDistanceTravelled;
private Set<Competitor> competitors = new HashSet<>();
private int races, trackedRaces, regattas;
private long numberOfGPSFixes, numberOfWindFixes;
private Distance totalDistanceTraveled = Distance.NULL;
private Triple<Competitor, Speed, TimePoint> maxSpeed = null;
private final TimePoint now = MillisecondsTimePoint.now();
public StatisticsCalculator() {
this(false, true);
}
public StatisticsCalculator(boolean calculateMaxSpeed, boolean calculateDistanceTravelled) {
this.calculateMaxSpeed = calculateMaxSpeed;
this.calculateDistanceTravelled = calculateDistanceTravelled;
}
public void addLeaderboard(Leaderboard leaderboard) {
races += LeaderboardUtil.calculateRaceCount(leaderboard);
trackedRaces += LeaderboardUtil.calculateTrackedRaceCount(leaderboard);
regattas++;
String disableStats = System.getenv("DISABLE_STATS");
if (disableStats == null || "false".equals(disableStats)) {
try {
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
doForTrackedRace(trackedRace);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Exception during calculation of event statistics", e);
}
}
}
private void doForTrackedRace(TrackedRace trackedRace) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
competitors.add(competitor);
doForCompetitor(trackedRace, competitor);
}
for (Mark mark : trackedRace.getMarks()) {
doForMark(trackedRace, mark);
}
for (WindSource windSource : trackedRace.getWindSources()) {
doForWindSource(trackedRace, windSource);
}
}
private void doForCompetitor(TrackedRace trackedRace, Competitor competitor) {
GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = trackedRace.getTrack(competitor);
competitorTrack.lockForRead();
try {
numberOfGPSFixes += Util.size(competitorTrack.getRawFixes());
} finally {
competitorTrack.unlockAfterRead();
}
if (trackedRace.hasStarted(now)) {
final NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
MarkPassing lastMarkPassingBeforeNow = null;
trackedRace.lockForRead(competitorMarkPassings);
try {
MarkPassing next = null;
Iterator<MarkPassing> i = competitorMarkPassings.descendingIterator();
while (i.hasNext() && (next = i.next()).getTimePoint().after(now))
;
if (next != null) {
lastMarkPassingBeforeNow = next;
}
} finally {
trackedRace.unlockAfterRead(competitorMarkPassings);
}
if (lastMarkPassingBeforeNow != null) {
// competitor has started and has at least one mark passing before now; compute distance traveled up to
// that
// mark passing, increasing likelihood of cache hits as compared to using "now" as the query time point
TimePoint from = trackedRace.getStartOfRace(), to = lastMarkPassingBeforeNow.getTimePoint();
doForCompetitorTrackAndTimeRange(competitor, competitorTrack, from, to);
}
}
}
protected void doForCompetitorTrackAndTimeRange(Competitor competitor,
GPSFixTrack<Competitor, GPSFixMoving> competitorTrack, TimePoint from, TimePoint to) {
if (calculateDistanceTravelled) {
totalDistanceTraveled = totalDistanceTraveled.add(competitorTrack.getDistanceTraveled(from, to));
}
if (calculateMaxSpeed) {
Pair<GPSFixMoving, Speed> competitorMaxSpeed = competitorTrack.getMaximumSpeedOverGround(from, to);
if (competitorMaxSpeed != null
&& (maxSpeed == null || competitorMaxSpeed.getB().compareTo(maxSpeed.getB()) > 0)
// only accept "reasonable" max speeds
&& competitorMaxSpeed.getB().compareTo(GPSFixTrack.DEFAULT_MAX_SPEED_FOR_SMOOTHING) <= 0) {
maxSpeed = new Triple<>(competitor, competitorMaxSpeed.getB(),
competitorMaxSpeed.getA().getTimePoint());
}
}
}
private void doForMark(TrackedRace trackedRace, Mark mark) {
GPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
markTrack.lockForRead();
try {
numberOfGPSFixes += Util.size(markTrack.getRawFixes());
} finally {
markTrack.unlockAfterRead();
}
}
private void doForWindSource(TrackedRace trackedRace, WindSource windSource) {
// don't count the "virtual" wind sources
if (windSource.canBeStored() || windSource.getType() == WindSourceType.RACECOMMITTEE) {
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrack.lockForRead();
try {
numberOfWindFixes += Util.size(windTrack.getRawFixes());
} finally {
windTrack.unlockAfterRead();
}
}
}
public int getNumberOfRaces() {
return races;
}
public int getNumberOfTrackedRaces() {
return trackedRaces;
}
public int getNumberOfRegattas() {
return regattas;
}
public long getNumberOfGPSFixes() {
return numberOfGPSFixes;
}
public long getNumberOfWindFixes() {
return numberOfWindFixes;
}
public Distance getTotalDistanceTraveled() {
return totalDistanceTraveled;
}
public Triple<Competitor, Speed, TimePoint> getMaxSpeed() {
return maxSpeed;
}
public int getNumberOfCompetitors() {
return competitors.size();
}
}
@@ -0,0 +1,33 @@
package com.sap.sailing.server.util;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Util;
public interface LeaderboardUtil {
public static int calculateRaceCount(Leaderboard sl) {
int nonCarryForwardRacesCount = 0;
for (RaceColumn column : sl.getRaceColumns()) {
if (!column.isCarryForward()) {
nonCarryForwardRacesCount += Util.size(column.getFleets());
}
}
return nonCarryForwardRacesCount;
}
public static int calculateTrackedRaceCount(Leaderboard sl) {
int count=0;
for (RaceColumn column : sl.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
TrackedRace trackedRace = column.getTrackedRace(fleet);
if(trackedRace != null && trackedRace.hasGPSData() && trackedRace.hasWindData()) {
count++;
}
}
}
return count;
}
}