/// <summary>
        /// Compute the counter value for this sample compared with the provided baseline sample (if any)
        /// </summary>
        /// <remarks>
        /// A baseline sample is required when the current metric requires multiple samples to determine results.
        /// The baseline sample must be for a date and time prior to this sample for correct results.
        /// </remarks>
        /// <param name="baselineSample">The previous baseline sample to calculate a difference for</param>
        /// <returns>The calculated counter value</returns>
        public override double ComputeValue(SampledMetricSample baselineSample)
        {
            if ((baselineSample == null) && (RequiresMultipleSamples))
            {
                throw new ArgumentNullException(nameof(baselineSample), "A baseline metric sample is required and none was provided.");
            }

            if ((baselineSample != null) && (baselineSample.Timestamp > Timestamp))
            {
                throw new ArgumentOutOfRangeException(nameof(baselineSample), baselineSample.Timestamp, "The baseline sample must be for a date & time before this sample to be valid for comparison.");
            }

            //Now lets do some math!  The math we have to do depends on the sampled metric type.
            MetricSampleType metricType = Metric.Definition.MetricSampleType;

            //First, eliminate the values that don't need math at all
            if (RequiresMultipleSamples == false)
            {
                return Value;
            }

            //and now we're down to stuff that requires math.
            double calculatedResult;
            CustomSampledMetricSamplePacket baselineSamplePacket = (CustomSampledMetricSamplePacket) baselineSample.Packet;

            if (metricType == MetricSampleType.TotalCount)
            {
                //here we want to calculate the difference between the start and end of our sampled period, ignoring interim samples.
                calculatedResult = Packet.RawValue - baselineSamplePacket.RawValue;
            }
            else if (metricType == MetricSampleType.TotalFraction)
            {
                double valueDelta = Packet.RawValue - baselineSamplePacket.RawValue;
                double baseDelta = Packet.BaseValue - baselineSamplePacket.BaseValue;

                //Protect from a divide by zero case.
                if ((baseDelta == 0) && (valueDelta != 0))
                {
                    throw new DivideByZeroException(string.Format(CultureInfo.InvariantCulture, "The baseline delta is zero however the value delta is not, indicating a data collection problem in the original data.  Value delta: {0}", valueDelta));
                }

                calculatedResult = valueDelta / baseDelta;
            }
            else if (metricType == MetricSampleType.IncrementalCount) 
            {
                //The new value is just the total at the end, so we just get the value property which knows enough to sum things.
                calculatedResult = Value;
            }
            else if (metricType == MetricSampleType.IncrementalFraction)
            {
                double value = Value;
                double baseValue = BaseValue;

                //Protect from a divide by zero case.
                if ((baseValue == 0) && (value != 0))
                {
                    throw new DivideByZeroException(string.Format(CultureInfo.InvariantCulture, "The baseline value is zero however the value is not, indicating a data collection problem in the original data.  Value: {0}", value));
                }

                calculatedResult = value / baseValue;
            }
            else 
            {
                // This is dumb, but FxCop doesn't seem to notice that the duplicate casts are in non-overlapping code paths.
                // So to make it happy, moving the cast outside these last two if's (now nested instead of chained).
                // Note: This will throw an exception if it fails to cast, before we check the MetricSampleType enum.
                CustomSampledMetricSample customSample = (CustomSampledMetricSample)baselineSample;
                if (metricType == MetricSampleType.RawCount)
                {
                    //we need to do a weighted average of the values in the range
                    //now life gets more fun - we have to do a weighted average of everything in between the baseline sample and this sample.
                    CustomSampledMetricSample[] samples = SampleRange(customSample);
                    calculatedResult = CalculateWeightedAverageValue(samples);
                }
                else if (metricType == MetricSampleType.RawFraction)
                {
                    //we do a weighted average of the values in the range, then divide
                    CustomSampledMetricSample[] samples = SampleRange(customSample);
                    double value = CalculateWeightedAverageValue(samples);
                    double baseValue = CalculateWeightedAverageBaseValue(samples);

                    //Protect from a divide by zero case.
                    if ((baseValue == 0) && (value != 0))
                    {
                        throw new DivideByZeroException(string.Format(CultureInfo.InvariantCulture, "The baseline value is zero however the value is not, indicating a data collection problem in the original data.  Value: {0}", value));
                    }

                    calculatedResult = value / baseValue;
                }
                else
                {
                    //oh hell.  We probably should have used a switch statement, but I didn't. Why?  Perhaps someone will 
                    //call that out in code review, but I think it was because of how much code is in each of these cases.
                    throw new ArgumentOutOfRangeException();
                }
            }

            return calculatedResult;
        }
 /// <summary>
 /// Compute the resultant value for this sample compared with the provided baseline sample
 /// </summary>
 /// <remarks>
 /// The baseline sample must be for a date and time prior to this sample for correct results.
 /// </remarks>
 /// <param name="baselineSample">The previous baseline sample to calculate a difference for</param>
 /// <returns>The calculated counter value</returns>
 public abstract double ComputeValue(SampledMetricSample baselineSample);
 /// <summary>
 /// Compares this sampled metric object to another.
 /// </summary>
 /// <param name="other"></param>
 /// <returns></returns>
 public int CompareTo(SampledMetricSample other)
 {
     //gateway to our base object
     return(base.CompareTo(other));
 }
