public void DefaultState()
        {
            DateTimeOffset dto = new DateTimeOffset(2017, 10, 2, 17, 5, 0, TimeSpan.FromHours(-7));

            var aggregationManager = new MetricAggregationManager();

            var measurementMetric = new MetricSeries(
                aggregationManager,
                "Measurement Metric",
                null,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

            var accumulatorMetric = new MetricSeries(
                aggregationManager,
                "Accumulator Metric",
                null,
                new MetricSeriesConfigurationForAccumulator(restrictToUInt32Values: false));

            measurementMetric.TrackValue(1);
            accumulatorMetric.TrackValue(2);

            AggregationPeriodSummary defaultPeriod = aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.Default, dto, futureFilter: null);

            Assert.IsNotNull(defaultPeriod);
            Assert.IsNotNull(defaultPeriod.NonpersistentAggregates);
            Assert.IsNotNull(defaultPeriod.PersistentAggregates);

            Assert.AreEqual(1, defaultPeriod.NonpersistentAggregates.Count);
            Assert.AreEqual("Measurement Metric", (defaultPeriod.NonpersistentAggregates[0]).MetricId);
            Assert.AreEqual(1, defaultPeriod.NonpersistentAggregates[0].Data["Count"]);
            Assert.AreEqual(1.0, defaultPeriod.NonpersistentAggregates[0].Data["Sum"]);

            Assert.AreEqual(1, defaultPeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", defaultPeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, defaultPeriod.PersistentAggregates[0].Data["Sum"]);

            AggregationPeriodSummary customPeriod = aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.Custom, dto, futureFilter: null);

            Assert.IsNotNull(customPeriod);
            Assert.IsNotNull(customPeriod.NonpersistentAggregates);
            Assert.IsNotNull(customPeriod.PersistentAggregates);

            Assert.AreEqual(0, customPeriod.NonpersistentAggregates.Count);

            Assert.AreEqual(1, customPeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", customPeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, customPeriod.PersistentAggregates[0].Data["Sum"]);

            AggregationPeriodSummary quickpulsePeriod = aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.QuickPulse, dto, futureFilter: null);

            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(0, quickpulsePeriod.NonpersistentAggregates.Count);

            Assert.AreEqual(1, quickpulsePeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", quickpulsePeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, quickpulsePeriod.PersistentAggregates[0].Data["Sum"]);
        }
示例#2
0
        public void FetchAndTrackMetrics()
        {
            // We know that GetNextCycleTargetTime(..) tries to snap cycles to 1 second into each minute.
            // But the timer wakes us up *approxumately* at that time. If we are within a few seconds of that time, we will snap exactly to that time.
            // If we are further away, we will just snap to a whole second. That way downstream systems do not need to worry about sub-second resolution.

            DateTimeOffset now = DateTimeOffset.Now;

            if (new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, now.Offset) <= now &&
                new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, 4, now.Offset) >= now)
            {
                now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, 1, now.Offset);
            }
            else
            {
                now = Util.RoundDownToSecond(now);
            }

            AggregationPeriodSummary aggregates = this.aggregationManager.StartOrCycleAggregators(
                MetricAggregationCycleKind.Default,
                futureFilter: null,
                tactTimestamp: now);

            if (aggregates != null)
            {
                Task fireAndForget = Task.Run(() => this.metricManager.TrackMetricAggregates(aggregates, flush: false));
            }
        }
        /// <summary>@ToDo: Complete documentation before stable release. {134}</summary>
        public void Flush()
        {
            DateTimeOffset           now        = DateTimeOffset.Now;
            AggregationPeriodSummary aggregates = this.aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.Default, futureFilter: null, tactTimestamp: now);

            this.TrackMetricAggregates(aggregates, flush: true);
        }
        internal void Flush(bool flushDownstreamPipeline)
        {
            DateTimeOffset           now        = DateTimeOffset.Now;
            AggregationPeriodSummary aggregates = this.aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.Default, futureFilter: null, tactTimestamp: now);

            this.TrackMetricAggregates(aggregates, flushDownstreamPipeline);
        }
        private AggregationPeriodSummary CycleAggregators(
            ref AggregatorCollection aggregators,
            DateTimeOffset tactTimestamp,
            IMetricSeriesFilter futureFilter,
            bool stopAggregators)
        {
            if (aggregators == _aggregatorsForPersistent)
            {
                throw new InvalidOperationException("Invernal SDK bug. Please report. Cannot cycle persistent aggregators.");
            }

            tactTimestamp = Util.RoundDownToSecond(tactTimestamp);

            // For non-persistent aggregators: create empty holder for the next aggregation period and swap for the previous holder:
            AggregatorCollection prevAggregators;

            if (stopAggregators)
            {
                prevAggregators = Interlocked.Exchange(ref aggregators, null);
            }
            else
            {
                AggregatorCollection nextAggregators = new AggregatorCollection(tactTimestamp, futureFilter);
                prevAggregators = Interlocked.Exchange(ref aggregators, nextAggregators);
            }

            List <MetricAggregate> persistentValsAggregations = GetPersistentAggregations(tactTimestamp, prevAggregators?.Filter);
            List <MetricAggregate> nonpersistentAggregations  = GetNonpersistentAggregations(tactTimestamp, prevAggregators);

            var summary = new AggregationPeriodSummary(persistentValsAggregations, nonpersistentAggregations);

            return(summary);
        }
        internal void TrackMetricAggregates(AggregationPeriodSummary aggregates, bool flush)
        {
            int nonpersistentAggregatesCount = (aggregates?.NonpersistentAggregates == null)
                                                    ? 0
                                                    : aggregates.NonpersistentAggregates.Count;

            int persistentAggregatesCount = (aggregates?.PersistentAggregates == null)
                                                    ? 0
                                                    : aggregates.PersistentAggregates.Count;

            int totalAggregatesCount = nonpersistentAggregatesCount + persistentAggregatesCount;

            if (totalAggregatesCount == 0)
            {
                return;
            }

            Task[] trackTasks = new Task[totalAggregatesCount];
            int    taskIndex  = 0;

            if (nonpersistentAggregatesCount != 0)
            {
                foreach (MetricAggregate telemetryItem in aggregates.NonpersistentAggregates)
                {
                    if (telemetryItem != null)
                    {
                        Task trackTask = this.telemetryPipeline.TrackAsync(telemetryItem, CancellationToken.None);
                        trackTasks[taskIndex++] = trackTask;
                    }
                }
            }

            if (aggregates.PersistentAggregates != null && aggregates.PersistentAggregates.Count != 0)
            {
                foreach (MetricAggregate telemetryItem in aggregates.PersistentAggregates)
                {
                    if (telemetryItem != null)
                    {
                        Task trackTask = this.telemetryPipeline.TrackAsync(telemetryItem, CancellationToken.None);
                        trackTasks[taskIndex++] = trackTask;
                    }
                }
            }

            Task.WaitAll(trackTasks);

            if (flush)
            {
                Task flushTask = this.telemetryPipeline.FlushAsync(CancellationToken.None);
                flushTask.Wait();
            }
        }
        internal void TrackMetricAggregates(AggregationPeriodSummary aggregates, bool flush)
        {
            int?nonpersistentAggregatesCount = aggregates?.NonpersistentAggregates?.Count;
            int?persistentAggregatesCount    = aggregates?.PersistentAggregates?.Count;

            int totalAggregatesCount = (nonpersistentAggregatesCount ?? 0) + (persistentAggregatesCount ?? 0);

            if (totalAggregatesCount == 0)
            {
                return;
            }

            Task[] trackTasks = new Task[totalAggregatesCount];
            int    taskIndex  = 0;

            if (nonpersistentAggregatesCount != 0)
            {
                foreach (MetricAggregate telemetryItem in aggregates.NonpersistentAggregates)
                {
                    if (telemetryItem != null)
                    {
                        Task trackTask = this.telemetryPipeline.TrackAsync(telemetryItem, CancellationToken.None);
                        trackTasks[taskIndex++] = trackTask;
                    }
                }
            }

            if (aggregates.PersistentAggregates != null && aggregates.PersistentAggregates.Count != 0)
            {
                foreach (MetricAggregate telemetryItem in aggregates.PersistentAggregates)
                {
                    if (telemetryItem != null)
                    {
                        Task trackTask = this.telemetryPipeline.TrackAsync(telemetryItem, CancellationToken.None);
                        trackTasks[taskIndex++] = trackTask;
                    }
                }
            }

            CoreEventSource.Log.MetricManagerCreatedTasks(trackTasks.Length);
            Task.WaitAll(trackTasks);

            if (flush)
            {
                Task flushTask = this.telemetryPipeline.FlushAsync(CancellationToken.None);
                flushTask.Wait();
            }
        }
        public void StopAggregators()
        {
            DateTimeOffset dto = new DateTimeOffset(2017, 10, 2, 17, 5, 0, TimeSpan.FromHours(-7));

            var aggregationManager = new MetricAggregationManager();

            var measurementMetric = new MetricSeries(
                aggregationManager,
                "Measurement Metric",
                null,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

            var accumulatorMetric = new MetricSeries(
                aggregationManager,
                "Accumulator Metric",
                null,
                new MetricSeriesConfigurationForAccumulator(restrictToUInt32Values: false));

            // Cannot stop default:

            Assert.ThrowsException <ArgumentException>(() => aggregationManager.StopAggregators(MetricAggregationCycleKind.Default, dto));

            // Stop cycles that never started:

            AggregationPeriodSummary customPeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.Custom, dto);

            Assert.IsNotNull(customPeriod);
            Assert.IsNotNull(customPeriod.NonpersistentAggregates);
            Assert.IsNotNull(customPeriod.PersistentAggregates);

            Assert.AreEqual(0, customPeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(0, customPeriod.PersistentAggregates.Count);

            AggregationPeriodSummary quickpulsePeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.QuickPulse, dto);

            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(0, quickpulsePeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(0, quickpulsePeriod.PersistentAggregates.Count);

            // Track a value. Stop cycles that never started again. Observe that persistent cycle was active by default:

            measurementMetric.TrackValue(1);
            accumulatorMetric.TrackValue(2);

            customPeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.Custom, dto);
            Assert.IsNotNull(customPeriod);
            Assert.IsNotNull(customPeriod.NonpersistentAggregates);
            Assert.IsNotNull(customPeriod.PersistentAggregates);

            Assert.AreEqual(0, customPeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(1, customPeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", customPeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, customPeriod.PersistentAggregates[0].Data["Sum"]);

            quickpulsePeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.QuickPulse, dto);
            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(0, quickpulsePeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(1, quickpulsePeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", quickpulsePeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, quickpulsePeriod.PersistentAggregates[0].Data["Sum"]);

            // Now start cycles, track values and stop them again. Observe that values were tracked:

            aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.Custom, dto, futureFilter: null);
            aggregationManager.StartOrCycleAggregators(MetricAggregationCycleKind.QuickPulse, dto, futureFilter: null);

            measurementMetric.TrackValue(3);
            accumulatorMetric.TrackValue(4);

            customPeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.Custom, dto);
            Assert.IsNotNull(customPeriod);
            Assert.IsNotNull(customPeriod.NonpersistentAggregates);
            Assert.IsNotNull(customPeriod.PersistentAggregates);

            Assert.AreEqual(1, customPeriod.NonpersistentAggregates.Count);
            Assert.AreEqual("Measurement Metric", customPeriod.NonpersistentAggregates[0].MetricId);
            Assert.AreEqual(1, customPeriod.NonpersistentAggregates[0].Data["Count"]);
            Assert.AreEqual(3.0, customPeriod.NonpersistentAggregates[0].Data["Sum"]);

            Assert.AreEqual(1, customPeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", customPeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(6.0, customPeriod.PersistentAggregates[0].Data["Sum"]);

            quickpulsePeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.QuickPulse, dto);
            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(1, quickpulsePeriod.NonpersistentAggregates.Count);
            Assert.AreEqual("Measurement Metric", quickpulsePeriod.NonpersistentAggregates[0].MetricId);
            Assert.AreEqual(1, quickpulsePeriod.NonpersistentAggregates[0].Data["Count"]);
            Assert.AreEqual(3.0, quickpulsePeriod.NonpersistentAggregates[0].Data["Sum"]);

            Assert.AreEqual(1, quickpulsePeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", quickpulsePeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(6.0, quickpulsePeriod.PersistentAggregates[0].Data["Sum"]);

            measurementMetric.TrackValue(5);
            accumulatorMetric.TrackValue(6);

            quickpulsePeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.Custom, dto);
            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(0, quickpulsePeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(1, customPeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", customPeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(6.0, customPeriod.PersistentAggregates[0].Data["Sum"]);

            quickpulsePeriod = aggregationManager.StopAggregators(MetricAggregationCycleKind.QuickPulse, dto);
            Assert.IsNotNull(quickpulsePeriod);
            Assert.IsNotNull(quickpulsePeriod.NonpersistentAggregates);
            Assert.IsNotNull(quickpulsePeriod.PersistentAggregates);

            Assert.AreEqual(0, quickpulsePeriod.NonpersistentAggregates.Count);
            Assert.AreEqual(1, quickpulsePeriod.PersistentAggregates.Count);
            Assert.AreEqual("Accumulator Metric", quickpulsePeriod.PersistentAggregates[0].MetricId);
            Assert.AreEqual(12.0, quickpulsePeriod.PersistentAggregates[0].Data["Sum"]);
        }
        private static void StartOrCycleAggregatorsTest(MetricAggregationCycleKind cycleKind, bool supportsSettingFilters)
        {
            DateTimeOffset dto = new DateTimeOffset(2017, 10, 2, 17, 5, 0, TimeSpan.FromHours(-7));

            var aggregationManager = new MetricAggregationManager();

            var measurementMetric = new MetricSeries(
                aggregationManager,
                "Measurement Metric",
                null,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

            var accumulatorMetric = new MetricSeries(
                aggregationManager,
                "Accumulator Metric",
                null,
                new MetricSeriesConfigurationForAccumulator(restrictToUInt32Values: false));

            // Cycle once, get nothing:
            AggregationPeriodSummary period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);

            Assert.IsNotNull(period);
            Assert.IsNotNull(period.NonpersistentAggregates);
            Assert.IsNotNull(period.PersistentAggregates);

            Assert.AreEqual(0, period.NonpersistentAggregates.Count);
            Assert.AreEqual(0, period.PersistentAggregates.Count);

            // Record something, cycle, check for it:

            measurementMetric.TrackValue(1);
            accumulatorMetric.TrackValue(2);

            period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);
            Assert.IsNotNull(period);
            Assert.IsNotNull(period.NonpersistentAggregates);
            Assert.IsNotNull(period.PersistentAggregates);

            Assert.AreEqual(1, period.NonpersistentAggregates.Count);
            Assert.AreEqual(1, period.PersistentAggregates.Count);

            Assert.IsNotNull(period.NonpersistentAggregates[0]);
            Assert.IsNotNull(period.PersistentAggregates[0]);

            Assert.AreEqual("Measurement Metric", period.NonpersistentAggregates[0].MetricId);
            Assert.AreEqual(1, period.NonpersistentAggregates[0].Data["Count"]);
            Assert.AreEqual(1.0, period.NonpersistentAggregates[0].Data["Sum"]);

            Assert.AreEqual("Accumulator Metric", period.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, period.PersistentAggregates[0].Data["Sum"]);

            // Now we should be empty again for non-persistent. Persistent stays:

            period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);
            Assert.IsNotNull(period);
            Assert.IsNotNull(period.NonpersistentAggregates);
            Assert.IsNotNull(period.PersistentAggregates);

            Assert.AreEqual(0, period.NonpersistentAggregates.Count);
            Assert.AreEqual(1, period.PersistentAggregates.Count);

            Assert.IsNotNull(period.PersistentAggregates[0]);

            Assert.AreEqual("Accumulator Metric", period.PersistentAggregates[0].MetricId);
            Assert.AreEqual(2.0, period.PersistentAggregates[0].Data["Sum"]);

            // Now set a deny filter. Track. Expect to get nothng.
            // Note: for persistent, values tracked under Deny filter should persist for the future, for non-persistent they are just discarded.

            if (false == supportsSettingFilters)
            {
                Assert.ThrowsException <ArgumentException>(() => aggregationManager.StartOrCycleAggregators(
                                                               cycleKind,
                                                               dto,
                                                               futureFilter: new AcceptAllFilter()));
            }
            else
            {
                period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: new DenyAllFilter());
                Assert.IsNotNull(period);
                Assert.IsNotNull(period.NonpersistentAggregates);
                Assert.IsNotNull(period.PersistentAggregates);

                measurementMetric.TrackValue(3);
                accumulatorMetric.TrackValue(4);

                period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);
                Assert.IsNotNull(period);
                Assert.IsNotNull(period.NonpersistentAggregates);
                Assert.IsNotNull(period.PersistentAggregates);

                Assert.AreEqual(0, period.NonpersistentAggregates.Count);
                Assert.AreEqual(0, period.PersistentAggregates.Count);

                period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);
                Assert.IsNotNull(period);
                Assert.IsNotNull(period.NonpersistentAggregates);
                Assert.IsNotNull(period.PersistentAggregates);

                Assert.AreEqual(0, period.NonpersistentAggregates.Count);
                Assert.AreEqual(1, period.PersistentAggregates.Count);

                Assert.IsNotNull(period.PersistentAggregates[0]);

                Assert.AreEqual("Accumulator Metric", period.PersistentAggregates[0].MetricId);
                Assert.AreEqual(6.0, period.PersistentAggregates[0].Data["Sum"]);

                // Validate that deny filter was removed:

                measurementMetric.TrackValue(5);
                accumulatorMetric.TrackValue(6);

                period = aggregationManager.StartOrCycleAggregators(cycleKind, dto, futureFilter: null);
                Assert.IsNotNull(period);
                Assert.IsNotNull(period.NonpersistentAggregates);
                Assert.IsNotNull(period.PersistentAggregates);

                Assert.AreEqual(1, period.NonpersistentAggregates.Count);
                Assert.AreEqual(1, period.PersistentAggregates.Count);

                Assert.IsNotNull(period.PersistentAggregates[0]);
                Assert.IsNotNull(period.NonpersistentAggregates[0]);

                Assert.AreEqual("Accumulator Metric", period.PersistentAggregates[0].MetricId);
                Assert.AreEqual(12.0, period.PersistentAggregates[0].Data["Sum"]);

                Assert.AreEqual("Measurement Metric", period.NonpersistentAggregates[0].MetricId);
                Assert.AreEqual(5.0, period.NonpersistentAggregates[0].Data["Sum"]);
            }
        }
        private void RecordNormalMetric(TelemetryConfiguration telemetryPipeline)
        {
            MetricSeries durationMeric = telemetryPipeline.GetMetricManager().CreateNewSeries(
                "Test Metrics",
                "Item Add duration",
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

            MockContainerDataStructure dataStructure = new MockContainerDataStructure((c) => TimeSpan.FromSeconds(c));

            DateTimeOffset experimentStart = new DateTimeOffset(2017, 9, 14, 0, 0, 0, TimeSpan.Zero);

            // Stop the default minute-ly cycle so that it does not interfere with our virtual time debugging:
            Task fireAndForget = telemetryPipeline.GetMetricManager().StopDefaultAggregationCycleAsync();

            telemetryPipeline.GetMetricManager().StartOrCycleAggregators(CycleKind.Custom, experimentStart, futureFilter: null);

            const int ExperimentLengthSecs = 60 * 10;
            const int IntervalLengthSecs   = 60;

            int totalSecs    = 0;
            int intervalSecs = 0;

            int       itemsThisTime   = 0;
            const int maxItemsAtATime = 4;

            int operationsCount = 0;

            while (totalSecs < ExperimentLengthSecs)
            {
                itemsThisTime = (itemsThisTime + 1) % maxItemsAtATime;

                int addItemCount    = 1 + (itemsThisTime + 1) % maxItemsAtATime;
                int removeItemCount = 1 + itemsThisTime % maxItemsAtATime;

                Trace.WriteLine($"{totalSecs})");
                Trace.WriteLine(addItemCount);
                Trace.WriteLine(removeItemCount);
                Trace.WriteLine("");

                TimeSpan duration;

                dataStructure.AddItems(addItemCount, out duration);
                operationsCount++;

                int durationSecs = (int)duration.TotalSeconds;
                durationMeric.TrackValue(durationSecs);

                totalSecs    += durationSecs;
                intervalSecs += durationSecs;

                dataStructure.RemoveItems(removeItemCount, out duration);
                operationsCount++;

                durationSecs = (int)duration.TotalSeconds;
                durationMeric.TrackValue(durationSecs);

                totalSecs    += durationSecs;
                intervalSecs += durationSecs;

                if (intervalSecs >= IntervalLengthSecs)
                {
                    AggregationPeriodSummary aggregatedMetrics = telemetryPipeline.GetMetricManager().StartOrCycleAggregators(
                        CycleKind.Custom,
                        experimentStart.AddSeconds(totalSecs),
                        futureFilter: null);
                    Assert.IsNotNull(aggregatedMetrics);

                    IReadOnlyList <MetricAggregate> aggregates = aggregatedMetrics.NonpersistentAggregates;
                    Assert.IsNotNull(aggregates);
                    Assert.AreEqual(1, aggregates.Count);

                    MetricAggregate aggregate = aggregates[0];
                    Assert.IsNotNull(aggregates);

                    Assert.AreEqual(1.0, aggregate.Data["Min"]);
                    Assert.AreEqual(4.0, aggregate.Data["Max"]);
                    Assert.AreEqual(operationsCount, aggregate.Data["Count"]);
                    Assert.AreEqual("Item Add duration", aggregate.MetricId);
                    Assert.IsNotNull(aggregate.Dimensions);
                    Assert.AreEqual(0, aggregate.Dimensions.Count);
                    Assert.AreEqual((double)intervalSecs, aggregate.Data["Sum"]);
                    Assert.AreEqual(experimentStart.AddSeconds(totalSecs - intervalSecs), aggregate.AggregationPeriodStart);

                    intervalSecs   %= IntervalLengthSecs;
                    operationsCount = 0;

                    Assert.AreEqual(0, intervalSecs, "For the above to work, the number of wirtual secs must exactly fit into IntervalLengthSecs.");
                }
            }
            {
                AggregationPeriodSummary aggregatedMetrics = telemetryPipeline.GetMetricManager().StartOrCycleAggregators(
                    CycleKind.Custom,
                    experimentStart.AddSeconds(totalSecs),
                    futureFilter: null);
                Assert.IsNotNull(aggregatedMetrics);

                IReadOnlyList <MetricAggregate> aggregates = aggregatedMetrics.NonpersistentAggregates;
                Assert.IsNotNull(aggregates);
                Assert.AreEqual(0, aggregates.Count);
            }
            {
                durationMeric.TrackValue("7");
                durationMeric.TrackValue("8");
                durationMeric.TrackValue("9.0");
                totalSecs += 24;
            }
            {
                AggregationPeriodSummary aggregatedMetrics = telemetryPipeline.GetMetricManager().StopAggregators(
                    CycleKind.Custom,
                    experimentStart.AddSeconds(totalSecs));
                Assert.IsNotNull(aggregatedMetrics);

                IReadOnlyList <MetricAggregate> aggregates = aggregatedMetrics.NonpersistentAggregates;
                Assert.IsNotNull(aggregates);

                MetricAggregate aggregate = aggregates[0];
                Assert.IsNotNull(aggregates);

                Assert.AreEqual(7.0, aggregate.Data["Min"]);
                Assert.AreEqual(9.0, aggregate.Data["Max"]);
                Assert.AreEqual(3, aggregate.Data["Count"]);
                Assert.AreEqual("Item Add duration", aggregate.MetricId);
                Assert.IsNotNull(aggregate.Dimensions);
                Assert.AreEqual(0, aggregate.Dimensions.Count);
                Assert.AreEqual(24.0, aggregate.Data["Sum"]);
                Assert.AreEqual(experimentStart.AddSeconds(totalSecs - 24), aggregate.AggregationPeriodStart);
            }
        }