Skip to content

Commit 7e24fb2

Browse files
[Tracer] Support remote config for DD_TRACE_SAMPLING_RULES and Adaptive Sampling (#5453)
* clean up * add mapping to DynamicConfigConfigurationSource * add CustomSamplingRules to ImmutableDynamicSettings * subscribe to new RCM capability * add SamplingRuleProvenance * add provenance to sampling rules * return SamplingMechanism and Priority based on Provenance * override ToString() to use in logs instead of rule name * fix compiler error in tests * rename provenance constant fields * rename mechanism constant field * update DynamicConfigurationTests * make provenance json field not required and fix fallback value * add unit test * rename ImmutableDynamicSettings.CustomSamplingRules to SamplingRules * tweak xml doc comment * rename DynamicConfigurationTests+Config.CustomSamplingRules to SamplingRules * update test * fix typo in comment * rename method * add BuildFromRemoteConfigurationString() (not implemented yet) and call it * rename GlobalSamplingRule to GlobalSamplingRateRule * disabled warning for unused auto-property setter * rename to CustomRuleConfig to LocalSamplingRuleConfig * add RemoteSamplingRuleConfig * implement BuildFromRemoteConfigurationString() * rename types, add code comments * add test for ConvertToLocalTags() * add test for remote config json * refactoring rules into derived classes * add argument names * add missing brace * rename field * fix build errors * minor refactoring * add remote rules before local rules * add the AgentSamplingRule from outside TracerSampler * return arrays instead of IEnumerable<CustomSamplingRule> * expand code comments * rename variables * remove ISamplingRule.Priority * fix compiler errors in unit tests * rename json models * fix unit tests by registering AgentSamplingRule * push more code down from CustomSamplingRule to derived types * remove outdated comment * clean up code * expand test * clean up code * rename test class * add remote sampling rules to configuration log entry * test for remote sampling rules * don't use Should().HaveCount() * add TraceSampler.RegisterAgentSamplingRule() * fix xml-doc comments * ignore additional AgentSamplingRule registrations * add xml-doc comments * add TraceSampler.RegisterRules()
1 parent 4d97710 commit 7e24fb2

24 files changed

+583
-161
lines changed

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DynamicConfigConfigurationSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal class DynamicConfigConfigurationSource : JsonConfigurationSource
2424
// { ConfigurationKeys.ServiceNameMappings, "tracing_service_mapping" },
2525
{ ConfigurationKeys.LogsInjectionEnabled, "log_injection_enabled" },
2626
{ ConfigurationKeys.GlobalSamplingRate, "tracing_sampling_rate" },
27-
// { ConfigurationKeys.CustomSamplingRules, "tracing_sampling_rules" },
27+
{ ConfigurationKeys.CustomSamplingRules, "tracing_sampling_rules" },
2828
// { ConfigurationKeys.SpanSamplingRules, "span_sampling_rules" },
2929
// { ConfigurationKeys.DataStreamsMonitoring.Enabled, "data_streams_enabled" },
3030
{ ConfigurationKeys.GlobalTags, "tracing_tags" }

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public void Start()
4646
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingLogsInjection, true); // 13
4747
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingSampleRate, true); // 12
4848
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingTracingEnabled, true); // 19
49+
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingSampleRules, true); // 29
4950
}
5051
}
5152

@@ -76,7 +77,7 @@ private static void OnConfigurationChanged(ConfigurationBuilder settings)
7677
TraceEnabled = settings.WithKeys(ConfigurationKeys.TraceEnabled).AsBool(),
7778
// RuntimeMetricsEnabled = settings.WithKeys(ConfigurationKeys.RuntimeMetricsEnabled).AsBool(),
7879
// DataStreamsMonitoringEnabled = settings.WithKeys(ConfigurationKeys.DataStreamsMonitoring.Enabled).AsBool(),
79-
// CustomSamplingRules = settings.WithKeys(ConfigurationKeys.CustomSamplingRules).AsString(),
80+
SamplingRules = settings.WithKeys(ConfigurationKeys.CustomSamplingRules).AsString(),
8081
GlobalSamplingRate = settings.WithKeys(ConfigurationKeys.GlobalSamplingRate).AsDouble(),
8182
// SpanSamplingRules = settings.WithKeys(ConfigurationKeys.SpanSamplingRules).AsString(),
8283
LogsInjectionEnabled = settings.WithKeys(ConfigurationKeys.LogsInjectionEnabled).AsBool(),

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal class ImmutableDynamicSettings : IEquatable<ImmutableDynamicSettings>
2020

2121
public double? GlobalSamplingRate { get; init; }
2222

