Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 0 additions & 1 deletion tracer/missing-nullability-files.csv
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ src/Datadog.Trace/TracerConstants.cs
src/Datadog.Trace/TracerManager.cs
src/Datadog.Trace/TracerManagerFactory.cs
src/Datadog.Trace/Agent/Api.cs
src/Datadog.Trace/Agent/ClientStatsPayload.cs
src/Datadog.Trace/Agent/IApi.cs
src/Datadog.Trace/Agent/IApiRequest.cs
src/Datadog.Trace/Agent/IApiRequestFactory.cs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// <copyright file="MessagePackHelper.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Datadog.Trace.Vendors.MessagePack;

namespace Datadog.Trace.SourceGenerators.Helpers;

internal static class MessagePackHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<byte> GetValueInRawMessagePackIEnumerable(string value)
{
var bytes = new byte[StringEncoding.UTF8.GetMaxByteCount(value.Length) + 5];
var offset = MessagePackBinary.WriteString(ref bytes, 0, value);
return new ArraySegment<byte>(bytes, 0, offset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <copyright file="InvalidFieldDiagnostic.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using Datadog.Trace.SourceGenerators.Helpers;
using Microsoft.CodeAnalysis;

namespace Datadog.Trace.SourceGenerators.MessagePackConstants.Diagnostics;

internal static class InvalidFieldDiagnostic
{
internal const string Id = "DDSG004";
private const string Title = "Invalid MessagePackField usage";
private const string Message = "MessagePackField attribute can only be applied to const string fields with non-empty values";

public static DiagnosticInfo CreateInfo(SyntaxNode? syntax) =>
new(
new DiagnosticDescriptor(
Id,
Title,
Message,
category: "CodeGeneration",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true),
syntax?.GetLocation());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// <copyright file="FieldToSerialize.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

namespace Datadog.Trace.SourceGenerators.MessagePackConstants;

internal readonly record struct FieldToSerialize
{
public readonly string FieldName;
public readonly string StringValue;

public FieldToSerialize(string fieldName, string stringValue)
{
FieldName = fieldName;
StringValue = stringValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// <copyright file="MessagePackConstantsGenerator.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using Datadog.Trace.SourceGenerators.Helpers;
using Datadog.Trace.SourceGenerators.MessagePackConstants;
using Datadog.Trace.SourceGenerators.MessagePackConstants.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

/// <inheritdoc />
[Generator]
public class MessagePackConstantsGenerator : IIncrementalGenerator
{
private const string MessagePackFieldAttributeFullName = "Datadog.Trace.SourceGenerators.MessagePackFieldAttribute";

/// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Register the attribute source
context.RegisterPostInitializationOutput(ctx => ctx.AddSource("MessagePackFieldAttribute.g.cs", Sources.Attribute));

// Find all const string fields with [MessagePackField] attribute
var messagePackFields =
context.SyntaxProvider.ForAttributeWithMetadataName(
MessagePackFieldAttributeFullName,
static (node, _) => true, // Accept all nodes for now
static (context, ct) => GetFieldToSerialize(context, ct))
.Where(static m => m is not null)!
.WithTrackingName(TrackingNames.FieldResults);

// Report diagnostics
context.ReportDiagnostics(
messagePackFields
.Where(static m => m.Errors.Count > 0)
.SelectMany(static (x, _) => x.Errors)
.WithTrackingName(TrackingNames.Diagnostics));

// Collect all valid fields
var allFields = messagePackFields
.Where(static m => m.Value.IsValid)
.Select(static (x, _) => x.Value.Field)
.Collect()
.WithTrackingName(TrackingNames.AllFields);

// Generate output
context.RegisterSourceOutput(
allFields,
static (spc, fields) => Execute(fields, spc));
}

private static void Execute(ImmutableArray<FieldToSerialize> fields, SourceProductionContext context)
{
if (fields.IsDefaultOrEmpty)
{
// nothing to do yet
return;
}

var source = Sources.CreateMessagePackConstants(fields);
context.AddSource("MessagePackConstants.g.cs", SourceText.From(source, Encoding.UTF8));
}

private static Result<(FieldToSerialize Field, bool IsValid)> GetFieldToSerialize(
GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
{
// ForAttributeWithMetadataName already provides the symbol via TargetSymbol
var fieldSymbol = ctx.TargetSymbol as IFieldSymbol;
List<DiagnosticInfo>? diagnostics = null;
var hasMisconfiguredInput = false;

// Verify it's a const string field
if (fieldSymbol == null || !fieldSymbol.IsConst || fieldSymbol.Type.SpecialType != SpecialType.System_String)
{
diagnostics ??= new List<DiagnosticInfo>();
diagnostics.Add(InvalidFieldDiagnostic.CreateInfo(ctx.TargetNode));
hasMisconfiguredInput = true;
}

var errors = diagnostics is { Count: > 0 }
? new EquatableArray<DiagnosticInfo>(diagnostics.ToArray())
: default;

if (hasMisconfiguredInput || fieldSymbol == null)
{
return new Result<(FieldToSerialize Field, bool IsValid)>((default, false), errors);
}

var constantValue = fieldSymbol.ConstantValue as string;
if (string.IsNullOrEmpty(constantValue))
{
diagnostics ??= new List<DiagnosticInfo>();
diagnostics.Add(InvalidFieldDiagnostic.CreateInfo(ctx.TargetNode));
return new Result<(FieldToSerialize Field, bool IsValid)>(
(default, false),
new EquatableArray<DiagnosticInfo>(diagnostics.ToArray()));
}

var field = new FieldToSerialize(
fieldName: fieldSymbol.Name,
stringValue: constantValue!);

return new Result<(FieldToSerialize Field, bool IsValid)>((field, true), errors);
}

internal static class TrackingNames
{
public const string FieldResults = nameof(FieldResults);
public const string Diagnostics = nameof(Diagnostics);
public const string AllFields = nameof(AllFields);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// <copyright file="Sources.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Datadog.Trace.SourceGenerators.Helpers;

namespace Datadog.Trace.SourceGenerators.MessagePackConstants
{
internal static class Sources
{
public const string Attribute = Constants.FileHeader +
@"namespace Datadog.Trace.SourceGenerators;

/// <summary>
/// Used to designate a const string field for pre-serialization to MessagePack bytes.
/// The generator will create a static byte array with the MessagePack-encoded value.
/// Used for source generation.
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
internal sealed class MessagePackFieldAttribute : System.Attribute
{
}
";

public static string CreateMessagePackConstants(ImmutableArray<FieldToSerialize> fields)
{
var sb = new StringBuilder();
sb.Append(Constants.FileHeader);
sb.Append(@"using System;

namespace Datadog.Trace.Agent.MessagePack;

/// <summary>
/// Pre-serialized MessagePack constants generated at compile time.
/// </summary>
internal static class MessagePackConstants
{");

// Group fields by name to detect duplicates
var fieldGroups = fields.GroupBy(f => f.FieldName).ToList();

foreach (var group in fieldGroups)
{
if (group.Count() > 1)
{
// Generate comment noting duplicate field names
sb.Append(@"
// Note: Multiple fields with name '")
.Append(group.Key)
.Append(@"' found. Using first occurrence.");
}

var field = group.First();
var tagByteArray = string.Join(", ", MessagePackHelper.GetValueInRawMessagePackIEnumerable(field.StringValue));

sb.Append(@"

// ")
.Append(field.FieldName)
.Append(@"Bytes = MessagePack.Serialize(""")
.Append(field.StringValue)
.Append(@""");");

sb.Append(@"
#if NETCOREAPP
internal static ReadOnlySpan<byte> ")
.Append(field.FieldName)
.Append(@"Bytes => new byte[] { ")
.Append(tagByteArray)
.Append(@" };")
.Append(@"
#else
internal static readonly byte[] ")
.Append(field.FieldName)
.Append(@"Bytes = new byte[] { ")
.Append(tagByteArray)
.Append(@" };")
.Append(@"
#endif");
}

sb.AppendLine(@"
}");

return sb.ToString();
}
}
}
16 changes: 12 additions & 4 deletions tracer/src/Datadog.Trace/Agent/ClientStatsPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable

using System.Threading;
using Datadog.Trace.Configuration;
Expand All @@ -11,22 +12,29 @@ namespace Datadog.Trace.Agent
internal sealed class ClientStatsPayload(MutableSettings settings)
{
private AppSettings _settings = CreateSettings(settings);
private string? _processTags = GetProcessTags(settings);
private long _sequence;

public string HostName { get; init; }
public string? HostName { get; init; }

public AppSettings Details => _settings;

public string ProcessTags { get; init; }
public string? ProcessTags => _processTags;

public long GetSequenceNumber() => Interlocked.Increment(ref _sequence);

public void UpdateDetails(MutableSettings settings)
=> Interlocked.Exchange(ref _settings, CreateSettings(settings));
{
Interlocked.Exchange(ref _settings, CreateSettings(settings));
Interlocked.Exchange(ref _processTags, GetProcessTags(settings));
}

private static AppSettings CreateSettings(MutableSettings settings)
=> new(settings.Environment, settings.ServiceVersion);

internal sealed record AppSettings(string Environment, string Version);
private static string? GetProcessTags(MutableSettings settings)
=> settings.ProcessTags?.SerializedTags;

internal sealed record AppSettings(string? Environment, string? Version);
}
}
Loading