/// <summary>
        /// Queues a collection of measurements for processing.
        /// </summary>
        /// <param name="measurements">Collection of measurements to queue for processing.</param>
        /// <remarks>
        /// Measurements are filtered against the defined <see cref="InputMeasurementKeys"/> so we override method
        /// so that dynamic updates to keys will be synchronized with filtering to prevent interference.
        /// </remarks>
        public override void QueueMeasurementsForProcessing(IEnumerable <IMeasurement> measurements)
        {
            if ((object)measurements == null)
            {
                return;
            }

            if (!m_startTimeSent && measurements.Any())
            {
                m_startTimeSent = true;

                IMeasurement measurement = measurements.FirstOrDefault(m => m != null);
                Ticks        timestamp   = 0;

                if ((object)measurement != null)
                {
                    timestamp = measurement.Timestamp;
                }

                m_parent.SendDataStartTime(m_clientID, timestamp);
            }

            if (m_isNaNFiltered)
            {
                measurements = measurements.Where(measurement => !double.IsNaN(measurement.Value));
            }

            // Order measurements by signal type for better compression for non-TSSC compression modes
            if (m_usePayloadCompression && !m_compressionModes.HasFlag(CompressionModes.TSSC))
            {
                base.QueueMeasurementsForProcessing(measurements.OrderBy(m => m.GetSignalType(DataSource)));
            }
            else
            {
                base.QueueMeasurementsForProcessing(measurements);
            }
        }
        private void ProcessMeasurements(IEnumerable <IMeasurement> measurements)
        {
            if (m_usePayloadCompression && m_compressionModes.HasFlag(CompressionModes.TSSC))
            {
                ProcessTSSCMeasurements(measurements);
                return;
            }

            // Includes data packet flags and measurement count
            const int PacketHeaderSize = DataPublisher.ClientResponseHeaderSize + 5;

            List <IBinaryMeasurement> packet;
            int packetSize;

            bool usePayloadCompression;
            bool useCompactMeasurementFormat;

            BufferBlockMeasurement bufferBlockMeasurement;

            byte[] bufferBlock;
            ushort bufferBlockSignalIndex;

            IBinaryMeasurement binaryMeasurement;
            int binaryLength;

            try
            {
                if (!Enabled)
                {
                    return;
                }

                packet     = new List <IBinaryMeasurement>();
                packetSize = PacketHeaderSize;

                usePayloadCompression       = m_usePayloadCompression;
                useCompactMeasurementFormat = m_useCompactMeasurementFormat || usePayloadCompression;

                foreach (IMeasurement measurement in measurements)
                {
                    bufferBlockMeasurement = measurement as BufferBlockMeasurement;

                    if ((object)bufferBlockMeasurement != null)
                    {
                        // Still sending buffer block measurements to client; we are expecting
                        // confirmations which will indicate whether retransmission is necessary,
                        // so we will restart the retransmission timer
                        m_bufferBlockRetransmissionTimer.Stop();

                        // Handle buffer block measurements as a special case - this can be any kind of data,
                        // measurement subscriber will need to know how to interpret buffer
                        bufferBlock = new byte[6 + bufferBlockMeasurement.Length];

                        // Prepend sequence number
                        BigEndian.CopyBytes(m_bufferBlockSequenceNumber, bufferBlock, 0);
                        m_bufferBlockSequenceNumber++;

                        // Copy signal index into buffer
                        bufferBlockSignalIndex = m_signalIndexCache.GetSignalIndex(bufferBlockMeasurement.Key);
                        BigEndian.CopyBytes(bufferBlockSignalIndex, bufferBlock, 4);

                        // Append measurement data and send
                        Buffer.BlockCopy(bufferBlockMeasurement.Buffer, 0, bufferBlock, 6, bufferBlockMeasurement.Length);
                        m_parent.SendClientResponse(m_clientID, ServerResponse.BufferBlock, ServerCommand.Subscribe, bufferBlock);

                        lock (m_bufferBlockCacheLock)
                        {
                            // Cache buffer block for retransmission
                            m_bufferBlockCache.Add(bufferBlock);
                        }

                        // Start the retransmission timer in case we never receive a confirmation
                        m_bufferBlockRetransmissionTimer.Start();
                    }
                    else
                    {
                        // Serialize the current measurement.
                        if (useCompactMeasurementFormat)
                        {
                            binaryMeasurement = new CompactMeasurement(measurement, m_signalIndexCache, m_includeTime, m_baseTimeOffsets, m_timeIndex, m_useMillisecondResolution);
                        }
                        else
                        {
                            binaryMeasurement = new SerializableMeasurement(measurement, m_parent.GetClientEncoding(m_clientID));
                        }

                        // Determine the size of the measurement in bytes.
                        binaryLength = binaryMeasurement.BinaryLength;

                        // If the current measurement will not fit in the packet based on the max
                        // packet size, process the current packet and start a new packet.
                        if (packetSize + binaryLength > DataPublisher.MaxPacketSize)
                        {
                            ProcessBinaryMeasurements(packet, useCompactMeasurementFormat, usePayloadCompression);
                            packet.Clear();
                            packetSize = PacketHeaderSize;
                        }

                        // Add the current measurement to the packet.
                        packet.Add(binaryMeasurement);
                        packetSize += binaryLength;
                    }
                }

                // Process the remaining measurements.
                if (packet.Count > 0)
                {
                    ProcessBinaryMeasurements(packet, useCompactMeasurementFormat, usePayloadCompression);
                }

                IncrementProcessedMeasurements(measurements.Count());

                // Update latency statistics
                m_parent.UpdateLatencyStatistics(measurements.Select(m => (long)(m_lastPublishTime - m.Timestamp)));
            }
            catch (Exception ex)
            {
                string message = $"Error processing measurements: {ex.Message}";
                OnProcessException(MessageLevel.Info, new InvalidOperationException(message, ex));
            }
        }