23-
public string? SpanSamplingRules { get; init; }
23+
public string? SamplingRules { get; init; }
2424

2525
public bool? LogsInjectionEnabled { get; init; }
2626

@@ -47,7 +47,7 @@ public bool Equals(ImmutableDynamicSettings? other)
4747
&& RuntimeMetricsEnabled == other.RuntimeMetricsEnabled
4848
&& DataStreamsMonitoringEnabled == other.DataStreamsMonitoringEnabled
4949
&& Nullable.Equals(GlobalSamplingRate, other.GlobalSamplingRate)
50-
&& SpanSamplingRules == other.SpanSamplingRules
50+
&& SamplingRules == other.SamplingRules
5151
&& LogsInjectionEnabled == other.LogsInjectionEnabled
5252
&& AreEqual(HeaderTags, other.HeaderTags)
5353
&& AreEqual(ServiceNameMappings, other.ServiceNameMappings)
@@ -81,15 +81,15 @@ public override int GetHashCode()
8181
RuntimeMetricsEnabled,
8282
DataStreamsMonitoringEnabled,
8383
GlobalSamplingRate,
84-
SpanSamplingRules,
84+
SamplingRules,
8585
LogsInjectionEnabled);
8686
}
8787

8888
private static bool AreEqual(IReadOnlyDictionary<string, string>? dictionary1, IReadOnlyDictionary<string, string>? dictionary2)
8989
{
9090
if (dictionary1 == null || dictionary2 == null)
9191
{
92-
return dictionary1 == dictionary2;
92+
return ReferenceEquals(dictionary1, dictionary2);
9393
}
9494

9595
if (dictionary1.Count != dictionary2.Count)

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public partial record ImmutableTracerSettings
3838
private readonly IReadOnlyDictionary<string, string> _globalTags;
3939
private readonly double? _globalSamplingRate;
4040
private readonly bool _runtimeMetricsEnabled;
41-
private readonly string? _spanSamplingRules;
4241

4342
/// <summary>
4443
/// Initializes a new instance of the <see cref="ImmutableTracerSettings"/> class
@@ -99,7 +98,7 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
9998
MaxTracesSubmittedPerSecondInternal = settings.MaxTracesSubmittedPerSecondInternal;
10099
CustomSamplingRulesInternal = settings.CustomSamplingRulesInternal;
101100
CustomSamplingRulesFormat = settings.CustomSamplingRulesFormat;
102-
_spanSamplingRules = settings.SpanSamplingRules;
101+
SpanSamplingRules = settings.SpanSamplingRules;
103102
_globalSamplingRate = settings.GlobalSamplingRateInternal;
104103
IntegrationsInternal = new ImmutableIntegrationSettingsCollection(settings.IntegrationsInternal, settings.DisabledIntegrationNamesInternal);
105104
_headerTags = new ReadOnlyDictionary<string, string>(settings.HeaderTagsInternal);
@@ -299,6 +298,13 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
299298
[GeneratePublicApi(PublicApiUsage.ImmutableTracerSettings_CustomSamplingRules_Get)]
300299
internal string? CustomSamplingRulesInternal { get; }
301300

301+
/// <summary>
302+
/// Gets the trace sampling rules from remote config.
303+
/// These contain custom remote rules and dynamic (aka adaptive) rules.
304+
/// They will be merged with local sampling rules.
305+
/// </summary>
306+
internal string? RemoteSamplingRules => DynamicSettings.SamplingRules;
307+
302308
/// <summary>
303309
/// Gets a value indicating the format for custom sampling rules ("regex" or "glob").
304310
/// </summary>
@@ -309,7 +315,7 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
309315
/// Gets a value indicating the span sampling rules.
310316
/// </summary>
311317
/// <seealso cref="ConfigurationKeys.SpanSamplingRules"/>
312-
internal string? SpanSamplingRules => DynamicSettings.SpanSamplingRules ?? _spanSamplingRules;
318+
internal string? SpanSamplingRules { get; }
313319

314320
/// <summary>
315321
/// Gets a value indicating a global rate for sampling.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ internal TracerSettings(IConfigurationSource? source, IConfigurationTelemetry te
213213
converter: value =>
214214
{
215215
// We intentionally report invalid values as "valid" in the converter,
216-
// because we don't want to automatically fallback to the
216+
// because we don't want to automatically fall back to the
217217
// default value.
218218
if (!SamplingRulesFormat.IsValid(value, out var normalizedFormat))
219219
{

tracer/src/Datadog.Trace/Sampling/AgentSamplingRule.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ internal class AgentSamplingRule : ISamplingRule
2828
Datadog.Trace.Sampling.SamplingMechanism.Default :
2929
Datadog.Trace.Sampling.SamplingMechanism.AgentRate;
3030

31-
/// <summary>
32-
/// Gets the lowest possible priority
33-
/// </summary>
34-
public int Priority => int.MinValue;
35-
3631
public bool IsMatch(Span span) => true;
3732

3833
public float GetSamplingRate(Span span)

tracer/src/Datadog.Trace/Sampling/CustomSamplingRule.cs

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Text.RegularExpressions;
11-
using Datadog.Trace.Logging;
12-
using Datadog.Trace.Vendors.Newtonsoft.Json;
1311

1412
namespace Datadog.Trace.Sampling
1513
{
16-
internal class CustomSamplingRule : ISamplingRule
14+
internal abstract class CustomSamplingRule : ISamplingRule
1715
{
18-
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<CustomSamplingRule>();
19-
2016
private readonly float _samplingRate;
2117
private readonly bool _alwaysMatch;
2218

@@ -28,7 +24,7 @@ internal class CustomSamplingRule : ISamplingRule
2824

2925
private bool _regexTimedOut;
3026

31-
public CustomSamplingRule(
27+
protected CustomSamplingRule(
3228
float rate,
3329
string patternFormat,
3430
string? serviceNamePattern,
@@ -54,46 +50,9 @@ _resourceNameRegex is null &&
5450
}
5551
}
5652

57-
public int SamplingMechanism => Datadog.Trace.Sampling.SamplingMechanism.TraceSamplingRule;
58-
59-
/// <summary>
60-
/// Gets the priority of the rule.
61-
/// Configuration rules will default to 1 as a priority and rely on order of specification.
62-
/// </summary>
63-
public int Priority => 1;
64-
65-
public static IEnumerable<CustomSamplingRule> BuildFromConfigurationString(string configuration, string patternFormat, TimeSpan timeout)
66-
{
67-
try
68-
{
69-
if (!string.IsNullOrWhiteSpace(configuration) &&
70-
JsonConvert.DeserializeObject<List<CustomRuleConfig>>(configuration) is { Count: > 0 } rules)
71-
{
72-
var samplingRules = new List<CustomSamplingRule>(rules.Count);
73-
74-
foreach (var r in rules)
75-
{
76-
samplingRules.Add(
77-
new CustomSamplingRule(
78-
rate: r.SampleRate,
79-
patternFormat: patternFormat,
80-
serviceNamePattern: r.Service,
81-
operationNamePattern: r.OperationName,
82-
resourceNamePattern: r.Resource,
83-
tagPatterns: r.Tags,
84-
timeout: timeout));
85-
}
53+
public abstract string Provenance { get; }
8654

87-
return samplingRules;
88-
}
89-
}
90-
catch (Exception ex)
91-
{
92-
Log.Error(ex, "Unable to parse the trace sampling rules.");
93-
}
94-
95-
return [];
96-
}
55+
public abstract int SamplingMechanism { get; }
9756

9857
public bool IsMatch(Span span)
9958
{
@@ -128,32 +87,5 @@ public float GetSamplingRate(Span span)
12887
span.SetMetric(Metrics.SamplingRuleDecision, _samplingRate);
12988
return _samplingRate;
13089
}
131-
132-
public override string ToString()
133-
{
134-
// later this will return different values depending on the rule's provenance:
135-
// local, customer (remote), or dynamic (remote)
136-
return "LocalSamplingRule";
137-
}
138-
139-
// ReSharper disable once ClassNeverInstantiated.Local
140-
private class CustomRuleConfig
141-
{
142-
[JsonRequired]
143-
[JsonProperty(PropertyName = "sample_rate")]
144-
public float SampleRate { get; set; }
145-
146-
[JsonProperty(PropertyName = "name")]
147-
public string? OperationName { get; set; }
148-
149-
[JsonProperty(PropertyName = "service")]
150-
public string? Service { get; set; }
151-
152-
[JsonProperty(PropertyName = "resource")]
153-
public string? Resource { get; set; }
154-
155-
[JsonProperty(PropertyName = "tags")]
156-
public Dictionary<string, string?>? Tags { get; set; }
157-
}
15890
}
15991
}

tracer/src/Datadog.Trace/Sampling/GlobalSamplingRule.cs renamed to tracer/src/Datadog.Trace/Sampling/GlobalSamplingRateRule.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="GlobalSamplingRule.cs" company="Datadog">
1+
// <copyright file="GlobalSamplingRateRule.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
@@ -7,21 +7,16 @@
77

88
namespace Datadog.Trace.Sampling
99
{
10-
internal class GlobalSamplingRule : ISamplingRule
10+
internal class GlobalSamplingRateRule : ISamplingRule
1111
{
1212
private readonly float _globalRate;
1313

14-
public GlobalSamplingRule(float rate)
14+
public GlobalSamplingRateRule(float rate)
1515
{
1616
_globalRate = rate;
1717
}
1818

19-
/// <summary>
20-
/// Gets the priority which is one beneath custom rules.
21-
/// </summary>
22-
public int Priority => 0;
23-
24-
public int SamplingMechanism => Datadog.Trace.Sampling.SamplingMechanism.TraceSamplingRule;
19+
public int SamplingMechanism => Datadog.Trace.Sampling.SamplingMechanism.LocalTraceSamplingRule;
2520

2621
public bool IsMatch(Span span) => true;
2722

tracer/src/Datadog.Trace/Sampling/ISamplingRule.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ namespace Datadog.Trace.Sampling
99
{
1010
internal interface ISamplingRule
1111
{
12-
/// <summary>
13-
/// Gets the rule priority.
14-
/// Higher number means higher priority.
15-
/// Not related to sampling priority.
16-
/// </summary>
17-
int Priority { get; }
18-
1912
int SamplingMechanism { get; }
2013

2114
bool IsMatch(Span span);
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// <copyright file="LocalCustomSamplingRule.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 System;
9+
using System.Collections.Generic;
10+
using Datadog.Trace.Logging;
11+
using Datadog.Trace.Vendors.Newtonsoft.Json;
12+
13+
namespace Datadog.Trace.Sampling;
14+
15+
internal sealed class LocalCustomSamplingRule : CustomSamplingRule
16+
{
17+
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<LocalCustomSamplingRule>();
18+
19+
public LocalCustomSamplingRule(
20+
float rate,
21+
string patternFormat,
22+
string? serviceNamePattern,
23+
string? operationNamePattern,
24+
string? resourceNamePattern,
25+
ICollection<KeyValuePair<string, string?>>? tagPatterns,
26+
TimeSpan timeout)
27+
: base(
28+
rate: rate,
29+
patternFormat: patternFormat,
30+
serviceNamePattern: serviceNamePattern,
31+
operationNamePattern: operationNamePattern,
32+
resourceNamePattern: resourceNamePattern,
33+
tagPatterns: tagPatterns,
34+
timeout: timeout)
35+
{
36+
}
37+
38+
public override string Provenance => SamplingRuleProvenance.Local;
39+
40+
public override int SamplingMechanism => Sampling.SamplingMechanism.LocalTraceSamplingRule;
41+
42+
public static LocalCustomSamplingRule[] BuildFromConfigurationString(
43+
string configuration,
44+
string patternFormat,
45+
TimeSpan timeout)
46+
{
47+
try
48+
{
49+
if (!string.IsNullOrWhiteSpace(configuration) &&
50+
JsonConvert.DeserializeObject<List<RuleConfigJsonModel>>(configuration) is { Count: > 0 } ruleModels)
51+
{
52+
var samplingRules = new LocalCustomSamplingRule[ruleModels.Count];
53+
54+
for (var i = 0; i < ruleModels.Count; i++)
55+
{
56+
var r = ruleModels[i];
57+
58+
var samplingRule = new LocalCustomSamplingRule(
59+
rate: r.SampleRate,
60+
patternFormat: patternFormat, // from DD_TRACE_SAMPLING_RULES_FORMAT
61+
serviceNamePattern: r.Service,
62+
operationNamePattern: r.OperationName,
63+
resourceNamePattern: r.Resource,
64+
tagPatterns: r.Tags,
65+
timeout: timeout);
66+
67+
samplingRules[i] = samplingRule;
68+
}
69+
70+
return samplingRules;
71+
}
72+
}
73+
catch (Exception ex)
74+
{
75+
Log.Error(ex, "Unable to parse the trace sampling rules.");
76+
}
77+
78+
return [];
79+
}
80+
81+
internal class RuleConfigJsonModel
82+
{
83+
[JsonRequired]
84+
[JsonProperty(PropertyName = "sample_rate")]
85+
public float SampleRate { get; set; }
86+
87+
[JsonProperty(PropertyName = "name")]
88+
public string? OperationName { get; set; }
89+
90+
[JsonProperty(PropertyName = "service")]
91+
public string? Service { get; set; }
92+
93+
[JsonProperty(PropertyName = "resource")]
94+
public string? Resource { get; set; }
95+
96+
[JsonProperty(PropertyName = "tags")]
97+
public Dictionary<string, string?>? Tags { get; set; }
98+
}
99+
}

0 commit comments

Comments
 (0)