File: Emitter.cs
Web Access
Project: src\src\Generators\Microsoft.Gen.Metrics\Microsoft.Gen.Metrics.csproj (Microsoft.Gen.Metrics)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.Gen.Metrics.Model;
using Microsoft.Gen.Shared;
 
namespace Microsoft.Gen.Metrics;
 
// Stryker disable all
 
internal sealed class Emitter : EmitterBase
{
    private static readonly Regex _regex = new("[:.-]+", RegexOptions.Compiled);
 
    public string EmitMetrics(IReadOnlyList<MetricType> metricTypes, CancellationToken cancellationToken)
    {
        Dictionary<string, List<MetricType>> metricClassesDict = [];
        foreach (var cl in metricTypes)
        {
            if (!metricClassesDict.TryGetValue(cl.Namespace, out var list))
            {
                list = [];
                metricClassesDict.Add(cl.Namespace, list);
            }
 
            list.Add(cl);
        }
 
        foreach (var entry in metricClassesDict.OrderBy(static x => x.Key))
        {
            cancellationToken.ThrowIfCancellationRequested();
            GenTypeByNamespace(entry.Key, entry.Value, cancellationToken);
        }
 
        return Capture();
    }
 
    private static string GetSanitizedParamName(string paramName)
    {
        return _regex.Replace(paramName, "_");
    }
 
    private void GenTypeByNamespace(string nspace, IEnumerable<MetricType> metricTypes, CancellationToken cancellationToken)
    {
        OutLn();
        if (!string.IsNullOrWhiteSpace(nspace))
        {
            OutLn($"namespace {nspace}");
            OutOpenBrace();
        }
 
        var first = true;
        foreach (var metricClass in metricTypes.OrderBy(static x => x.Name))
        {
            if (first)
            {
                first = false;
            }
            else
            {
                OutLn();
            }
 
            cancellationToken.ThrowIfCancellationRequested();
            GenType(metricClass, nspace);
        }
 
        if (!string.IsNullOrWhiteSpace(nspace))
        {
            OutCloseBrace();
        }
    }
 
    private void GenType(MetricType metricType, string nspace)
    {
        GenInstrumentCreateMethods(metricType, nspace);
        OutLn();
 
        var first = true;
        foreach (var metricMethod in metricType.Methods)
        {
            if (first)
            {
                first = false;
            }
            else
            {
                OutLn();
            }
 
            GenInstrumentClass(metricMethod);
        }
    }
 
    private void GenInstrumentCreateMethods(MetricType metricType, string nspace)
    {
        var parent = metricType.Parent;
        var parentTypes = new List<string>();
 
        // loop until you find top level nested class
        while (parent != null)
        {
            parentTypes.Add($"{parent.Modifiers} {parent.Keyword} {parent.Name} {parent.Constraints}");
            parent = parent.Parent;
        }
 
        // write down top level nested class first
        for (int i = parentTypes.Count - 1; i >= 0; i--)
        {
            OutLn(parentTypes[i]);
            OutOpenBrace();
        }
 
        OutGeneratedCodeAttribute();
        OutLn($"{metricType.Modifiers} {metricType.Keyword} {metricType.Name} {metricType.Constraints}");
        OutOpenBrace();
        var first = true;
        foreach (var metricMethod in metricType.Methods)
        {
            if (first)
            {
                first = false;
            }
            else
            {
                OutLn();
            }
 
            GenInstrumentCreateMethod(metricMethod, nspace);
        }
 
        OutCloseBrace();
 
        parent = metricType.Parent;
        while (parent != null)
        {
            OutCloseBrace();
            parent = parent.Parent;
        }
    }
 
