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

[Draft PR] Added X-Ray trace id generator #1268

Closed
wants to merge 1 commit into from
Closed
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
123 changes: 123 additions & 0 deletions src/OpenTelemetry/Trace/AWSXRayActivityTraceIdGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;

namespace OpenTelemetry.Trace
{
public static class AWSXRayActivityTraceIdGenerator
{
private const int RandomNumberHexDigits = 24; // 96 bits

private const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;
private const long MicrosecondPerSecond = TimeSpan.TicksPerSecond / TicksPerMicrosecond;

private static readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly long UnixEpochMicroseconds = EpochStart.Ticks / TicksPerMicrosecond;
private static readonly Random Global = new Random();
private static readonly ThreadLocal<Random> Local = new ThreadLocal<Random>(() =>
{
int seed;
lock (Global)
{
seed = Global.Next();
}

return new Random(seed);
});

/// <summary>
/// Replace the trace id of root activity.
/// </summary>
/// <param name="builder">Instance of <see cref="TracerProviderBuilder"/>.</param>
/// <returns>The instance of <see cref="TracerProviderBuilder"/>.</returns>
public static TracerProviderBuilder AddXRayActivityTraceIdGenerator(this TracerProviderBuilder builder)
{
var awsXRayActivityListener = new ActivityListener
{
ActivityStarted = (activity) =>
{
// Replace every root activity's trace id with X-Ray compatiable trace id
if (string.IsNullOrEmpty(activity.ParentId))
{
var awsXRayTraceId = GenerateAWSXRayCompatiableActivityTraceId();

// Root node's parent id is no longer null, which will fail the sampler checker
// 00-traceid-0000000000000000-00
activity.SetParentId(awsXRayTraceId, default, activity.ActivityTraceFlags);
}
},

ShouldListenTo = (_) => true,
};

ActivitySource.AddActivityListener(awsXRayActivityListener);

return builder;
}

public static ActivityTraceId GenerateAWSXRayCompatiableActivityTraceId()
{
var epoch = (int)DateTime.UtcNow.ToUnixTimeSeconds(); // first 8 digit as time stamp

var randomNumber = GenerateHexNumber(RandomNumberHexDigits); // remaining 24 random digit

return ActivityTraceId.CreateFromString(string.Concat(epoch.ToString("x", CultureInfo.InvariantCulture), randomNumber).AsSpan());
}

/// <summary>
/// Convert a given time to Unix time which is the number of seconds since 1st January 1970, 00:00:00 UTC.
/// </summary>
/// <param name="date">.Net representation of time.</param>
/// <returns>The number of seconds elapsed since 1970-01-01 00:00:00 UTC. The value is expressed in whole and fractional seconds with resolution of microsecond.</returns>
private static decimal ToUnixTimeSeconds(this DateTime date)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if would prevent a duplication, today we have in OpenTelemetry/Internal/DateTimeOffsetExtensions.net452.cs a method similar to this. The only difference is that an extension for DateTimeOffset instead of DateTime

{
long microseconds = date.Ticks / TicksPerMicrosecond;
long microsecondsSinceEpoch = microseconds - UnixEpochMicroseconds;
return (decimal)microsecondsSinceEpoch / MicrosecondPerSecond;
}

/// <summary>
/// Generate a random 24-digit hex number.
/// </summary>
/// <param name="digits">Digits of the hex number.</param>
/// <returns>The generated hex number.</returns>
private static string GenerateHexNumber(int digits)
{
if (digits < 0)
{
throw new ArgumentException("Length can't be a negative number.", "digits");
}

byte[] bytes = new byte[digits / 2];
NextBytes(bytes);
string hexNumber = string.Concat(bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture)).ToArray());
if (digits % 2 != 0)
{
hexNumber += Next(16).ToString("x", CultureInfo.InvariantCulture);
}

return hexNumber;
}

/// <summary>
/// Fills the elements of a specified array of bytes with random numbers.
/// </summary>
/// <param name="buffer">An array of bytes to contain random numbers.</param>
private static void NextBytes(byte[] buffer)
{
Local.Value.NextBytes(buffer);
}

/// <summary>
/// Returns a non-negative random integer that is less than the specified maximum.
/// </summary>
/// <param name="maxValue">Max value of the random integer.</param>
/// <returns>A 32-bit signed integer that is greater than or equal to 0, and less than maxValue.</returns>
private static int Next(int maxValue)
{
return Local.Value.Next(maxValue);
}
}
}
2 changes: 1 addition & 1 deletion src/OpenTelemetry/Trace/ActivitySourceAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private void RunGetRequestedDataAlwaysOffSampler(Activity activity)
private void RunGetRequestedDataOtherSampler(Activity activity)
{
ActivityContext parentContext;
if (string.IsNullOrEmpty(activity.ParentId))
if (string.IsNullOrEmpty(activity.ParentId) || activity.ParentSpanId.ToHexString().Equals("0000000000000000"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this maynot be a perf issue, if ToHexString() is not actually allocating a string, but returns the caches string value. to be confirmed.

{
parentContext = default;
}
Expand Down