Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/Generators/Microsoft.Gen.Metrics/MetricFactoryEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,15 @@ private void GenMetricFactoryMethods(MetricMethod metricMethod, string nspace)
OutOpenBrace();
OutLn($"return {GetMetricDictionaryName(metricMethod)}.GetOrAdd({meterParam.Name}, static _meter =>");
OutLn(" {");
OutLn($" var instrument = _meter.{createMethodName}(@\"{metricMethod.MetricName}\");");
if (string.IsNullOrEmpty(metricMethod.MetricUnit))
{
OutLn($" var instrument = _meter.{createMethodName}(@\"{metricMethod.MetricName}\");");
}
else
{
OutLn($" var instrument = _meter.{createMethodName}(@\"{metricMethod.MetricName}\", \"{metricMethod.MetricUnit}\");");
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
}

OutLn($" return new {nsprefix}{metricMethod.MetricTypeName}(instrument);");
OutLn(" });");
OutCloseBrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal sealed class MetricMethod
public Dictionary<string, string> TagDescriptionDictionary = [];
public string? Name;
public string? MetricName;
public string? MetricUnit;
public string? XmlDefinition;
public bool IsExtensionMethod;
public string Modifiers = string.Empty;
Expand Down
35 changes: 25 additions & 10 deletions src/Generators/Microsoft.Gen.Metrics/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -273,13 +274,14 @@ private static bool TryGetTagNameFromAttribute(ISymbol symbol, SymbolHolder symb
return false;
}

private (string metricName, HashSet<string> tagNames, Dictionary<string, string> dimensionDescriptions) ExtractAttributeParameters(
private (string metricName, string metricUnit, HashSet<string> tagNames, Dictionary<string, string> dimensionDescriptions) ExtractAttributeParameters(
AttributeData attribute,
SemanticModel semanticModel)
{
var tagHashSet = new HashSet<string>();
var tagDescriptionMap = new Dictionary<string, string>();
string metricNameFromAttribute = string.Empty;
string metricUnitFromAttribute = string.Empty;
if (!attribute.NamedArguments.IsDefaultOrEmpty)
{
foreach (var arg in attribute.NamedArguments)
Expand All @@ -288,7 +290,11 @@ private static bool TryGetTagNameFromAttribute(ISymbol symbol, SymbolHolder symb
arg.Key is "MetricName" or "Name")
{
metricNameFromAttribute = (arg.Value.Value ?? string.Empty).ToString().Replace("\\\\", "\\");
break;
}
else if (arg.Value.Kind == TypedConstantKind.Primitive &&
arg.Key is "Unit")
{
metricUnitFromAttribute = (arg.Value.Value ?? string.Empty).ToString();
}
}
}
Expand Down Expand Up @@ -330,7 +336,7 @@ private static bool TryGetTagNameFromAttribute(ISymbol symbol, SymbolHolder symb
}
}

return (metricNameFromAttribute, tagHashSet, tagDescriptionMap);
return (metricNameFromAttribute, metricUnitFromAttribute, tagHashSet, tagDescriptionMap);
}

