示例#1
0
文件: Probe.cs 项目: jirlong/sensus
        /// <summary>
        /// Stores a <see cref="Datum"/> within the <see cref="LocalDataStore"/>. Will not throw an <see cref="Exception"/>.
        /// </summary>
        /// <param name="datum">Datum.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        public void StoreDatum(Datum datum, CancellationToken?cancellationToken = null)
        {
            // track/limit the raw rate
            if (_rawRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Drop)
            {
                return;
            }

            // track the storage rate
            _storageRateCalculator.Add(datum);

            // set properties that we were unable to set within the datum constructor. datum is allowed to
            // be null, indicating the the probe attempted to obtain data but it didn't find any (in the
            // case of polling probes).
            if (datum != null)
            {
                datum.ProtocolId    = Protocol.Id;
                datum.ParticipantId = Protocol.ParticipantId;
            }

            // track the most recent datum regardless of whether the datum is null or whether we're storing data
            Datum previousDatum = _mostRecentDatum;

            _mostRecentDatum          = datum;
            _mostRecentStoreTimestamp = DateTimeOffset.UtcNow;

            // fire events to notify observers of the stored data and associated UI values
            MostRecentDatumChanged?.Invoke(this, new Tuple <Datum, Datum>(previousDatum, _mostRecentDatum));

            // don't update the UI too often, as doing so at really high rates causes UI deadlocks.
            if (_uiUpdateRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Keep)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubCaption)));
            }

            // store non-null data
            if (_storeData && datum != null)
            {
                #region update chart data
                ChartDataPoint chartDataPoint = null;

                try
                {
                    chartDataPoint = GetChartDataPointFromDatum(datum);
                }
                catch (NotImplementedException)
                {
                }

                if (chartDataPoint != null)
                {
                    lock (_chartData)
                    {
                        _chartData.Add(chartDataPoint);

                        while (_chartData.Count > 0 && _chartData.Count > _maxChartDataCount)
                        {
                            _chartData.RemoveAt(0);
                        }
                    }
                }
                #endregion

                // write datum to local data store. catch any exceptions, as the caller (e.g., a listening
                // probe) could very well be unprotected on the UI thread. throwing an exception here can crash the app.
                try
                {
                    _protocol.LocalDataStore.WriteDatum(datum, cancellationToken.GetValueOrDefault());
                }
                catch (Exception ex)
                {
                    SensusServiceHelper.Get().Logger.Log("Failed to write datum:  " + ex, LoggingLevel.Normal, GetType());
                }
            }
        }
示例#2
0
        public sealed override Task <bool> StoreDatumAsync(Datum datum, CancellationToken?cancellationToken = default(CancellationToken?))
        {
            bool store = true;

            float maxDataStoresPerSecond = _maxDataStoresPerSecond.GetValueOrDefault(-1);

            // 0 (or negligible) data per second:  don't store. if max data per second is not set, the following inequality will be false.
            if (Math.Abs(maxDataStoresPerSecond) < DATA_RATE_EPSILON)
            {
                store = false;
            }
            // non-negligible (or default -1) data per second:  check data rate
            else if (maxDataStoresPerSecond > 0)
            {
                _incomingDataRateCalculator.Add(datum);

                // recalculate the sampling modulus after accumulating a full sample size in the data rate calculator
                if ((_incomingDataRateCalculator.TotalAdded % _incomingDataRateCalculator.SampleSize) == 0)
                {
                    double incomingDataPerSecond = _incomingDataRateCalculator.DataPerSecond;
                    double extraDataPerSecond    = incomingDataPerSecond - maxDataStoresPerSecond;

                    // if we're not over the limit then store all samples
                    if (extraDataPerSecond <= 0)
                    {
                        _samplingModulus            = 1;
                        _samplingModulusMatchAction = SamplingModulusMatchAction.Store;
                    }
                    // otherwise calculate a modulus that will get as close as possible to the desired rate given the empirical rate
                    else
                    {
                        double samplingModulusMatchRate = extraDataPerSecond / incomingDataPerSecond;
                        _samplingModulusMatchAction = SamplingModulusMatchAction.Drop;

                        if (samplingModulusMatchRate > 0.5)
                        {
                            samplingModulusMatchRate    = 1 - samplingModulusMatchRate;
                            _samplingModulusMatchAction = SamplingModulusMatchAction.Store;
                        }

                        if (_samplingModulusMatchAction == SamplingModulusMatchAction.Store)
                        {
                            // round the (store) modulus down to oversample -- more is better, right?
                            _samplingModulus = (int)Math.Floor(1 / samplingModulusMatchRate);
                        }
                        else
                        {
                            // round the (drop) modulus up to oversample -- more is better, right?
                            _samplingModulus = (int)Math.Ceiling(1 / samplingModulusMatchRate);
                        }
                    }
                }

                bool isModulusMatch = (_incomingDataRateCalculator.TotalAdded % _samplingModulus) == 0;

                if ((_samplingModulusMatchAction == SamplingModulusMatchAction.Store && !isModulusMatch) || (_samplingModulusMatchAction == SamplingModulusMatchAction.Drop && isModulusMatch))
                {
                    store = false;
                }
            }

            if (store)
            {
                return(base.StoreDatumAsync(datum, cancellationToken));
            }
            else
            {
                return(Task.FromResult(false));
            }
        }
