Example #1
0
        /// <summary>
        /// Add a result to the weighted rolling average history.
        /// </summary>
        /// <param name="ipAndPort">IP:Port string to identify where the results go</param>
        /// <param name="result">The result to add to the history. This result becomes the current result.</param>
        public void AddResult(string ipAndPort, QosResult result)
        {
            if (result.PacketLoss + Mathf.Epsilon < 0.0f || result.PacketLoss - Mathf.Epsilon > 1.0f)
            {
                throw new ArgumentOutOfRangeException(nameof(result), "PacketLoss must be in the range [0.0..1.0]");
            }

            m_ResultsLock.EnterWriteLock();
            try
            {
                WeightedMovingAverage wma = null;
                if (!m_Results.TryGetValue(ipAndPort, out wma))
                {
                    // Tracking new server
                    wma = new WeightedMovingAverage(m_NumResults, m_Weight);
                }

                wma.AddResult(new QosStatsResult((int)result.AverageLatencyMs, result.PacketLoss));
                m_Results[ipAndPort] = wma;
            }
            finally
            {
                m_ResultsLock.ExitWriteLock();
            }
        }
Example #2
0
        private bool TryCopyResult(ulong id, ref QosResult result)
        {
            var rc = TryFindFirstMatchingResult(id, out var r);

            if (rc == true)
            {
                result = r;
            }

            return(rc);
        }
Example #3
0
 private bool TryFindFirstMatchingResult(ulong id, out QosResult r)
 {
     for (var i = 0; i < m_QosServers.Length; ++i)
     {
         if (m_QosServers[i].Id == id)
         {
             r = QosResults[i];
             return(true);
         }
     }
     r = new QosResult();
     return(false);
 }
Example #4
0
        public void Execute()
        {
            if (m_QosServers.Length == 0)
            {
                return;    // Nothing to do.
            }
            m_JobExpireTimeUtc = DateTime.UtcNow.AddMilliseconds(TimeoutMs);

            // Create the local socket
            int errorcode = 0;

            (m_Socket, errorcode) = CreateAndBindSocket();
            if (m_Socket == -1 || errorcode != 0)
            {
                // Can't run the job
                Debug.LogError("Failed to create and bind the local socket for QoS Check");
            }
            else
            {
                m_Identifier = (ushort)new Random().Next(ushort.MinValue, ushort.MaxValue);
                for (int i = 0; i < m_QosServers.Length; ++i)
                {
                    QosResult         result = QosResults[i];
                    InternalQosServer server = m_QosServers[i];

                    if (QosHelper.ExpiredUtc(m_JobExpireTimeUtc))
                    {
                        Debug.LogWarning($"Ran out of time to finish remaining QoS Check for endpoint {i}.");
                        break;
                    }

                    // If we've already visited this server, just copy those results here.
                    if (QosServerVisited(server.Id))
                    {
                        if (TryCopyResult(server.Id, ref result) == false)
                        {
                            Debug.LogError($"Visited server must have a previous result available");
                            break;
                        }
                    }
                    else if (DateTime.UtcNow > server.BackoffUntilUtc) // Only contact this server if we are allowed
                    {
                        // For each iteration of the loop, give the remaining endpoints an equal fraction of the remaining
                        // overall job time.  For example if there are five endpoints that each get 1000ms (5000ms total),
                        // and the first endpoint finishes in 200ms, the remaining endpoints will get 1200ms to complete
                        // (4800ms remaining / 4 endpoints = 1200ms/endpoint).
                        double   allottedTimeMs = QosHelper.RemainingUtc(m_JobExpireTimeUtc).TotalMilliseconds / (m_QosServers.Length - i);
                        DateTime startTimeUtc   = DateTime.UtcNow;
                        DateTime expireTimeUtc  = DateTime.UtcNow.AddMilliseconds(allottedTimeMs);

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                        Debug.Log($"QoS Check {i} gets {(expireTimeUtc - DateTime.UtcNow).TotalMilliseconds:F0}ms to complete.");
#endif

                        ++m_Identifier;
                        int err = SendQosRequests(server.RemoteEndpoint, m_Identifier, expireTimeUtc, ref result);
                        if (err != 0)
                        {
                            Debug.LogError($"Error {err} sending QoS requests.  Will attempt to receive responses anyway.");
                        }
                        err = RecvQosResponses(server.RemoteEndpoint, m_Identifier, expireTimeUtc, ref result);
                        if (err != 0)
                        {
                            Debug.LogError($"Error {err} receiving QoS responses. Will attempt to continue anyway.");
                        }

                        Debug.Log($"Received {result.ResponsesReceived}/{result.RequestsSent} responses from endpoint {i} in {(DateTime.UtcNow - startTimeUtc).TotalMilliseconds:F0}ms");

                        // Mark this server as visited

                        SetQosServerVisited(server.Id);
                    }
                    else
                    {
                        Debug.LogWarning($"Did not contact endpoint {i} due to backoff restrictions.");
                    }

                    // Save the result (even if we didn't contact the server)
                    QosResults[i] = result;
                }
            }

            NativeBindings.network_close(ref m_Socket, ref errorcode);
        }