예제 #4
0
        /// <summary>
        /// Calculate the value set for the provided date range inclusive with samples exactly on the provided interval.
        /// </summary>
        /// <remarks></remarks>
        /// <param name="interval">The interval to bias to.</param>
        /// <param name="intervals">The number of intervals to have between each value exactly.</param>
        /// <param name="startDateTime">The exact date and time desired to start the value set.</param>
        /// <param name="endDateTime">The exact end date and time to not exceed.</param>
        /// <returns>A new metric value set with all calculated values.</returns>
        private MetricValueCollection OnCalculateValuesOnInterval(MetricSampleInterval interval, int intervals, DateTimeOffset startDateTime, DateTimeOffset endDateTime)
        {
            MetricValueCollection newMetricValueCollection = new MetricValueCollection(this, interval, intervals, Definition.UnitCaption);

            //based on the requested interval, calculate the delta between each sample and the tolerance (how close we have to be
            //to the requested time to be pulled forward in time as the best sample)
            TimeSpan sampleTolerance = CalculateOffsetTolerance(interval);

            DateTimeOffset targetDateTime          = startDateTime;
            DateTimeOffset targetToleranceDateTime = targetDateTime + sampleTolerance;

            SampledMetricSample baselineSample = null;
            double previousMetricValue         = 0;
            int    firstSampleIndex            = 0;

            //First, we need to find the value for the start date & time and the sample index into the collection.
            for (int curSampleIndex = 0; curSampleIndex < base.Samples.Count; curSampleIndex++)
            {
                SampledMetricSample curSample = (SampledMetricSample)Samples[curSampleIndex];

                //is this the sample that exactly covers our target date & time?  It is if it's equal to our target, or
                //it is a more exact fit than the next sample.
                if ((curSample.Timestamp == targetDateTime) ||
                    ((curSample.Timestamp < targetToleranceDateTime) && (Samples[curSampleIndex + 1].Timestamp > targetToleranceDateTime)))
                {
                    //yes, this sample is the best fit for our start date & time.  The next one is after our "pull forward" tolerance.
                    //but to get a value, we may have to back up one more interval from this sample to establish its value
                    if (curSample.RequiresMultipleSamples)
                    {
                        //back up as much as we can - as close to one full interval as possible so we get the most accurate initial value sample
                        DateTimeOffset baselineTargetDateTime = CalculateOffset(targetDateTime, interval, -intervals);

                        //start with the first sample before us... if there is one.
                        for (int baselineSampleIndex = curSampleIndex - 1; baselineSampleIndex >= 0; baselineSampleIndex--)
                        {
                            //keep walking back until we are before our target (or run out of choices)
                            baselineSample = (SampledMetricSample)Samples[curSampleIndex];
                            if (baselineSample.Timestamp <= baselineTargetDateTime)
                            {
                                //this is our best fit - it's the first that covers our baseline date time.
                                break;
                            }
                        }
                    }

                    //we either got our baseline or we didn't - if we didn't and we need it we can't calculate value.
                    if ((baselineSample != null) || (curSample.RequiresMultipleSamples == false))
                    {
                        //Calculate the initial metric value sample and add it to the collection.
                        previousMetricValue =
                            CalculateSample(newMetricValueCollection, baselineSample, curSample, startDateTime);
                    }

                    //and this sample becomes our baseline into the next routine
                    baselineSample   = curSample;
                    firstSampleIndex = curSampleIndex;
                    break;  //and we're done - we only wanted to get the start value.
                }
            }

            //Now that we've found our first sample, and the offset into the collection for the first sample, we can go on (provide there are more samples)
            firstSampleIndex++;   //because we used the current sample in the for loop above.
            if (firstSampleIndex < (Samples.Count - 1))
            {
                //we now want to look for the first sample after the start date.  If the user was silly and requested an end date that is less
                //than one interval from the start date, we'll never get into our while loop this way.
                targetDateTime          = CalculateOffset(targetDateTime, interval, intervals);
                targetToleranceDateTime = targetDateTime + sampleTolerance;

                int curSampleIndex            = firstSampleIndex; //we start with the first sample after what was used above.
                SampledMetricSample curSample = null;

                //keep looping until we fill up the timespan or run out of samples.
                while ((targetDateTime <= endDateTime) && (curSampleIndex < (Samples.Count - 1)))
                {
                    //if we have no sample on deck, we must have used it in the last pass of the loop (or this is the first pass)
                    //so get it now.
                    if (curSample == null)
                    {
                        curSample = (SampledMetricSample)Samples[curSampleIndex];
                    }

                    //is this the sample that exactly covers our target date & time?  It is if it's equal to our target, or
                    //it is a more exact fit than the next sample.
                    if ((curSample.Timestamp == targetDateTime) ||
                        ((curSample.Timestamp < targetToleranceDateTime) && (Samples[curSampleIndex + 1].Timestamp > targetToleranceDateTime)))
                    {
                        //yes, this sample is the best fit for our start date & time.  The next one is after our "pull forward" tolerance.
                        if ((baselineSample != null) || (curSample.RequiresMultipleSamples == false))
                        {
                            //Calculate the next metric value sample and add it to the collection.
                            previousMetricValue =
                                CalculateSample(newMetricValueCollection, baselineSample, curSample, targetDateTime);
                        }

                        //and this sample becomes our baseline into the next round
                        baselineSample = curSample;

                        //and we need a new current sample.
                        curSample = null;
                        curSampleIndex++;

                        //and now we have recorded a metric value for the requested date & time if possible, move that forward.
                        targetDateTime          = CalculateOffset(targetDateTime, interval, intervals);
                        targetToleranceDateTime = targetDateTime + sampleTolerance;
                    }
                    else if (curSample.Timestamp < targetToleranceDateTime)
                    {
                        //this sample AND the next sample are both before target tolerance - we're going to skip this guy and record nothing
                        //(because we have more samples than we need for the interval we want)
                        curSample = null;
                        curSampleIndex++;
                    }
                    else
                    {
                        //the sample on deck doesn't apply yet - it's in the future.  We need to "invent" a sample for the current date and time.
                        //we'll just re-record the last metric value
                        new MetricValue(newMetricValueCollection, targetDateTime, previousMetricValue);

                        //and now we have definitely recorded a metric value for the requested date & time, move that forward.
                        targetDateTime          = CalculateOffset(targetDateTime, interval, intervals);
                        targetToleranceDateTime = targetDateTime + sampleTolerance;
                    }
                }
            }

            return(newMetricValueCollection);
        }
