Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sampler.AWS] Part-2: Add rules cache and rule matching logic #1124

Merged
merged 9 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
srprash marked this conversation as resolved.
Show resolved Hide resolved
srprash marked this conversation as resolved.
Show resolved Hide resolved
{
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);
srprash marked this conversation as resolved.
Show resolved Hide resolved
}

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
srprash marked this conversation as resolved.
Show resolved Hide resolved
{
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