tapping_device 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +67 -58
- data/lib/tapping_device/version.rb +1 -1
- data/lib/tapping_device.rb +7 -28
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16d62dfe61cf06c2f3932ed32bc3a5c20ca652c743a1a238f156ff738103357e
|
4
|
+
data.tar.gz: ee964e53de1c35e0b3bc1d6be28f0fb5417d7a51bff5e9f8a16cc355297e3fcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b6fe7b5a903f3d315c8258c1b4d7b22b24688eed0b13c7a8d505418bfc9f4e19cb8b1ef77a2494fadea1f4152130bb7ae41f401cadd37751d979169a417f6dc
|
7
|
+
data.tar.gz: 2a865ef86a2161a5b932b677e0ab6a953b8db083589388c9d0e59ba40214ae607691df7ed020306023c87fb92d1514821f0cccd4826fc97764a47de19f3d61f9
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
|
4
4
|
|
5
|
-
`tapping_device` is a gem built on top of Ruby’s `TracePoint` class that allows you to tap method calls of specified objects. The purpose for this gem is to make debugging Rails applications easier. For example, you can use it to see who calls
|
5
|
+
`tapping_device` is a gem built on top of Ruby’s `TracePoint` class that allows you to tap method calls of specified objects. The purpose for this gem is to make debugging Rails applications easier. For example, you can use it to see who calls your `Post` records
|
6
6
|
|
7
7
|
```ruby
|
8
8
|
class PostsController < ApplicationController
|
9
|
+
include TappingDevice::Trackable
|
10
|
+
|
9
11
|
def show
|
10
12
|
@post = Post.find(params[:id])
|
11
|
-
|
12
|
-
device = TappingDevice.new do |payload|
|
13
|
+
tap_on!(@post) do |payload|
|
13
14
|
puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
14
15
|
end
|
15
|
-
device.tap_on!(@post)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
```
|
@@ -28,10 +28,9 @@ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_di
|
|
28
28
|
Or you can use `tap_assoc!`. This is very useful for tracking potential n+1 query calls, here’s a sample from my work
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
|
31
|
+
tap_assoc!(order) do |payload|
|
32
32
|
puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
33
33
|
end
|
34
|
-
device.tap_assoc!(order)
|
35
34
|
```
|
36
35
|
|
37
36
|
```
|
@@ -42,12 +41,12 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
|
|
42
41
|
Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
|
43
42
|
```
|
44
43
|
|
45
|
-
However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don
|
44
|
+
However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don’t use this on production**
|
46
45
|
|
47
46
|
|
48
47
|
## Installation
|
49
48
|
|
50
|
-
Add this line to your application
|
49
|
+
Add this line to your application’s Gemfile:
|
51
50
|
|
52
51
|
```ruby
|
53
52
|
gem 'tapping_device', group: :development
|
@@ -66,44 +65,14 @@ $ gem install tapping_device
|
|
66
65
|
```
|
67
66
|
|
68
67
|
## Usage
|
68
|
+
In order to use `tapping_device`, you need to include `TappingDevice::Trackable` module in where you want to track your code.
|
69
69
|
|
70
|
-
###
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
device = TappingDevice.new do |payload|
|
75
|
-
if payload[:method_name].to_s.match?(/foo/)
|
76
|
-
puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
```
|
70
|
+
### Methods
|
71
|
+
- `tap_init!(class)` - tracks a class’ instance initialization
|
72
|
+
- `tap_on!(object)` - tracks any calls received by the object
|
73
|
+
- `tap_assoc!(activerecord_object)` - tracks association calls on a record, like `post.comments`
|
80
74
|
|
81
|
-
###
|
82
|
-
|
83
|
-
Because `tapping_device` is built upon `TracePoint`, which literally scans **every method call** on **every object**. Even if we filter out the calls we’re not interested in, just filtering out through those method calls takes time if your application isn’t a small one. So it’s very important to stop the tapping device at a certain point. You can do this in 2 ways:
|
84
|
-
|
85
|
-
#### Use `device.stop_when(&block)` to set a stop condition
|
86
|
-
To define a stop condition, you can use `stop_when` method.
|
87
|
-
|
88
|
-
```ruby
|
89
|
-
device.stop_when do |payload|
|
90
|
-
device.calls.count >= 10 # stop after gathering 10 calls' data
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
**If you don’t set a stop condition, you need to use tapping methods that has exclamation mark**, like `device.tap_on!(post)`.
|
95
|
-
|
96
|
-
#### `device.stop!`
|
97
|
-
If you don’t define a stop condition, you can also use `device.stop!` to stop it manually.
|
98
|
-
|
99
|
-
### Start tapping
|
100
|
-
|
101
|
-
#### Methods
|
102
|
-
- `TappingDevice#tap_init(class)` - tracks a class’ instance initialization
|
103
|
-
- `TappingDevice#tap_on(object)` - tracks any calls received by the object
|
104
|
-
- `TappingDevice#tap_assoc(activerecord_object)` - tracks association calls on a record, like `post.comments`
|
105
|
-
|
106
|
-
#### Info of the call
|
75
|
+
### Info of the call
|
107
76
|
All tapping methods (start with `tap_`) takes a block and yield a hash as block argument.
|
108
77
|
|
109
78
|
```ruby
|
@@ -140,14 +109,34 @@ The hash contains
|
|
140
109
|
- `exclude_by_paths: [/path/]` - an array of call path patterns that we want to skip. This could be very helpful when working on large project like Rails applications.
|
141
110
|
- `filter_by_paths: [/path/]` - only contain calls from the specified paths
|
142
111
|
|
143
|
-
|
112
|
+
```ruby
|
113
|
+
tap_on!(@post, exclude_by_paths: [/active_record/]) do |payload|
|
114
|
+
puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
```
|
119
|
+
Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
120
|
+
Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
121
|
+
Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
122
|
+
Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
123
|
+
.......
|
124
|
+
|
125
|
+
# versus
|
126
|
+
|
127
|
+
Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
128
|
+
Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
129
|
+
Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
130
|
+
```
|
131
|
+
|
132
|
+
|
133
|
+
### `#tap_init!`
|
144
134
|
|
145
135
|
```ruby
|
146
136
|
calls = []
|
147
|
-
|
137
|
+
tap_init!(Student) do |payload|
|
148
138
|
calls << [payload[:method_name], payload[:arguments]]
|
149
139
|
end
|
150
|
-
device.tap_init!(Student)
|
151
140
|
|
152
141
|
Student.new("Stan", 18)
|
153
142
|
Student.new("Jane", 23)
|
@@ -159,16 +148,14 @@ puts(calls.to_s) #=> [[:initialize, [[:name, "Stan"], [:age, 18]]], [:initialize
|
|
159
148
|
|
160
149
|
```ruby
|
161
150
|
class PostsController < ApplicationController
|
151
|
+
include TappingDevice::Trackable
|
152
|
+
|
162
153
|
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
163
154
|
|
164
|
-
# GET /posts/1
|
165
|
-
# GET /posts/1.json
|
166
155
|
def show
|
167
|
-
|
156
|
+
tap_on!(@post) do |payload|
|
168
157
|
puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
169
158
|
end
|
170
|
-
|
171
|
-
device.tap_on!(@post)
|
172
159
|
end
|
173
160
|
end
|
174
161
|
```
|
@@ -184,10 +171,9 @@ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_di
|
|
184
171
|
### `tap_assoc!`
|
185
172
|
|
186
173
|
```ruby
|
187
|
-
|
174
|
+
tap_assoc!(order) do |payload|
|
188
175
|
puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
|
189
176
|
end
|
190
|
-
device.tap_assoc!(order)
|
191
177
|
```
|
192
178
|
|
193
179
|
```
|
@@ -198,12 +184,35 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
|
|
198
184
|
Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
|
199
185
|
```
|
200
186
|
|
201
|
-
###
|
187
|
+
### Advance Usages
|
188
|
+
|
189
|
+
Tapping methods introduced above like `tap_on!` are designed for simple use cases. They’re actually short for
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
device = TappingDevice.new { # tapping action }
|
193
|
+
device.tap_on!(object)
|
194
|
+
```
|
195
|
+
|
196
|
+
And if you want to do some more configurations like stopping them manually or setting stop condition, you must have a `TappingDevie` instance. You can either get them like the above code, or save the return value of `tap_*!` method calls.
|
197
|
+
|
198
|
+
#### Stop tapping
|
199
|
+
|
200
|
+
Once you have a `TappingDevice` instance in hand, you will be able to stop the tapping by
|
201
|
+
1. Manually calling `device.stop!`
|
202
|
+
2. Setting stop condition with `device.stop_when`, like
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
device.stop_when do |payload|
|
206
|
+
device.calls.count >= 10 # stop after gathering 10 calls’ data
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
#### Device states & Managing Devices
|
202
211
|
|
203
|
-
|
212
|
+
Each `TappingDevice` instance can have 3 states:
|
204
213
|
|
205
|
-
- `Initial` - means the instance is initialized but hasn’t
|
206
|
-
- `Enabled` - means the instance
|
214
|
+
- `Initial` - means the instance is initialized but hasn’t tapped on anything.
|
215
|
+
- `Enabled` - means the instance are tapping on something (has called `tap_*` methods).
|
207
216
|
- `Disabled` - means the instance has been disabled. It will no longer receive any call info.
|
208
217
|
|
209
218
|
When debugging, we may create many device instances and tap objects in several places. Then it’ll be quite annoying to manage their states. So `TappingDevice` has several class methods that allows you to manage all `TappingDevice` instances:
|
data/lib/tapping_device.rb
CHANGED
@@ -5,7 +5,6 @@ require "tapping_device/exceptions"
|
|
5
5
|
|
6
6
|
class TappingDevice
|
7
7
|
CALLER_START_POINT = 2
|
8
|
-
FORCE_STOP_WHEN_MESSAGE = "You must set stop_when condition before start tapping"
|
9
8
|
|
10
9
|
attr_reader :options, :calls, :trace_point
|
11
10
|
|
@@ -61,21 +60,6 @@ class TappingDevice
|
|
61
60
|
track(record, condition: :tap_associations?, block: @block, **@options)
|
62
61
|
end
|
63
62
|
|
64
|
-
def tap_init(klass)
|
65
|
-
validate_tapping(__method__)
|
66
|
-
tap_init!(klass)
|
67
|
-
end
|
68
|
-
|
69
|
-
def tap_on(object)
|
70
|
-
validate_tapping(__method__)
|
71
|
-
tap_on!(object)
|
72
|
-
end
|
73
|
-
|
74
|
-
def tap_assoc(record)
|
75
|
-
validate_tapping(__method__)
|
76
|
-
tap_assoc!(record)
|
77
|
-
end
|
78
|
-
|
79
63
|
def set_block(&block)
|
80
64
|
@block = block
|
81
65
|
end
|
@@ -90,18 +74,15 @@ class TappingDevice
|
|
90
74
|
|
91
75
|
private
|
92
76
|
|
93
|
-
def validate_tapping(method_name)
|
94
|
-
unless @stop_when
|
95
|
-
raise TappingDevice::Exception.new <<~ERROR
|
96
|
-
You must set stop_when condition before calling #{method_name}. Or you can use #{method_name}! to force tapping.
|
97
|
-
Tapping without stop condition can largely slow down or even halt your application, because it'll need to
|
98
|
-
screen literally every call happened.
|
99
|
-
ERROR
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
77
|
def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [], filter_by_paths: nil)
|
104
78
|
@trace_point = TracePoint.new(:return) do |tp|
|
79
|
+
validation_params = {
|
80
|
+
receiver: tp.self,
|
81
|
+
method_name: tp.callee_id
|
82
|
+
}
|
83
|
+
|
84
|
+
if send(condition, object, validation_params)
|
85
|
+
|
105
86
|
filepath, line_number = caller(CALLER_START_POINT).first.split(":")[0..1]
|
106
87
|
|
107
88
|
# this needs to be placed upfront so we can exclude noise before doing more work
|
@@ -126,8 +107,6 @@ class TappingDevice
|
|
126
107
|
}
|
127
108
|
|
128
109
|
yield_parameters[:trace] = caller[CALLER_START_POINT..(CALLER_START_POINT + with_trace_to)] if with_trace_to
|
129
|
-
|
130
|
-
if send(condition, object, yield_parameters)
|
131
110
|
if @block
|
132
111
|
@calls << block.call(yield_parameters)
|
133
112
|
else
|
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.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-11-
|
11
|
+
date: 2019-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|