    private void GenInstrumentClass(MetricMethod metricMethod)
    {
        const string CounterObjectName = "counter";
        const string HistogramObjectName = "histogram";
 
        const string CounterRecordStatement = "Add";
        const string HistogramRecordStatement = "Record";
 
        const string CounterOfTTypeDefinitionTemplate = "global::System.Diagnostics.Metrics.Counter<{0}>";
        const string HistogramOfTTypeDefinitionTemplate = "global::System.Diagnostics.Metrics.Histogram<{0}>";
 
        string objectName;
        string typeDefinition;
        string recordStatement;
        string metricValueType = metricMethod.GenericType;
 
        switch (metricMethod.InstrumentKind)
        {
            case InstrumentKind.Counter:
            case InstrumentKind.CounterT:
                recordStatement = CounterRecordStatement;
                typeDefinition = string.Format(CultureInfo.InvariantCulture, CounterOfTTypeDefinitionTemplate, metricValueType);
                objectName = CounterObjectName;
                break;
            case InstrumentKind.Histogram:
            case InstrumentKind.HistogramT:
                recordStatement = HistogramRecordStatement;
                typeDefinition = string.Format(CultureInfo.InvariantCulture, HistogramOfTTypeDefinitionTemplate, metricValueType);
                objectName = HistogramObjectName;
                break;
            default:
                throw new NotSupportedException($"Instrument type '{metricMethod.InstrumentKind}' is not supported to generate metric");
        }
 
        var tagListInit = metricMethod.TagKeys.Count != 0 ||
                          metricMethod.StrongTypeConfigs.Count != 0;
 
        var accessModifier = metricMethod.MetricTypeModifiers.Contains("public")
            ? "public"
            : "internal";
 
        OutGeneratedCodeAttribute();
        OutLn($"{accessModifier} sealed class {metricMethod.MetricTypeName}");
        OutLn($"{{");
        OutLn($"    private readonly {typeDefinition} _{objectName};");
        OutLn();
        OutLn($"    public {metricMethod.MetricTypeName}({typeDefinition} {objectName})");
        OutLn($"    {{");
        OutLn($"        _{objectName} = {objectName};");
        OutLn($"    }}");
        OutLn();
 
        OutIndent();
        Out($"    public void {recordStatement}({metricValueType} value");
        GenTagsParameters(metricMethod);
        Out(")");
        OutLn();
 
        OutLn($"    {{");
 
        const int MaxTagsWithoutEnabledCheck = 8;
        if (metricMethod.TagKeys.Count > MaxTagsWithoutEnabledCheck ||
            metricMethod.StrongTypeConfigs.Count > MaxTagsWithoutEnabledCheck)
        {
            OutLn($"        if (!_{objectName}.Enabled)");
            OutLn($"        {{");
            OutLn($"            return;");
            OutLn($"        }}");
            OutLn();
        }
 
        if (metricMethod.IsTagTypeClass)
        {
            OutLn($"        if (o == null)");
            OutLn($"        {{");
            OutLn($"            throw new global::System.ArgumentNullException(nameof(o));");
            OutLn($"        }}");
            OutLn();
        }
 
        if (tagListInit)
        {
            Indent();
            Indent();
            OutLn("var tagList = new global::System.Diagnostics.TagList");
            OutOpenBrace();
            GenTagList(metricMethod);
            Unindent();
            OutLn("};");
            Unindent();
            Unindent();
            OutLn();
        }
 
        OutLn($"        _{objectName}.{recordStatement}(value{(tagListInit ? ", tagList" : string.Empty)});");
        OutLn($"    }}");
        OutLn("}");
    }
 
    private void GenInstrumentCreateMethod(MetricMethod metricMethod, string nspace)
    {
        var nsprefix = string.IsNullOrWhiteSpace(nspace)
            ? string.Empty
            : $"global::{nspace}.";
 
        var thisModifier = metricMethod.IsExtensionMethod
                ? "this "
                : string.Empty;
 
        var meterParam = metricMethod.AllParameters[0];
        var accessModifier = metricMethod.Modifiers.Contains("public")
                ? "public"
                : "internal";
 
        OutGeneratedCodeAttribute();
        OutIndent();
        Out($"{accessModifier} static partial {nsprefix}{metricMethod.MetricTypeName} {metricMethod.Name}({thisModifier}");
        foreach (var p in metricMethod.AllParameters)
        {
            if (p != meterParam)
            {
                Out(", ");
            }
 
            Out($"{p.Type} {p.Name}");
        }
 
        Out(")");
        OutLn();
        OutIndent();
        Out($"    => {nsprefix}GeneratedInstrumentsFactory.Create{metricMethod.MetricTypeName}(");
        foreach (var p in metricMethod.AllParameters)
        {
            if (p != meterParam)
            {
                Out(", ");
            }
 
            Out(p.Name);
        }
 
        Out(");");
        OutLn();
    }
 
    private void GenTagList(MetricMethod metricMethod)
    {
        if (string.IsNullOrEmpty(metricMethod.StrongTypeObjectName))
        {
            foreach (var tagName in metricMethod.TagKeys)
            {
                var paramName = GetSanitizedParamName(tagName);
                OutLn($"new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{tagName}\", {paramName}),");
            }
        }
        else
        {
            foreach (var config in metricMethod.StrongTypeConfigs)
            {
                if (config.StrongTypeMetricObjectType != StrongTypeMetricObjectType.String &&
                    config.StrongTypeMetricObjectType != StrongTypeMetricObjectType.Enum)
                {
                    continue;
                }
 
                var paramName = GetSanitizedParamName(config.Name);
                var paramInvoke = config.StrongTypeMetricObjectType == StrongTypeMetricObjectType.Enum
                    ? $"{paramName}.ToString()"
                    : $"{paramName}!";
 
                var access = string.IsNullOrEmpty(config.Path)
                    ? "o." + paramInvoke
                    : "o." + config.Path + "." + paramInvoke;
 
                OutLn($"new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{config.TagName}\", {access}),");
            }
        }
    }
 
    private void GenTagsParameters(MetricMethod metricMethod)
    {
        if (string.IsNullOrEmpty(metricMethod.StrongTypeObjectName))
        {
            foreach (var tagName in metricMethod.TagKeys)
            {
                var paramName = GetSanitizedParamName(tagName);
                Out($", object? {paramName}");
            }
        }
        else
        {
            Out($", global::{metricMethod.StrongTypeObjectName} o");
        }
    }
}