Skip to content

Commit

Permalink
[Sampler.AWS] Part-2: Add rules cache and rule matching logic (#1124)
Browse files Browse the repository at this point in the history
  • Loading branch information
srprash authored Apr 26, 2023
1 parent 4cc8429 commit 1a58431
Show file tree
Hide file tree
Showing 25 changed files with 1,301 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.Build() -> OpenTelemetry.S
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetEndpoint(string! endpoint) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetPollingInterval(System.TimeSpan pollingInterval) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
override OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder() -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder(OpenTelemetry.Resources.Resource! resource) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.Build() -> OpenTelemetry.S
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetEndpoint(string! endpoint) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetPollingInterval(System.TimeSpan pollingInterval) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
override OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder() -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder(OpenTelemetry.Resources.Resource! resource) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.Build() -> OpenTelemetry.S
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetEndpoint(string! endpoint) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder.SetPollingInterval(System.TimeSpan pollingInterval) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
override OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder() -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
static OpenTelemetry.Sampler.AWS.AWSXRayRemoteSampler.Builder(OpenTelemetry.Resources.Resource! resource) -> OpenTelemetry.Sampler.AWS.AWSXRayRemoteSamplerBuilder!
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Sampler.AWS/AWSSamplerEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ public void FailedToDeserializeResponse(string format, string error)
{
this.WriteEvent(3, format, error);
}

[Event(4, Message = "Using fallback sampler. Either rules cache has expired or no rules matched the request.", Level = EventLevel.Informational)]
public void InfoUsingFallbackSampler()
{
this.WriteEvent(4);
}
}
86 changes: 71 additions & 15 deletions src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
// </copyright>

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Sampler.AWS;
Expand All @@ -25,38 +28,71 @@ namespace OpenTelemetry.Sampler.AWS;
/// </summary>
public sealed class AWSXRayRemoteSampler : Trace.Sampler, IDisposable
{
internal AWSXRayRemoteSampler(TimeSpan pollingInterval, string endpoint)
internal static readonly TimeSpan DefaultTargetInterval = TimeSpan.FromSeconds(10);

private static readonly Random Random = new Random();

[SuppressMessage("Performance", "CA5394: Do not use insecure randomness", Justification = "Secure random is not required for jitters.")]
internal AWSXRayRemoteSampler(Resource resource, TimeSpan pollingInterval, string endpoint, Clock clock)
{
this.Resource = resource;
this.PollingInterval = pollingInterval;
this.Endpoint = endpoint;
this.Client = new AWSXRaySamplerClient(endpoint);
this.Clock = clock;
this.ClientId = GenerateClientId();
this.Client = new AWSXRaySamplerClient(this.Endpoint);
this.FallbackSampler = new FallbackSampler(this.Clock);
this.RulesCache = new RulesCache(this.Clock, this.ClientId, this.Resource, this.FallbackSampler);

// upto 5 seconds of jitter for rule polling
this.RulePollerJitter = TimeSpan.FromMilliseconds(Random.Next(1, 5000));

// execute the first update right away
this.RulePollerTimer = new Timer(this.GetAndUpdateSampler, null, TimeSpan.Zero, this.PollingInterval);
// execute the first update right away and schedule subsequent update later.
this.RulePollerTimer = new Timer(this.GetAndUpdateRules, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
}

internal TimeSpan PollingInterval { get; }
internal TimeSpan RulePollerJitter { get; set; }

internal Clock Clock { get; set; }

internal string ClientId { get; set; }

internal string Endpoint { get; }
internal Resource Resource { get; set; }

internal AWSXRaySamplerClient Client { get; }
internal string Endpoint { get; set; }

internal Timer RulePollerTimer { get; }
internal AWSXRaySamplerClient Client { get; set; }

internal RulesCache RulesCache { get; set; }

internal Timer RulePollerTimer { get; set; }

internal TimeSpan PollingInterval { get; set; }

internal Trace.Sampler FallbackSampler { get; set; }

/// <summary>
/// Initializes a <see cref="AWSXRayRemoteSamplerBuilder"/> for the sampler.
/// </summary>
/// <param name="resource">an instance of <see cref="Resources.Resource"/>
/// to identify the service attributes for sampling. This resource should
/// be the same as what the OpenTelemetry SDK is configured with.</param>
/// <returns>an instance of <see cref="AWSXRayRemoteSamplerBuilder"/>.</returns>
public static AWSXRayRemoteSamplerBuilder Builder()
public static AWSXRayRemoteSamplerBuilder Builder(Resource resource)
{
return new AWSXRayRemoteSamplerBuilder();
return new AWSXRayRemoteSamplerBuilder(resource);
}

/// <inheritdoc/>
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
// TODO: add the actual functionality for sampling.
throw new System.NotImplementedException();
if (this.RulesCache.Expired())
{
AWSSamplerEventSource.Log.InfoUsingFallbackSampler();
return this.FallbackSampler.ShouldSample(in samplingParameters);
}

return this.RulesCache.ShouldSample(in samplingParameters);
}

/// <inheritdoc/>
Expand All @@ -66,19 +102,39 @@ public void Dispose()
GC.SuppressFinalize(this);
}