示例#3
0
        /// <summary>
        /// Stores a <see cref="Datum"/> within the <see cref="LocalDataStore"/>. Will not throw an <see cref="Exception"/>.
        /// </summary>
        /// <param name="datum">Datum.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        public async Task StoreDatumAsync(Datum datum, CancellationToken?cancellationToken = null)
        {
            // it's possible for the current method to be called when the protocol is not running. the obvious case is when
            // the protocol is paused, but there are other race-like conditions. we try to prevent this (e.g., by forcing
            // the user to start the protocol before taking a survey saved from a previous run of the app), but there are
            // probably corner cases we haven't accounted for. at the very least, there are race conditions (e.g., taking a
            // survey when a protocol is about to stop) that could cause data to be stored without a running protocol.
            if (_protocol.State != ProtocolState.Running)
            {
                return;
            }

            // track/limit the raw rate of non-null data. all null data will pass this test, and this is
            // fine given such data are generated by polling probes when no data were retrieved. such
            // return values from polling probes are used to indicate that the poll was completed, which
            // will be reflected in the _mostRecentStoreTimestamp below.
            if (datum != null)
            {
                // impose a limit on the raw data rate
                if (_rawRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Drop)
                {
                    return;
                }

                // set properties that we were unable to set within the datum constructor.
                datum.ProtocolId    = Protocol.Id;
                datum.ParticipantId = Protocol.ParticipantId;

                // tag the data if we're in tagging mode, indicated with a non-null event id on the protocol. avoid
                // any race conditions related to starting/stopping a tagging by getting the required values and
                // then checking both for validity. we need to guarantee that any tagged datum has both an id and tags.
                string        taggedEventId   = Protocol.TaggedEventId;
                List <string> taggedEventTags = Protocol.TaggedEventTags.ToList();
                if (!string.IsNullOrWhiteSpace(taggedEventId) && taggedEventTags.Count > 0)
                {
                    datum.TaggedEventId   = taggedEventId;
                    datum.TaggedEventTags = taggedEventTags;
                }

                // if the protocol is configured with a sensing agent,
                if (Protocol.Agent != null)
                {
                    datum.SensingAgentStateDescription = Protocol.Agent.StateDescription;
                }
            }

            // store non-null data
            if (_storeData && datum != null)
            {
                #region update chart data
                ChartDataPoint chartDataPoint = null;

                try
                {
                    chartDataPoint = GetChartDataPointFromDatum(datum);
                }
                catch (NotImplementedException)
                {
                }

                if (chartDataPoint != null)
                {
                    lock (_chartData)
                    {
                        _chartData.Add(chartDataPoint);

                        while (_chartData.Count > 0 && _chartData.Count > _maxChartDataCount)
                        {
                            _chartData.RemoveAt(0);
                        }
                    }
                }
                #endregion

                // write datum to local data store. catch any exceptions, as the caller (e.g., a listening
                // probe) could very well be unprotected on the UI thread. throwing an exception here can crash the app.
                try
                {
                    _protocol.LocalDataStore.WriteDatum(datum, cancellationToken.GetValueOrDefault());

                    // track the storage rate
                    _storageRateCalculator.Add(datum);
                }
                catch (Exception ex)
                {
                    SensusServiceHelper.Get().Logger.Log("Failed to write datum:  " + ex, LoggingLevel.Normal, GetType());
                }
            }

            // update the timestamp of the most recent store. this is used to calculate storage latency, so we
            // do not restrict its values to those obtained when non-null data are stored (see above). some
            // probes call this method with null data to signal that they have run their collection to completion.
            _mostRecentStoreTimestamp = DateTimeOffset.UtcNow;

            // don't update the UI too often, as doing so at really high rates causes UI deadlocks. always let
            // null data update the UI, as these are only generated by polling probes at low rates.
            if (datum == null || _uiUpdateRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Keep)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubCaption)));
            }

            // track the most recent datum regardless of whether the datum is null or whether we're storing data
            Datum previousDatum = _mostRecentDatum;
            _mostRecentDatum = datum;

            // notify observers of the stored data and associated UI values
            await(MostRecentDatumChanged?.Invoke(previousDatum, _mostRecentDatum) ?? Task.CompletedTask);

            // let the script probe's agent observe the data, as long as the probe is enabled and there is an agent.
            Protocol.TryGetProbe(typeof(ScriptProbe), out Probe scriptProbe);
            if (scriptProbe?.Enabled ?? false)
            {
                // agents might be third-party and badly behaving...catch their exceptions.
                try
                {
                    await((scriptProbe as ScriptProbe).Agent?.ObserveAsync(datum) ?? Task.CompletedTask);
                }
                catch (Exception ex)
                {
                    SensusServiceHelper.Get().Logger.Log("Exception while script probe agent was observing datum:  " + ex.Message, LoggingLevel.Normal, GetType());
                }
            }

            // let the protocol's sensing agent observe the data, and schedule any returned control
            // completion check. agents might be third-party and badly behaving...catch their exceptions.
            try
            {
                await Protocol.ScheduleAgentControlCompletionCheckAsync(await (Protocol.Agent?.ObserveAsync(datum, cancellationToken.GetValueOrDefault()) ?? Task.FromResult <ControlCompletionCheck>(null)));
            }
            catch (Exception ex)
            {
                SensusServiceHelper.Get().Logger.Log("Exception while sensing agent was observing datum:  " + ex.Message, LoggingLevel.Normal, GetType());
            }
        }