Example #5
0
        /// <summary>
        /// Receive QoS responses from the given endpoint
        /// </summary>
        /// <param name="remoteEndpoint">Where to expect responses to come from</param>
        /// <param name="identifier">Identifier that will accompany a valid response</param>
        /// <param name="expireTimeUtc">How long to wait for responses</param>
        /// <param name="result">Results from the receive side of the check (packets received, latency, packet loss)</param>
        /// <returns>
        /// errorcode - the last error code (if any). 0 means no error.
        /// </returns>
        private int RecvQosResponses(NetworkEndPoint remoteEndpoint, ushort identifier, DateTime expireTimeUtc, ref QosResult result)
        {
            if (result.RequestsSent == 0)
            {
                return(0); // Not expecting any responses
            }

            NativeArray <int> responseLatency = new NativeArray <int>((int)result.RequestsSent, Allocator.Temp);

            for (int i = 0; i < responseLatency.Length; ++i)
            {
                responseLatency[i] = -1;
            }

            QosResponse response  = new QosResponse();
            int         errorcode = 0;

            while (result.ResponsesReceived < result.RequestsSent && !QosHelper.ExpiredUtc(expireTimeUtc))
            {
                errorcode = 0;
                int received = 0;
                (received, errorcode) = response.Recv(m_Socket, remoteEndpoint, expireTimeUtc);
                if (received == -1)
                {
                    Debug.LogError($"Invalid or no response received (errorcode = {errorcode})");
                }
                else if (!response.Verify())
                {
                    Debug.LogWarning("Ignoring invalid QosResponse");
                    ++result.InvalidResponses;
                }
                else if (response.Identifier != identifier)
                {
                    Debug.LogWarning($"Identifier 0x{response.Identifier:X4} != expected identifier 0x{identifier:X4}; ignoring...");
                    ++result.InvalidResponses;
                }
                else
                {
                    if (response.Sequence >= result.RequestsSent) // Sequence can't be more than number of requests sent
                    {
                        Debug.LogWarning($"Ignoring response with sequence {response.Sequence} that is higher than max sequence expected");
                        ++result.InvalidResponses;
                    }
                    else if (responseLatency[response.Sequence] == -1)
                    {
                        responseLatency[response.Sequence] = response.LatencyMs;
                        ++result.ResponsesReceived;
                    }
                    else
                    {
                        Debug.Log($"Duplicate response {response.Sequence} received for QosCheck identifier 0x{response.Identifier:X4}");
                        ++result.DuplicateResponses;
                    }

                    // Determine if we've had flow control applied to us.  If so, save the most significant result based
                    // on the unit count.  In this version, both Ban and Throttle have the same result: client back-off.
                    var fc = response.ParseFlowControl();
                    if (fc.type != FcType.None && fc.units > result.FcUnits)
                    {
                        result.FcType  = fc.type;
                        result.FcUnits = fc.units;
                    }
                }
            }

            // Calculate average latency and log results
            result.AverageLatencyMs = 0;
            if (result.ResponsesReceived > 0)
            {
                uint validResponses = 0;
                for (int i = 0, length = responseLatency.Length; i < length; i++)
                {
                    var latency = responseLatency[i];
                    if (latency >= 0)
                    {
                        result.AverageLatencyMs += (uint)latency;
                        validResponses++;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                        Debug.Log($"Received response {i} for QosCheck identifier 0x{identifier:X4} with latency {latency}ms");
#endif
                    }
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    else
                    {
                        Debug.LogWarning($"Failed to receive response {i} for QosCheck identifier 0x{identifier:X4}");
                    }
#endif
                }

                result.AverageLatencyMs /= validResponses;
            }

            result.UpdatePacketLoss();
            responseLatency.Dispose();
            return(errorcode);
        }
Example #6
0
        /// <summary>
        /// Send QoS requests to the given endpoint.
        /// </summary>
        /// <param name="remoteEndpoint">Server that QoS requests should be sent to</param>
        /// <param name="expireTime">When to stop trying to send requests</param>
        /// <param name="result">Results from the send side of the check (packets sent)</param>
        /// <returns>
        /// errorcode - the last error code generated (if any).  0 indicates no error.
        /// </returns>
        private int SendQosRequests(NetworkEndPoint remoteEndpoint, ushort identifier, DateTime expireTime, ref QosResult result)
        {
            QosRequest request = new QosRequest
            {
                Title      = m_TitleBytesUtf8.ToArray(),
                Identifier = identifier
            };

#if UNITY_EDITOR || DEVELOPMENT_BUILD
            Debug.Log($"Identifier for this QoS check is: 0x{request.Identifier:X4}");
#endif

            // Send all requests.
            result.RequestsSent = 0;
            while (result.RequestsSent < RequestsPerEndpoint && !QosHelper.ExpiredUtc(expireTime))
            {
                request.Timestamp = (ulong)(DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
                request.Sequence  = (byte)result.RequestsSent;

                int errorcode = 0;
                int sent      = 0;
                (sent, errorcode) = request.Send(m_Socket, ref remoteEndpoint, expireTime);
                if (errorcode != 0)
                {
                    Debug.LogError($"Send returned error code {errorcode}, can't continue");
                    return(errorcode);
                }
                else if (sent != request.Length)
                {
                    Debug.LogWarning($"Sent {sent} of {request.Length} bytes, ignoring this request");
                    ++result.InvalidRequests;
                }
                else
                {
                    ++result.RequestsSent;
                }
            }

            return(0);
        }