Skip to content

Enable dim cap to Metrics API #1244

@cijothomas

Description

@cijothomas

Problem Statement
The Metrics API currently does not offer a setting to apply dimension capping. Instead, when dimension values reach the limit, TrackValue/TryGetSeries returns false, and the value is not tracked. The expectation is that the caller is responsible for handling this situation.

Consider the following example with a metric "AnimalsSold" with 2 dimensions "Dimension1" and "Dimension". The config sets a limit of 2 to each dimensions.

var metConfig = new MetricConfiguration(seriesCountLimit: 100, valuesPerDimensionLimit:2,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

Metric animalsSold = client.GetMetric("AnimalsSold", "Dimension1", "Dimension2", metConfig);

// Start tracking.
animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value1");
animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value2");

// the following call gives 3rd unique value for dimension2, which is above the limit of 2.
animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value3");
//The above call does not track the metric, and returns false.

There only recommended action for clients to do when the cap is hit is to revisit the dimension value and ensure their cardinality is limited. This prevents future issues, but the tracked values are lost, affecting Metric charts etc.
Because of this, the recommended approach of tracking metric involve checking the return value and doing something (like sending a telemetry) to indicate caps are hit.


if (! animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value3"))
            {
                telemetryClient.TrackTrace("Metric value not tracked as value of one of the dimension exceeded the cap. Revisit the dimensions to ensure they are within the limits",
                                           SeverityLevel.Error);
            }

Instead of losing data when a cap is hit, some users may prefer to instead keep tracking the value, but with a special dimension value to which all the dimension values encountered after cap is hit is rolled into. In the above case, the dimension value "Dim2Value3" could be replaced with something like "Other". This approach may sound an easy solution, but it severely affects data quality, as SDK Metric System is only aware of dimension values from its current execution. Thus values rolled into "Other" will vary across application instances, across service instances or even across restarts of the same instance. In the above example, in one instance "Dim2Value3" may be rolled into "Other", but in another instance, "Dim2Value2" could be the one rolled into "Other".
When querying for this metric, it is impossible to determine what constitutes "Other". In short, if any dimension has "Other", then the metric value should not be filtered/split by that dimension.

Proposal:
Even though rolling of dimension values into a special value "Other" also has its own limitations, this may be the only approach many users want to take as it is simple to use and reason about. With proper usage of dimension values, the probability of ever hitting this is low.

Because of this, we are adding a new feature to the current metric API to do this dimension capping. At 1st stage, this will be exposed as a boolean field on MetricConfiguration, named ApplyDimensionCapping, and will be false by default, to keep current behavior. If this flag is turned on, then calls to TrackValue or TryGetDataSeries does not return false when any dimension exceeds its cap. It continues to track the values to the metric. But all the values encountered after the cap is hit will be rolled into a special value "DIMENSION_CAPPED". When querying for metric, the value "DIMENSION_CAPPED" will be like every other dimension value and its up to user to interpret it. (and ignore it)

ApplyDimensionCapping should be used only as a 'graceful' safeguard when some dimension accidentally goes out of limits. While overall metric value will be accurate, values involving any dimension which has "DIMENSION_CAPPED" should be ignored. (i.e No filtering or splitting involving this dimension)

example usage:

// current api and usage
var metConfig = new MetricConfiguration(seriesCountLimit: 100, valuesPerDimensionLimit:2,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));
            Metric animalsSold = client.GetMetric("AnimalsSold", "Dimension1", "Dimension2", metConfig);

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value1");
            animalsSold.TrackValue(150, "Dim1Value1", "Dim2Value1");

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value2");
            animalsSold.TrackValue(175, "Dim1Value1", "Dim2Value2");

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value3"); // This line returns false and value is not tracked.
            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value4"); // This line returns false and value is not tracked.
            // the above would result in animalsSold reporting the following 2 time series
          // "Dim1Value1", "Dim2Value1"  with sum of 150
          // "Dim1Value1", "Dim2Value2"  with sum of 175
         // i.e total animalSold will be 150+175=325, even though the actual animalssold is 525. The last 2 calls are not tracked, hence users must inspect return value and take action.
// new usage based on this proposal
var metConfig = new MetricConfiguration(seriesCountLimit: 100, valuesPerDimensionLimit:2,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));
            metConfig.ApplyDimensionCapping = true;
            Metric animalsSold = client.GetMetric("AnimalsSold", "Dimension1", "Dimension2", metConfig);

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value1");
            animalsSold.TrackValue(150, "Dim1Value1", "Dim2Value1");

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value2");
            animalsSold.TrackValue(175, "Dim1Value1", "Dim2Value2");

            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value3");            
            animalsSold.TrackValue(100, "Dim1Value1", "Dim2Value4"); 

            // the above would result in animalsSold reporting the following 3 time series
          // "Dim1Value1", "Dim2Value1"  with sum of 150
          // "Dim1Value1", "Dim2Value2"  with sum of 175
          // "Dim1Value1", "DIMENSION_CAPPED"  with sum of 200
         //   total animalSold will be 525
        // Since "Dimension2" has the value DIMENSION_CAPPED, queries like AnimalsSold with "Dimension2=value" should not be trusted. However, queries like AnimalsSold (total) or AnimalsSold with "Dimension1=value" represent true values.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions