Skip to content

Commit bb3bf39

Browse files
committed
RnD Week: Span Origin #1
1 parent da073fd commit bb3bf39

File tree

3 files changed

+138
-3
lines changed

3 files changed

+138
-3
lines changed

tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ internal class ProbeProcessor
2323
private DebuggerExpression? _condition;
2424
private DebuggerExpression? _metric;
2525

26+
/// <summary>
27+
/// The next snapshot to be uploaded as part of the "Span Origin" feature.
28+
/// </summary>
29+
public static readonly AsyncLocal<string> NextSnapshot = new();
30+
2631
/// <summary>
2732
/// Initializes a new instance of the <see cref="ProbeProcessor"/> class, that correlated to probe id
2833
/// </summary>
@@ -436,7 +441,7 @@ private void ProcessLine<TCapture>(ref CaptureInfo<TCapture> info, ref DebuggerS
436441
}
437442

438443
var snapshot = snapshotCreator.FinalizeLineSnapshot(ProbeInfo.ProbeId, ref info);
439-
LiveDebugger.Instance.AddSnapshot(ProbeInfo.ProbeId, snapshot);
444+
NextSnapshot.Value = snapshot;
440445
snapshotCreator.Stop();
441446
break;
442447

tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,10 +836,10 @@ internal DebuggerSnapshotCreator AddGeneralInfo(string service, string traceId,
836836
_jsonWriter.WriteValue(UnknownValue);
837837

838838
_jsonWriter.WritePropertyName("dd.trace_id");
839-
_jsonWriter.WriteValue(traceId);
839+
_jsonWriter.WriteValue("TO_BE_ADDED_TRACE_ID");
840840

841841
_jsonWriter.WritePropertyName("dd.span_id");
842-
_jsonWriter.WriteValue(spanId);
842+
_jsonWriter.WriteValue("TO_BE_ADDED_SPAN_ID");
843843

844844
return this;
845845
}

tracer/src/Datadog.Trace/Tracer.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,26 @@
55

66
using System;
77
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Reflection;
810
using System.Threading;
911
using System.Threading.Tasks;
1012
using Datadog.Trace.Agent;
1113
using Datadog.Trace.Agent.DiscoveryService;
1214
using Datadog.Trace.ClrProfiler;
1315
using Datadog.Trace.Configuration;
16+
using Datadog.Trace.Debugger.Configurations.Models;
17+
using Datadog.Trace.Debugger.Expressions;
18+
using Datadog.Trace.Debugger.Instrumentation;
19+
using Datadog.Trace.Debugger.PInvoke;
20+
using Datadog.Trace.PlatformHelpers;
1421
using Datadog.Trace.Sampling;
1522
using Datadog.Trace.Tagging;
1623
using Datadog.Trace.Telemetry;
1724
using Datadog.Trace.Util;
25+
using Datadog.Trace.Vendors.dnlib.DotNet;
26+
using Datadog.Trace.Vendors.dnlib.DotNet.Emit;
27+
using Datadog.Trace.Vendors.dnlib.DotNet.Pdb.Symbols;
1828
using Datadog.Trace.Vendors.StatsdClient;
1929

2030
namespace Datadog.Trace
@@ -412,6 +422,8 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa
412422
OperationName = operationName,
413423
};
414424

