opentelemetry-dotnet-instru.../tools/IntegrationsJsonGenerator/Program.cs

257 lines
9.8 KiB
C#

// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using IntegrationsJsonGenerator;
const string instrumentMethodAttributeName = "OpenTelemetry.AutoInstrumentation.Instrumentations.InstrumentMethodAttribute";
var thisFilePath = GetSourceFilePathName();
var solutionFolder = Path.Combine(thisFilePath, "..", "..", "..");
var autoInstrumentationLibPath = Path.Combine(solutionFolder, "bin", "tracer-home", "net", "OpenTelemetry.AutoInstrumentation.dll");
var autoInstrumentationLib = Assembly.LoadFrom(autoInstrumentationLibPath);
var assemblyInstrumentMethodAttributes = autoInstrumentationLib.DefinedTypes
.Where(type => InheritsFrom(type, instrumentMethodAttributeName)).Select(x => x.FullName);
var integrations = new Dictionary<(string IntegrationType, string IntegrationName), Integration>();
foreach (var typeInfo in autoInstrumentationLib.GetTypes())
{
foreach (var attribute in typeInfo.GetCustomAttributes()
.Where(a => assemblyInstrumentMethodAttributes.Contains(a.GetType().FullName)))
{
var integration = ConvertToIntegration(typeInfo.FullName!, attribute);
if (!integrations.ContainsKey((integration.IntegrationType, integration.IntegrationName)))
{
integrations.Add(
(integration.IntegrationType, integration.IntegrationName),
new Integration
{
Name = integration.IntegrationName,
Type = integration.IntegrationType,
MethodReplacements = new List<MethodReplacement> { integration.MethodReplacement }
});
}
else
{
var integration2 = integrations[(integration.IntegrationType, integration.IntegrationName)];
integration2.MethodReplacements.Add(integration.MethodReplacement);
}
}
}
var productionIntegrations = integrations.Where(x => x.Key.IntegrationName != "StrongNamedValidation").Select(x => x.Value)
.OrderBy(x => x.Name).ToArray();
var testIntegrations = integrations.Where(x => x.Key.IntegrationName == "StrongNamedValidation").Select(x => AppendMockIntegrations(x.Value))
.OrderBy(x => x.Name).ToArray();
Dictionary<string, List<string>> byteCodeIntegrationsByType = new();
foreach (var (integrationType, integrationName) in integrations.Keys)
{
if (byteCodeIntegrationsByType.TryGetValue(integrationType, out var integrationNames))
{
integrationNames.Add(integrationName);
}
else
{
byteCodeIntegrationsByType.Add(integrationType, new List<string> { integrationName });
}
}
UpdateIntegrationFile(Path.Combine(solutionFolder, "integrations.json"), productionIntegrations);
UpdateIntegrationFile(Path.Combine(solutionFolder, "test", "IntegrationTests", "StrongNamedTestsIntegrations.json"), testIntegrations);
UpdateNativeInstrumentationFile(Path.Combine(solutionFolder, "src", "OpenTelemetry.AutoInstrumentation.Native", "bytecode_instrumentations.h"), byteCodeIntegrationsByType);
bool InheritsFrom(Type type, string baseType)
{
while (true)
{
if (type.FullName == baseType)
{
return true;
}
if (type.BaseType is null)
{
return false;
}
type = type.BaseType;
}
}
(string IntegrationType, string IntegrationName, MethodReplacement MethodReplacement) ConvertToIntegration(string wrapperTypeName, Attribute attribute)
{
var integrationName = GetPropertyValue<string>("IntegrationName", attribute);
var integrationType = GetPropertyValue<object>("Type", attribute).ToString()!;
var methodReplacement = new MethodReplacement
{
Wrapper =
{
Type = wrapperTypeName
},
Target =
{
Assembly = GetPropertyValue<string>("AssemblyName", attribute), Type = GetPropertyValue<string>("TypeName", attribute),
Method = GetPropertyValue<string>("MethodName", attribute)
}
};
var returnTypeName = GetPropertyValue<string>("ReturnTypeName", attribute);
var parameterTypeNames = GetPropertyValue<string[]>("ParameterTypeNames", attribute);
methodReplacement.Target.SignatureTypes = new[] { returnTypeName }.Concat(parameterTypeNames).ToArray();
var minVersion = GetPropertyValue<string>("MinimumVersion", attribute).Split('.');
methodReplacement.Target.MinimumMajor = int.Parse(minVersion[0]);
if (minVersion.Length > 1 && minVersion[1] != "*")
{
methodReplacement.Target.MinimumMinor = int.Parse(minVersion[1]);
}
if (minVersion.Length > 2 && minVersion[2] != "*")
{
methodReplacement.Target.MinimumPath = int.Parse(minVersion[2]);
}
var maxVersion = GetPropertyValue<string>("MaximumVersion", attribute).Split('.');
methodReplacement.Target.MaximumMajor = int.Parse(maxVersion[0]);
if (maxVersion.Length > 1 && maxVersion[1] != "*")
{
methodReplacement.Target.MaximumMinor = int.Parse(maxVersion[1]);
}
if (maxVersion.Length > 2 && maxVersion[2] != "*")
{
methodReplacement.Target.MaximumPath = int.Parse(maxVersion[2]);
}
return (integrationType, integrationName, methodReplacement);
}
T GetPropertyValue<T>(string propertyName, Attribute attribute)
{
var type = attribute.GetType();
var getMethod = type.GetProperty(propertyName)!.GetGetMethod()!;
if (!getMethod.ReturnType.IsAssignableTo(typeof(T)))
{
throw new ArgumentException($"Property {propertyName} is not assignable to {typeof(T)}");
}
var value = getMethod.Invoke(attribute, Array.Empty<object>())!;
return (T)value;
}
void UpdateIntegrationFile(string filePath, Integration[] productionIntegrations1)
{
using var fileStream = new FileStream(filePath, FileMode.Truncate);
var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
JsonSerializer.Serialize(fileStream, productionIntegrations1, jsonSerializerOptions);
}
void UpdateNativeInstrumentationFile(string filePath, Dictionary<string, List<string>> bytecodeIntegrations)
{
using var fileStream = new FileStream(filePath, FileMode.Truncate);
using var writer = new StreamWriter(fileStream);
writer.WriteLine("""
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
""");
writer.WriteLine();
writer.WriteLine($"// Auto-generated file, do not change it - generated by the {nameof(IntegrationsJsonGenerator)}");
writer.WriteLine("#ifndef BYTECODE_INSTRUMENTATIONS_H");
writer.WriteLine("#define BYTECODE_INSTRUMENTATIONS_H");
writer.WriteLine();
writer.WriteLine("#include \"string.h\"");
writer.WriteLine();
writer.WriteLine("namespace trace");
writer.WriteLine("{");
foreach (var bytecodeIntegration in bytecodeIntegrations)
{
writer.Write("inline std::unordered_map<WSTRING, WSTRING> ");
writer.Write(bytecodeIntegration.Key.ToLowerInvariant());
writer.Write("_integration_names = {");
writer.Write(string.Join(", ", bytecodeIntegration.Value.Select(name => $"{{WStr(\"{name}\"), WStr(\"OTEL_DOTNET_AUTO_{bytecodeIntegration.Key.ToUpperInvariant()}S_{name.ToUpperInvariant()}_INSTRUMENTATION_ENABLED\")}}")));
writer.WriteLine("};");
}
writer.WriteLine("}");
writer.WriteLine("#endif");
}
static string GetSourceFilePathName([CallerFilePath] string? callerFilePath = null)
=> callerFilePath ?? string.Empty;
static Integration AppendMockIntegrations(Integration testIntegration)
{
// Add some special cases used by the integration tests. This way the integrations
// file used in the integrations test doesn't change on each run of the tool.
var targetAssembly = testIntegration.MethodReplacements[0].Target.Assembly;
var targetType = testIntegration.MethodReplacements[0].Target.Type;
var targetSignatureTypes = testIntegration.MethodReplacements[0].Target.SignatureTypes;
testIntegration.MethodReplacements.Add(new MethodReplacement
{
Target = new Target
{
Assembly = targetAssembly,
Type = targetType,
Method = "InstrumentationTargetMissingBytecodeInstrumentationType",
SignatureTypes = targetSignatureTypes,
MaximumMajor = 1,
MinimumMajor = 1,
},
Wrapper = new Wrapper
{
Type = "OpenTelemetry.AutoInstrumentation.Instrumentations.Validations.MissingInstrumentationType",
},
});
testIntegration.MethodReplacements.Add(new MethodReplacement
{
Target = new Target
{
Assembly = testIntegration.MethodReplacements[0].Target.Assembly,
Type = targetType,
Method = "InstrumentationTargetMissingBytecodeInstrumentationMethod",
SignatureTypes = targetSignatureTypes,
MaximumMajor = 1,
MinimumMajor = 1,
},
Wrapper = new Wrapper
{
Type = "OpenTelemetry.AutoInstrumentation.DuckTyping.DuckAttribute",
},
});
return testIntegration;
}