Skip to content

Commit ed3fd24

Browse files
andrewlockchrisnas
andauthored
Refactor the profiling configuration reading (#7303)
## Summary of changes Refactors how we determine the profiler's current configuration state ## Reason for change Today, the profiler determines if it's enabled or disabled independently of the tracer, in the native code. We then try to mirror that state in the managed code, for several reasons - To know whether we need to send app-domain information to the profiler (e.g. service name, env) - To know whether we need to send thread-context-change events to the profiler (for CodeHotspots/EndpointProfiling) - For telemetry reasons - For recording the state in DSM payloads As part of the hands-off config work, we will calculate whether to enable the profiler based on the _full_ managed config, instead of just using environment variables. However, when managed activation is disabled (for back-compat investigation purposes), we _do_ need to just use env vars, to "mirror" the state that will be calculated by the native continuous profiler, essentially working in the existing "legacy" state. The refactoring in this PR is intended to lay the groundwork for the future hands-off config work in the profiler, as well as to solve some existing issues with inconsistent/missing telemetry ## Implementation details - Add a `ProfilerState` `enum` to track the profiler as a 3-state enablement which represents its real state, instead of trying to force it as a boolean - Use `EnvironmentConfigurationSource` to record variables read from env in telemetry correctly. - Add support for the `DD_PROFILER_MANAGED_ACTIVATION_ENABLED` which will be used shortly ## Test coverage Added unit tests for the new `ProfilerSettings` which encapsulates most of the logic ## Other details This PR technically requires support for `DD_PROFILER_MANAGED_ACTIVATION_ENABLED` has been merged in order to report the correct values. This isn't a big deal, as it will likely be merged soon, but if it isn't, we should tweak the logic in `ProfilerSettings` so that it's correct. Alternatively, I can split this into two PRs - one that is correct _today_, and one that is correct only with the profiler PR --------- Co-authored-by: chrisnas <[email protected]>
1 parent c38a5fe commit ed3fd24

19 files changed

+352
-127
lines changed

tracer/src/Datadog.Trace/Configuration/TracerSettings.cs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Datadog.Trace.ClrProfiler.ServerlessInstrumentation;
2121
using Datadog.Trace.Configuration.ConfigurationSources.Telemetry;
2222
using Datadog.Trace.Configuration.Telemetry;
23+
using Datadog.Trace.ContinuousProfiler;
2324
using Datadog.Trace.Logging;
2425
using Datadog.Trace.Logging.DirectSubmission;
2526
using Datadog.Trace.Processors;
@@ -139,28 +140,6 @@ internal TracerSettings(IConfigurationSource? source, IConfigurationTelemetry te
139140
AzureAppServiceMetadata = new ImmutableAzureAppServiceSettings(source, _telemetry);
140141
}
141142

142-
// With SSI, beyond ContinuousProfiler.ConfigurationKeys.ProfilingEnabled (true or auto vs false),
143-
// the profiler could be enabled via ContinuousProfiler.ConfigurationKeys.SsiDeployed:
144-
// - if it contains "profiler", the profiler is enabled after 30 seconds + at least 1 span
145-
// - if not, the profiler needed to be loaded by the CLR but no profiling will be done, only telemetry metrics will be sent
146-
// So, for the Tracer, the profiler should be seen as enabled if ContinuousProfiler.ConfigurationKeys.SsiDeployed has a value
147-
// (even without "profiler") so that spans will be sent to the profiler.
148-
ProfilingEnabledInternal = config
149-
.WithKeys(ContinuousProfiler.ConfigurationKeys.ProfilingEnabled)
150-
.GetAs(
151-
converter: x => x switch
152-
{
153-
"auto" => true,
154-
_ when x.ToBoolean() is { } boolean => boolean,
155-
_ => ParsingResult<bool>.Failure(),
156-
},
157-
getDefaultValue: () =>
158-
{
159-
var profilingSsiDeployed = config.WithKeys(ContinuousProfiler.ConfigurationKeys.SsiDeployed).AsString();
160-
return (profilingSsiDeployed != null);
161-
},
162-
validator: null);
163-
164143
var otelTags = config
165144
.WithKeys(ConfigurationKeys.OpenTelemetry.ResourceAttributes)
166145
.AsDictionaryResult(separator: '=');
@@ -890,13 +869,6 @@ static void RecordDisabledIntegrationsTelemetry(IntegrationSettingsCollection in
890869
/// <seealso cref="ConfigurationKeys.ApmTracingEnabled"/>
891870
internal bool ApmTracingEnabled => DynamicSettings.ApmTracingEnabled ?? _apmTracingEnabled;
892871

893-
/// <summary>
894-
/// Gets a value indicating whether profiling is enabled.
895-
/// Default is <c>false</c>.
896-
/// </summary>
897-
/// <seealso cref="ContinuousProfiler.ConfigurationKeys.ProfilingEnabled"/>
898-
internal bool ProfilingEnabledInternal { get; }
899-
900872
/// <summary>
901873
/// Gets the names of disabled integrations.
902874
/// </summary>

tracer/src/Datadog.Trace/ContinuousProfiler/ConfigurationKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ internal static class ConfigurationKeys
1111
public const string CodeHotspotsEnabled = "DD_PROFILING_CODEHOTSPOTS_ENABLED";
1212
public const string EndpointProfilingEnabled = "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED";
1313
public const string SsiDeployed = "DD_INJECTION_ENABLED";
14+
public const string ProfilerManagedActivationEnabled = "DD_PROFILER_MANAGED_ACTIVATION_ENABLED";
1415
}
1516
}

