Ejemplo n.º 1
0
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// Reads the GeoTracker server settings from the application's configuration
        /// using the specified key prefix.
        /// </summary>
        /// <param name="keyPrefix">The application configuration key prefix.</param>
        /// <returns>The server settings.</returns>
        /// <remarks>
        /// <para>
        /// The GeoTracker server settings are loaded from the application
        /// configuration, using the specified key prefix.  The following
        /// settings are recognized by the class:
        /// </para>
        /// <div class="tablediv">
        /// <table class="dtTABLE" cellspacing="0" ID="Table1">
        /// <tr valign="top">
        /// <th width="1">Setting</th>
        /// <th width="1">Default</th>
        /// <th width="90%">Description</th>
        /// </tr>
        /// <tr valign="top">
        ///     <td>ServerEP</td>
        ///     <td><b>logical://LillTek/GeoTracker/Server</b></td>
        ///     <td>
        ///     The external LillTek Messaging endpoint for the GeoTracker server cluster.
        ///     This is the endpoint that <see cref="GeoTrackerClient" /> instances will
        ///     use to communicate with the cluster.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>ClusterEP</td>
        ///     <td><b>logical://LillTek/GeoTracker/Cluster</b></td>
        ///     <td>
        ///     The internal LillTek Messaging endpoint for the root of the GeoTracker server cluster.
        ///     This is the endpoint that cluster servers will use to communicate with each other.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>ClusterTopology</td>
        ///     <td><see cref="DynamicHashedTopology"/></td>
        ///     <td>
        ///     Describes the topology provider to be used to distribute traffic to
        ///     GeoTracker server instances within the cluster.  This provide must
        ///     implement <see cref="TopologyCapability" />.<see cref="TopologyCapability.Locality" />.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>ClusterArgs</td>
        ///     <td>None</td>
        ///     <td>
        ///     Cluster topology specific parameters formatted as <b>name=value</b> pairs separated
        ///     by LF ('\n') characters.  Use the <see cref="Config" /> "{{" ... ""}}" setting syntax
        ///     and place each argument on a separate line in the config file.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>GeoFixArchiver</td>
        ///     <td><see cref="NullGeoFixArchiver"/></td>
        ///     <td>
        ///     The pluggable <see cref="IGeoFixArchiver" /> type to be used to archive
        ///     <see cref="GeoFix" />es received by the server.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>GeoFixArchiverArgs</td>
        ///     <td>Empty</td>
        ///     <td>
        ///     Archiver specific parameters formatted as <b>name=value</b> pairs separated
        ///     by LF ('\n') characters.  Use the <see cref="Config" /> "{{" ... ""}}" setting syntax
        ///     and place each argument on a separate line in the config file.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>GeoFixRetentionInterval</td>
        ///     <td>1h</td>
        ///     <td>
        ///     The length of time entity <see cref="GeoFix" />es will be retained by GeoTracker.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>GeoFixPurgeInterval</td>
        ///     <td>1m</td>
        ///     <td>
        ///     The interval at which old cached entity <see cref="GeoFix" />es will be purged.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>MaxEntityGeoFixes</td>
        ///     <td>50</td>
        ///     <td>
        ///     The maximum number of <see cref="GeoFix" />es to be cached in memory for an entity.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IndexHighWatermarkLimit</td>
        ///     <td>1000</td>
        ///     <td>
        ///     <para>
        ///     Used to decide when to attempt split an index block into sub-blocks when the number
        ///     of entities in a block is greater than or equal to this value.
        ///     </para>
        ///     <note>
        ///     <b>IndexHighWatermarkLimit</b> must be greater than or equal to <b>IndexLowWatermarkLimit</b>.
        ///     </note>
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IndexLowWatermarkLimit</td>
        ///     <td>750</td>
        ///     <td>
        ///     <para>
        ///     Used to decide when to attempt coalesce sub-blocks when the number
        ///     of entities in a block is greater than or equal to this value.
        ///     </para>
        ///     <note>
        ///     <b>IndexHighWatermarkLimit</b> must be greater than or equal to <b>IndexLowWatermarkLimit</b>.
        ///     </note>
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IndexMaxGroupTableLevel</td>
        ///     <td>2</td>
        ///     <td>
        ///     Controls how deep into the index heirarchy group hash tables will be maintained by
        ///     the GeoTracker server.  Enabling these tables deeper in the heirarchy may result in better
        ///     performance for queries with group constraints but potentially at the cost of
        ///     severe memory utilizaton.
        ///     </td>
        /// </tr>
        /// <tr>
        ///     <td>IndexBalancingInterval</td>
        ///     <td>5m</td>
        ///     <td>
        ///     The interval at which the server will attempt to rebalance the location index.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IPGeocodeEnabled</td>
        ///     <td><c>true</c></td>
        ///     <td>
        ///     Controls whether IP geocoding services are to be made available by the GeoTracker server.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IPGeocodeSourceUri</td>
        ///     <td><b>see note</b></td>
        ///     <td>
        ///     The URL where the current IP to location Geocoding database from <a href="http://maxmind.com">maxmind.com</a>
        ///     can be downloaded.  This must be a decompressed GeoIP City or GeoLite City database file encrypted
        ///     into a <see cref="SecureFile" />.  This setting defaults to: http://www.lilltek.com/Config/GeoTracker/IP2City.encrypted.dat
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IPGeocodeSourceRsaKey</td>
        ///     <td><b>See note</b></td>
        ///     <td>
        ///     The private RSA key used to decrypt the downloaded Geocoding database.  (defaults to
        ///     the value used to manually encrypt the file hosted on http://www.lilltek.com).
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IPGeocodeSourcePollInterval</td>
        ///     <td><b>1d</b></td>
        ///     <td>
        ///     The interval at which server instances will poll for updates to the IP Geocode database,
        ///     Note that server will use the HTTP <b>If-Modified-Since</b> header for efficency since this
        ///     database is updated only once a month.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>IPGeocodeSourceTimeout</td>
        ///     <td><b>5m</b></td>
        ///     <td>
        ///     The maximum time the server will wait for the download of the IP Geocode database file.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>SweepInterval</td>
        ///     <td><b>2.5m</b></td>
        ///     <td>
        ///     The interval at which old <see cref="GeoFix" />es, entities, and groups will be
        ///     swept up and discarded by the server.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>BkInterval</td>
        ///     <td><b>1s</b></td>
        ///     <td>
        ///     The minimum interval at which background activities will be scheduled.
        ///     </td>
        /// </tr>
        /// </table>
        /// </div>
        /// </remarks>
        public static GeoTrackerServerSettings LoadConfig(string keyPrefix)
        {
            var config   = new Config(keyPrefix);
            var settings = new GeoTrackerServerSettings();

            settings.ServerEP                    = config.Get("ServerEP", settings.ServerEP);
            settings.ClusterEP                   = config.Get("ClusterEP", settings.ClusterEP);
            settings.ClusterTopology             = config.Get("ClusterTopology", settings.ClusterTopology);
            settings.ClusterArgs                 = new ArgCollection(config.Get("ClusterArgs", settings.ClusterArgs.ToString()), '=', '\n');
            settings.GeoFixArchiver              = config.Get("GeoFixArchiver", settings.GeoFixArchiver);
            settings.GeoFixArchiverArgs          = ArgCollection.Parse(config.Get("GeoFixArchiverArgs", settings.GeoFixArchiverArgs.ToString()), '=', '\n');
            settings.GeoFixRetentionInterval     = config.Get("GeoFixRetentionInterval", settings.GeoFixRetentionInterval);
            settings.GeoFixPurgeInterval         = config.Get("GeoFixPurgeInterval", settings.GeoFixPurgeInterval);
            settings.MaxEntityGeoFixes           = config.Get("MaxEntityGeoFixes", settings.MaxEntityGeoFixes);
            settings.IndexHighWatermarkLimit     = config.Get("IndexHighWatermarkLimit", settings.IndexHighWatermarkLimit);
            settings.IndexLowWatermarkLimit      = config.Get("IndexLowWatermarkLimit", settings.IndexLowWatermarkLimit);
            settings.IndexMaxGroupTableLevel     = config.Get("IndexMaxGroupTableLevel", settings.IndexMaxGroupTableLevel);
            settings.IndexBalancingInterval      = config.Get("IndexBalancingInterval", settings.IndexBalancingInterval);
            settings.IPGeocodeEnabled            = config.Get("IPGeocodeEnabled", settings.IPGeocodeEnabled);
            settings.IPGeocodeSourceUri          = config.Get("IPGeocodeSourceUri", settings.IPGeocodeSourceUri);
            settings.IPGeocodeSourceRsaKey       = config.Get("IPGeocodeSourceRsaKey", settings.IPGeocodeSourceRsaKey);
            settings.IPGeocodeSourcePollInterval = config.Get("IPGeocodeSourcePollInterval", settings.IPGeocodeSourcePollInterval);
            settings.IPGeocodeSourceTimeout      = config.Get("IPGeocodeSourceTimeout", settings.IPGeocodeSourceTimeout);
            settings.SweepInterval               = config.Get("SweepInterval", settings.SweepInterval);
            settings.BkInterval                  = config.Get("BkInterval", settings.BkInterval);

            return(settings);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Constructs and starts the cache instance.
        /// </summary>
        /// <param name="settings">The GeoTracker settings.</param>
        public GeoFixCache(GeoTrackerServerSettings settings)
        {
            this.settings   = settings;
            this.isRunning  = true;
            this.rwLock     = new ReaderWriterLockSlim();
            this.purgeTimer = new PolledTimer(settings.GeoFixPurgeInterval);

            this.bkThread      = new Thread(new ThreadStart(BkThread));
            this.bkThread.Name = "GeoTracker: Cache";
            this.bkThread.Start();
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Associates the service handler with a message router by registering
        /// the necessary application message handlers.
        /// </summary>
        /// <param name="router">The message router.</param>
        /// <param name="settings">The configuration settings.</param>
        /// <param name="perfCounters">The application's performance counter set (or <c>null</c>).</param>
        /// <param name="perfPrefix">The string to prefix any performance counter names (or <c>null</c>).</param>
        /// <remarks>
        /// <para>
        /// Applications that expose performance counters will pass a non-<c>null</c> <b>perfCounters</b>
        /// instance.  The service handler should add any counters it implements to this set.
        /// If <paramref name="perfPrefix" /> is not <c>null</c> then any counters added should prefix their
        /// names with this parameter.
        /// </para>
        /// </remarks>
        public void Start(MsgRouter router, GeoTrackerServerSettings settings, PerfCounterSet perfCounters, string perfPrefix)
        {
            if (this.isRunning)
            {
                throw new InvalidOperationException("This node has already been started.");
            }

            if (router == null)
            {
                throw new ArgumentNullException("router");
            }

            // Initialize the performance counters

            this.startTime = DateTime.UtcNow;
            this.perf      = new Perf(perfCounters, perfPrefix);

            // General initialization

            this.settings      = settings;
            this.bkTimer       = new GatedTimer(new TimerCallback(OnBkTimer), null, settings.BkInterval);
            this.ipGeocoder    = new IPGeocoder(this);
            this.clusterClient = Helper.CreateInstance <ITopologyProvider>(settings.ClusterTopology);
            this.clusterServer = Helper.CreateInstance <ITopologyProvider>(settings.ClusterTopology);
            this.fixCache      = new GeoFixCache(settings);
            this.archiver      = Helper.CreateInstance <IGeoFixArchiver>(settings.GeoFixArchiver);

            EntityState.MaxEntityFixes = settings.MaxEntityGeoFixes;

            try
            {
                // Initialize the router

                this.router = router;
                this.router.Dispatcher.AddTarget(this, "GeoTrackerServerEP", new SimpleEPMunger(settings.ServerEP), null);

                // Initialize the cluster

                this.clusterClient.OpenClient(router, settings.ClusterEP, settings.ClusterArgs);
                this.clusterServer.OpenServer(router, "GeoTrackerClusterEP", settings.ClusterEP, this, settings.ClusterArgs);

                // Start the archiver.

                archiver.Start(this, settings.GeoFixArchiverArgs);

                this.isRunning = true;
            }
            catch
            {
                Stop();
                throw;
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Initalizes the fix archiver instance.
        /// </summary>
        /// <param name="node">The parent <see cref="GeoTrackerNode" /> instance.</param>
        /// <param name="args">This implementation recognizes special arguments (see the remarks).</param>
        /// <remarks>
        /// <para>
        /// This archiver recognizes the following arguments:
        /// </para>
        /// <list type="table">
        ///     <item>
        ///         <term><b>ConnectionString</b></term>
        ///         <description>
        ///         <b>Required:</b> This specifies the SQL connection string the archiver will use
        ///         to connect to the database server.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term><b>BufferSize</b></term>
        ///         <description>
        ///         <b>Optional:</b> This specifies the number of fixes the archiver will buffer before
        ///         submitting the fixes to the database in a batch.  (defaults to <b>1000</b>).
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term><b>BufferInterval</b></term>
        ///         <description>
        ///         <b>Optional: </b> The maximum length of time the archiver will buffer fixes before
        ///         flushing to the database, regardless of how full thbe buffer is.  (defaults to <b>5 minutes</b>).
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term><b>AddScript</b></term>
        ///         <description>
        ///         <para>
        ///         The SQL script template the archiver will use for submitting fixes to the database.
        ///         This script will include macros where the archiver will substitute the values
        ///         from the <see cref="GeoFix" /> being persisted.  Here are the supported macros:
        ///         </para>
        ///         <list type="table">
        ///             <item>
        ///                 <term><b>@(TimeUtc)</b></term>
        ///                 <description>
        ///                 The time (UTC) the fix was taken.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(EntityID)</b></term>
        ///                 <description>
        ///                 The ID of the entity being tracked.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(GroupID)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The ID of the group the entity belongs to.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Technology)</b></term>
        ///                 <description>
        ///                 The technology used to obtain the position.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Latitude)</b></term>
        ///                 <description>
        ///                 The latitude.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Longitude)</b></term>
        ///                 <description>
        ///                 The longitude.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Altitude)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The altitude in meters.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Course)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The direction of travel in degrees from true north.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(Speed)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The speed in kilometers per hour.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(HorizontalAccuracy)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The estimated horizontal accuracy of the fix in meters.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(VerticalAccurancy)</b></term>
        ///                 <description>
        ///                 <b>Nullable:</b> The estimated vertical accuracy of the fix in meters.
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <term><b>@(NetworkStatus)</b></term>
        ///                 <description>
        ///                 Identifies how or if the entity was connected to the Internet when the fix was taken.
        ///                 </description>
        ///             </item>
        ///         </list>
        ///         <note>
        ///         We're using the <b>"@"</b> rather than the usual <b>"$"</b> character to mark the
        ///         macro name to avoid conflicting with environment variable expansions performed
        ///         when loading the application configuration.
        ///         </note>
        ///         </description>
        ///     </item>
        /// </list>
        /// <para>
        /// The most important archiver parameter is <b>AddScript</b>.  This is the template the archiver
        /// will use to generate the SQL statements that will add <see cref="GeoFix" />es to the database.
        /// The script can be as simple as an ad-hoc table <b>insert</b> or a call to a stored procedure.
        /// Add one or more of the macro identifiers listed above to the script.  The archiver will replace
        /// these macros with actual fields from the <see cref="GeoFix" />, quoting them as necessary.
        /// Null <see cref="GeoFix" /> fields or numeric fields set to <see cref="double.NaN" /> will
        /// be generated as <c>null</c>.  See the <b>Nullable</b> tags in the macro definitions above.
        /// </para>
        /// <para>
        /// Here's an exmple of a simple sample script that will insert the <see cref="GeoFix.TimeUtc" />,
        /// <b>entity ID</b>, <see cref="GeoFix.Latitude" />, and <see cref="GeoFix.Longitude" />
        /// <see cref="GeoFix" /> fields into a table.
        /// </para>
        /// <code language="none">
        /// insert into FixArchive(Time,Entity,Lat,Lon) values (@(TimeUtc),@(EntityID),@(Latitude),@(longitude))
        /// </code>
        /// <note>
        /// <see cref="IGeoFixArchiver" /> implementations must silently handle any internal
        /// error conditions.  <see cref="GeoTrackerNode" /> does not expect to see any
        /// exceptions raised from calls to any of these methods.  Implementations should
        /// catch any exceptions thrown internally and log errors or warnings as necessary.
        /// </note>
        /// </remarks>
        public void Start(GeoTrackerNode node, ArgCollection args)
        {
            lock (syncLock)
            {
                try
                {
                    if (isRunning)
                    {
                        throw new InvalidProgramException("SqlGeoFixArchiver: Archiver has already been started.");
                    }

                    if (isStopped)
                    {
                        throw new InvalidOperationException("SqlGeoFixArchiver: Cannot restart a stopped archiver.");
                    }

                    this.node           = node;
                    this.settings       = node.Settings;
                    this.bufferedFixes  = new List <FixRecord>();
                    this.conString      = args.Get("ConnectionString", string.Empty);
                    this.bufferSize     = args.Get("BufferSize", 1000);
                    this.bufferInterval = args.Get("BufferInterval", TimeSpan.FromMinutes(5));
                    this.addScript      = args.Get("AddScript", string.Empty).Replace("@(", "$(");

                    if (string.IsNullOrWhiteSpace(this.conString))
                    {
                        throw new ArgumentException("SqlGeoFixArchiver: The [ConnectionString] argument is required.");
                    }

                    if (string.IsNullOrWhiteSpace(this.addScript))
                    {
                        throw new ArgumentException("SqlGeoFixArchiver: The [AddScript] argument is required.");
                    }

                    this.isRunning   = true;
                    this.flushTimer  = new PolledTimer(bufferInterval, false);
                    this.flushThread = new Thread(new ThreadStart(FlushThread));
                    this.flushThread.Start();
                }
                catch (Exception e)
                {
                    SysLog.LogException(e);
                }
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Constructs the instance and starts the background downloading thread.
        /// </summary>
        /// <param name="node">The parent <see cref="GeoTrackerNode" /> instance.</param>
        /// <remarks>
        /// <note>
        /// The constructor will not start a background thread if IP geocoding is disabled.
        /// </note>
        /// </remarks>
        public IPGeocoder(GeoTrackerNode node)
        {
            settings = node.Settings;

            if (!settings.IPGeocodeEnabled)
            {
                return;
            }

            // Initialize the service including loading the MaxMind database if present.

            running     = true;
            stopPending = false;
            pollDataNow = false;
            maxMind     = null;

            try
            {
                if (File.Exists(dataPath))
                {
                    maxMind = new LookupService(dataPath, LookupService.GEOIP_MEMORY_CACHE);
                    maxMind.close();
                }
            }
            catch (Exception e)
            {
                // Assume that the database file is corrupted if there's an exception
                // and delete it so the download thread will download a new copy.

                SysLog.LogException(e);
                SysLog.LogError("GeoTracker: The MaxMind database file [{0}] appears to be corrupted.  This will be deleted so the downloader can get a fresh copy.", dataPath);

                Helper.DeleteFile(dataPath);
            }

            // Start the background downloader thread.

            downloadThread      = new Thread(new ThreadStart(DownloadThread));
            downloadThread.Name = "GeoTracker: GeoData Downloader";
            downloadThread.Start();
        }
Ejemplo n.º 6
0
        private List <EntityState> entities;                // The entities within the block (or null)

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="settings">The GeoTracker server settings.</param>
        /// <param name="bounds">The boundry for the area indexed by this instance.</param>
        /// <param name="depth">The depth of of this instance in the hierarchy (0 for the top level).</param>
        /// <remarks>
        /// <note>
        /// This constructor initially creates a leaf index block.  Subsequent calls to <see cref="Balance" />
        /// may split this block into sub-blocks or join sub-blocks back into a leaf node.
        /// </note>
        /// </remarks>
        public MercatorBlock(GeoTrackerServerSettings settings, GeoRectangle bounds, int depth)
        {
            this.Bounds    = bounds;
            this.Depth     = depth;
            this.subBlocks = null;
            this.entities  = new List <EntityState>();

            if (depth == 0)
            {
                edgeLength = 10.0;
            }
            else
            {
                edgeLength = 10.0 / (Math.Pow(BlockDimension, depth));
            }

            if (depth <= settings.IndexMaxGroupTableLevel)
            {
                groupMap = new Dictionary <string, bool>();
            }
        }
Ejemplo n.º 7
0
 /// <summary>
 /// Balances the entities within the block by creating sub-blocks if this is a leaf and the
 /// number of entities exceeds the high watermark or collapsing sub-blocks back into a leaf
 /// if the number of entities is less than the low watermark.
 /// </summary>
 public void Balance(GeoTrackerServerSettings settings)
 {
 }
Ejemplo n.º 8
0
 /// <summary>
 /// Associates the service handler with a message router by registering
 /// the necessary application message handlers.
 /// </summary>
 /// <param name="router">The message router.</param>
 /// <param name="keyPrefix">The configuration key prefix or (null to use <b>LillTek.GeoTracker.Server</b>).</param>
 /// <param name="perfCounters">The application's performance counter set (or <c>null</c>).</param>
 /// <param name="perfPrefix">The string to prefix any performance counter names (or <c>null</c>).</param>
 /// <remarks>
 /// <para>
 /// Applications that expose performance counters will pass a non-<c>null</c> <b>perfCounters</b>
 /// instance.  The service handler should add any counters it implements to this set.
 /// If <paramref name="perfPrefix" /> is not <c>null</c> then any counters added should prefix their
 /// names with this parameter.
 /// </para>
 /// </remarks>
 public void Start(MsgRouter router, string keyPrefix, PerfCounterSet perfCounters, string perfPrefix)
 {
     Start(router, GeoTrackerServerSettings.LoadConfig(keyPrefix ?? ConfigPrefix), perfCounters, perfPrefix);
 }