diff --git a/tracer/src/Datadog.Trace/Ci/Agent/CIVisibilityProtocolWriter.cs b/tracer/src/Datadog.Trace/Ci/Agent/CIVisibilityProtocolWriter.cs index 7b303de4c423..ff5f7d691bea 100644 --- a/tracer/src/Datadog.Trace/Ci/Agent/CIVisibilityProtocolWriter.cs +++ b/tracer/src/Datadog.Trace/Ci/Agent/CIVisibilityProtocolWriter.cs @@ -74,7 +74,7 @@ internal sealed class CIVisibilityProtocolWriter : IEventWriter public CIVisibilityProtocolWriter( TestOptimizationSettings settings, ICIVisibilityProtocolWriterSender sender, - IFormatterResolver? formatterResolver = null, + IFormatterResolver formatterResolver, int? concurrency = null, int batchInterval = DefaultBatchInterval, int maxItemsInQueue = DefaultMaxItemsInQueue) @@ -90,15 +90,15 @@ public CIVisibilityProtocolWriter( { var buffers = new Buffers( sender, - new CITestCyclePayload(settings, formatterResolver: formatterResolver), - new CICodeCoveragePayload(settings, formatterResolver: formatterResolver)); + new CITestCyclePayload(settings, formatterResolver), + new CICodeCoveragePayload(settings, formatterResolver)); _buffersArray[i] = buffers; var tskFlush = Task.Run(() => InternalFlushEventsAsync(this, buffers)); tskFlush.ContinueWith(t => Log.Error(t.Exception, "CIVisibilityProtocolWriter: Error in sending ci visibility events"), TaskContinuationOptions.OnlyOnFaulted); _buffersArray[i].SetFlushTask(tskFlush); } - Log.Information("CIVisibilityProtocolWriter Initialized with concurrency level of: {ConcurrencyLevel}", concurrencyLevel); + Log.Debug("CIVisibilityProtocolWriter Initialized with concurrency level of: {ConcurrencyLevel}", concurrencyLevel); } public void WriteEvent(IEvent @event) @@ -349,7 +349,7 @@ internal void SetFlushTask(Task flushTask) public Task FlushCiTestCycleBufferWhenTimeElapsedAsync(int batchInterval) { - return CiTestCycleBufferWatch.ElapsedMilliseconds >= batchInterval ? + return CiTestCycleBufferWatch.GetElapsedMilliseconds() >= batchInterval ? FlushCiTestCycleBufferAsync() : Task.CompletedTask; } @@ -367,7 +367,7 @@ async Task InternalFlushCiTestCycleBufferAsync() public Task FlushCiCodeCoverageBufferWhenTimeElapsedAsync(int batchInterval) { - return CiCodeCoverageBufferWatch.ElapsedMilliseconds >= batchInterval ? + return CiCodeCoverageBufferWatch.GetElapsedMilliseconds() >= batchInterval ? FlushCiCodeCoverageBufferAsync() : Task.CompletedTask; } diff --git a/tracer/src/Datadog.Trace/Ci/Agent/CIWriterHttpSender.cs b/tracer/src/Datadog.Trace/Ci/Agent/CIWriterHttpSender.cs index 1be95724c3fe..724e0ad72398 100644 --- a/tracer/src/Datadog.Trace/Ci/Agent/CIWriterHttpSender.cs +++ b/tracer/src/Datadog.Trace/Ci/Agent/CIWriterHttpSender.cs @@ -16,6 +16,7 @@ using Datadog.Trace.Configuration; using Datadog.Trace.Logging; using Datadog.Trace.Telemetry; +using Datadog.Trace.Util; using Datadog.Trace.Util.Http; using Datadog.Trace.Vendors.Serilog.Events; @@ -34,7 +35,7 @@ public CIWriterHttpSender(IApiRequestFactory apiRequestFactory) { _apiRequestFactory = apiRequestFactory; _isDebugEnabled = GlobalSettings.Instance.DebugEnabled; - Log.Information("CIWriterHttpSender Initialized."); + Log.Debug("CIWriterHttpSender Initialized."); } public Task SendPayloadAsync(EventPlatformPayload payload) @@ -146,7 +147,7 @@ private async Task SendPayloadAsync(EventPlatformPayload payload, Func #nullable enable -using System.Diagnostics; using System.IO; -using Datadog.Trace.Ci.Agent.MessagePack; using Datadog.Trace.Ci.Configuration; using Datadog.Trace.Ci.EventModel; using Datadog.Trace.Telemetry; +using Datadog.Trace.Util; using Datadog.Trace.Vendors.MessagePack; namespace Datadog.Trace.Ci.Agent.Payloads; @@ -18,13 +17,11 @@ internal abstract class CIVisibilityProtocolPayload : EventPlatformPayload { private readonly EventsBuffer _events; private readonly IFormatterResolver _formatterResolver; - private readonly Stopwatch _serializationWatch; - public CIVisibilityProtocolPayload(TestOptimizationSettings settings, IFormatterResolver? formatterResolver = null) + public CIVisibilityProtocolPayload(TestOptimizationSettings settings, IFormatterResolver formatterResolver) : base(settings) { - _formatterResolver = formatterResolver ?? CIFormatterResolver.Instance; - _serializationWatch = new Stopwatch(); + _formatterResolver = formatterResolver; // Because we don't know the size of the events array envelope we left 500kb for that. _events = new EventsBuffer(settings.MaximumAgentlessPayloadSize - (500 * 1024), _formatterResolver); @@ -48,7 +45,7 @@ public CIVisibilityProtocolPayload(TestOptimizationSettings settings, IFormatter public override bool TryProcessEvent(IEvent @event) { - _serializationWatch.Restart(); + var sw = RefStopwatch.Create(); var success = _events.TryWrite(@event); if (success) { @@ -74,7 +71,7 @@ public override bool TryProcessEvent(IEvent @event) } } - TelemetryFactory.Metrics.RecordDistributionCIVisibilityEndpointEventsSerializationMs(TelemetryEndpoint, _serializationWatch.Elapsed.TotalMilliseconds); + TelemetryFactory.Metrics.RecordDistributionCIVisibilityEndpointEventsSerializationMs(TelemetryEndpoint, sw.ElapsedMilliseconds); return success; } diff --git a/tracer/src/Datadog.Trace/Ci/Agent/Payloads/MultipartPayload.cs b/tracer/src/Datadog.Trace/Ci/Agent/Payloads/MultipartPayload.cs index 1594cb466783..37d5b60c1631 100644 --- a/tracer/src/Datadog.Trace/Ci/Agent/Payloads/MultipartPayload.cs +++ b/tracer/src/Datadog.Trace/Ci/Agent/Payloads/MultipartPayload.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using Datadog.Trace.Agent; -using Datadog.Trace.Ci.Agent.MessagePack; using Datadog.Trace.Ci.Configuration; using Datadog.Trace.Vendors.MessagePack; @@ -21,11 +20,9 @@ internal abstract class MultipartPayload : EventPlatformPayload private readonly EventsBuffer _events; private readonly List _items; - private readonly IFormatterResolver _formatterResolver; private readonly int _maxItemsPerPayload; - private readonly int _maxBytesPerPayload; - public MultipartPayload(TestOptimizationSettings settings, int maxItemsPerPayload = DefaultMaxItemsPerPayload, int maxBytesPerPayload = DefaultMaxBytesPerPayload, IFormatterResolver? formatterResolver = null) + public MultipartPayload(TestOptimizationSettings settings, IFormatterResolver formatterResolver, int maxItemsPerPayload = DefaultMaxItemsPerPayload, int maxBytesPerPayload = DefaultMaxBytesPerPayload) : base(settings) { if (maxBytesPerPayload < HeaderSize) @@ -34,12 +31,10 @@ public MultipartPayload(TestOptimizationSettings settings, int maxItemsPerPayloa } _maxItemsPerPayload = maxItemsPerPayload; - _maxBytesPerPayload = maxBytesPerPayload; - _formatterResolver = formatterResolver ?? CIFormatterResolver.Instance; _items = new List(Math.Min(maxItemsPerPayload, DefaultMaxItemsPerPayload)); // Because we don't know the size of the events array envelope we left 1024kb for that. - _events = new EventsBuffer(Math.Min(_maxBytesPerPayload, DefaultMaxBytesPerPayload) - HeaderSize, _formatterResolver); + _events = new EventsBuffer(Math.Min(maxBytesPerPayload, DefaultMaxBytesPerPayload) - HeaderSize, formatterResolver); } public override bool HasEvents => _events.Count > 0; diff --git a/tracer/src/Datadog.Trace/Ci/Coverage/DefaultWithGlobalCoverageEventHandler.cs b/tracer/src/Datadog.Trace/Ci/Coverage/DefaultWithGlobalCoverageEventHandler.cs index def7a45753b9..35087671e8b0 100644 --- a/tracer/src/Datadog.Trace/Ci/Coverage/DefaultWithGlobalCoverageEventHandler.cs +++ b/tracer/src/Datadog.Trace/Ci/Coverage/DefaultWithGlobalCoverageEventHandler.cs @@ -13,6 +13,7 @@ using Datadog.Trace.Ci.Coverage.Models.Global; using Datadog.Trace.Ci.Coverage.Util; using Datadog.Trace.Telemetry; +using Datadog.Trace.Util; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.Serilog.Events; @@ -54,7 +55,7 @@ public unsafe GlobalCoverageInfo GetCodeCoveragePercentage() { lock (_coverages) { - var sw = Stopwatch.StartNew(); + var sw = RefStopwatch.Create(); var globalCoverage = new GlobalCoverageInfo(); IEnumerable GetModuleValues() @@ -151,7 +152,7 @@ IEnumerable GetModuleValues() // Clean coverages Clear(); - Log.Information("Total time to calculate global coverage: {TotalMilliseconds}ms", sw.Elapsed.TotalMilliseconds); + Log.Information("Total time to calculate global coverage: {TotalMilliseconds}ms", sw.ElapsedMilliseconds); return globalCoverage; } } diff --git a/tracer/src/Datadog.Trace/Ci/Processors/OriginTagTraceProcessor.cs b/tracer/src/Datadog.Trace/Ci/Processors/OriginTagTraceProcessor.cs index 8b4c5b8e0b0c..0469b84fff27 100644 --- a/tracer/src/Datadog.Trace/Ci/Processors/OriginTagTraceProcessor.cs +++ b/tracer/src/Datadog.Trace/Ci/Processors/OriginTagTraceProcessor.cs @@ -27,7 +27,7 @@ public OriginTagTraceProcessor(bool isPartialFlushEnabled, bool isCiVisibilityPr _isPartialFlushEnabled = isPartialFlushEnabled; _isCiVisibilityProtocol = isCiVisibilityProtocol; - Log.Information("OriginTraceProcessor initialized."); + Log.Debug("OriginTraceProcessor initialized."); } public SpanCollection Process(in SpanCollection trace) diff --git a/tracer/src/Datadog.Trace/Ci/Processors/TestSuiteVisibilityProcessor.cs b/tracer/src/Datadog.Trace/Ci/Processors/TestSuiteVisibilityProcessor.cs index d016dc27118f..0d0cd4bcfb4f 100644 --- a/tracer/src/Datadog.Trace/Ci/Processors/TestSuiteVisibilityProcessor.cs +++ b/tracer/src/Datadog.Trace/Ci/Processors/TestSuiteVisibilityProcessor.cs @@ -19,7 +19,7 @@ internal sealed class TestSuiteVisibilityProcessor : ITraceProcessor public TestSuiteVisibilityProcessor() { - Log.Information("TestSuiteVisibilityProcessor initialized."); + Log.Debug("TestSuiteVisibilityProcessor initialized."); } public SpanCollection Process(in SpanCollection trace) diff --git a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs index 924e585014c9..9051664063f5 100644 --- a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs +++ b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs @@ -10,6 +10,7 @@ using Datadog.Trace.Agent; using Datadog.Trace.Agent.DiscoveryService; using Datadog.Trace.Ci.Agent; +using Datadog.Trace.Ci.Agent.MessagePack; using Datadog.Trace.Ci.Configuration; using Datadog.Trace.Ci.Sampling; using Datadog.Trace.Configuration; @@ -22,7 +23,6 @@ using Datadog.Trace.RuntimeMetrics; using Datadog.Trace.Sampling; using Datadog.Trace.Telemetry; -using Datadog.Trace.Vendors.StatsdClient; namespace Datadog.Trace.Ci { @@ -97,7 +97,7 @@ protected override IAgentWriter GetAgentWriter(TracerSettings settings, IStatsdM { if (!string.IsNullOrEmpty(_settings.ApiKey)) { - return new CIVisibilityProtocolWriter(_settings, new CIWriterHttpSender(_testOptimizationTracerManagement.GetRequestFactory(settings))); + return new CIVisibilityProtocolWriter(_settings, new CIWriterHttpSender(_testOptimizationTracerManagement.GetRequestFactory(settings)), CIFormatterResolver.Instance); } Environment.FailFast("An API key is required in Agentless mode."); @@ -107,7 +107,7 @@ protected override IAgentWriter GetAgentWriter(TracerSettings settings, IStatsdM // With agent scenario: if (_enabledEventPlatformProxy) { - return new CIVisibilityProtocolWriter(_settings, new CIWriterHttpSender(_testOptimizationTracerManagement.GetRequestFactory(settings))); + return new CIVisibilityProtocolWriter(_settings, new CIWriterHttpSender(_testOptimizationTracerManagement.GetRequestFactory(settings)), CIFormatterResolver.Instance); } // Event platform proxy not found diff --git a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs index a6a562ff4dbe..5c8af130c0f9 100644 --- a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs +++ b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs @@ -123,6 +123,8 @@ private static void PropagateStableConfiguration() /// public static void Initialize() { + using var cd = CodeDurationRef.Create(); + if (Interlocked.Exchange(ref _firstInitialization, 0) != 1) { // Initialize() was already called before @@ -133,10 +135,10 @@ public static void Initialize() { TracerDebugger.WaitForDebugger(); - var swTotal = Stopwatch.StartNew(); + var swTotal = RefStopwatch.Create(); Log.Debug("Initialization started."); - var sw = Stopwatch.StartNew(); + var sw = RefStopwatch.Create(); bool versionMismatch = GetNativeTracerVersion() != TracerConstants.ThreePartVersion; if (versionMismatch) @@ -145,7 +147,7 @@ public static void Initialize() } else { - InitializeNoNativeParts(sw); + InitializeNoNativeParts(ref sw); try { @@ -196,7 +198,7 @@ public static void Initialize() TelemetryFactory.Metrics.RecordDistributionSharedInitTime(MetricTags.InitializationComponent.CallTargetDefsPinvoke, sw.ElapsedMilliseconds); sw.Restart(); - InitializeTracer(sw); + InitializeTracer(ref sw); } #if NETSTANDARD2_0 || NETCOREAPP3_1 @@ -263,7 +265,7 @@ private static void RunShutdown(Exception ex) NativeCallTargetUnmanagedMemoryHelper.Free(); } - internal static void InitializeNoNativeParts(Stopwatch sw = null) + internal static void InitializeNoNativeParts(ref RefStopwatch sw) { if (Interlocked.Exchange(ref _firstNonNativePartsInitialization, 0) != 1) { @@ -423,14 +425,11 @@ internal static void InitializeNoNativeParts(Stopwatch sw = null) Log.Debug("Tracer.Instance is null after InitializeNoNativeParts was invoked"); } - if (sw != null) - { - TelemetryFactory.Metrics.RecordDistributionSharedInitTime(MetricTags.InitializationComponent.Managed, sw.ElapsedMilliseconds); - sw.Restart(); - } + TelemetryFactory.Metrics.RecordDistributionSharedInitTime(MetricTags.InitializationComponent.Managed, sw.ElapsedMilliseconds); + sw.Restart(); } - private static void InitializeTracer(Stopwatch sw) + private static void InitializeTracer(ref RefStopwatch sw) { var tracer = Tracer.Instance; if (tracer is null) diff --git a/tracer/src/Datadog.Trace/Logging/Internal/DatadogLogging.cs b/tracer/src/Datadog.Trace/Logging/Internal/DatadogLogging.cs index 91c53962d083..e409f6256fce 100644 --- a/tracer/src/Datadog.Trace/Logging/Internal/DatadogLogging.cs +++ b/tracer/src/Datadog.Trace/Logging/Internal/DatadogLogging.cs @@ -54,7 +54,11 @@ static DatadogLogging() public static IDatadogLogger GetLoggerFor(Type classType) { // Tells us which types are loaded, when, and how often. - SharedLogger.Debug("Logger retrieved for: {AssemblyQualifiedName}", classType.AssemblyQualifiedName); + if (SharedLogger.IsEnabled(LogEventLevel.Debug)) + { + SharedLogger.Debug("Logger retrieved for: {AssemblyQualifiedName}", classType.AssemblyQualifiedName); + } + return SharedLogger; } diff --git a/tracer/src/Datadog.Trace/Logging/Internal/DatadogSerilogLogger.cs b/tracer/src/Datadog.Trace/Logging/Internal/DatadogSerilogLogger.cs index 68e5abd82f11..75287e40ace8 100644 --- a/tracer/src/Datadog.Trace/Logging/Internal/DatadogSerilogLogger.cs +++ b/tracer/src/Datadog.Trace/Logging/Internal/DatadogSerilogLogger.cs @@ -11,6 +11,7 @@ using Datadog.Trace.Logging.Internal.Configuration; using Datadog.Trace.Telemetry; using Datadog.Trace.Telemetry.Metrics; +using Datadog.Trace.Util; using Datadog.Trace.Vendors.Serilog; using Datadog.Trace.Vendors.Serilog.Context; using Datadog.Trace.Vendors.Serilog.Core.Pipeline; @@ -21,7 +22,7 @@ namespace Datadog.Trace.Logging internal sealed class DatadogSerilogLogger : IDatadogLogger { internal const string SkipTelemetryProperty = "SkipTelemetry"; - private static readonly object[] NoPropertyValues = Array.Empty(); + private static readonly object[] NoPropertyValues = []; private readonly ILogRateLimiter _rateLimiter; private ILogger _logger; @@ -53,6 +54,9 @@ public void Debug(string messageTemplate, T0 property0, T1 property1 public void Debug(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") => Write(LogEventLevel.Debug, exception: null, messageTemplate, property0, property1, property2, property3, sourceLine, sourceFile, skipTelemetry: false); + public void Debug(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, T4 property4, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") + => Write(LogEventLevel.Debug, exception: null, messageTemplate, property0, property1, property2, property3, property4, sourceLine, sourceFile, skipTelemetry: false); + public void Debug(string messageTemplate, object?[] args, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") => Write(LogEventLevel.Debug, exception: null, messageTemplate, args, sourceLine, sourceFile, skipTelemetry: false); @@ -83,6 +87,12 @@ public void Information(string messageTemplate, T0 property0, T1 propert public void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") => Write(LogEventLevel.Information, exception: null, messageTemplate, property0, property1, property2, sourceLine, sourceFile, skipTelemetry: false); + public void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") + => Write(LogEventLevel.Information, exception: null, messageTemplate, property0, property1, property2, property3, sourceLine, sourceFile, skipTelemetry: false); + + public void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, T4 property4, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") + => Write(LogEventLevel.Information, exception: null, messageTemplate, property0, property1, property2, property3, property4, sourceLine, sourceFile, skipTelemetry: false); + public void Information(string messageTemplate, object?[] args, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = "") => Write(LogEventLevel.Information, exception: null, messageTemplate, args, sourceLine, sourceFile, skipTelemetry: false); @@ -200,38 +210,77 @@ public void CloseAndFlush() private void Write(LogEventLevel level, Exception? exception, string messageTemplate, T property, int sourceLine, string sourceFile, bool skipTelemetry) { - if (_logger.IsEnabled(level)) + // Avoid boxing + array allocation if disabled + if (!_logger.IsEnabled(level)) { - // Avoid boxing + array allocation if disabled - WriteIfNotRateLimited(level, exception, messageTemplate, [property], sourceLine, sourceFile, skipTelemetry); + return; } + + using var array = FixedSizeArrayPool.OneItemPool.Get(); + array.Array[0] = property; + WriteIfNotRateLimited(level, exception, messageTemplate, array.Array, sourceLine, sourceFile, skipTelemetry); } private void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 property0, T1 property1, int sourceLine, string sourceFile, bool skipTelemetry) { - if (_logger.IsEnabled(level)) + // Avoid boxing + array allocation if disabled + if (!_logger.IsEnabled(level)) { - // Avoid boxing + array allocation if disabled - WriteIfNotRateLimited(level, exception, messageTemplate, [property0, property1], sourceLine, sourceFile, skipTelemetry); + return; } + + using var array = FixedSizeArrayPool.TwoItemPool.Get(); + array.Array[0] = property0; + array.Array[1] = property1; + WriteIfNotRateLimited(level, exception, messageTemplate, array.Array, sourceLine, sourceFile, skipTelemetry); } private void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 property0, T1 property1, T2 property2, int sourceLine, string sourceFile, bool skipTelemetry) { - if (_logger.IsEnabled(level)) + // Avoid boxing + array allocation if disabled + if (!_logger.IsEnabled(level)) { - // Avoid boxing + array allocation if disabled - WriteIfNotRateLimited(level, exception, messageTemplate, [property0, property1, property2], sourceLine, sourceFile, skipTelemetry); + return; } + + using var array = FixedSizeArrayPool.ThreeItemPool.Get(); + array.Array[0] = property0; + array.Array[1] = property1; + array.Array[2] = property2; + WriteIfNotRateLimited(level, exception, messageTemplate, array.Array, sourceLine, sourceFile, skipTelemetry); } private void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, int sourceLine, string sourceFile, bool skipTelemetry) { - if (_logger.IsEnabled(level)) + // Avoid boxing + array allocation if disabled + if (!_logger.IsEnabled(level)) + { + return; + } + + using var array = FixedSizeArrayPool.FourItemPool.Get(); + array.Array[0] = property0; + array.Array[1] = property1; + array.Array[2] = property2; + array.Array[3] = property3; + WriteIfNotRateLimited(level, exception, messageTemplate, array.Array, sourceLine, sourceFile, skipTelemetry); + } + + private void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, T4 property4, int sourceLine, string sourceFile, bool skipTelemetry) + { + // Avoid boxing + array allocation if disabled + if (!_logger.IsEnabled(level)) { - // Avoid boxing + array allocation if disabled - WriteIfNotRateLimited(level, exception, messageTemplate, [property0, property1, property2, property3], sourceLine, sourceFile, skipTelemetry); + return; } + + using var array = FixedSizeArrayPool.FiveItemPool.Get(); + array.Array[0] = property0; + array.Array[1] = property1; + array.Array[2] = property2; + array.Array[3] = property3; + array.Array[4] = property4; + WriteIfNotRateLimited(level, exception, messageTemplate, array.Array, sourceLine, sourceFile, skipTelemetry); } private void Write(LogEventLevel level, Exception? exception, string messageTemplate, object?[] args, int sourceLine, string sourceFile, bool skipTelemetry) diff --git a/tracer/src/Datadog.Trace/Logging/Internal/IDatadogLogger.cs b/tracer/src/Datadog.Trace/Logging/Internal/IDatadogLogger.cs index 09c6734d1cce..1c29f8e26f5b 100644 --- a/tracer/src/Datadog.Trace/Logging/Internal/IDatadogLogger.cs +++ b/tracer/src/Datadog.Trace/Logging/Internal/IDatadogLogger.cs @@ -28,6 +28,8 @@ internal interface IDatadogLogger void Debug(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); + void Debug(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, T4 property4, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); + void Debug(string messageTemplate, object?[] args, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); void Debug(Exception? exception, string messageTemplate, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); @@ -48,6 +50,10 @@ internal interface IDatadogLogger void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); + void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); + + void Information(string messageTemplate, T0 property0, T1 property1, T2 property2, T3 property3, T4 property4, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); + void Information(string messageTemplate, object?[] args, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); void Information(Exception? exception, string messageTemplate, [CallerLineNumber] int sourceLine = 0, [CallerFilePath] string sourceFile = ""); diff --git a/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs b/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs index 0af380d50b05..4e1bf9f5a227 100644 --- a/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs +++ b/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs @@ -31,7 +31,7 @@ internal sealed class NormalizerTraceProcessor : ITraceProcessor public NormalizerTraceProcessor() { - Log.Information("NormalizerTraceProcessor initialized."); + Log.Debug("NormalizerTraceProcessor initialized."); } public SpanCollection Process(in SpanCollection trace) diff --git a/tracer/src/Datadog.Trace/Processors/TruncatorTraceProcessor.cs b/tracer/src/Datadog.Trace/Processors/TruncatorTraceProcessor.cs index b94793a685c3..427901e5e19b 100644 --- a/tracer/src/Datadog.Trace/Processors/TruncatorTraceProcessor.cs +++ b/tracer/src/Datadog.Trace/Processors/TruncatorTraceProcessor.cs @@ -22,7 +22,7 @@ internal sealed class TruncatorTraceProcessor : ITraceProcessor public TruncatorTraceProcessor() { - Log.Information("TruncatorTraceProcessor initialized."); + Log.Debug("TruncatorTraceProcessor initialized."); } public SpanCollection Process(in SpanCollection trace) diff --git a/tracer/src/Datadog.Trace/TraceClock.cs b/tracer/src/Datadog.Trace/TraceClock.cs index 5260a960e0b6..e5a2ba7fea07 100644 --- a/tracer/src/Datadog.Trace/TraceClock.cs +++ b/tracer/src/Datadog.Trace/TraceClock.cs @@ -51,7 +51,7 @@ public static TraceClock Instance public DateTimeOffset UtcNow { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _utcStart.Add(Elapsed); + get => _utcStart.AddTicks(StopwatchHelpers.GetElapsedTicks(Stopwatch.GetTimestamp() - _timestamp)); } private TimeSpan Elapsed diff --git a/tracer/src/Datadog.Trace/TracerManagerFactory.cs b/tracer/src/Datadog.Trace/TracerManagerFactory.cs index 9f66bd798f25..e25f9fd18887 100644 --- a/tracer/src/Datadog.Trace/TracerManagerFactory.cs +++ b/tracer/src/Datadog.Trace/TracerManagerFactory.cs @@ -30,7 +30,6 @@ using Datadog.Trace.Util; using Datadog.Trace.Vendors.StatsdClient; using NativeInterop = Datadog.Trace.ContinuousProfiler.NativeInterop; -using Stopwatch = System.Diagnostics.Stopwatch; namespace Datadog.Trace { @@ -165,7 +164,7 @@ internal TracerManager CreateTracerManager( { if (remoteConfigurationManager == null) { - var sw = Stopwatch.StartNew(); + var sw = RefStopwatch.Create(); remoteConfigurationManager = RemoteConfigurationManager.Create( diff --git a/tracer/src/Datadog.Trace/Util/CodeDuration.cs b/tracer/src/Datadog.Trace/Util/CodeDuration.cs new file mode 100644 index 000000000000..8261229bbe67 --- /dev/null +++ b/tracer/src/Datadog.Trace/Util/CodeDuration.cs @@ -0,0 +1,197 @@ +// +// 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. +// + +#nullable enable + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using Datadog.Trace.Logging; +using Datadog.Trace.Vendors.Serilog.Events; + +namespace Datadog.Trace.Util; + +/// +/// Value-type duration helper that can be used in async methods (it can flow across awaits). +/// +/// +/// Avoid copying; use as a local variable only (copying can lead to multiple Dispose calls). +/// +/// +/// +/// using var duration = CodeDuration.Create(); +/// await DoWorkAsync(); +/// +/// +internal struct CodeDuration : IDisposable +{ + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(CodeDuration)); + private readonly string? _prefix; + private readonly string? _memberName; + private readonly string? _sourceFilePath; + private readonly int _sourceLineNumber; + private long _started; + + private CodeDuration(string memberName, string sourceFilePath, int sourceLineNumber) + { + this = default; + if (Log.IsEnabled(LogEventLevel.Debug)) + { + _prefix = CodeDurationBase.GetPrefix(Interlocked.Increment(ref CodeDurationBase.Count) - 1); + _memberName = memberName; + _sourceFilePath = sourceFilePath; + _sourceLineNumber = sourceLineNumber; + Log.Debug("{Prefix}[CodeDuration - Start: {MemberName} | {SourceFilePath}:{SourceLineNumber}]", _prefix, _memberName, _sourceFilePath, _sourceLineNumber); + _started = Stopwatch.GetTimestamp(); + } + } + + public static CodeDuration Create([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + return new(memberName, sourceFilePath, sourceLineNumber); + } + + public void Debug(string message) + { + if (_started > 0) + { + var currentTime = Stopwatch.GetTimestamp(); + Log.Debug( + "{Prefix}[CodeDuration - Message: {Message} | {Duration}ms | {MemberName}]", + _prefix, + message, + StopwatchHelpers.GetElapsedMilliseconds(currentTime - _started), + _memberName); + } + } + + public void Dispose() + { + var started = _started; + if (started > 0) + { + _started = 0; + var endTime = Stopwatch.GetTimestamp(); + Interlocked.Decrement(ref CodeDurationBase.Count); + Log.Debug( + "{Prefix}[CodeDuration - End: {Duration}ms | {MemberName} | {SourceFilePath}:{SourceLineNumber}]", + _prefix, + StopwatchHelpers.GetElapsedMilliseconds(endTime - started), + _memberName, + _sourceFilePath, + _sourceLineNumber); + } + } +} + +/// +/// Ref-struct duration helper for synchronous using scopes only. +/// +/// +/// Cannot be captured or used across await. +/// +/// +/// +/// using var duration = CodeDurationRef.Create(); +/// DoWork(); +/// +/// +internal ref struct CodeDurationRef +{ + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(CodeDurationRef)); + private readonly string? _prefix; + private readonly string? _memberName; + private readonly string? _sourceFilePath; + private readonly int _sourceLineNumber; + private long _started; + + private CodeDurationRef(string memberName, string sourceFilePath, int sourceLineNumber) + { + this = default; + if (Log.IsEnabled(LogEventLevel.Debug)) + { + _prefix = CodeDurationBase.GetPrefix(Interlocked.Increment(ref CodeDurationBase.Count) - 1); + _memberName = memberName; + _sourceFilePath = sourceFilePath; + _sourceLineNumber = sourceLineNumber; + Log.Debug("{Prefix}[CodeDuration - Start: {MemberName} | {SourceFilePath}:{SourceLineNumber}]", _prefix, _memberName, _sourceFilePath, _sourceLineNumber); + _started = Stopwatch.GetTimestamp(); + } + } + + public static CodeDurationRef Create([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + return new(memberName, sourceFilePath, sourceLineNumber); + } + + public void Debug(string message) + { + if (_started > 0) + { + var currentTime = Stopwatch.GetTimestamp(); + Log.Debug( + "{Prefix}[CodeDuration - Message: {Message} | {Duration}ms | {MemberName}]", + _prefix, + message, + StopwatchHelpers.GetElapsedMilliseconds(currentTime - _started), + _memberName); + } + } + + public void Dispose() + { + var started = _started; + if (started > 0) + { + _started = 0; + var endTime = Stopwatch.GetTimestamp(); + Interlocked.Decrement(ref CodeDurationBase.Count); + Log.Debug( + "{Prefix}[CodeDuration - End: {Duration}ms | {MemberName} | {SourceFilePath}:{SourceLineNumber}]", + _prefix, + StopwatchHelpers.GetElapsedMilliseconds(endTime - started), + _memberName, + _sourceFilePath, + _sourceLineNumber); + } + } +} + +internal static class CodeDurationBase +{ + private const int IndentSize = 4; + private static readonly string[] PrefixCache = BuildPrefixCache(); + +#pragma warning disable SA1401 + public static int Count; +#pragma warning restore SA1401 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetPrefix(int depth) + { + // Fast-path cached prefixes for small nesting depths. + if ((uint)depth < (uint)PrefixCache.Length) + { + return PrefixCache[depth]; + } + + // Fallback for deep nesting. + return new string(' ', depth * IndentSize); + } + + private static string[] BuildPrefixCache() + { + // 0..10 levels should cover most practical nesting without large static init costs. + var cache = new string[11]; + cache[0] = string.Empty; + for (var i = 1; i < cache.Length; i++) + { + cache[i] = new string(' ', i * IndentSize); + } + + return cache; + } +} diff --git a/tracer/src/Datadog.Trace/Util/FixedSizeArrayPool.cs b/tracer/src/Datadog.Trace/Util/FixedSizeArrayPool.cs new file mode 100644 index 000000000000..c93239c3b904 --- /dev/null +++ b/tracer/src/Datadog.Trace/Util/FixedSizeArrayPool.cs @@ -0,0 +1,135 @@ +// +// 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. +// +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Datadog.Trace.Util; + +internal sealed class FixedSizeArrayPool +{ + private const int MaxStackRetained = 63; + private readonly ConcurrentStack _items; + private readonly int _arrayItems; + private T[]? _fastPath; + private int _stackCount; + + public FixedSizeArrayPool(int arrayItems) + { + if (arrayItems <= 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(arrayItems), arrayItems, "Array size must be greater than 0."); + } + + _arrayItems = arrayItems; + _items = new(); + } + + public static FixedSizeArrayPool OneItemPool => field ??= new(1); + + public static FixedSizeArrayPool TwoItemPool => field ??= new(2); + + public static FixedSizeArrayPool ThreeItemPool => field ??= new(3); + + public static FixedSizeArrayPool FourItemPool => field ??= new(4); + + public static FixedSizeArrayPool FiveItemPool => field ??= new(5); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPoolItem Get() => new(InternalGetArray(), this); + + private T[] InternalGetArray() + { + if (Interlocked.Exchange(ref _fastPath, null) is { } cached) + { + return cached; + } + + if (_items.TryPop(out var value)) + { + Interlocked.Decrement(ref _stackCount); + return value; + } + + value = new T[_arrayItems]; + return value; + } + + private void InternalReturnArray(T[] value) + { + if (value is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(value)); + } + + if (value.Length != _arrayItems) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), $"Attempted to return an array of length {value.Length} to a pool of size {_arrayItems}."); + } + +#if NETCOREAPP3_0_OR_GREATER + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(value, 0, value.Length); + } +#else + Array.Clear(value, 0, value.Length); +#endif + + if (Interlocked.CompareExchange(ref _fastPath, value, null) is not null) + { + // Bound the number of retained arrays in the stack to avoid unbounded growth. + if (Interlocked.Increment(ref _stackCount) <= MaxStackRetained) + { + _items.Push(value); + } + else + { + Interlocked.Decrement(ref _stackCount); + } + } + } + + internal ref struct ArrayPoolItem + { + private readonly FixedSizeArrayPool _owner; + private T[]? _array; + + internal ArrayPoolItem(T[] array, FixedSizeArrayPool owner) + { + _array = array; + _owner = owner; + } + + public T[] Array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if DEBUG + if (_array is null) + { + throw new ObjectDisposedException(nameof(ArrayPoolItem)); + } +#endif + return _array ?? []; + } + } + + public void Dispose() + { + var arr = _array; + _array = null; + if (arr is not null) + { + _owner.InternalReturnArray(arr); + } + } + } +} diff --git a/tracer/src/Datadog.Trace/Util/RefStopwatch.cs b/tracer/src/Datadog.Trace/Util/RefStopwatch.cs new file mode 100644 index 000000000000..b017154efcb5 --- /dev/null +++ b/tracer/src/Datadog.Trace/Util/RefStopwatch.cs @@ -0,0 +1,39 @@ +// +// 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. +// + +#nullable enable +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Datadog.Trace.Util; + +/// +/// Struct version of the Stopwatch +/// This version doesn't allocate (ref struct) in the heap but it's only useful in sync method, for async method is better to use the existing Stopwatch. +/// +internal ref struct RefStopwatch +{ + private long _started; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefStopwatch() + { + _started = Stopwatch.GetTimestamp(); + } + + public TimeSpan Elapsed => StopwatchHelpers.GetElapsed(Stopwatch.GetTimestamp() - _started); + + public double ElapsedMilliseconds => StopwatchHelpers.GetElapsedMilliseconds(Stopwatch.GetTimestamp() - _started); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RefStopwatch Create() => new(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Restart() + { + _started = Stopwatch.GetTimestamp(); + } +} diff --git a/tracer/src/Datadog.Trace/Util/StopwatchHelpers.cs b/tracer/src/Datadog.Trace/Util/StopwatchHelpers.cs index eab6ec7017e4..1f8a5e38d8f1 100644 --- a/tracer/src/Datadog.Trace/Util/StopwatchHelpers.cs +++ b/tracer/src/Datadog.Trace/Util/StopwatchHelpers.cs @@ -4,23 +4,30 @@ // using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Datadog.Trace.Util { internal static class StopwatchHelpers { private static readonly double DateTimeTickFrequency = 10000000.0 / Stopwatch.Frequency; + private static readonly double TimestampToMilliseconds = 1000.0 / Stopwatch.Frequency; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TimeSpan GetElapsed(long stopwatchTicks) - { - var ticks = (long)(stopwatchTicks * DateTimeTickFrequency); + => new(GetElapsedTicks(stopwatchTicks)); - return new TimeSpan(ticks); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long GetElapsedTicks(long stopwatchTicks) + => (long)(stopwatchTicks * DateTimeTickFrequency); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GetElapsedMilliseconds(long stopwatchTicks) + => stopwatchTicks * TimestampToMilliseconds; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GetElapsedMilliseconds(this Stopwatch sw) + => sw.ElapsedTicks * TimestampToMilliseconds; } } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Agent/CiVisibilityProtocolWriterTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Agent/CiVisibilityProtocolWriterTests.cs index 72ff00f73255..876e5a66fe07 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Agent/CiVisibilityProtocolWriterTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Agent/CiVisibilityProtocolWriterTests.cs @@ -17,6 +17,7 @@ using Datadog.Trace.Agent.Transports; using Datadog.Trace.Ci; using Datadog.Trace.Ci.Agent; +using Datadog.Trace.Ci.Agent.MessagePack; using Datadog.Trace.Ci.Configuration; using Datadog.Trace.Ci.Coverage.Models.Tests; using Datadog.Trace.Ci.EventModel; @@ -40,13 +41,14 @@ public async Task AgentlessTestEventTest() { var settings = new TestOptimizationSettings(NullConfigurationSource.Instance, NullConfigurationTelemetry.Instance); var sender = new Mock(); - var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object); + var formatter = CIFormatterResolver.Instance; + var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, formatter); var span = new Span(new SpanContext(1, 1), DateTimeOffset.UtcNow, new TestSpanTags()); span.Type = SpanTypes.Test; span.SetTag(TestTags.Type, TestTags.TypeTest); - var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings); + var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings, formatter); expectedPayload.TryProcessEvent(new TestEvent(span)); var expectedBytes = expectedPayload.ToArray(); @@ -70,13 +72,14 @@ public async Task AgentlessStreamTestEventTest() { var settings = new TestOptimizationSettings(NullConfigurationSource.Instance, NullConfigurationTelemetry.Instance); var sender = new Mock(); - var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object); + var formatter = CIFormatterResolver.Instance; + var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, formatter); var span = new Span(new SpanContext(1, 1), DateTimeOffset.UtcNow, new TestSpanTags()); span.Type = SpanTypes.Test; span.SetTag(TestTags.Type, TestTags.TypeTest); - var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings); + var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings, formatter); expectedPayload.TryProcessEvent(new TestEvent(span)); var mStreamExpected = new MemoryStream(); expectedPayload.WriteTo(mStreamExpected); @@ -102,7 +105,8 @@ public async Task AgentlessCodeCoverageEvent() { var settings = new TestOptimizationSettings(NullConfigurationSource.Instance, NullConfigurationTelemetry.Instance); var sender = new Mock(); - var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object); + var formatter = CIFormatterResolver.Instance; + var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, formatter); var coveragePayload = new TestCoverage { SessionId = 42, @@ -118,7 +122,7 @@ public async Task AgentlessCodeCoverageEvent() ] }; - var expectedPayload = new Ci.Agent.Payloads.CICodeCoveragePayload(settings); + var expectedPayload = new Ci.Agent.Payloads.CICodeCoveragePayload(settings, formatter); expectedPayload.TryProcessEvent(coveragePayload); var expectedFormItems = expectedPayload.ToArray(); @@ -177,7 +181,8 @@ public async Task SlowSenderTest() var flushTcs = new TaskCompletionSource(); var sender = new Mock(); - var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, concurrency: 1); + var formatter = CIFormatterResolver.Instance; + var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, formatter, concurrency: 1); var lstPayloads = new List(); sender.Setup(x => x.SendPayloadAsync(It.IsAny())) @@ -189,7 +194,7 @@ public async Task SlowSenderTest() }); var span = new Span(new SpanContext(1, 1), DateTimeOffset.UtcNow); - var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings); + var expectedPayload = new Ci.Agent.Payloads.CITestCyclePayload(settings, formatter); expectedPayload.TryProcessEvent(new Ci.EventModel.SpanEvent(span)); expectedPayload.TryProcessEvent(new Ci.EventModel.SpanEvent(span)); expectedPayload.TryProcessEvent(new Ci.EventModel.SpanEvent(span)); @@ -230,8 +235,9 @@ public async Task ConcurrencyFlushTest() { var settings = new TestOptimizationSettings(NullConfigurationSource.Instance, NullConfigurationTelemetry.Instance); var sender = new Mock(); + var formatter = CIFormatterResolver.Instance; // We set 8 threads of concurrency and a batch interval of 10 seconds to avoid the autoflush. - var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, concurrency: 8, batchInterval: 10_000); + var agentlessWriter = new CIVisibilityProtocolWriter(settings, sender.Object, formatter, concurrency: 8, batchInterval: 10_000); var lstPayloads = new List(); const int numSpans = 2_000; @@ -286,18 +292,19 @@ static int GetNumberOfSpans(List payloads) [Fact] public void EventsBufferTest() { + var formatter = CIFormatterResolver.Instance; int headerSize = Ci.Agent.Payloads.EventsBuffer.HeaderSize; var span = new Span(new SpanContext(1, 1), DateTimeOffset.UtcNow); var spanEvent = new Ci.EventModel.SpanEvent(span); - var individualType = MessagePackSerializer.Serialize(spanEvent, Ci.Agent.MessagePack.CIFormatterResolver.Instance); + var individualType = MessagePackSerializer.Serialize(spanEvent, formatter); int bufferSize = 256; int maxBufferSize = (int)(4.5 * 1024 * 1024); while (bufferSize < maxBufferSize) { - var eventBuffer = new Ci.Agent.Payloads.EventsBuffer(bufferSize, Ci.Agent.MessagePack.CIFormatterResolver.Instance); + var eventBuffer = new Ci.Agent.Payloads.EventsBuffer(bufferSize, formatter); while (eventBuffer.TryWrite(spanEvent)) { // . @@ -336,11 +343,12 @@ public void CoverageBufferTest() ] }; - var coveragePayloadInBytes = MessagePackSerializer.Serialize(coveragePayload, Ci.Agent.MessagePack.CIFormatterResolver.Instance); + var formatter = CIFormatterResolver.Instance; + var coveragePayloadInBytes = MessagePackSerializer.Serialize(coveragePayload, formatter); while (bufferSize < maxBufferSize) { - var payloadBuffer = new Ci.Agent.Payloads.CICodeCoveragePayload(settings, maxItemsPerPayload: int.MaxValue, maxBytesPerPayload: bufferSize); + var payloadBuffer = new Ci.Agent.Payloads.CICodeCoveragePayload(settings, formatter, maxItemsPerPayload: int.MaxValue, maxBytesPerPayload: bufferSize); while (payloadBuffer.TryProcessEvent(coveragePayload)) { // . diff --git a/tracer/test/Datadog.Trace.Tests/Util/FixedSizeArrayPoolTests.cs b/tracer/test/Datadog.Trace.Tests/Util/FixedSizeArrayPoolTests.cs new file mode 100644 index 000000000000..2d930541c410 --- /dev/null +++ b/tracer/test/Datadog.Trace.Tests/Util/FixedSizeArrayPoolTests.cs @@ -0,0 +1,119 @@ +// +// 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. +// + +using System; +using Datadog.Trace.Util; +using FluentAssertions; +using Xunit; + +namespace Datadog.Trace.Tests.Util +{ + public class FixedSizeArrayPoolTests + { + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Constructor_InvalidArrayItems_Throws(int size) + { + Action action = () => new FixedSizeArrayPool(size); + + action.Should() + .Throw() + .WithParameterName("arrayItems"); + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(5)] + public void Get_ReturnsArrayOfExpectedLength(int size) + { + var pool = new FixedSizeArrayPool(size); + + using var item = pool.Get(); + + item.Array.Should().HaveCount(size); + } + + [Fact] + public void Get_WhenPoolEmpty_ReturnsDistinctArrays() + { + var pool = new FixedSizeArrayPool(2); + + using var item1 = pool.Get(); + using var item2 = pool.Get(); + + item1.Array.Should().NotBeSameAs(item2.Array); + } + + [Fact] + public void Return_ReusesArrayFromFastPath_AndClearsReferenceTypes() + { + var pool = new FixedSizeArrayPool(2); + string[] array; + + using (var item = pool.Get()) + { + array = item.Array; + array[0] = "value"; + array[1] = "other"; + } + + using var item2 = pool.Get(); + + item2.Array.Should().BeSameAs(array); + item2.Array[0].Should().BeNull(); + item2.Array[1].Should().BeNull(); + } + + [Fact] + public void Return_UsesStackAfterFastPath() + { + var pool = new FixedSizeArrayPool(1); + + var item1 = pool.Get(); + var item2 = pool.Get(); + var array1 = item1.Array; + var array2 = item2.Array; + + item1.Dispose(); + item2.Dispose(); + + using var first = pool.Get(); + using var second = pool.Get(); + + first.Array.Should().BeSameAs(array1); + second.Array.Should().BeSameAs(array2); + } + + [Fact] + public void Dispose_CanBeCalledMultipleTimes() + { + var pool = new FixedSizeArrayPool(1); + var item = pool.Get(); + + item.Dispose(); + item.Dispose(); + } + + [Fact] + public void Return_WrongLength_Throws() + { + var pool = new FixedSizeArrayPool(2); + + Action action = () => ReturnWrongLengthArray(pool); + + action.Should() + .Throw() + .WithParameterName("value"); + } + + private static void ReturnWrongLengthArray(FixedSizeArrayPool pool) + { + var item = new FixedSizeArrayPool.ArrayPoolItem(new int[1], pool); + item.Dispose(); + } + } +} diff --git a/tracer/test/benchmarks/Benchmarks.Trace/CIVisibilityProtocolWriterBenchmark.cs b/tracer/test/benchmarks/Benchmarks.Trace/CIVisibilityProtocolWriterBenchmark.cs index 8545d40dfd1c..d97c71886e89 100644 --- a/tracer/test/benchmarks/Benchmarks.Trace/CIVisibilityProtocolWriterBenchmark.cs +++ b/tracer/test/benchmarks/Benchmarks.Trace/CIVisibilityProtocolWriterBenchmark.cs @@ -4,6 +4,7 @@ using Datadog.Trace; using Datadog.Trace.Agent; using Datadog.Trace.Ci.Agent; +using Datadog.Trace.Ci.Agent.MessagePack; using Datadog.Trace.Ci.Agent.Payloads; using Datadog.Trace.Ci.Configuration; using Datadog.Trace.Configuration; @@ -40,7 +41,7 @@ public void GlobalSetup() config[ConfigurationKeys.TraceEnabled] = false; var settings = new TestOptimizationSettings(new DictionaryObjectConfigurationSource(config), NullConfigurationTelemetry.Instance); - _eventWriter = new CIVisibilityProtocolWriter(settings, new FakeCIVisibilityProtocolWriter()); + _eventWriter = new CIVisibilityProtocolWriter(settings, new FakeCIVisibilityProtocolWriter(), CIFormatterResolver.Instance); // Warmup to reduce noise WriteAndFlushEnrichedTraces().GetAwaiter().GetResult();