-
Notifications
You must be signed in to change notification settings - Fork 73
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
added MockTracer and MockTracer.Tests projects #54
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
04422c6
added MockTracer and MockTracer.Tests projects
Aaronontheweb e7740fb
modified build.sh to run OpenTracing.MockTracer.Tests
Aaronontheweb 3ac4fb6
Merge remote-tracking branch 'upstream/master' into mock-tracer
cwe1ss df3b063
Updated to latest Java & C# API, some reorganizations
cwe1ss 5507bb4
small fixes
cwe1ss 1812e73
Reference is now a class again and implements Equals (generated by VS)
cwe1ss 66ca581
Merge remote-tracking branch 'upstream/master' into mock-tracer
cwe1ss d7b1309
PrinterPropagator -> ConsolePropagator
cwe1ss File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Linq; | ||
using System.Threading; | ||
|
||
namespace OpenTracing.Mock | ||
{ | ||
/// <summary> | ||
/// MockSpans are created via <see cref="MockTracer.BuildSpan"/>, but they are also returned via calls to | ||
/// <see cref="MockTracer.FinishedSpans"/>. They provide accessors to all Span state. | ||
/// </summary> | ||
/// <seealso cref="MockTracer.FinishedSpans"/> | ||
public sealed class MockSpan : ISpan | ||
{ | ||
/// <summary> | ||
/// Used to monotonically update ids | ||
/// </summary> | ||
private static long _nextIdCounter = 0; | ||
|
||
/// <summary> | ||
/// A simple-as-possible (consecutive for repeatability) id generator. | ||
/// </summary> | ||
private static long NextId() | ||
{ | ||
return Interlocked.Increment(ref _nextIdCounter); | ||
} | ||
|
||
private readonly object _lock = new object(); | ||
|
||
private readonly MockTracer _mockTracer; | ||
private MockSpanContext _context; | ||
private DateTimeOffset _finishTimestamp; | ||
private bool _finished; | ||
private readonly Dictionary<string, object> _tags; | ||
private readonly List<Reference> _references; | ||
private readonly List<LogEntry> _logEntries = new List<LogEntry>(); | ||
private readonly List<Exception> _errors = new List<Exception>(); | ||
|
||
/// <summary> | ||
/// The spanId of the Span's first <see cref="References.ChildOf"/> reference, or the first reference of any type, | ||
/// or 0 if no reference exists. | ||
/// </summary> | ||
/// <seealso cref="MockSpanContext.SpanId"/> | ||
/// <seealso cref="MockSpan.References"/> | ||
public long ParentId { get; } | ||
|
||
public DateTimeOffset StartTimestamp { get; } | ||
|
||
/// <summary> | ||
/// The finish time of the Span; only valid after a call to <see cref="Finish()"/>. | ||
/// </summary> | ||
public DateTimeOffset FinishTimestamp | ||
{ | ||
get | ||
{ | ||
if (_finishTimestamp == DateTimeOffset.MinValue) | ||
throw new InvalidOperationException("Must call Finish() before FinishTimestamp"); | ||
|
||
return _finishTimestamp; | ||
} | ||
} | ||
|
||
public string OperationName { get; private set; } | ||
|
||
/// <summary> | ||
/// A copy of all tags set on this span. | ||
/// </summary> | ||
public Dictionary<string, object> Tags => new Dictionary<string, object>(_tags); | ||
|
||
/// <summary> | ||
/// A copy of all log entries added to this span. | ||
/// </summary> | ||
public List<LogEntry> LogEntries => new List<LogEntry>(_logEntries); | ||
|
||
/// <summary> | ||
/// A copy of exceptions thrown by this class (e.g. adding a tag after span is finished). | ||
/// </summary> | ||
public List<Exception> GeneratedErrors => new List<Exception>(_errors); | ||
|
||
public List<Reference> References => new List<Reference>(_references); | ||
|
||
public MockSpanContext Context | ||
{ | ||
// C# doesn't have "return type covariance" so we use the trick with the explicit interface implementation | ||
// and this separate property. | ||
get | ||
{ | ||
lock (_lock) | ||
{ | ||
return _context; | ||
} | ||
} | ||
} | ||
|
||
ISpanContext ISpan.Context => Context; | ||
|
||
public MockSpan( | ||
MockTracer tracer, | ||
string operationName, | ||
DateTimeOffset startTimestamp, | ||
Dictionary<string, object> initialTags, | ||
List<Reference> references) | ||
{ | ||
_mockTracer = tracer; | ||
OperationName = operationName; | ||
StartTimestamp = startTimestamp; | ||
|
||
_tags = initialTags == null | ||
? new Dictionary<string, object>() | ||
: new Dictionary<string, object>(initialTags); | ||
|
||
_references = references == null | ||
? new List<Reference>() | ||
: references.ToList(); | ||
|
||
var parentContext = FindPreferredParentRef(_references); | ||
|
||
if (parentContext == null) | ||
{ | ||
// we are a root span | ||
_context = new MockSpanContext(NextId(), NextId(), new Dictionary<string, string>()); | ||
ParentId = 0; | ||
} | ||
else | ||
{ | ||
// we are a child span | ||
_context = new MockSpanContext(parentContext.TraceId, NextId(), MergeBaggages(_references)); | ||
ParentId = parentContext.SpanId; | ||
} | ||
} | ||
|
||
public ISpan SetOperationName(string operationName) | ||
{ | ||
CheckForFinished("Setting operationName [{0}] on already finished span", operationName); | ||
OperationName = operationName; | ||
return this; | ||
} | ||
|
||
public ISpan SetTag(string key, bool value) | ||
{ | ||
return SetObjectTag(key, value); | ||
} | ||
|
||
public ISpan SetTag(string key, double value) | ||
{ | ||
return SetObjectTag(key, value); | ||
} | ||
|
||
public ISpan SetTag(string key, int value) | ||
{ | ||
return SetObjectTag(key, value); | ||
} | ||
|
||
public ISpan SetTag(string key, string value) | ||
{ | ||
return SetObjectTag(key, value); | ||
} | ||
|
||
private ISpan SetObjectTag(string key, object value) | ||
{ | ||
lock (_lock) | ||
{ | ||
CheckForFinished("Setting tag [{0}:{1}] on already finished span", key, value); | ||
_tags[key] = value; | ||
return this; | ||
} | ||
} | ||
|
||
public ISpan Log(IDictionary<string, object> fields) | ||
{ | ||
return Log(DateTimeOffset.UtcNow, fields); | ||
} | ||
|
||
public ISpan Log(DateTimeOffset timestamp, IDictionary<string, object> fields) | ||
{ | ||
lock (_lock) | ||
{ | ||
CheckForFinished("Adding logs {0} at {1} to already finished span.", fields, timestamp); | ||
_logEntries.Add(new LogEntry(timestamp, fields)); | ||
return this; | ||
} | ||
} | ||
|
||
public ISpan Log(string @event) | ||
{ | ||
return Log(DateTimeOffset.UtcNow, @event); | ||
} | ||
|
||
public ISpan Log(DateTimeOffset timestamp, string @event) | ||
{ | ||
return Log(timestamp, new Dictionary<string, object> { { "event", @event } }); | ||
} | ||
|
||
public ISpan SetBaggageItem(string key, string value) | ||
{ | ||
lock (_lock) | ||
{ | ||
CheckForFinished("Adding baggage [{0}:{1}] to already finished span.", key, value); | ||
_context = _context.WithBaggageItem(key, value); | ||
return this; | ||
} | ||
} | ||
|
||
public string GetBaggageItem(string key) | ||
{ | ||
lock (_lock) | ||
{ | ||
return _context.GetBaggageItem(key); | ||
} | ||
} | ||
|
||
public void Finish() | ||
{ | ||
Finish(DateTimeOffset.UtcNow); | ||
} | ||
|
||
public void Finish(DateTimeOffset finishTimestamp) | ||
{ | ||
lock (_lock) | ||
{ | ||
CheckForFinished("Tried to finish already finished span"); | ||
_finishTimestamp = finishTimestamp; | ||
_mockTracer.AppendFinishedSpan(this); | ||
_finished = true; | ||
} | ||
} | ||
|
||
private static MockSpanContext FindPreferredParentRef(IList<Reference> references) | ||
{ | ||
if (!references.Any()) | ||
return null; | ||
|
||
// return the context of the parent, if applicable | ||
foreach (var reference in references) | ||
{ | ||
if (OpenTracing.References.ChildOf.Equals(reference.ReferenceType)) | ||
return reference.Context; | ||
} | ||
|
||
// otherwise, return the context of the first reference | ||
return references.First().Context; | ||
} | ||
|
||
private static Dictionary<string, string> MergeBaggages(IList<Reference> references) | ||
{ | ||
var baggage = new Dictionary<string, string>(); | ||
foreach (var reference in references) | ||
{ | ||
if (reference.Context.GetBaggageItems() != null) | ||
{ | ||
foreach (var bagItem in reference.Context.GetBaggageItems()) | ||
{ | ||
baggage[bagItem.Key] = bagItem.Value; | ||
} | ||
} | ||
} | ||
|
||
return baggage; | ||
} | ||
|
||
private void CheckForFinished(string format, params object[] args) | ||
{ | ||
if (_finished) | ||
{ | ||
var ex = new InvalidOperationException(string.Format(format, args)); | ||
_errors.Add(ex); | ||
throw ex; | ||
} | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return $"TraceId: {_context.TraceId}, SpanId: {_context.SpanId}, ParentId: {ParentId}, OperationName: {OperationName}"; | ||
} | ||
|
||
public sealed class LogEntry | ||
{ | ||
public DateTimeOffset Timestamp { get; } | ||
|
||
public IReadOnlyDictionary<string, object> Fields { get; } | ||
|
||
public LogEntry(DateTimeOffset timestamp, IDictionary<string, object> fields) | ||
{ | ||
Timestamp = timestamp; | ||
Fields = new ReadOnlyDictionary<string, object>(fields); | ||
} | ||
} | ||
|
||
public sealed class Reference : IEquatable<Reference> | ||
{ | ||
public MockSpanContext Context { get; } | ||
|
||
/// <summary> | ||
/// See <see cref="OpenTracing.References"/>. | ||
/// </summary> | ||
public string ReferenceType { get; } | ||
|
||
public Reference(MockSpanContext context, string referenceType) | ||
{ | ||
Context = context ?? throw new ArgumentNullException(nameof(context)); | ||
ReferenceType = referenceType ?? throw new ArgumentNullException(nameof(referenceType)); | ||
} | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
return Equals(obj as Reference); | ||
} | ||
|
||
public bool Equals(Reference other) | ||
{ | ||
return other != null && | ||
EqualityComparer<MockSpanContext>.Default.Equals(Context, other.Context) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[not asking for a change here] |
||
ReferenceType == other.ReferenceType; | ||
} | ||
|
||
public override int GetHashCode() | ||
{ | ||
var hashCode = 2083322454; | ||
hashCode = hashCode * -1521134295 + EqualityComparer<MockSpanContext>.Default.GetHashCode(Context); | ||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(ReferenceType); | ||
return hashCode; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:( locks hurt
System.Collections.Concurrent
avoid locks, and definitely minimize them (e.g. why is the string format inside the lock?)This is fine if this is genuinely a MockTracer, only used for testing, but it almost seems like it's an InMemoryTracer. If we are not going to have a separate InMemoryTracer, then I'd suggest we ensure that this tracer is also optimized for production code for folks who want to instrument but do not want to send their data off to another system quite yet.
Actually, I revise the above to question explicitly -- for a developer instrumenting but not yet set up with a trace sink location, what options do we plan to expose for them to be able to, for instance, just hook up all trace data to go to standard out (ideally we expose an event so they can send to whatever
out
they wish).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tracer is meant to be used for (unit) testing and definitely not for production. Performance therefore isn't that much of a concern.
Using
Concurrent
would be nice but it's not enough (as e.g._baggage
is a regular variable that is replaced) so we need this locking logic anyway. Right now it's at least consistent and more similar to what Java has.I guess this would be a good fit for a opentracing-contrib project. Something like this shouldn't be part of the core API library as it probably needs a different release cycle etc.