[SuppressMessage(
"Usage",
"CA5394: Do not use insecure randomness",
Justification = "using insecure random is fine here since clientId doesn't need to be secure.")]
private static string GenerateClientId()
{
char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
char[] clientIdChars = new char[24];
for (int i = 0; i < clientIdChars.Length; i++)
{
clientIdChars[i] = hex[Random.Next(hex.Length)];
}

return new string(clientIdChars);
}

private void Dispose(bool disposing)
{
if (disposing)
{
this.RulePollerTimer?.Dispose();
this.Client?.Dispose();
this.RulesCache?.Dispose();
}
}

private async void GetAndUpdateSampler(object? state)
private async void GetAndUpdateRules(object? state)
{
await this.Client.GetSamplingRules().ConfigureAwait(false);
List<SamplingRule> rules = await this.Client.GetSamplingRules().ConfigureAwait(false);

this.RulesCache.UpdateRules(rules);

// TODO: more functionality to be added.
// schedule the next rule poll.
this.RulePollerTimer.Change(this.PollingInterval.Add(this.RulePollerJitter), Timeout.InfiniteTimeSpan);
}
}
23 changes: 21 additions & 2 deletions src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSamplerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// </copyright>

using System;
using OpenTelemetry.Resources;

namespace OpenTelemetry.Sampler.AWS;

Expand All @@ -27,13 +28,17 @@ public class AWSXRayRemoteSamplerBuilder

private static readonly TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5);

private Resource resource;
private TimeSpan pollingInterval;
private string endpoint;
private Clock clock;

internal AWSXRayRemoteSamplerBuilder()
internal AWSXRayRemoteSamplerBuilder(Resource resource)
{
this.resource = resource;
this.pollingInterval = DefaultPollingInterval;
this.endpoint = DefaultEndpoint;
this.clock = Clock.GetDefault();
}

/// <summary>
Expand Down Expand Up @@ -78,6 +83,20 @@ public AWSXRayRemoteSamplerBuilder SetEndpoint(string endpoint)
/// <returns>an instance of <see cref="AWSXRayRemoteSampler"/>.</returns>
public AWSXRayRemoteSampler Build()
{
return new AWSXRayRemoteSampler(this.pollingInterval, this.endpoint);
return new AWSXRayRemoteSampler(this.resource, this.pollingInterval, this.endpoint, this.clock);
}

// This is intended for testing with a mock clock.
// Should not be exposed to public.
internal AWSXRayRemoteSamplerBuilder SetClock(Clock clock)
{
if (clock == null)
{
throw new ArgumentNullException(nameof(clock));
}

this.clock = clock;

return this;
}
}
3 changes: 0 additions & 3 deletions src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ public async Task<List<SamplingRule>> GetSamplingRules()
}
}
}

// TODO: this line here is only for testing. Remove in next more complete iterations.
// Console.WriteLine("Got sampling rules! Count: " + samplingRules.Count);
}
}
catch (Exception ex)
Expand Down
3 changes: 2 additions & 1 deletion src/OpenTelemetry.Sampler.AWS/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
Initial release of `OpenTelemetry.Sampler.AWS`.

* Feature - AWSXRayRemoteSampler - Add support for AWS X-Ray remote sampling
([#1091](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1091))
([#1091](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1091),
[#1124](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1124))
36 changes: 36 additions & 0 deletions src/OpenTelemetry.Sampler.AWS/Clock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <copyright file="Clock.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;

namespace OpenTelemetry.Sampler.AWS;

// A time keeper for the purpose of this sampler.
internal abstract class Clock
{
public static Clock GetDefault()
{
return SystemClock.GetInstance();
}

public abstract DateTime Now();

public abstract long NowInSeconds();

public abstract DateTime ToDateTime(double seconds);

public abstract double ToDouble(DateTime dateTime);
}
38 changes: 38 additions & 0 deletions src/OpenTelemetry.Sampler.AWS/FallbackSampler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <copyright file="FallbackSampler.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using OpenTelemetry.Trace;

namespace OpenTelemetry.Sampler.AWS;

internal class FallbackSampler : Trace.Sampler
{
private static readonly Trace.Sampler AlwaysOn = new AlwaysOnSampler();

private readonly Clock clock;

public FallbackSampler(Clock clock)
{
this.clock = clock;
}

public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
// For now just do an always on sampler.
// TODO: update to a rate limiting sampler.
return AlwaysOn.ShouldSample(samplingParameters);
}
}
Loading

0 comments on commit 1a58431

Please sign in to comment.