425+
SpanOriginResolution(span);
426+
415427
// Apply any global tags
416428
if (Settings.GlobalTags.Count > 0)
417429
{
@@ -441,5 +453,123 @@ internal Task FlushAsync()
441453
{
442454
return TracerManager.AgentWriter.FlushTracesAsync();
443455
}
456+
457+
private static void SpanOriginResolution(Span span)
458+
{
459+
// Deal with pending snapshot
460+
if (!string.IsNullOrEmpty(ProbeProcessor.NextSnapshot.Value))
461+
{
462+
var nextSnapshotToBeUploaded = ProbeProcessor.NextSnapshot.Value;
463+
ProbeProcessor.NextSnapshot.Value = string.Empty;
464+
nextSnapshotToBeUploaded = nextSnapshotToBeUploaded.Replace("TO_BE_ADDED_SPAN_ID", span.SpanId.ToString())
465+
.Replace("TO_BE_ADDED_TRACE_ID", span.TraceId.ToString());
466+
Datadog.Trace.Debugger.LiveDebugger.Instance.AddSnapshot("SpanOrigin", nextSnapshotToBeUploaded);
467+
}
468+
469+
var stackFrames = new System.Diagnostics.StackTrace();
470+
471+
var encounteredUserCode = false;
472+
System.Reflection.MethodBase firstNonUserCodeMethod = null;
473+
System.Reflection.MethodBase firstUserCodeMethod = null;
474+
475+
foreach (var frame in stackFrames.GetFrames()!)
476+
{
477+
var method = frame.GetMethod();
478+
if (method?.DeclaringType == null)
479+
{
480+
continue;
481+
}
482+
483+
static bool IsUserCode(string methodFullName) // Not Comprehensive Enough
484+
{
485+
return !(methodFullName.StartsWith("Microsoft") ||
486+
methodFullName.StartsWith("System") ||
487+
methodFullName.StartsWith("Datadog.Trace") ||
488+
methodFullName.StartsWith("Serilog") ||
489+
methodFullName.StartsWith("MySql.Data"));
490+
}
491+
492+
if (IsUserCode(method.DeclaringType.FullName))
493+
{
494+
encounteredUserCode = true;
495+
firstUserCodeMethod = method;
496+
break;
497+
}
498+
499+
if (!encounteredUserCode)
500+
{
501+
firstNonUserCodeMethod = method;
502+
}
503+
}
504+
505+
if (firstNonUserCodeMethod == null || firstUserCodeMethod == null)
506+
{
507+
// TODO LOG
508+
System.Diagnostics.Debugger.Break();
509+
return;
510+
}
511+
512+
var nonUserMethod = firstNonUserCodeMethod;
513+
var userMethod = firstUserCodeMethod;
514+
515+
var userModule = ModuleDefMD.Load(userMethod.Module.Assembly.ManifestModule);
516+
var userRid = MDToken.ToRID(userMethod.MetadataToken);
517+
var userMdMethod = userModule.ResolveMethod(userRid);
518+
519+
if (!userMdMethod.Body.HasInstructions)
520+
{
521+
// TODO LOG
522+
System.Diagnostics.Debugger.Break();
523+
return;
524+
}
525+
526+
var nonUserModule = ModuleDefMD.Load(nonUserMethod.Module.Assembly.ManifestModule);
527+
var nonUserRid = MDToken.ToRID(nonUserMethod.MetadataToken);
528+
var nonUserMdMethod = nonUserModule.ResolveMethod(nonUserRid);
529+
530+
// We have to assign the module context to be able to resolve memberRef to memberdef.
531+
userModule.Context = ModuleDef.CreateModuleContext();
532+
var nonUserMethodFullName = nonUserMdMethod.FullName;
533+
534+
var callsToInstrument = userMdMethod.Body.Instructions.Where(
535+
instruction => instruction.OpCode.FlowControl == FlowControl.Call &&
536+
(((instruction.Operand as IMethod) != null &&
537+
((instruction.Operand as IMethod)!).FullName == nonUserMethodFullName) ||
538+
(((IMethod)instruction.Operand).DeclaringType.FullName == nonUserMethod.DeclaringType.BaseType.FullName &&
539+
((IMethod)instruction.Operand).Name == nonUserMethod.Name)));
540+
541+
var calls = callsToInstrument as Instruction[] ?? callsToInstrument.ToArray();
542+
if (!calls.Any())
543+
{
544+
// TODO LOG
545+
System.Diagnostics.Debugger.Break();
546+
return;
547+
}
548+
549+
var userSymbolMethod = Datadog.Trace.Pdb.DatadogPdbReader.CreatePdbReader(userMethod.Module.Assembly).ReadMethodSymbolInfo((int)(userMethod.MetadataToken));
550+
551+
var lineProbes = calls.Select(call => CreateLineProbe(call, userMethod, userSymbolMethod)).ToArray();
552+
553+
foreach (var probe in lineProbes)
554+
{
555+
ProbeExpressionsProcessor.Instance.AddProbeProcessor(new LogProbe { CaptureSnapshot = true, Id = "SpanOrigin", Where = new Where() });
556+
}
557+
558+
var chosenProbe = lineProbes.First();
559+
span.Tags.SetTag("source.file_path", chosenProbe.ProbeFilePath);
560+
span.Tags.SetTag("source.line_number", chosenProbe.LineNumber.ToString());
561+
562+
// Add probes
563+
DebuggerNativeMethods.InstrumentProbes(
564+
Array.Empty<NativeMethodProbeDefinition>(),
565+
lineProbes,
566+
Array.Empty<NativeRemoveProbeRequest>());
567+
}
568+
569+
private static NativeLineProbeDefinition CreateLineProbe(Instruction callInstruction, MethodBase userMethod, SymbolMethod userSymbolMethod)
570+
{
571+
var closestSequencePoint = userSymbolMethod.SequencePoints.Reverse().First(sp => sp.Offset <= callInstruction.Offset);
572+
return new NativeLineProbeDefinition(Guid.NewGuid().ToString(), userMethod.Module.ModuleVersionId, userMethod.MetadataToken, (int)(closestSequencePoint.Offset), closestSequencePoint.Line, closestSequencePoint.Document.URL);
573+
}
444574
}
445575
}

0 commit comments

Comments
 (0)