tracer/src/Datadog.Trace/ContinuousProfiler/Profiler.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,29 @@
44
// </copyright>
55

66
using System.Threading;
7+
using Datadog.Trace.Configuration;
8+
using Datadog.Trace.Telemetry;
79

810
namespace Datadog.Trace.ContinuousProfiler
911
{
1012
internal class Profiler
1113
{
1214
private static Profiler _instance;
1315

14-
internal Profiler(IContextTracker contextTracker, IProfilerStatus status)
16+
internal Profiler(IContextTracker contextTracker, IProfilerStatus status, ProfilerSettings settings)
1517
{
1618
ContextTracker = contextTracker;
1719
Status = status;
20+
Settings = settings;
1821
}
1922

2023
public static Profiler Instance
2124
{
2225
get { return LazyInitializer.EnsureInitialized(ref _instance, () => Create()); }
2326
}
2427

28+
public ProfilerSettings Settings { get; }
29+
2530
public IProfilerStatus Status { get; }
2631

2732
public IContextTracker ContextTracker { get; }
@@ -33,9 +38,10 @@ internal static void SetInstanceForTests(Profiler value)
3338

3439
private static Profiler Create()
3540
{
36-
var status = new ProfilerStatus();
41+
var settings = new ProfilerSettings(GlobalConfigurationSource.Instance, TelemetryFactory.Config);
42+
var status = new ProfilerStatus(settings);
3743
var contextTracker = new ContextTracker(status);
38-
return new Profiler(contextTracker, status);
44+
return new Profiler(contextTracker, status, settings);
3945
}
4046
}
4147
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// <copyright file="ProfilerSettings.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using Datadog.Trace.Configuration;
9+
using Datadog.Trace.Configuration.ConfigurationSources.Telemetry;
10+
using Datadog.Trace.Configuration.Telemetry;
11+
12+
namespace Datadog.Trace.ContinuousProfiler;
13+
14+
internal class ProfilerSettings
15+
{
16+
public ProfilerSettings(IConfigurationSource config, IConfigurationTelemetry telemetry)
17+
: this(config, new EnvironmentConfigurationSource(), telemetry)
18+
{
19+
}
20+
21+
// Internal for testing only
22+
internal ProfilerSettings(IConfigurationSource config, IConfigurationSource envConfig, IConfigurationTelemetry telemetry)
23+
{
24+
if (!IsProfilingSupported)
25+
{
26+
ProfilerState = ProfilerState.Disabled;
27+
telemetry.Record(ConfigurationKeys.ProfilingEnabled, false, ConfigurationOrigins.Calculated);
28+
return;
29+
}
30+
31+
// If managed activation is enabled, we need to _just_ read from the environment variables,
32+
// as that's all that applies
33+
var envConfigBuilder = new ConfigurationBuilder(envConfig, telemetry);
34+
var managedActivationEnabled = envConfigBuilder
35+
.WithKeys(ConfigurationKeys.ProfilerManagedActivationEnabled)
36+
.AsBool(true);
37+
38+
// If we're using managed activation, we use the "full" config source set.
39+
// Otherwise we only read from the environment variables, to "match" the behavior of the profiler
40+
var profilingConfig = managedActivationEnabled
41+
? new ConfigurationBuilder(config, telemetry)
42+
: envConfigBuilder;
43+
44+
// With SSI, beyond ContinuousProfiler.ConfigurationKeys.ProfilingEnabled (true or auto vs false),
45+
// the profiler could be enabled via ContinuousProfiler.ConfigurationKeys.SsiDeployed. If it is non-empty, then the
46+
// profiler is "active", though won't begin profiling until 30 seconds have passed + at least 1 span has been generated.
47+
var profilingEnabled = profilingConfig
48+
.WithKeys(ConfigurationKeys.ProfilingEnabled)
49+
// We stick with strings here instead of using the `GetAs` method,
50+
// so that telemetry continues to store true/false/auto, instead of the enum values.
51+
.AsString(
52+
converter: x => x switch
53+
{
54+
"auto" => "auto",
55+
_ when x.ToBoolean() is { } boolean => boolean ? "true" : "false",
56+
_ => ParsingResult<string>.Failure(),
57+
},
58+
getDefaultValue: () =>
59+
{
60+
// If there's no explicit `DD_PROFILING_ENABLED` key,
61+
// we set the state based on the SSI value, only checking env vars (not the full stack)
62+
var isSsiDeployment = envConfigBuilder
63+
.WithKeys(ConfigurationKeys.SsiDeployed)
64+
.AsString();
65+
66+
return isSsiDeployment switch
67+
{
68+
{ Length: > 0 } => "auto",
69+
_ => "false",
70+
};
71+
},
72+
validator: null);
73+
74+
ProfilerState = profilingEnabled switch
75+
{
76+
"auto" => ProfilerState.Auto,
77+
"true" => ProfilerState.Enabled,
78+
_ => ProfilerState.Disabled,
79+
};
80+
}
81+
82+
// Internal for testing only
83+
internal ProfilerSettings(ProfilerState state)
84+
{
85+
ProfilerState = state;
86+
}
87+
88+
/// <summary>
89+
/// Gets a value indicating whether the profiler is supported on this platform at all.
90+
/// If it's not supported, we should not try to P/Invoke to it or do any context tracking.
91+
/// </summary>
92+
public static bool IsProfilingSupported
93+
{
94+
get
95+
{
96+
var fd = FrameworkDescription.Instance;
97+
return
98+
(fd.OSPlatform == OSPlatformName.Windows && fd.ProcessArchitecture is ProcessArchitecture.X64 or ProcessArchitecture.X86) ||
99+
(fd.OSPlatform == OSPlatformName.Linux && fd.ProcessArchitecture is ProcessArchitecture.X64);
100+
}
101+
}
102+
103+
public ProfilerState ProfilerState { get; }
104+
105+
public bool IsProfilerEnabled => ProfilerState != ProfilerState.Disabled;
106+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// <copyright file="ProfilerState.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
namespace Datadog.Trace.ContinuousProfiler;
9+
10+
internal enum ProfilerState
11+
{
12+
/// <summary>
13+
/// The profiler is explicitly disabled via configuration
14+
/// </summary>
15+
Disabled = 0,
16+
17+
/// <summary>
18+
/// The profiler is explicitly enabled via configuration
19+
/// </summary>
20+
Enabled = 1,
21+
22+
/// <summary>
23+
/// The profiler is in "auto" mode; i.e. will start after a delay and if traces are created
24+
/// </summary>
25+
Auto = 2,
26+
}

tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerStatus.cs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,30 @@
55

66
using System;
77
using System.Runtime.InteropServices;
8-
using Datadog.Trace.ExtensionMethods;
98
using Datadog.Trace.Logging;
10-
using Datadog.Trace.Util;
119

1210
namespace Datadog.Trace.ContinuousProfiler
1311
{
1412
internal class ProfilerStatus : IProfilerStatus
1513
{
1614
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ProfilerStatus));
1715

18-
private readonly bool _isProfilingEnabled;
16+
private readonly ProfilerState _profilerState;
1917
private readonly object _lockObj;
2018
private bool _isInitialized;
2119
private IntPtr _engineStatusPtr;
2220

23-
public ProfilerStatus()
21+
public ProfilerStatus(ProfilerSettings settings)
2422
{
25-
var fd = FrameworkDescription.Instance;
26-
var isSupported =
27-
(fd.OSPlatform == OSPlatformName.Windows && (fd.ProcessArchitecture == ProcessArchitecture.X64 || fd.ProcessArchitecture == ProcessArchitecture.X86)) ||
28-
(fd.OSPlatform == OSPlatformName.Linux && fd.ProcessArchitecture == ProcessArchitecture.X64);
29-
30-
_isProfilingEnabled = false;
31-
32-
if (isSupported)
23+
_profilerState = settings.ProfilerState;
24+
var state = _profilerState switch
3325
{
34-
var manualDeployement = EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.ProfilingEnabled);
35-
if (manualDeployement != null)
36-
{
37-
// it is possible that SSI installation script is setting the environment variable to "auto" to enable the profiler
38-
// instead of "true" to avoid starting the profiler immediately after the installation
39-
_isProfilingEnabled = manualDeployement.ToBoolean() ?? (manualDeployement == "auto");
40-
}
41-
else
42-
{
43-
// the profiler is declared "enabled" just if the SSI environment variable exists to be sure that telemetry metrics
44-
// will contain the right status (i.e. we need the tracer to send the spans even if the profiler is not started yet)
45-
_isProfilingEnabled = (EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.SsiDeployed) != null);
46-
}
47-
}
26+
ProfilerState.Enabled => "enabled",
27+
ProfilerState.Auto => "auto",
28+
_ => "disabled"
29+
};
4830