private string GetSymbolXmlCommentSummary(ISymbol symbol)
Expand Down Expand Up @@ -422,20 +428,20 @@ private void GetTagDescription(
if (!methodAttribute.ConstructorArguments.IsDefaultOrEmpty
&& methodAttribute.ConstructorArguments[0].Kind == TypedConstantKind.Type)
{
KeyValuePair<string, TypedConstant> namedArg = default;
ImmutableArray<KeyValuePair<string, TypedConstant>> namedArgs = ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty;
var ctorArg = methodAttribute.ConstructorArguments[0];

if (!methodAttribute.NamedArguments.IsDefaultOrEmpty)
{
namedArg = methodAttribute.NamedArguments[0];
namedArgs = methodAttribute.NamedArguments;
}

strongTypeAttrParams = ExtractStrongTypeAttributeParameters(ctorArg, namedArg, symbols);
strongTypeAttrParams = ExtractStrongTypeAttributeParameters(ctorArg, namedArgs, symbols);
}
else
{
var parameters = ExtractAttributeParameters(methodAttribute, semanticModel);
(strongTypeAttrParams.MetricNameFromAttribute, strongTypeAttrParams.TagHashSet, strongTypeAttrParams.TagDescriptionDictionary) = parameters;
(strongTypeAttrParams.MetricNameFromAttribute, strongTypeAttrParams.MetricUnitFromAttribute, strongTypeAttrParams.TagHashSet, strongTypeAttrParams.TagDescriptionDictionary) = parameters;
}

string metricNameFromMethod = methodSymbol.ReturnType.Name;
Expand All @@ -444,6 +450,7 @@ private void GetTagDescription(
{
Name = methodSymbol.Name,
MetricName = string.IsNullOrWhiteSpace(strongTypeAttrParams.MetricNameFromAttribute) ? metricNameFromMethod : strongTypeAttrParams.MetricNameFromAttribute,
MetricUnit = strongTypeAttrParams.MetricUnitFromAttribute,
InstrumentKind = instrumentKind,
GenericType = genericType.ToDisplayString(_genericTypeSymbolFormat),
TagKeys = strongTypeAttrParams.TagHashSet,
Expand Down Expand Up @@ -605,14 +612,22 @@ private void Diag(DiagnosticDescriptor desc, Location? location, params object?[

private StrongTypeAttributeParameters ExtractStrongTypeAttributeParameters(
TypedConstant constructorArg,
KeyValuePair<string, TypedConstant> namedArgument,
ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments,
SymbolHolder symbols)
{
var strongTypeAttributeParameters = new StrongTypeAttributeParameters();

if (namedArgument is { Key: "Name", Value.Value: { } })
// i want to check namedArguments array for key == name and for key == unit
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
foreach (var namedArgument in namedArguments)
{
strongTypeAttributeParameters.MetricNameFromAttribute = namedArgument.Value.Value.ToString();
if (namedArgument.Key == "Name" && namedArgument.Value.Value is { } nameValue)
{
strongTypeAttributeParameters.MetricNameFromAttribute = nameValue.ToString();
}
else if (namedArgument.Key == "Unit" && namedArgument.Value.Value is { } unitValue)
{
strongTypeAttributeParameters.MetricUnitFromAttribute = unitValue.ToString();
}
}

if (constructorArg.IsNull ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.Gen.Metrics;
internal sealed class StrongTypeAttributeParameters
{
public string MetricNameFromAttribute = string.Empty;
public string MetricUnitFromAttribute = string.Empty;
public HashSet<string> TagHashSet = [];
public Dictionary<string, string> TagDescriptionDictionary = [];
public List<StrongTypeConfig> StrongTypeConfigs = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ public CounterAttribute(Type type)
/// Gets the type that supplies metric tags values.
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
/// </summary>
public Type? Type { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,9 @@ public CounterAttribute(Type type)
/// Gets the type that supplies metric tag values.
/// </summary>
public Type? Type { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public GaugeAttribute(Type type)
/// Gets the type that supplies metric tag values.
/// </summary>
public Type? Type { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public HistogramAttribute(Type type)
/// Gets the type that supplies metric tag values.
/// </summary>
public Type? Type { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,9 @@ public HistogramAttribute(Type type)
/// Gets the type that supplies metric tag values.
/// </summary>
public Type? Type { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ public TagNameAttribute(string name)
/// Gets the name of the tag.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets or sets the unit of measurement for the metric.
/// </summary>
public string? Unit { get; set; }
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
using TestClasses;
using Xunit;
namespace Microsoft.Gen.Metrics.Test;

public partial class MetricTests
{
[Fact]
public void ValidateCounterWithUnit()
{
// Verify that a counter created with a unit works correctly
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
using var collector = new MetricCollector<long>(_meter, "CounterWithUnit");

// You'll need to add this to TestClasses/MetricsWithUnit.cs
CounterWithUnit counter = MetricsWithUnit.CreateCounterWithUnit(_meter);
counter.Add(100L);

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(100L, measurement.Value);
Assert.Empty(measurement.Tags);
Assert.NotNull(collector.Instrument);
Assert.Equal("seconds", collector.Instrument.Unit);
}

[Fact]
public void ValidateHistogramWithUnit()
{
// Verify that a histogram created with a unit works correctly
using var collector = new MetricCollector<long>(_meter, "HistogramWithUnit");

// You'll need to add this to TestClasses/HistogramTestExtensions.cs
Comment thread
mariamgerges marked this conversation as resolved.
Outdated
HistogramWithUnit histogram = MetricsWithUnit.CreateHistogramWithUnit(_meter);
histogram.Record(50L);

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(50L, measurement.Value);
Assert.Empty(measurement.Tags);

Assert.NotNull(collector.Instrument);
Assert.Equal("milliseconds", collector.Instrument.Unit);
}

[Fact]
public void ValidateCounterWithUnitAndDimensions()
{
const long Value = 12345L;

using var collector = new MetricCollector<long>(_meter, "CounterWithUnitAndDims");

CounterWithUnitAndDims counter = MetricsWithUnit.CreateCounterWithUnitAndDims(_meter);
counter.Add(Value, "dim1Value", "dim2Value");

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(Value, measurement.Value);
Assert.Equal(new (string, object?)[] { ("s1", "dim1Value"), ("s2", "dim2Value") },
measurement.Tags.Select(x => (x.Key, x.Value)));

// Verify the instrument has the correct unit
Assert.NotNull(collector.Instrument);
Assert.Equal("bytes", collector.Instrument.Unit);
}

[Fact]
public void ValidateHistogramWithUnitAndDimensions()
{
const int Value = 9876;

using var collector = new MetricCollector<int>(_meter, "HistogramWithUnitAndDims");

HistogramWithUnitAndDims histogram = MetricsWithUnit.CreateHistogramWithUnitAndDims(_meter);
histogram.Record(Value, "val1");

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(Value, measurement.Value);
var tag = Assert.Single(measurement.Tags);
Assert.Equal(new KeyValuePair<string, object?>("s1", "val1"), tag);

// Verify the instrument has the correct unit
Assert.NotNull(collector.Instrument);
Assert.Equal("requests", collector.Instrument.Unit);
}

[Fact]
public void ValidateGenericCounterWithUnit()
{
using var collector = new MetricCollector<double>(_meter, "GenericDoubleCounterWithUnit");

GenericDoubleCounterWithUnit counter = MetricsWithUnit.CreateGenericDoubleCounterWithUnit(_meter);
counter.Add(3.14);

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(3.14, measurement.Value);
Assert.Empty(measurement.Tags);

// Verify the instrument has the correct unit
Assert.NotNull(collector.Instrument);
Assert.Equal("meters", collector.Instrument.Unit);
}

[Fact]
public void ValidateCounterWithEmptyUnit()
{
// Test that counters with empty/null units work
using var collector = new MetricCollector<long>(_meter, nameof(Counter0D));
Counter0D counter0D = CounterTestExtensions.CreateCounter0D(_meter);
counter0D.Add(10L);

var measurement = Assert.Single(collector.GetMeasurementSnapshot());
Assert.Equal(10L, measurement.Value);

// Verify the instrument has no unit (or default unit)
Assert.NotNull(collector.Instrument);
Assert.Null(collector.Instrument.Unit);
}
}
Comment thread
mariamgerges marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics;

namespace TestClasses
{
#pragma warning disable SA1402 // File may only contain a single type
[SuppressMessage("Usage", "CA1801:Review unused parameters",
Justification = "For testing emitter for classes without namespace")]
public static partial class MetricsWithUnit
{
[Counter(Unit = "seconds")]
public static partial CounterWithUnit CreateCounterWithUnit(Meter meter);

[Counter("s1", "s2", Unit = "bytes", Name = "CounterWithUnitAndDims")]
public static partial CounterWithUnitAndDims CreateCounterWithUnitAndDims(Meter meter);

[Counter(typeof(Dimensions), Unit = "bytes")]
public static partial CounterStrongTypeWithUnit CreateCounterStrongTypeWithUnit(Meter meter);

[Counter<double>(Unit = "meters", Name = "GenericDoubleCounterWithUnit")]
public static partial GenericDoubleCounterWithUnit CreateGenericDoubleCounterWithUnit(Meter meter);

[Histogram(Unit = "milliseconds", Name = "HistogramWithUnit")]
public static partial HistogramWithUnit CreateHistogramWithUnit(Meter meter);

[Histogram(typeof(Dimensions), Unit = "s")]
public static partial HistogramStrongTypeWithUnit CreateHistogramStrongTypeWithUnit(Meter meter);

[Histogram<int>("s1", Unit = "requests", Name = "HistogramWithUnitAndDims")]
public static partial HistogramWithUnitAndDims CreateHistogramWithUnitAndDims(Meter meter);
}

public class Dimensions
{
public string? Dim1;
public string? Dim2;
}
#pragma warning disable SA1402
}
Comment thread
evgenyfedorov2 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -660,4 +660,23 @@ static partial class MetricClass
}");
Assert.Empty(d);
}

[Fact]
public async Task StructTypeWithUnit()
{
var d = await RunGenerator(@"
public struct HistogramStruct
{
[Dimension(""Dim1_FromAttribute"")]
public string? Dim1 { get; set; }
}

public static partial class MetricClass
{
[Histogram(typeof(HistogramStruct), Name=""TotalCountTest"", Unit = ""s"")]
public static partial TotalCount CreateTotalCountCounter(Meter meter);
}");

Assert.Empty(d);
}
}
Loading
Loading