void IPacket.ReadFields(PacketDefinition definition, SerializedPacket packet) { switch (definition.Version) { case 1: int rawSampleType; packet.GetField("metricSampleType", out rawSampleType); m_MetricSampleType = (MetricSampleType)rawSampleType; break; default: throw new GibraltarPacketVersionException(definition.Version); } }
/// <summary> /// Indicates whether two samples are required to calculate a metric value or not. /// </summary> /// <remarks> /// Many sample types require multiple samples to determine an output value because they work with /// the change between two points. /// </remarks> internal static bool SampledMetricTypeRequiresMultipleSamples(MetricSampleType metricSampleType) { bool multipleRequired; //based purely on the counter type, according to Microsoft documentation switch (metricSampleType) { case MetricSampleType.RawFraction: case MetricSampleType.RawCount: //these just require one sample multipleRequired = false; break; default: //everything else requires more than one sample multipleRequired = true; break; } return(multipleRequired); }
/// <summary> /// Generate a display caption for the supplied sample metric type /// </summary> /// <param name="metricSampleType">The sample metric type to make a caption for</param> /// <returns>An end-user display caption</returns> public static string SampledMetricTypeCaption(MetricSampleType metricSampleType) { string returnVal; switch (metricSampleType) { case MetricSampleType.TotalCount: returnVal = "Count of Items"; break; case MetricSampleType.TotalFraction: returnVal = "Percentage"; break; case MetricSampleType.IncrementalCount: returnVal = "Count of Items"; break; case MetricSampleType.IncrementalFraction: returnVal = "Percentage"; break; case MetricSampleType.RawCount: returnVal = "Count of Items"; break; case MetricSampleType.RawFraction: returnVal = "Percentage"; break; default: throw new ArgumentOutOfRangeException(nameof(metricSampleType)); } return(returnVal); }
/// <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> /// Create a new custom sampled metric definition packet from the provided information /// </summary> /// <remarks>Definition packets are the lightweight internals used for persistence.</remarks> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The specific unit representation of the data being captured for this metric</param> /// <param name="unitCaption">The display caption for the calculated values captured under this metric.</param> /// <param name="description">A description of what is tracked by this metric, suitable for end-user display.</param> public CustomSampledMetricDefinitionPacket(string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string unitCaption, string description) : base(metricTypeName, categoryName, counterName, unitCaption, description) { m_MetricSampleType = metricSampleType; }
/// <summary> /// Create a new custom sampled metric definition packet from the provided information /// </summary> /// <remarks>Definition packets are the lightweight internals used for persistence.</remarks> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The specific unit representation of the data being captured for this metric</param> public CustomSampledMetricDefinitionPacket(string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType) : base(metricTypeName, categoryName, counterName) { m_MetricSampleType = metricSampleType; }
/// <summary> /// Create a new metric definition for the active log. /// </summary> /// <remarks>At any one time there should only be one metric definition with a given combination of /// metric type, category, and counter name. These values together are used to correlate metrics /// between sessions. The metric definition will automatically be added to the provided collection.</remarks> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> /// <param name="unitCaption">The display caption for the calculated values captured under this metric.</param> /// <param name="description">A description of what is tracked by this metric, suitable for end-user display.</param> public CustomSampledMetricDefinition(string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string unitCaption, string description) : base(Log.Metrics, new CustomSampledMetricDefinitionPacket(metricTypeName, categoryName, counterName, metricSampleType, unitCaption, description)) { m_RequiresMultipleSamples = SampledMetricTypeRequiresMultipleSamples(metricSampleType); }
/// <summary>Creates a new metric definition from the provided information, or returns an existing matching definition if found.</summary> /// <remarks>If the metric definition doesn't exist, it will be created. /// If the metric definition does exist, but is not a Custom Sampled Metric (or a derived class) an exception will be thrown. /// Definitions are looked up and added to the active logging metrics collection (Log.Metrics)</remarks> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> /// <param name="unitCaption">The display caption for the calculated values captured under this metric.</param> /// <param name="description">A description of what is tracked by this metric, suitable for end-user display.</param> public static CustomSampledMetricDefinition AddOrGet(string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string unitCaption, string description) { //just forward into our call that requires the definition to be specified return(AddOrGet(Log.Metrics, metricTypeName, categoryName, counterName, metricSampleType, unitCaption, description)); }
/// <summary>Creates a new metric definition from the provided information, or returns an existing matching definition if found.</summary> /// <remarks>If the metric definition doesn't exist, it will be created. /// If the metric definition does exist, but is not a Custom Sampled Metric (or a derived class) an exception will be thrown. /// Definitions are looked up and added to the provided definitions dictionary.</remarks> /// <param name="definitions">The definitions dictionary this definition is a part of</param> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> /// <param name="unitCaption">The display caption for the calculated values captured under this metric.</param> /// <param name="description">A description of what is tracked by this metric, suitable for end-user display.</param> public static CustomSampledMetricDefinition AddOrGet(MetricDefinitionCollection definitions, string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string unitCaption, string description) { //we must have a definitions collection, or we have a problem if (definitions == null) { throw new ArgumentNullException(nameof(definitions)); } //we need to find the definition, adding it if necessary string definitionKey = GetKey(metricTypeName, categoryName, counterName); IMetricDefinition definition; //we need the try and create to be atomic in a multi-threaded environment lock (definitions.Lock) { if (definitions.TryGetValue(definitionKey, out definition)) { //if the metric definition exists, but is of the wrong type we have a problem. if ((definition is CustomSampledMetricDefinition) == false) { throw new ArgumentException("A metric already exists with the provided type, category, and counter name but it is not compatible with being a custom sampled metric. Please use a different counter name.", nameof(counterName)); } } else { //we didn't find one, make a new one definition = new CustomSampledMetricDefinition(definitions, metricTypeName, categoryName, counterName, metricSampleType, unitCaption, description); } } return((CustomSampledMetricDefinition)definition); }
/// <summary> /// Create a new metric definition. /// </summary> /// <remarks>At any one time there should only be one metric definition with a given combination of /// metric type, category, and counter name. These values together are used to correlate metrics /// between sessions. The metric definition will automatically be added to the provided collection.</remarks> /// <param name="definitions">The definitions dictionary this definition is a part of</param> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> public CustomSampledMetricDefinition(MetricDefinitionCollection definitions, string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType) : base(definitions, new CustomSampledMetricDefinitionPacket(metricTypeName, categoryName, counterName, metricSampleType)) { m_RequiresMultipleSamples = SampledMetricTypeRequiresMultipleSamples(metricSampleType); }
/// <summary>Creates a new metric instance from the provided definition information, or returns any existing instance if found.</summary> /// <remarks>If the metric definition doesn't exist, it will be created. If the metric doesn't exist, it will be created. /// If the metric definition does exist, but is not a Custom Sampled Metric (or a derived class) an exception will be thrown.</remarks> /// <param name="definitions">The definitions dictionary this definition is a part of</param> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> /// <param name="instanceName">The unique name of this instance within the metric's collection.</param> public static CustomSampledMetric AddOrGet(MetricDefinitionCollection definitions, string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string instanceName) { //we must have a definitions collection, or we have a problem if (definitions == null) { throw new ArgumentNullException(nameof(definitions)); } //we need to find the definition, adding it if necessary string definitionKey = MetricDefinition.GetKey(metricTypeName, categoryName, counterName); IMetricDefinition definition; //Establish a lock on the definitions collection so our lookup & create are atomic. lock (definitions.Lock) { if (definitions.TryGetValue(definitionKey, out definition)) { //if the metric definition exists, but is of the wrong type we have a problem. if ((definition is CustomSampledMetricDefinition) == false) { throw new ArgumentException("A metric already exists with the provided type, category, and counter name but it is not compatible with being a custom sampled metric. Please use a different counter name.", nameof(counterName)); } } else { //we didn't find one, make a new one definition = new CustomSampledMetricDefinition(definitions, metricTypeName, categoryName, counterName, metricSampleType); } } //now we have our definition, proceed to create a new metric if it doesn't exist string metricKey = MetricDefinition.GetKey(metricTypeName, categoryName, counterName, instanceName); IMetric metric; //see if we can get the metric already. If not, we'll create it lock (((MetricCollection)definition.Metrics).Lock) { if (definition.Metrics.TryGetValue(metricKey, out metric) == false) { metric = new CustomSampledMetric((CustomSampledMetricDefinition)definition, instanceName); } } return((CustomSampledMetric)metric); }
/// <summary>Creates a new metric instance from the provided definition information, or returns any existing instance if found.</summary> /// <remarks>If the metric definition doesn't exist, it will be created. If the metric doesn't exist, it will be created. /// If the metric definition does exist, but is not a Custom Sampled Metric (or a derived class) an exception will be thrown. /// Definitions are looked up and added to the active logging metrics collection (Log.Metrics)</remarks> /// <param name="metricTypeName">The unique metric type</param> /// <param name="categoryName">The name of the category with which this definition is associated.</param> /// <param name="counterName">The name of the definition within the category.</param> /// <param name="metricSampleType">The type of data captured for each metric under this definition.</param> /// <param name="instanceName">The unique name of this instance within the metric's collection.</param> public static CustomSampledMetric AddOrGet(string metricTypeName, string categoryName, string counterName, MetricSampleType metricSampleType, string instanceName) { //just forward into our call that requires the definition to be specified return(AddOrGet(Log.Metrics, metricTypeName, categoryName, counterName, metricSampleType, instanceName)); }