예제 #5
0
        /// <summary>
        /// Calculate one effective value from the provided objects
        /// </summary>
        /// <param name="metricValueCollection">The value set to add the new value to</param>
        /// <param name="baselineSample">The baseline to calculate from.  Only used (and required) for metrics that require multiple samples.</param>
        /// <param name="valueSample">The value sample to perform the calculation with.</param>
        /// <param name="timeStampOverride">An optional override timestamp</param>
        private static double CalculateSample(MetricValueCollection metricValueCollection, SampledMetricSample baselineSample, SampledMetricSample valueSample, DateTimeOffset?timeStampOverride)
        {
            double calculatedValue;

            if (valueSample.RequiresMultipleSamples == false)
            {
                calculatedValue = valueSample.ComputeValue(); //we just need one
            }
            else
            {
                calculatedValue = valueSample.ComputeValue(baselineSample);
            }

            DateTimeOffset timeStamp = ((timeStampOverride == null) ? valueSample.Timestamp : (DateTimeOffset)timeStampOverride);

            //now create & add the value to our values collection.
            new MetricValue(metricValueCollection, timeStamp, calculatedValue);

            return(calculatedValue);
        }
예제 #6
0
        /// <summary>
        /// Calculate a value set for the provided date range inclusive using all of the sample data, even if
        /// that produces irregular sample intervals.
        /// </summary>
        /// <param name="startDateTime">The exact date and time desired to start the value set.</param>
        /// <param name="endDateTime">The exact end date and time to not exceed.</param>
        /// <returns>A new metric value set with all calculated values.</returns>
        private MetricValueCollection OnCalculateValuesShortest(DateTimeOffset startDateTime, DateTimeOffset endDateTime)
        {
            MetricValueCollection newMetricValueCollection = new MetricValueCollection(this, MetricSampleInterval.Shortest, 0, Definition.UnitCaption);

            //ohhhhkay, now we need to figure out what samples we want to include and not include.
            //we want to start by getting the sample one interval before the user wants to have values for so we can have a value exactly
            //on that starting point
            SampledMetricSample baselineSample         = null;
            SampledMetricSample previousBaselineSample = null;

            foreach (SampledMetricSample curSample in base.Samples)
            {
                //is this sample in our range?
                if (curSample.Timestamp < startDateTime)
                {
                    //It isn't, but there are still reasons we'd be interested in it.
                    //because the next one could be in the range and we'll want to mess with this one.
                    previousBaselineSample = baselineSample;
                    baselineSample         = curSample;
                }
                else if (curSample.Timestamp > endDateTime)
                {
                    //no, BUT if it's the first sample after our range, and we didn't end EXACTLY
                    //on the range then we want to include it, but we'll short it back to the end of the timeframe.
                    if ((baselineSample != null) && (baselineSample.Timestamp < endDateTime))
                    {
                        CalculateSample(newMetricValueCollection, baselineSample, curSample, endDateTime);
                    }

                    //and no matter what, we're done.  Either we got a special "Bracket" sample, or there isn't one.
                    break;
                }
                else
                {
                    //the sample is in the range - neither after nor before.  We definitely use this sample.

                    //but before we do this, is this the FIRST one in the range?
                    if ((baselineSample != null) && (baselineSample.Timestamp < startDateTime))
                    {
                        //yes it is - the baseline is before our start date, and we're after or equal.
                        //If it's not only the last before we get in but ALSO the first one in isn't EXACTLY on the line, we need to
                        //create a fake interim sample, based on this guy and the one BEFORE him.
                        if (curSample.Timestamp > startDateTime)
                        {
                            //we have to insert a fake sample based on the baseline and the guy that came before him.
                            //but guard against us not having enough previous information to do this (this will happen more often than not)
                            if ((previousBaselineSample != null) || (baselineSample.RequiresMultipleSamples == false))
                            {
                                CalculateSample(newMetricValueCollection, previousBaselineSample, baselineSample, startDateTime);
                            }
                        }
                    }

                    //calculate our sample, however we have to guard against there being no baseline
                    // (because we're the first sample of a metric that requires multiple samples)
                    if ((baselineSample != null) || (curSample.RequiresMultipleSamples == false))
                    {
                        CalculateSample(newMetricValueCollection, baselineSample, curSample, null);
                    }

                    //and this sample becomes the new baseline
                    baselineSample = curSample;
                }
            }

            return(newMetricValueCollection);
        }