-
Notifications
You must be signed in to change notification settings - Fork 375
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
DEBUG-2334 Dynamic Instrumentation code tracker component #3942
Conversation
use Hash and Mutex instead
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.
I have a small feedback, but nothing critical to say
lib/datadog/di/code_tracker.rb
Outdated
compiled_trace_point = TracePoint.trace(:script_compiled) do |tp| | ||
# Useful attributes of the trace point object here: | ||
# .instruction_sequence | ||
# .method_id | ||
# .path (refers to the code location that called the require/eval/etc., | ||
# not where the loaded code is; use .path on the instruction sequence | ||
# to obtain the location of the compiled code) | ||
# .eval_script | ||
# | ||
# For now just map the path to the instruction sequence. | ||
path = tp.instruction_sequence.path | ||
registry_lock.synchronize do | ||
registry[path] = tp.instruction_sequence | ||
end | ||
end |
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.
Does script_compiled
get emitted for each individual method in a file?
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.
No, it should be emitted once per file.
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.
Wait, in that case, how do we know the correct iseq to target, if 1 file has N iseqs? (Or I may be misunderstanding how this works?)
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.
There is one iseq per file.
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.
Interesting... I think my mental model was slightly off for this one.
As you pointed out taking for instance
# test.rb
def a
puts "a!"
end
def b
puts "b!"
end
and doing
[1] pry(main)> iseq = RubyVM::InstructionSequence.compile(File.read("test.rb"))
[3] pry(main)> puts RubyVM::InstructionSequence.disasm(iseq)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(8,3)> (catch: FALSE)
0000 definemethod :a, a ( 2)[Li]
0003 definemethod :b, b ( 6)[Li]
0006 putobject :b
0008 leave
== disasm: #<ISeq:a@<compiled>:2 (2,0)-(4,3)> (catch: FALSE)
0000 putself ( 3)[LiCa]
0001 putstring "a!"
0003 opt_send_without_block <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0005 leave ( 4)[Re]
== disasm: #<ISeq:b@<compiled>:6 (6,0)-(8,3)> (catch: FALSE)
0000 putself ( 7)[LiCa]
0001 putstring "b!"
0003 opt_send_without_block <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0005 leave ( 8)[Re]
=> nil
e.g. will mean we get it all in one go.
Yet... it seems there's still separate objects for the different iseqs -- there's a RubyVM::InstructionSequence#each_child
and the object ids of the objects it returns are different from the top-level iseq so they seem like separate objects arranged in a tree, not one object that's presenting different views of itself.
That said, for the usage we'll be making in DI, maybe this extra distinction doesn't matter very much? And I learned something new :)
lib/datadog/di/code_tracker.rb
Outdated
# disable our trace point and do nothing. | ||
if @compiled_trace_point | ||
# Disable the local variable, leave instance variable as it is. | ||
compiled_trace_point.disable |
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.
Is it me or was the tracepoint was not enabled before we disable it?
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.
TracePoint.trace
enables the trace point. .new
does not enable. I agree it can be confusing at first glance.
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.
Aaaaah that's super subtle, missed it! May be worth adding a comment or using an explicit enable to make it easier on the readers? (but just a suggestion)
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.
Added a note.
Co-authored-by: Sergey Fedorov <oni.strech@gmail.com>
Co-authored-by: Sergey Fedorov <oni.strech@gmail.com>
Co-authored-by: datadog-datadog-prod-us1[bot] <88084959+datadog-datadog-prod-us1[bot]@users.noreply.github.com>
Co-authored-by: datadog-datadog-prod-us1[bot] <88084959+datadog-datadog-prod-us1[bot]@users.noreply.github.com>
Co-authored-by: Sergey Fedorov <oni.strech@gmail.com>
f65506a
to
17952c4
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3942 +/- ##
==========================================
- Coverage 97.87% 97.87% -0.01%
==========================================
Files 1305 1313 +8
Lines 78224 78352 +128
Branches 3876 3886 +10
==========================================
+ Hits 76559 76684 +125
- Misses 1665 1668 +3 ☔ View full report in Codecov by Sentry. |
Co-authored-by: Sergey Fedorov <oni.strech@gmail.com>
I don't know why coverage for the 3 lines is missing. Those lines are exercised by the tests but perhaps they are not counted due to being under a trace point. |
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.
👍 Left a few final notes/suggestions/questions, but overall it LGTM
lib/datadog/di/code_tracker.rb
Outdated
path = tp.instruction_sequence.path | ||
registry_lock.synchronize do | ||
registry[path] = tp.instruction_sequence | ||
end |
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.
I wonder if there's situations where the path will be nil
? e.g. does this tracepoint only fire on actual files, or would it catch evals and other things where the path may not exist? 🤔
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.
The trace point is indeed called for eval'd code, but path
will not be nil here because it is synthesized to be (eval at <file>:<line>)
in these cases. There is also .absolute_path
which is in fact nil for eval'd code, I changed to using this and filter out eval'd iseqs since I do not see how DI will be able to target such code by file & line.
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.
👍 nice, looks good!
# Tracks loaded Ruby code by source file and maintains a map from | ||
# source file to the loaded code (instruction sequences). | ||
# Also arranges for code in the loaded files to be instrumented by | ||
# line probes that have already been received by the library. | ||
# | ||
# The loaded code is used to target line trace points when installing | ||
# line probes which dramatically improves efficiency of line trace points. | ||
# | ||
# Note that, since most files will only be loaded one time (via the | ||
# "require" mechanism), the code tracker needs to be global and not be | ||
# recreated when the DI component is created. | ||
# | ||
# @api private | ||
class CodeTracker |
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.
Might be worth documenting what parts of this class are concurrent and why.
e.g.If I understand it well (and I'm speculating/reverse engineering), the concurrency in registry
is because iseqs_for_path
may be concurrent with the tracepoint, but usually there will be no concurrency between multiple invocations for iseqs_for_path
(presumably because remote configuration apply is sequential).
(I don't quite understand when there can be concurrency in start
/stop
; given that usually the dd-trace-rb components system takes care of enforcing that starting and stopping things is a sequential operation as well)
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.
start & stop should not be running at the same time. start/stop could (I suppose) be running while the trace point is invoked. Since starting and stopping should happen once and never in normal usage, I reused the mutexes.
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.
I'll admit that if you confirm that start/stop should not be concurrent then I don't quite understand what trace_point_lock
is protecting 🤔
I saw you setup a meeting to discuss this, so let's continue the discussion there. In any case, I don't think the discussion should be a blocker to merging this PR, as we can always tweak this in a later PR.
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.
well done, my points are resolved 👍🏼
Co-authored-by: datadog-datadog-prod-us1[bot] <88084959+datadog-datadog-prod-us1[bot]@users.noreply.github.com>
* master: DEBUG-2334 Dynamic Instrumentation code tracker component (DataDog#3942) Fix typo in cleanup step Add Rails 4.2 to system-tests GH workflow Add all supported weblogs to system-tests GH Workflow file
What does this PR do?
Adds the code tracker component. This is responsible for tracking the mapping from source file path to RubyVM::InstructionSequence object used for setting targeted trace points.
Motivation:
Efficient instrumentation of lines.
Additional Notes:
There will be further functionality added to CodeTracker later to instrument loaded code (requires the instrumentation component that hasn't been PR'ed yet).
How to test the change?
Unit tests at this time.
Unsure? Have a question? Request a review!