Files
sailing-analytics/doc/README
T
Frederik Petersen a988003d15 Moved central windstore into racingeventservice
also removed addRace and addEvent services from legacy adminapp
2013-11-20 09:02:55 +01:00

609 lines
28 KiB
Plaintext
Executable File

This is a short readme file outlining the architecture of the SAP
Sailing server. For hints for how to perform typical administrative
tasks, please see the ADMIN file which should be stored right next to
this README file.
The architecture mainly consists of
- A 64-bit Linux server running the following services.
- Apache web server handling inbound HTTP requests on port 80 (see
hostname configurations in /etc/httpd/conf.d/kiel.conf)
- dev/test/prod1/prod2 OSGi server instances running the Java
component
- UDP mirror process that duplicates UDP packets received for "the"
production instance and forwards them to the two inbound UDP ports
for prod1 and prod2, respectively.
- dev/test/prod Python servers running the web application for
browser, iPhone and iPad that connect to a Java OSGi server to
complete its requests
- a Pound load balancer that does alive checks for the prod1/prod2
Java instances to enable quick fail-over
- a number of MongoDB instances for dev/test/prod1/prod2 that store
leaderboard information for the Python application as well as wind
information for the Java component
We have a VNC server running on port 5902 with a KDE window
manager. There, we operate console windows for the various Java
instances and the UDP mirror.
Inbound Port Assignments
------------------------
- TCP
22: ssh
80: apache
5902: vnc
8026: Python "dev" instance
....: Python "test" instance
....: Python "prod" instance
8886: servlet container of "dev" Java instance
8887: servlet container of "test" Java instance
8888: servlet container of "prod1" Java instance
8889: servlet container of "prod2" Java instance
10200: MongoDB "dev" instance
10201: MongoDB "test" instance
10202: MontoDB "prod" instance
12347: SSH forward to localhost:22 required for "mvn deploy"
- UDP
2010: wind input for "dev" Java instance
2011: wind input for "test" Java instance
2012: wind input for "prod" (mirrored to 2013/2014)
2013: wind input for "prod1" Java instance
2014: wind input for "prod2" Java instance
Java
----
We use Equinox as an OSGi container into which we deploy a set of
bundles that implement TracTrac connectivity, Expedition wind
connectivity, sailing domain knowledge, race tracking capabilities as
well as servlets with RESTful APIs for use by web clients.
Most data is managed on the TracTrac servers. The only data currently
stored persistently by the Java component is the wind data received
through the supported channels. This helps in re-playing a race later
and understanding the wind conditions at the time which should support
fine-grained after-race analysis.
* Jetty
A Jetty servlet container runs embedded in Equinox. Servlets are
registered (currently in the com.sap.sailing.server bundle) using the
org.eclipse.equinox.http.registry.servlets extension point. See
com.sap.sailing.server/plugin.xml for the currently registered
servlets.
Static content to be provided by Jetty is also registered as an
"extension" for the org.eclipse.equinox.http.registry.resources
extension point. We have a few very simple forms registered that aid
in testing the web application provided by Jetty.
The port on which Jetty listens for incoming connections is configured
using the system / bundle property org.osgi.service.http.port.
* Binary Libraries
The Java component as of this writing has two bundles that wrap
binary libraries: com.tractrac.clientmodule and com.mongodb.driver.
* External Communications
The com.tractrac.clientmodule bundle is used to obtain data about
races / regattas and register for the corresponding live data
feeds. These feeds contain information about boat positions, race
course changes such as marks being moved, replaced or added, mark
positions, mark passings and race start/stop events.
To improve rankings and predictions, wind data is tracked by the Java
component. Wind can be fed through wind sources. Currently supported
are "WEB" and "EXPEDITION." The WEB wind source is a REST API by which
a single wind datum with optional position specification, time point,
wind bearing and speed can be provided. EXPEDITION uses a connector
bundle (com.sap.sailing.expeditionconnector) to receive wind data via
UDP from one or more Expedition instances.
The Expedition connector can listen on inbound UDP ports for messages
sent point-to-point or using UDP broadcast by the Expedition tool
which in turn receives its data from boat-mounted sensors and other
devices, such as the Nexus on-board system. Expedition enriches this
data and sends selected attributes over the network.
The connector understands the Expedition data feed format, supports
Expedition checksum verification and knows about the most important
attribute IDs such as true wind bearing, GPS time, latitude and
longitude.
* Storing Wind in MongoDB
The Java component piggy-backs on the MongoDB persistence solution
installed mainly for the Python server. Storing wind is helpful
particularly for post-race analysis and to support the case of a
re-start of the Java component(s) on the server, e.g., in case an
update or a quick-fix needs to be installed.
Since all other data can be reloaded from the TracTrac server, we
don't store that redundantly. But the wind information isn't retrieved
from TracTrac and hence needs to be stored on our server.
By default we use the database named "winddb". The MongoDB settings
can be configured using system / bundle properties:
mongo.port (default is 27017)
mongo.hostname (default is 127.0.0.1)
mongo.dbname (default is winddb)
* List of REST APIs and their parameters
The distribution of services across the /admin and /moderator servlet
is currently not well sorted out and requires some clean-up. The
general idea is to have administrative tasks accessible through the
/admin servlet whereas things that the moderator will see are accessed
through the /moderator servlet. We may want to consider merging both
into one in the future.
/admin
action=addevent
2014: wind input for "prod2" Java instance
Java
----
We use Equinox as an OSGi container into which we deploy a set of
bundles that implement TracTrac connectivity, Expedition wind
connectivity, sailing domain knowledge, race tracking capabilities as
well as servlets with RESTful APIs for use by web clients.
Most data is managed on the TracTrac servers. The only data currently
stored persistently by the Java component is the wind data received
through the supported channels. This helps in re-playing a race later
and understanding the wind conditions at the time which should support
fine-grained after-race analysis.
* Jetty
A Jetty servlet container runs embedded in Equinox. Servlets are
registered (currently in the com.sap.sailing.server bundle) using the
org.eclipse.equinox.http.registry.servlets extension point. See
com.sap.sailing.server/plugin.xml for the currently registered
servlets.
Static content to be provided by Jetty is also registered as an
"extension" for the org.eclipse.equinox.http.registry.resources
extension point. We have a few very simple forms registered that aid
in testing the web application provided by Jetty.
The port on which Jetty listens for incoming connections is configured
using the system / bundle property org.osgi.service.http.port.
* Binary Libraries
The Java component as of this writing has two bundles that wrap
binary libraries: com.tractrac.clientmodule and com.mongodb.driver.
* External Communications
The com.tractrac.clientmodule bundle is used to obtain data about
races / regattas and register for the corresponding live data
feeds. These feeds contain information about boat positions, race
course changes such as marks being moved, replaced or added, mark
positions, mark passings and race start/stop events.
To improve rankings and predictions, wind data is tracked by the Java
component. Wind can be fed through wind sources. Currently supported
are "WEB" and "EXPEDITION." The WEB wind source is a REST API by which
a single wind datum with optional position specification, time point,
wind bearing and speed can be provided. EXPEDITION uses a connector
bundle (com.sap.sailing.expeditionconnector) to receive wind data via
UDP from one or more Expedition instances.
The Expedition connector can listen on inbound UDP ports for messages
sent point-to-point or using UDP broadcast by the Expedition tool
which in turn receives its data from boat-mounted sensors and other
devices, such as the Nexus on-board system. Expedition enriches this
data and sends selected attributes over the network.
The connector understands the Expedition data feed format, supports
Expedition checksum verification and knows about the most important
attribute IDs such as true wind bearing, GPS time, latitude and
longitude.
For converting magnetic bearings to true bearings the
com.sap.sailing.declination bundle can talk to a web page at
http://www.ngdc.noaa.gov/geomagmodels/struts/calcDeclination. It is
possible to download and store declinations in a file shipped with the
bundle. The resources/ folder in the bundle's source holds files named
"declination-yyyy.txt" where "yyyy" represents the year of which the
file holds declination values. For 2011 there is a file that holds
declination values and annual change rates for a 1x1 degree grid. If
values of greater precision are requested, this may result in online
requests. After a timeout, interpolation/extrapolation based on the
known values and annual change rates is tried.
* Storing Wind in MongoDB
The Java component piggy-backs on the MongoDB persistence solution
installed mainly for the Python server. Storing wind is helpful
particularly for post-race analysis and to support the case of a
re-start of the Java component(s) on the server, e.g., in case an
update or a quick-fix needs to be installed.
Since all other data can be reloaded from the TracTrac server, we
don't store that redundantly. But the wind information isn't retrieved
from TracTrac and hence needs to be stored on our server.
By default we use the database named "winddb". The MongoDB settings
can be configured using system / bundle properties:
mongo.port (default is 27017)
mongo.hostname (default is 127.0.0.1)
mongo.dbname (default is winddb)
* List of REST APIs and their parameters
The distribution of services across the /admin and /moderator servlet
is currently not well sorted out and requires some clean-up. The
general idea is to have administrative tasks accessible through the
/admin servlet whereas things that the moderator will see are accessed
through the /moderator servlet. We may want to consider merging both
into one in the future.
/admin
action=listracesinregatta: outputs a JSON object describing the
races and their paramURLs available for tracking in
the following form:
[ { "name": ..., // race name
"ID": ..., // a UUID for the race
"paramURL": ..., // the paramURL to use for addrace
"replayURL": ... } ...]// URL to use for TracTrac 2D view
eventJSONURL: the URL from where metadata about the entire regatta
can be obtained; the resulting document includes the
parameter and simulation URLs for the individual
races that are part of the event/regatta.
action=stopregatta: stops tracking of all races of the regatta; this
also stops all wind tracking for the races of the regatta
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
action=stoprace: stops tracking of a single race, also stopping the
wind trackers that were started for this race; other
races of the same regatta that were being tracked will
continue being tracked.
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
action=receiveexpeditionwind: starts a wind tracker that receives
wind data through a UDP port from an Expedition
client; the wind is fed into a wind track for the
race specified by regattaname/racename.
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
correctexpeditionwindbearingbydeclination: optional; if "true",
the wind bearings received from Expedition will be
corrected by the magnetic declination for the
time and location specified by the Expedition
message.
action=stopreceivingexpeditionwind: stops an expedition wind tracker
for the race specified by regattaname/racename
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
action=listwindtrackers: lists all wind trackers currently
registered in the server instance. The output
structure is an array of {regattaname,racename,windtrackerinfo}
objects where "windtrackerinfo" is a tracker type-specific
string field which may contain, e.g., the tracker type
name or, in case of a UDP port-based tracker,
the UDP port number the tracker is receiving
wind information on.
action=setwind: feeds a single wind measurement into the WEB
wind track of the race specified. If no time is
specified, the measurement is assumed to have been
taken at the current time.
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
truebearingdegrees: as the name suggests; can have decimal places
knotspeed: optional; as the name suggests; can have decimal
places. If omitted, 1kn is used as a default
latdeg/lngdeg: optional; latitude and longitude in degrees,
optionally with decimal places
time: optional; time stamp for the wind measurement
in ISO format in arbitrary precision; takes
precedence over a "timeasmillis" parameter.
Example values:
2011-05-27T13:27:19.283
2010-06-30
2011-08-01T15:44:00
timeasmillis: optional; time stamp for the wind measurement
in milliseconds since 1970-01-01T00:00:00 UTC
action=removewind: removes a single wind measurement with the
millisecond-exact time stamp again from the
specified race's specified wind source. A time
should be provided, although the current time
will be used as the default. However, it's
unlikely that this will match the time point
of the wind to remove by the millisecond.
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
sourcename: name of the wind source, such as WEB or EXPEDITION
time: time stamp for the wind measurement
in ISO format in arbitrary precision; takes
precedence over a "timeasmillis" parameter.
Example values:
2011-05-27T13:27:19.283
2010-06-30
2011-08-01T15:44:00
timeasmillis: time stamp for the wind measurement
in milliseconds since 1970-01-01T00:00:00 UTC
action=showwind: shows the wind tracks that exist for the race
specified. The output is a JSON object with the
following structure:
{ [ "WEB": { "truebearingdeg": ...,
"knotspeed": ...,
"meterspersecondspeed": ...,
"timepoint": ... (optional, if time point is known),
"dampenedtruebearingdeg": ... (optional),
"dampenedknotspeed": ... (optional),
"dampenedmeterspersecondspeed": ... (optional)
"latdeg": ... (optional, if position known),
"lngdeg": ... (optional, if position known)
}
"EXPEDITION": { ... }
}
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
action=addwindtomarksforonehour: Can be used to translate one or
more positions in the direction and distance that
the wind blows at the position's location during
one hour. One use case is drawing the wind lines
through the mark positions. If no time point
is specified, the current time is used for the
wind lookup. The result is a JSON array with
objects of the following structure:
{ "markLatDeg": ..., // position provided
"markLngDeg": ..., // position provided
"windTrueBearingDeg": ..., // wind info
"windKnotSpeed": ..., // wind info
"toLatDeg": ..., // translated position
"toLngDeg" } // translated position
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
time: optional; time stamp for the wind measurement
in ISO format in arbitrary precision; takes
precedence over a "timeasmillis" parameter.
Example values:
2011-05-27T13:27:19.283
2010-06-30
2011-08-01T15:44:00
timeasmillis: optional; time stamp for the wind measurement
in milliseconds since 1970-01-01T00:00:00 UTC
latdeg/lngdeg: can occur multiple times with an equal number of
latdeg and lngdeg parameter occurrences. Specifies
the positions to be translated.
/moderator
action=listevents: shows the events with their races that are
currently being tracked by this server instance. The
result is a JSON array with entries describing the
events like this:
{ "name": ..., // event name
"boatclass": ..., // optional; name of boat class
"competitors": [ // the event's competitors
{ "name", ..., // competitor's name
"sailID": ... // sail ID if known
"nationality": ..., // 3-letter IOC country code
"nationalityISO2": ..., // 2-letter ISO country code (may be empty)
"nationalityISO3": ..., // 3-letter ISO country code (may be empty)
"team": [ { // the sailors forming the team
"name": ...,
"description": ... } ... ] } ... ]
"races": [ // list of races in the regatta
"name": ...,
"boatclass": ...,
"start": ..., // race start time as millisecond
// timestamp, Integer.MAX_VALUE
// if not started yet
"legs": [ { // legs in the course's order
"start": ..., // name of the leg start mark
"end": ... } // name of the leg end mark
... ]
... ]
action=showboatpositions: outputs a JSON object describing the boats
with their positions for the race specified. Output
format:
{ "name": ..., // race name
"competitors": [ {
"name": ...,
"track": [ {
"timepoint": ..., // millisecond timestamp
"latdeg": ..., // boat position
"lngdeg": ...,
"truebearingdeg": ..., // true boat bearing in degrees
"knotspeed": ..., // boat speed in knots
"tack": [STARBOARD|PORT] } ... ] // side on which sails are
} ... ] }
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
since/sinceasmillis: optional; ISO / milliseconds timestamp
describing the earliest time point for boat
positions to be returned; if not specified,
known boat positions are listed from the
"beginning of time"
to/toasmillis: optional; optional; ISO / milliseconds timestamp
describing the latest time point for boat
positions to be returned; if not specified,
known boat positions are listed until the
"end of time"
action=showwaypoints: shows the positions of a course marks
at a specified point in time; if no time/timeasmillis
is provided, the race start time is used; if that is
not available, the start of tracking is used;
if that is not available either, the time point
of the newest event is used; if that is not
available, the current time is used. The output
is an array of JSON objects with following format each:
{ "name": ..., // waypoint name
"marks": [ { // usually one mark or two marks in case of a gate
"name": ..., // mark name
"lat": ..., // optional; if mark pos known
"lng": ... } ... ] } // optionsl; if mark pos known
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
time: optional; time stamp for the wind measurement
in ISO format in arbitrary precision; takes
precedence over a "timeasmillis" parameter.
Example values:
2011-05-27T13:27:19.283
2010-06-30
2011-08-01T15:44:00
timeasmillis: optional; time stamp for the wind measurement
in milliseconds since 1970-01-01T00:00:00 UTC
action=showrace: shows lots of tracking information about a
tracked race at a given point in time. If no
time point is provided (time / timeasmillis),
the time point of the event received last is
used. If no such time point exists, the current
time is used. The output has the following
structure:
{ "name": ..., // race name
"startoftracking": ..., // millisecond timestamp
"endoftracking": ..., // millisecond timestamp
"start": ..., // millisecond timestamp
"end": ..., // millisecond timestamp
"timeofnewestevent": ...,// greatest millisecond timestamp
// of any event received so far
"timeoflastevent": ..., // millisecond timestamp
// of the event received last
"updatecount": ..., // integer number telling
// the number of events
// received so far; a bit
// like a "transaction time"
"wind": { // optional, if wind set
"truebearingdeg": ...,
"knotspeed": ...,
"meterspersecondspeed": ... },
"legs": [ { // leg tracking info per leg
"from": ..., // name of from-waypoint
"fromwaypointid": ..., // a unique integer for the
// from-waypoint
"to": ..., // name of to-waypoint
"towaypointid": ..., // a unique integer for the
// from-waypoint
"upordownwindleg": ...,// true or false
"competitors": [ { // competitor list
"name": ...,
// current SOG is optional
"currentSpeedOverGroundInKnots": ...,
"currentSpeedOverGroundInMetersPerSecond": ...,
// average SOG is optionsl
"averageSpeedOverGroundInKnots": ...,
"averageSpeedOverGroundInMetersPerSecond": ...,
// distance traveled is optional
// next comes the speed over ground if the competitor is still in the
// leg, or the average speed over ground if the competitor has already
// finished the leg, but only if the respective value is defined
"currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": ...,
"currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": ...,
"distanceTraveledOverGroundInMeters": ...,
// VMG info is optional
"velocityMadeGoodInKnots": ...,
"velocityMadeGoodInMetersPerSecond": ...,
"averageVelocityMadeGoodInKnots": ...,
"averageVelocityMadeGoodInMetersPerSecond": ...,
"rank": ..., // competitor's rank in this leg
"gapToLeaderInSeconds": ..., // optional, only if wind is known
"estimatedTimeToNextMarkInSeconds": ..., // optional, only if wind is known
"windwardDistanceToGoInMeters": ..., // optional, only if wind is known
"started": ..., // true or false, tells if competitor has started leg
"finished": ... // true of false, tells if competitor has finished leg
} ... ] // competitor's rank in leg
} ... ],
"ranks": [ { // the overall total ranks, across legs
"competitor": ..., // name of competitor
"rank": ... } ... ] }
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
racename: name of the race, as returned by the "listevents"
action on the /moderator servlet
time: optional; time stamp for the wind measurement
in ISO format in arbitrary precision; takes
precedence over a "timeasmillis" parameter.
Example values:
2011-05-27T13:27:19.283
2010-06-30
2011-08-01T15:44:00
timeasmillis: optional; time stamp for the wind measurement
in milliseconds since 1970-01-01T00:00:00 UTC
sinceupdate: optional; an integer number that if provided,
causes the call to block on the server
until the update count returned will be greater
than the value provided for sinceupdate.
* Server Directory Structures
git/:
This is our git "hub" directory with checked-out workspace.
We use it also as the directory to check out a specific branch
that we want to build using Maven.
servers/:
The four Java server instances dev, test, prod1 and prod2 have their
directories here.
[dev|test|prod1|prod2]/
configuration/
config.ini: contains the set of initial bundles to launch upon
server start as well as bundle properties such as the
HTTP port to listen to and the mongo.port property
start: the start script used to launch the respective server instance
update: used to copy the built artifacts from the corresponding
maven-repositories/ subdirectory to the plugins/ subdirectory
plugins/: contains the JAR files for the server instance
maven-repositories/
[dev|test|prod]/: the target repositories for the "mvn deploy" tasks
for the respective git branch (dev|test|prod)
* Maven Environment
Our Java OSGi bundles are built and tested using the Apache Maven build system together
with the Tycho plugin for Maven. The bundles carrying the JUnit tests (typically ending
with *.test in their bundle name) are built using eclipse-test-plugin as packaging
type. The regular OSGi bundles are built using eclipse-plugin as packaging type.
When invoking "mvn install" in the ~trac/git/java directory, all bundles for the Java
server will be built and the tests will start to run. A report is printed at the end. If
it decides for "SUCCESS" then all builds and tests finished successfully. However, this
hasn't had any effect on any of the running servers, not even the maven repository.
For that, see the respective instructions in the ADMIN file located next to this README
file (search for "mvn deploy").
Python
------
TODO Simon to write a few warm words here...