49-
Log.Information("Continuous Profiler is {IsEnabled}.", _isProfilingEnabled ? "enabled" : "disabled");
31+
Log.Information("Continuous Profiler mode = {ProfilerState}", state);
5032
_lockObj = new();
5133
_isInitialized = false;
5234
}
@@ -55,7 +37,7 @@ public bool IsProfilerReady
5537
{
5638
get
5739
{
58-
if (!_isProfilingEnabled)
40+
if (_profilerState == ProfilerState.Disabled)
5941
{
6042
return false;
6143
}

tracer/src/Datadog.Trace/DataStreamsMonitoring/Aggregation/DataStreamsMessagePackFormatter.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Generic;
1010
using System.IO;
1111
using Datadog.Trace.Configuration;
12+
using Datadog.Trace.ContinuousProfiler;
1213
using Datadog.Trace.Vendors.Datadog.Sketches;
1314
using Datadog.Trace.Vendors.MessagePack;
1415

@@ -51,7 +52,7 @@ internal class DataStreamsMessagePackFormatter
5152
private readonly byte[] _productMaskBytes = StringEncoding.UTF8.GetBytes("ProductMask");
5253
private readonly byte[] _isInDefaultStateBytes = StringEncoding.UTF8.GetBytes("IsInDefaultState");
5354

54-
public DataStreamsMessagePackFormatter(TracerSettings tracerSettings, string defaultServiceName)
55+
public DataStreamsMessagePackFormatter(TracerSettings tracerSettings, ProfilerSettings profilerSettings, string defaultServiceName)
5556
{
5657
var env = tracerSettings.Environment;
5758
// .NET tracer doesn't yet support primary tag
@@ -60,7 +61,7 @@ public DataStreamsMessagePackFormatter(TracerSettings tracerSettings, string def
6061
? []
6162
: StringEncoding.UTF8.GetBytes(env);
6263
_serviceValueBytes = StringEncoding.UTF8.GetBytes(defaultServiceName);
63-
_productMask = GetProductsMask(tracerSettings);
64+
_productMask = GetProductsMask(tracerSettings, profilerSettings);
6465
_isInDefaultState = tracerSettings.IsDataStreamsMonitoringInDefaultState;
6566
}
6667

@@ -75,15 +76,15 @@ private enum Products : long
7576
Profiling = 1 << 3, // 00001000
7677
}
7778

78-
private static long GetProductsMask(TracerSettings tracerSettings)
79+
private static long GetProductsMask(TracerSettings tracerSettings, ProfilerSettings profilerSettings)
7980
{
8081
var productsMask = (long)Products.Apm;
8182
if (tracerSettings.IsDataStreamsMonitoringEnabled)
8283
{
8384
productsMask |= (long)Products.Dsm;
8485
}
8586

86-
if (tracerSettings.ProfilingEnabledInternal)
87+
if (profilerSettings.IsProfilerEnabled)
8788
{
8889
productsMask |= (long)Products.Profiling;
8990
}

tracer/src/Datadog.Trace/DataStreamsMonitoring/DataStreamsManager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Datadog.Trace.Agent.DiscoveryService;
1212
using Datadog.Trace.Configuration;
13+
using Datadog.Trace.ContinuousProfiler;
1314
using Datadog.Trace.DataStreamsMonitoring.Aggregation;
1415
using Datadog.Trace.DataStreamsMonitoring.Hashes;
1516
using Datadog.Trace.Headers;
@@ -50,11 +51,12 @@ public DataStreamsManager(
5051

5152
public static DataStreamsManager Create(
5253
TracerSettings settings,
54+
ProfilerSettings profilerSettings,
5355
IDiscoveryService discoveryService,
5456
string defaultServiceName)
5557
{
5658
var writer = settings.IsDataStreamsMonitoringEnabled
57-
? DataStreamsWriter.Create(settings, discoveryService, defaultServiceName)
59+
? DataStreamsWriter.Create(settings, profilerSettings, discoveryService, defaultServiceName)
5860
: null;
5961

6062
return new DataStreamsManager(settings.Environment, defaultServiceName, writer, settings.IsDataStreamsMonitoringInDefaultState);

tracer/src/Datadog.Trace/DataStreamsMonitoring/DataStreamsWriter.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Threading.Tasks;
1212
using Datadog.Trace.Agent.DiscoveryService;
1313
using Datadog.Trace.Configuration;
14+
using Datadog.Trace.ContinuousProfiler;
1415
using Datadog.Trace.DataStreamsMonitoring.Aggregation;
1516
using Datadog.Trace.DataStreamsMonitoring.Transport;
1617
using Datadog.Trace.Logging;
@@ -70,12 +71,13 @@ public DataStreamsWriter(
7071

7172
public static DataStreamsWriter Create(
7273
TracerSettings settings,
74+
ProfilerSettings profilerSettings,
7375
IDiscoveryService discoveryService,
7476
string defaultServiceName)
7577
=> new(
7678
settings,
7779
new DataStreamsAggregator(
78-
new DataStreamsMessagePackFormatter(settings, defaultServiceName),
80+
new DataStreamsMessagePackFormatter(settings, profilerSettings, defaultServiceName),
7981
bucketDurationMs: DataStreamsConstants.DefaultBucketDurationMs),
8082
new DataStreamsApi(DataStreamsTransportStrategy.GetAgentIntakeFactory(settings.Exporter)),
8183
bucketDurationMs: DataStreamsConstants.DefaultBucketDurationMs,

0 commit comments

Comments
 (0)