Example #3
0
        /// <summary>
        /// Queues a collection of measurements for processing.
        /// </summary>
        /// <param name="measurements">Collection of measurements to queue for processing.</param>
        /// <remarks>
        /// Measurements are filtered against the defined <see cref="InputMeasurementKeys"/> so we override method
        /// so that dynamic updates to keys will be synchronized with filtering to prevent interference.
        /// </remarks>
        public override void QueueMeasurementsForProcessing(IEnumerable <IMeasurement> measurements)
        {
            if ((object)measurements == null)
            {
                return;
            }

            if (!m_startTimeSent && measurements.Any())
            {
                m_startTimeSent = true;

                IMeasurement measurement = measurements.FirstOrDefault(m => (object)m != null);
                Ticks        timestamp   = 0;

                if ((object)measurement != null)
                {
                    timestamp = measurement.Timestamp;
                }

                m_parent.SendDataStartTime(m_clientID, timestamp);
            }

            if (m_isNaNFiltered)
            {
                measurements = measurements.Where(measurement => !double.IsNaN(measurement.Value));
            }

            if (!measurements.Any() || !Enabled)
            {
                return;
            }

            if (TrackLatestMeasurements)
            {
                double publishInterval;

                // Keep track of latest measurements
                base.QueueMeasurementsForProcessing(measurements);
                publishInterval = (m_publishInterval > 0) ? m_publishInterval : LagTime;

                if (DateTime.UtcNow.Ticks > m_lastPublishTime + Ticks.FromSeconds(publishInterval))
                {
                    List <IMeasurement> currentMeasurements = new List <IMeasurement>();
                    Measurement         newMeasurement;

                    // Create a new set of measurements that represent the latest known values setting value to NaN if it is old
                    foreach (TemporalMeasurement measurement in LatestMeasurements)
                    {
                        newMeasurement = new Measurement
                        {
                            Key        = measurement.Key,
                            Value      = measurement.GetValue(RealTime),
                            Adder      = measurement.Adder,
                            Multiplier = measurement.Multiplier,
                            Timestamp  = measurement.Timestamp,
                            StateFlags = measurement.StateFlags
                        };

                        currentMeasurements.Add(newMeasurement);
                    }

                    // Order measurements by signal type for better compression when enabled
                    if (m_usePayloadCompression)
                    {
                        ProcessMeasurements(currentMeasurements.OrderBy(measurement => measurement.GetSignalType(DataSource)));
                    }
                    else
                    {
                        ProcessMeasurements(currentMeasurements);
                    }
                }
            }
            else
            {
                // Order measurements by signal type for better compression for non-TSSC compression modes
                if (m_usePayloadCompression && !m_compressionModes.HasFlag(CompressionModes.TSSC))
                {
                    ProcessMeasurements(measurements.OrderBy(measurement => measurement.GetSignalType(DataSource)));
                }
                else
                {
                    ProcessMeasurements(measurements);
                }
            }
        }