tapping_device 0.4.8 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.github/workflows/ruby.yml +12 -5
- data/.ruby-version +1 -0
- data/Gemfile.lock +15 -15
- data/README.md +90 -371
- data/images/print_calls - single entry.png +0 -0
- data/images/print_calls.png +0 -0
- data/images/print_traces.png +0 -0
- data/lib/tapping_device.rb +57 -22
- data/lib/tapping_device/output_payload.rb +145 -0
- data/lib/tapping_device/payload.rb +2 -34
- data/lib/tapping_device/trackable.rb +31 -10
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +3 -3
- metadata +14 -7
- data/lib/tapping_device/sql_tapping_methods.rb +0 -89
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapping_device
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '13.0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '13.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rspec
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,17 +122,20 @@ dependencies:
|
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 0.17.1
|
125
|
-
description: tapping_device
|
125
|
+
description: tapping_device lets you understand what your Ruby objects do without
|
126
|
+
digging into the code
|
126
127
|
email:
|
127
128
|
- stan001212@gmail.com
|
128
129
|
executables: []
|
129
130
|
extensions: []
|
130
131
|
extra_rdoc_files: []
|
131
132
|
files:
|
133
|
+
- ".DS_Store"
|
132
134
|
- ".github/workflows/gempush.yml"
|
133
135
|
- ".github/workflows/ruby.yml"
|
134
136
|
- ".gitignore"
|
135
137
|
- ".rspec"
|
138
|
+
- ".ruby-version"
|
136
139
|
- ".travis.yml"
|
137
140
|
- CODE_OF_CONDUCT.md
|
138
141
|
- Gemfile
|
@@ -142,11 +145,14 @@ files:
|
|
142
145
|
- Rakefile
|
143
146
|
- bin/console
|
144
147
|
- bin/setup
|
148
|
+
- images/print_calls - single entry.png
|
149
|
+
- images/print_calls.png
|
150
|
+
- images/print_traces.png
|
145
151
|
- lib/tapping_device.rb
|
146
152
|
- lib/tapping_device/exceptions.rb
|
147
153
|
- lib/tapping_device/manageable.rb
|
154
|
+
- lib/tapping_device/output_payload.rb
|
148
155
|
- lib/tapping_device/payload.rb
|
149
|
-
- lib/tapping_device/sql_tapping_methods.rb
|
150
156
|
- lib/tapping_device/trackable.rb
|
151
157
|
- lib/tapping_device/version.rb
|
152
158
|
- tapping_device.gemspec
|
@@ -175,5 +181,6 @@ requirements: []
|
|
175
181
|
rubygems_version: 3.0.3
|
176
182
|
signing_key:
|
177
183
|
specification_version: 4
|
178
|
-
summary: tapping_device
|
184
|
+
summary: tapping_device lets you understand what your Ruby objects do without digging
|
185
|
+
into the code
|
179
186
|
test_files: []
|
@@ -1,89 +0,0 @@
|
|
1
|
-
class TappingDevice
|
2
|
-
module SqlTappingMethods
|
3
|
-
CALL_STACK_SKIPPABLE_METHODS = [:transaction, :tap]
|
4
|
-
|
5
|
-
# SQLListener acts like an interface for us to intercept activerecord query instrumentations
|
6
|
-
# this means we only need to register one subscriber no matter how many objects we want to tap on
|
7
|
-
class SQLListener
|
8
|
-
def call(name, start, finish, message_id, values);end
|
9
|
-
end
|
10
|
-
@@sql_listener = SQLListener.new
|
11
|
-
|
12
|
-
ActiveSupport::Notifications.subscribe("sql.active_record", @@sql_listener)
|
13
|
-
|
14
|
-
def tap_sql!(object)
|
15
|
-
@call_stack = []
|
16
|
-
@target ||= object
|
17
|
-
@trace_point = with_trace_point_on_target(object, event: [:call, :c_call]) do |start_tp|
|
18
|
-
########## Check if the call is worth recording ##########
|
19
|
-
filepath, line_number = get_call_location(start_tp, padding: 1) # we need extra padding because of `with_trace_point_on_target`
|
20
|
-
method = start_tp.callee_id
|
21
|
-
next if already_recording?(method)
|
22
|
-
|
23
|
-
########## Start the recording ##########
|
24
|
-
# 1. Mark recording state by pushing method into @call_stack
|
25
|
-
# 2. Subscribe sql instrumentations generated by activerecord
|
26
|
-
# 3. Record those sqls and run device callbacks
|
27
|
-
# 4. Start waiting for current call's return callback
|
28
|
-
@call_stack.push(method)
|
29
|
-
payload = build_payload(tp: start_tp, filepath: filepath, line_number: line_number)
|
30
|
-
device = tap_on_sql_instrumentation!(payload)
|
31
|
-
|
32
|
-
with_trace_point_on_target(object, event: :return) do |return_tp|
|
33
|
-
next unless return_tp.callee_id == method
|
34
|
-
|
35
|
-
########## End recording ##########
|
36
|
-
# 1. Close itself
|
37
|
-
# 2. Stop our subscription on SQLListener
|
38
|
-
# 3. Remove current method from @call_stack
|
39
|
-
# 4. Stop the device if stop condition is fulfilled
|
40
|
-
return_tp.disable
|
41
|
-
device.stop!
|
42
|
-
@call_stack.pop
|
43
|
-
stop_if_condition_fulfilled(payload)
|
44
|
-
|
45
|
-
########## Track descendant objects ##########
|
46
|
-
# if the method creates another Relation object
|
47
|
-
if return_tp.defined_class == ActiveRecord::QueryMethods
|
48
|
-
create_child_device.tap_sql!(return_tp.return_value)
|
49
|
-
end
|
50
|
-
end.enable
|
51
|
-
end
|
52
|
-
|
53
|
-
@trace_point.enable unless self.class.suspend_new
|
54
|
-
|
55
|
-
self
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def tap_on_sql_instrumentation!(payload)
|
62
|
-
device = TappingDevice.new do |sql_listener_payload|
|
63
|
-
values = sql_listener_payload.arguments[:values]
|
64
|
-
|
65
|
-
next if should_be_skipped_by_paths?(payload.filepath) ||
|
66
|
-
["SCHEMA", "TRANSACTION"].include?(values[:name]) ||
|
67
|
-
values[:sql].match?(/SAVEPOINT/)
|
68
|
-
|
69
|
-
payload[:sql] = values[:sql]
|
70
|
-
record_call!(payload)
|
71
|
-
end
|
72
|
-
device.tap_on!(@@sql_listener)
|
73
|
-
end
|
74
|
-
|
75
|
-
# usually, AR's query methods (like `first`) will end up calling `find_by_sql`
|
76
|
-
# then to TappingDevice, both `first` and `find_by_sql` generates the sql
|
77
|
-
# but the results are duplicated, we should only consider the `first` call
|
78
|
-
def already_recording?(method)
|
79
|
-
!@call_stack.empty? || CALL_STACK_SKIPPABLE_METHODS.include?(method)
|
80
|
-
end
|
81
|
-
|
82
|
-
def with_trace_point_on_target(object, event:)
|
83
|
-
TracePoint.new(*event) do |tp|
|
84
|
-
if is_from_target?(object, tp)
|
85
|
-
yield(tp)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|